@tuongaz/seeflow 0.1.40 → 0.1.42
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 +2 -15
- package/dist/web/assets/{index-DTNk6GGk.js → index-BPUoNIBm.js} +1541 -1541
- package/dist/web/assets/{index-BwdVgB2y.css → index-BlkUOp7f.css} +1 -1
- package/dist/web/assets/{index.es-D_iCCj4R.js → index.es-mje3R_63.js} +1 -1
- package/dist/web/assets/{jspdf.es.min-C9FG4HQT.js → jspdf.es.min-DX3imOs2.js} +3 -3
- package/dist/web/index.html +2 -2
- package/examples/ecommerce-platform/.seeflow/flow.json +47 -47
- package/examples/ecommerce-platform/.seeflow/style.json +10 -10
- package/examples/order-pipeline/.seeflow/flow.json +17 -17
- package/examples/order-pipeline/.seeflow/style.json +4 -4
- package/package.json +1 -1
- package/src/api.ts +101 -14
- package/src/atomic-write.ts +16 -0
- package/src/cli-e2e.ts +420 -0
- package/src/cli-helpers.ts +65 -0
- package/src/cli.ts +371 -17
- package/src/mcp.ts +116 -23
- package/src/merge.ts +1 -1
- package/src/node-files.ts +45 -0
- package/src/operations.ts +304 -98
- package/src/proxy.ts +35 -6
- package/src/registry.ts +2 -1
- package/src/schema.ts +31 -25
- package/src/short-id.ts +24 -0
- package/src/status-runner.ts +9 -8
- package/src/watcher.ts +14 -14
- /package/examples/ecommerce-platform/.seeflow/{details/auth-service.md → nodes/node-3zFtHg6ENc/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/cart-service.md → nodes/node-5F424NWbEu/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/api-gateway.md → nodes/node-CbwYqb7NfB/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{scripts/platform-health.html → nodes/node-XwygzfKPZ5/view.html} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/notification-service.md → nodes/node-fkptXw7uvs/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/product-service.md → nodes/node-kwBY8YPmYM/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/payment-service.md → nodes/node-mPqan8rFYN/detail.md} +0 -0
- /package/examples/ecommerce-platform/.seeflow/{details/order-service.md → nodes/node-yKrg9DV5fJ/detail.md} +0 -0
- /package/examples/order-pipeline/.seeflow/{details/inventory-service.md → nodes/node-GXTKUcE3ye/detail.md} +0 -0
- /package/examples/order-pipeline/.seeflow/{details/post-orders.md → nodes/node-XKIyds0TDg/detail.md} +0 -0
- /package/examples/order-pipeline/.seeflow/{details/payment-service.md → nodes/node-YOYiHJpY0i/detail.md} +0 -0
- /package/examples/order-pipeline/.seeflow/{details/fulfillment-service.md → nodes/node-zUIH7WFnhK/detail.md} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "E-Commerce Platform",
|
|
4
4
|
"nodes": [
|
|
5
5
|
{
|
|
6
|
-
"id": "
|
|
6
|
+
"id": "node-cQOUPXanaX",
|
|
7
7
|
"type": "shapeNode",
|
|
8
8
|
"data": {
|
|
9
9
|
"shape": "user",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
|
-
"id": "
|
|
15
|
+
"id": "node-CbwYqb7NfB",
|
|
16
16
|
"type": "playNode",
|
|
17
17
|
"data": {
|
|
18
18
|
"name": "API Gateway",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"kind": "request"
|
|
22
22
|
},
|
|
23
23
|
"description": "Auth, rate-limiting, routing.",
|
|
24
|
-
"detail": "file://
|
|
24
|
+
"detail": "file://nodes/node-CbwYqb7NfB/detail.md",
|
|
25
25
|
"playAction": {
|
|
26
26
|
"kind": "script",
|
|
27
27
|
"interpreter": "bun",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
|
-
"id": "
|
|
36
|
+
"id": "node-3zFtHg6ENc",
|
|
37
37
|
"type": "stateNode",
|
|
38
38
|
"data": {
|
|
39
39
|
"name": "Auth Service",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"kind": "request"
|
|
43
43
|
},
|
|
44
44
|
"description": "JWT issuance + OAuth2 / OIDC.",
|
|
45
|
-
"detail": "file://
|
|
45
|
+
"detail": "file://nodes/node-3zFtHg6ENc/detail.md"
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
"id": "
|
|
49
|
+
"id": "node-kwBY8YPmYM",
|
|
50
50
|
"type": "stateNode",
|
|
51
51
|
"data": {
|
|
52
52
|
"name": "Product Catalog",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"kind": "request"
|
|
56
56
|
},
|
|
57
57
|
"description": "SKUs, variants, pricing, full-text search.",
|
|
58
|
-
"detail": "file://
|
|
58
|
+
"detail": "file://nodes/node-kwBY8YPmYM/detail.md"
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
|
-
"id": "
|
|
62
|
+
"id": "node-5F424NWbEu",
|
|
63
63
|
"type": "playNode",
|
|
64
64
|
"data": {
|
|
65
65
|
"name": "Cart Service",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"kind": "request"
|
|
69
69
|
},
|
|
70
70
|
"description": "Add/remove items, apply coupons, checkout.",
|
|
71
|
-
"detail": "file://
|
|
71
|
+
"detail": "file://nodes/node-5F424NWbEu/detail.md",
|
|
72
72
|
"playAction": {
|
|
73
73
|
"kind": "script",
|
|
74
74
|
"interpreter": "bun",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
"id": "
|
|
83
|
+
"id": "node-yKrg9DV5fJ",
|
|
84
84
|
"type": "playNode",
|
|
85
85
|
"data": {
|
|
86
86
|
"name": "Order Service",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"stateSource": {
|
|
89
89
|
"kind": "event"
|
|
90
90
|
},
|
|
91
|
-
"description": "pending
|
|
92
|
-
"detail": "file://
|
|
91
|
+
"description": "pending → confirmed → shipped → delivered.",
|
|
92
|
+
"detail": "file://nodes/node-yKrg9DV5fJ/detail.md",
|
|
93
93
|
"playAction": {
|
|
94
94
|
"kind": "script",
|
|
95
95
|
"interpreter": "bun",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
|
-
"id": "
|
|
104
|
+
"id": "node-mPqan8rFYN",
|
|
105
105
|
"type": "stateNode",
|
|
106
106
|
"data": {
|
|
107
107
|
"name": "Payment Service",
|
|
@@ -110,11 +110,11 @@
|
|
|
110
110
|
"kind": "event"
|
|
111
111
|
},
|
|
112
112
|
"description": "Stripe + PayPal gateway integration.",
|
|
113
|
-
"detail": "file://
|
|
113
|
+
"detail": "file://nodes/node-mPqan8rFYN/detail.md"
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
"id": "
|
|
117
|
+
"id": "node-fkptXw7uvs",
|
|
118
118
|
"type": "stateNode",
|
|
119
119
|
"data": {
|
|
120
120
|
"name": "Notification Service",
|
|
@@ -123,96 +123,96 @@
|
|
|
123
123
|
"kind": "event"
|
|
124
124
|
},
|
|
125
125
|
"description": "Email + SMS + push via AWS SES / SNS.",
|
|
126
|
-
"detail": "file://
|
|
126
|
+
"detail": "file://nodes/node-fkptXw7uvs/detail.md"
|
|
127
127
|
}
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
|
-
"id": "
|
|
130
|
+
"id": "node-5SDiw3Wz6s",
|
|
131
131
|
"type": "shapeNode",
|
|
132
132
|
"data": {
|
|
133
133
|
"shape": "database",
|
|
134
134
|
"name": "PostgreSQL",
|
|
135
|
-
"description": "Orders, users, products
|
|
135
|
+
"description": "Orders, users, products — primary OLTP store"
|
|
136
136
|
}
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
|
-
"id": "
|
|
139
|
+
"id": "node-XwygzfKPZ5",
|
|
140
140
|
"type": "htmlNode",
|
|
141
141
|
"data": {
|
|
142
142
|
"name": "Platform Health",
|
|
143
|
-
"
|
|
143
|
+
"html": "file://nodes/node-XwygzfKPZ5/view.html"
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
],
|
|
147
147
|
"connectors": [
|
|
148
148
|
{
|
|
149
|
-
"id": "
|
|
150
|
-
"source": "
|
|
151
|
-
"target": "
|
|
149
|
+
"id": "conn-4XKU3GcGPF",
|
|
150
|
+
"source": "node-cQOUPXanaX",
|
|
151
|
+
"target": "node-CbwYqb7NfB",
|
|
152
152
|
"kind": "http",
|
|
153
153
|
"method": "POST",
|
|
154
154
|
"label": "REST API"
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
|
-
"id": "
|
|
158
|
-
"source": "
|
|
159
|
-
"target": "
|
|
157
|
+
"id": "conn-OxNUxp7qB3",
|
|
158
|
+
"source": "node-CbwYqb7NfB",
|
|
159
|
+
"target": "node-3zFtHg6ENc",
|
|
160
160
|
"kind": "http",
|
|
161
161
|
"method": "POST",
|
|
162
162
|
"label": "POST /auth"
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
|
-
"id": "
|
|
166
|
-
"source": "
|
|
167
|
-
"target": "
|
|
165
|
+
"id": "conn-7HlJF6KVHx",
|
|
166
|
+
"source": "node-CbwYqb7NfB",
|
|
167
|
+
"target": "node-kwBY8YPmYM",
|
|
168
168
|
"kind": "http",
|
|
169
169
|
"method": "GET",
|
|
170
170
|
"label": "GET /products"
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
|
-
"id": "
|
|
174
|
-
"source": "
|
|
175
|
-
"target": "
|
|
173
|
+
"id": "conn-ORfUTiooia",
|
|
174
|
+
"source": "node-CbwYqb7NfB",
|
|
175
|
+
"target": "node-5F424NWbEu",
|
|
176
176
|
"kind": "http",
|
|
177
177
|
"method": "POST",
|
|
178
178
|
"label": "POST /cart"
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
|
-
"id": "
|
|
182
|
-
"source": "
|
|
183
|
-
"target": "
|
|
181
|
+
"id": "conn-EABXtQv89M",
|
|
182
|
+
"source": "node-5F424NWbEu",
|
|
183
|
+
"target": "node-yKrg9DV5fJ",
|
|
184
184
|
"kind": "event",
|
|
185
185
|
"eventName": "cart.checkout",
|
|
186
186
|
"label": "cart.checkout"
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
|
-
"id": "
|
|
190
|
-
"source": "
|
|
191
|
-
"target": "
|
|
189
|
+
"id": "conn-kjyg3RDDvu",
|
|
190
|
+
"source": "node-yKrg9DV5fJ",
|
|
191
|
+
"target": "node-mPqan8rFYN",
|
|
192
192
|
"kind": "event",
|
|
193
193
|
"eventName": "order.created",
|
|
194
194
|
"label": "order.created"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
|
-
"id": "
|
|
198
|
-
"source": "
|
|
199
|
-
"target": "
|
|
197
|
+
"id": "conn-wqFq0shXO5",
|
|
198
|
+
"source": "node-mPqan8rFYN",
|
|
199
|
+
"target": "node-fkptXw7uvs",
|
|
200
200
|
"kind": "event",
|
|
201
201
|
"eventName": "payment.captured",
|
|
202
202
|
"label": "payment.captured"
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
|
-
"id": "
|
|
206
|
-
"source": "
|
|
207
|
-
"target": "
|
|
205
|
+
"id": "conn-8ftFXZvD4r",
|
|
206
|
+
"source": "node-yKrg9DV5fJ",
|
|
207
|
+
"target": "node-5SDiw3Wz6s",
|
|
208
208
|
"kind": "http",
|
|
209
209
|
"method": "POST",
|
|
210
210
|
"label": "read/write"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
"id": "
|
|
214
|
-
"source": "
|
|
215
|
-
"target": "
|
|
213
|
+
"id": "conn-VTfjsOckF2",
|
|
214
|
+
"source": "node-mPqan8rFYN",
|
|
215
|
+
"target": "node-5SDiw3Wz6s",
|
|
216
216
|
"kind": "http",
|
|
217
217
|
"method": "POST",
|
|
218
218
|
"label": "read/write"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"nodes": {
|
|
3
|
-
"
|
|
3
|
+
"node-cQOUPXanaX": {
|
|
4
4
|
"position": {
|
|
5
5
|
"x": 80,
|
|
6
6
|
"y": 215.5
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"borderColor": "green",
|
|
10
10
|
"borderSize": 1
|
|
11
11
|
},
|
|
12
|
-
"
|
|
12
|
+
"node-CbwYqb7NfB": {
|
|
13
13
|
"position": {
|
|
14
14
|
"x": 320,
|
|
15
15
|
"y": 227
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"borderColor": "green",
|
|
19
19
|
"borderSize": 1
|
|
20
20
|
},
|
|
21
|
-
"
|
|
21
|
+
"node-3zFtHg6ENc": {
|
|
22
22
|
"position": {
|
|
23
23
|
"x": 660,
|
|
24
24
|
"y": 50
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"borderColor": "green",
|
|
28
28
|
"borderSize": 1
|
|
29
29
|
},
|
|
30
|
-
"
|
|
30
|
+
"node-kwBY8YPmYM": {
|
|
31
31
|
"position": {
|
|
32
32
|
"x": 660,
|
|
33
33
|
"y": 218
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"borderColor": "green",
|
|
37
37
|
"borderSize": 1
|
|
38
38
|
},
|
|
39
|
-
"
|
|
39
|
+
"node-5F424NWbEu": {
|
|
40
40
|
"position": {
|
|
41
41
|
"x": 660,
|
|
42
42
|
"y": 413
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"borderColor": "green",
|
|
46
46
|
"borderSize": 1
|
|
47
47
|
},
|
|
48
|
-
"
|
|
48
|
+
"node-yKrg9DV5fJ": {
|
|
49
49
|
"position": {
|
|
50
50
|
"x": 1000,
|
|
51
51
|
"y": 413
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"borderColor": "green",
|
|
55
55
|
"borderSize": 1
|
|
56
56
|
},
|
|
57
|
-
"
|
|
57
|
+
"node-mPqan8rFYN": {
|
|
58
58
|
"position": {
|
|
59
59
|
"x": 1340,
|
|
60
60
|
"y": 349
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"borderColor": "green",
|
|
64
64
|
"borderSize": 1
|
|
65
65
|
},
|
|
66
|
-
"
|
|
66
|
+
"node-fkptXw7uvs": {
|
|
67
67
|
"position": {
|
|
68
68
|
"x": 1680,
|
|
69
69
|
"y": 339
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"borderColor": "green",
|
|
73
73
|
"borderSize": 1
|
|
74
74
|
},
|
|
75
|
-
"
|
|
75
|
+
"node-5SDiw3Wz6s": {
|
|
76
76
|
"position": {
|
|
77
77
|
"x": 1720,
|
|
78
78
|
"y": 507
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"borderColor": "green",
|
|
82
82
|
"borderSize": 1
|
|
83
83
|
},
|
|
84
|
-
"
|
|
84
|
+
"node-XwygzfKPZ5": {
|
|
85
85
|
"position": {
|
|
86
86
|
"x": 1340,
|
|
87
87
|
"y": 50
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Order Pipeline",
|
|
4
4
|
"nodes": [
|
|
5
5
|
{
|
|
6
|
-
"id": "
|
|
6
|
+
"id": "node-XKIyds0TDg",
|
|
7
7
|
"type": "playNode",
|
|
8
8
|
"data": {
|
|
9
9
|
"name": "POST /orders",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"kind": "request"
|
|
13
13
|
},
|
|
14
14
|
"description": "Creates order, kicks off the pipeline.",
|
|
15
|
-
"detail": "file://
|
|
15
|
+
"detail": "file://nodes/node-XKIyds0TDg/detail.md",
|
|
16
16
|
"playAction": {
|
|
17
17
|
"kind": "script",
|
|
18
18
|
"interpreter": "bun",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
"id": "
|
|
27
|
+
"id": "node-GXTKUcE3ye",
|
|
28
28
|
"type": "stateNode",
|
|
29
29
|
"data": {
|
|
30
30
|
"name": "Inventory Service",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"kind": "event"
|
|
34
34
|
},
|
|
35
35
|
"description": "Reserves stock.",
|
|
36
|
-
"detail": "file://
|
|
36
|
+
"detail": "file://nodes/node-GXTKUcE3ye/detail.md",
|
|
37
37
|
"icon": "a-arrow-down-icon"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
"id": "
|
|
41
|
+
"id": "node-YOYiHJpY0i",
|
|
42
42
|
"type": "stateNode",
|
|
43
43
|
"data": {
|
|
44
44
|
"name": "Payment Service",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"kind": "event"
|
|
48
48
|
},
|
|
49
49
|
"description": "Charges card.",
|
|
50
|
-
"detail": "file://
|
|
50
|
+
"detail": "file://nodes/node-YOYiHJpY0i/detail.md"
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
|
-
"id": "
|
|
54
|
+
"id": "node-zUIH7WFnhK",
|
|
55
55
|
"type": "stateNode",
|
|
56
56
|
"data": {
|
|
57
57
|
"name": "Fulfillment Service",
|
|
@@ -60,31 +60,31 @@
|
|
|
60
60
|
"kind": "event"
|
|
61
61
|
},
|
|
62
62
|
"description": "Enqueues shipment.",
|
|
63
|
-
"detail": "file://
|
|
63
|
+
"detail": "file://nodes/node-zUIH7WFnhK/detail.md"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
67
|
"connectors": [
|
|
68
68
|
{
|
|
69
|
-
"id": "
|
|
70
|
-
"source": "
|
|
71
|
-
"target": "
|
|
69
|
+
"id": "conn-jJjuWBfe3a",
|
|
70
|
+
"source": "node-XKIyds0TDg",
|
|
71
|
+
"target": "node-GXTKUcE3ye",
|
|
72
72
|
"kind": "event",
|
|
73
73
|
"eventName": "order.created",
|
|
74
74
|
"label": "order.created"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
"id": "
|
|
78
|
-
"source": "
|
|
79
|
-
"target": "
|
|
77
|
+
"id": "conn-8DkPOzrnYo",
|
|
78
|
+
"source": "node-GXTKUcE3ye",
|
|
79
|
+
"target": "node-YOYiHJpY0i",
|
|
80
80
|
"kind": "event",
|
|
81
81
|
"eventName": "stock.reserved",
|
|
82
82
|
"label": "stock.reserved"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
|
-
"id": "
|
|
86
|
-
"source": "
|
|
87
|
-
"target": "
|
|
85
|
+
"id": "conn-qp90Rd2cgw",
|
|
86
|
+
"source": "node-YOYiHJpY0i",
|
|
87
|
+
"target": "node-zUIH7WFnhK",
|
|
88
88
|
"kind": "event",
|
|
89
89
|
"eventName": "payment.captured",
|
|
90
90
|
"label": "payment.captured"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"nodes": {
|
|
3
|
-
"
|
|
3
|
+
"node-XKIyds0TDg": {
|
|
4
4
|
"position": {
|
|
5
5
|
"x": -181.96549037838943,
|
|
6
6
|
"y": 138.95644990042385
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"borderColor": "green",
|
|
10
10
|
"borderSize": 1
|
|
11
11
|
},
|
|
12
|
-
"
|
|
12
|
+
"node-GXTKUcE3ye": {
|
|
13
13
|
"position": {
|
|
14
14
|
"x": 66.21484197841792,
|
|
15
15
|
"y": -126.31035925520507
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"borderColor": "green",
|
|
21
21
|
"borderSize": 1
|
|
22
22
|
},
|
|
23
|
-
"
|
|
23
|
+
"node-YOYiHJpY0i": {
|
|
24
24
|
"position": {
|
|
25
25
|
"x": 469.1422114437386,
|
|
26
26
|
"y": 206.48855752663212
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"fontSize": 15,
|
|
30
30
|
"borderColor": "green"
|
|
31
31
|
},
|
|
32
|
-
"
|
|
32
|
+
"node-zUIH7WFnhK": {
|
|
33
33
|
"position": {
|
|
34
34
|
"x": 747.51321046746,
|
|
35
35
|
"y": -37.78865629343234
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -15,14 +15,18 @@ import type { EventBus } from './events.ts';
|
|
|
15
15
|
import { type LayoutOptions, computeLayout } from './layout.ts';
|
|
16
16
|
import {
|
|
17
17
|
ConnectorPatchBodySchema,
|
|
18
|
+
ConnectorsBulkBodySchema,
|
|
18
19
|
CreateProjectBodySchema,
|
|
19
20
|
NodePatchBodySchema,
|
|
21
|
+
NodesBulkBodySchema,
|
|
20
22
|
PositionBodySchema,
|
|
21
23
|
RegisterBodySchema,
|
|
22
24
|
ReorderBodySchema,
|
|
23
25
|
type ValidateBody,
|
|
24
26
|
addConnectorImpl,
|
|
27
|
+
addConnectorsBulkImpl,
|
|
25
28
|
addNodeImpl,
|
|
29
|
+
addNodesBulkImpl,
|
|
26
30
|
createProjectImpl,
|
|
27
31
|
deleteConnectorImpl,
|
|
28
32
|
deleteFlowImpl,
|
|
@@ -134,7 +138,7 @@ function resolveProjectFile(
|
|
|
134
138
|
return { kind: 'ok', absPath: realTarget, seeflowRoot: realRoot };
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
// Allowed extensions for /files/upload. Lowercased; matched after dropping the
|
|
141
|
+
// Allowed extensions for /nodes/:nodeId/files/upload. Lowercased; matched after dropping the
|
|
138
142
|
// leading `.`. Stored as a Set so future expansion (PDF, video) is one-edit.
|
|
139
143
|
const UPLOAD_ALLOWED_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']);
|
|
140
144
|
const UPLOAD_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -609,17 +613,23 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
609
613
|
return c.json({ ok: true, absPath: resolved.absPath });
|
|
610
614
|
});
|
|
611
615
|
|
|
612
|
-
// POST /api/projects/:id/files/upload — accept a multipart
|
|
613
|
-
// persist it under `<project>/.seeflow/
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
api.post('/projects/:id/files/upload', async (c) => {
|
|
616
|
+
// POST /api/projects/:id/nodes/:nodeId/files/upload — accept a multipart
|
|
617
|
+
// image upload and persist it under `<project>/.seeflow/nodes/<nodeId>/`.
|
|
618
|
+
// Multipart shape: `file` (Blob) and optional `filename` (the original OS
|
|
619
|
+
// name). Allowlist + 5 MB cap guard against arbitrary uploads; the
|
|
620
|
+
// destination folder is scoped to the node, so delete_node's removeNodeDir
|
|
621
|
+
// cascade cleans up the asset along with the node row.
|
|
622
|
+
api.post('/projects/:id/nodes/:nodeId/files/upload', async (c) => {
|
|
619
623
|
const projectId = c.req.param('id');
|
|
624
|
+
const nodeId = c.req.param('nodeId');
|
|
620
625
|
const entry = registry.getById(projectId);
|
|
621
626
|
if (!entry) return c.json({ error: 'unknown project' }, 404);
|
|
622
627
|
|
|
628
|
+
// node id shape: `node-<10 base62 chars>` (matches shortId() output).
|
|
629
|
+
if (!/^node-[A-Za-z0-9]{10}$/.test(nodeId)) {
|
|
630
|
+
return c.json({ error: 'invalid nodeId' }, 400);
|
|
631
|
+
}
|
|
632
|
+
|
|
623
633
|
let form: FormData;
|
|
624
634
|
try {
|
|
625
635
|
form = await c.req.formData();
|
|
@@ -643,20 +653,20 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
643
653
|
return c.json({ error: 'invalid filename or extension' }, 400);
|
|
644
654
|
}
|
|
645
655
|
|
|
646
|
-
const
|
|
656
|
+
const nodeDir = join(entry.repoPath, '.seeflow', 'nodes', nodeId);
|
|
647
657
|
try {
|
|
648
|
-
mkdirSync(
|
|
658
|
+
mkdirSync(nodeDir, { recursive: true });
|
|
649
659
|
} catch (err) {
|
|
650
660
|
return c.json(
|
|
651
661
|
{
|
|
652
|
-
error: `Failed to create
|
|
662
|
+
error: `Failed to create node dir: ${err instanceof Error ? err.message : String(err)}`,
|
|
653
663
|
},
|
|
654
664
|
500,
|
|
655
665
|
);
|
|
656
666
|
}
|
|
657
667
|
|
|
658
|
-
const finalName = pickUploadFilename(
|
|
659
|
-
const absPath = join(
|
|
668
|
+
const finalName = pickUploadFilename(nodeDir, sanitized.base, sanitized.ext);
|
|
669
|
+
const absPath = join(nodeDir, finalName);
|
|
660
670
|
try {
|
|
661
671
|
await Bun.write(absPath, fileField);
|
|
662
672
|
} catch (err) {
|
|
@@ -666,7 +676,7 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
666
676
|
);
|
|
667
677
|
}
|
|
668
678
|
|
|
669
|
-
return c.json({ path: `
|
|
679
|
+
return c.json({ path: `nodes/${nodeId}/${finalName}` });
|
|
670
680
|
});
|
|
671
681
|
|
|
672
682
|
api.delete('/flows/:id', (c) => {
|
|
@@ -1058,6 +1068,45 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1058
1068
|
}
|
|
1059
1069
|
});
|
|
1060
1070
|
|
|
1071
|
+
// Bulk-create up to 100 nodes in one transactional write. Either the whole
|
|
1072
|
+
// batch lands and a single flow:reload broadcast fires, or nothing lands.
|
|
1073
|
+
// Intended for skill/LLM seeding where N singular calls would burn tokens
|
|
1074
|
+
// and round-trip latency. Per-item shape mirrors the singular endpoint.
|
|
1075
|
+
api.post('/flows/:id/nodes/bulk', async (c) => {
|
|
1076
|
+
const id = c.req.param('id');
|
|
1077
|
+
|
|
1078
|
+
let body: unknown;
|
|
1079
|
+
try {
|
|
1080
|
+
body = await c.req.json();
|
|
1081
|
+
} catch {
|
|
1082
|
+
return c.json({ error: 'Body must be valid JSON' }, 400);
|
|
1083
|
+
}
|
|
1084
|
+
const parsed = NodesBulkBodySchema.safeParse(body);
|
|
1085
|
+
if (!parsed.success) {
|
|
1086
|
+
return c.json({ error: 'Invalid bulk nodes body', issues: parsed.error.issues }, 400);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const result = await addNodesBulkImpl({ registry, watcher }, id, parsed.data);
|
|
1090
|
+
switch (result.kind) {
|
|
1091
|
+
case 'ok':
|
|
1092
|
+
return c.json({ ok: true, nodes: result.data.nodes });
|
|
1093
|
+
case 'flowNotFound':
|
|
1094
|
+
return c.json({ error: 'unknown demo' }, 404);
|
|
1095
|
+
case 'fileNotFound':
|
|
1096
|
+
return c.json({ error: `Flow file not found: ${result.path}` }, 404);
|
|
1097
|
+
case 'badJson':
|
|
1098
|
+
return c.json({ error: `Flow file is not valid JSON: ${result.message}` }, 400);
|
|
1099
|
+
case 'badSchema':
|
|
1100
|
+
return c.json({ error: 'Flow failed schema validation', issues: result.issues }, 400);
|
|
1101
|
+
case 'duplicateIdInBatch':
|
|
1102
|
+
return c.json({ error: `Duplicate id in batch: ${result.id}` }, 400);
|
|
1103
|
+
case 'idAlreadyExists':
|
|
1104
|
+
return c.json({ error: `Node id already exists: ${result.id}` }, 400);
|
|
1105
|
+
case 'writeFailed':
|
|
1106
|
+
return c.json({ error: `Failed to write demo file: ${result.message}` }, 500);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1061
1110
|
// DELETE a node and cascade-remove every connector with source === nodeId or
|
|
1062
1111
|
// target === nodeId in the same atomic write. Final-ResolvedFlowSchema validation
|
|
1063
1112
|
// is still run after the mutation — connector cascade closure means it
|
|
@@ -1165,6 +1214,44 @@ export function createApi(options: ApiOptions): Hono {
|
|
|
1165
1214
|
}
|
|
1166
1215
|
});
|
|
1167
1216
|
|
|
1217
|
+
// Bulk-create up to 100 connectors in one transactional write. Mirrors the
|
|
1218
|
+
// /nodes/bulk shape. Dangling source/target on any item rolls back the whole
|
|
1219
|
+
// batch via the post-mutation ResolvedFlowSchema parse.
|
|
1220
|
+
api.post('/flows/:id/connectors/bulk', async (c) => {
|
|
1221
|
+
const id = c.req.param('id');
|
|
1222
|
+
|
|
1223
|
+
let body: unknown;
|
|
1224
|
+
try {
|
|
1225
|
+
body = await c.req.json();
|
|
1226
|
+
} catch {
|
|
1227
|
+
return c.json({ error: 'Body must be valid JSON' }, 400);
|
|
1228
|
+
}
|
|
1229
|
+
const parsed = ConnectorsBulkBodySchema.safeParse(body);
|
|
1230
|
+
if (!parsed.success) {
|
|
1231
|
+
return c.json({ error: 'Invalid bulk connectors body', issues: parsed.error.issues }, 400);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const result = await addConnectorsBulkImpl({ registry, watcher }, id, parsed.data);
|
|
1235
|
+
switch (result.kind) {
|
|
1236
|
+
case 'ok':
|
|
1237
|
+
return c.json({ ok: true, connectors: result.data.connectors });
|
|
1238
|
+
case 'flowNotFound':
|
|
1239
|
+
return c.json({ error: 'unknown demo' }, 404);
|
|
1240
|
+
case 'fileNotFound':
|
|
1241
|
+
return c.json({ error: `Flow file not found: ${result.path}` }, 404);
|
|
1242
|
+
case 'badJson':
|
|
1243
|
+
return c.json({ error: `Flow file is not valid JSON: ${result.message}` }, 400);
|
|
1244
|
+
case 'badSchema':
|
|
1245
|
+
return c.json({ error: 'Flow failed schema validation', issues: result.issues }, 400);
|
|
1246
|
+
case 'duplicateIdInBatch':
|
|
1247
|
+
return c.json({ error: `Duplicate id in batch: ${result.id}` }, 400);
|
|
1248
|
+
case 'idAlreadyExists':
|
|
1249
|
+
return c.json({ error: `Connector id already exists: ${result.id}` }, 400);
|
|
1250
|
+
case 'writeFailed':
|
|
1251
|
+
return c.json({ error: `Failed to write demo file: ${result.message}` }, 500);
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1168
1255
|
// DELETE a connector. Just removes the entry from demo.connectors — node
|
|
1169
1256
|
// deletion is what cascades, not connector deletion.
|
|
1170
1257
|
api.delete('/flows/:id/connectors/:connId', async (c) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const writeFileAtomic = (filePath: string, content: string): void => {
|
|
4
|
+
const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
5
|
+
try {
|
|
6
|
+
writeFileSync(tempPath, content);
|
|
7
|
+
renameSync(tempPath, filePath);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
try {
|
|
10
|
+
if (existsSync(tempPath)) unlinkSync(tempPath);
|
|
11
|
+
} catch {
|
|
12
|
+
// best-effort cleanup
|
|
13
|
+
}
|
|
14
|
+
throw err;
|
|
15
|
+
}
|
|
16
|
+
};
|