@brika/blocks-builtin 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 +351 -0
- package/icon.svg +25 -0
- package/locales/en/plugin.json +183 -0
- package/locales/fr/plugin.json +183 -0
- package/package.json +133 -0
- package/src/main.ts +486 -0
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# @brika/blocks-builtin
|
|
2
|
+
|
|
3
|
+
Core reactive blocks for BRIKA workflow automations. This plugin provides essential building blocks for creating visual workflows.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The built-in blocks plugin is automatically loaded by the BRIKA hub and provides fundamental workflow control and data manipulation blocks using the reactive stream architecture.
|
|
8
|
+
|
|
9
|
+
## Available Blocks
|
|
10
|
+
|
|
11
|
+
### Triggers
|
|
12
|
+
|
|
13
|
+
#### Clock
|
|
14
|
+
Emit periodic ticks on an interval.
|
|
15
|
+
|
|
16
|
+
- **Inputs**: None (source block)
|
|
17
|
+
- **Outputs**: `tick` — `{ count: number, ts: number }`
|
|
18
|
+
- **Config**:
|
|
19
|
+
- `interval` (duration) — Interval between ticks
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
- id: clock
|
|
23
|
+
type: "@brika/blocks-builtin:clock"
|
|
24
|
+
config:
|
|
25
|
+
interval: 5000 # 5 seconds
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Flow Control
|
|
29
|
+
|
|
30
|
+
#### Condition
|
|
31
|
+
Branch based on a boolean condition.
|
|
32
|
+
|
|
33
|
+
- **Inputs**: `in` (generic)
|
|
34
|
+
- **Outputs**: `then`, `else` (passthrough)
|
|
35
|
+
- **Config**:
|
|
36
|
+
- `field` — Field path to check (e.g., `"value"`, `"data.status"`)
|
|
37
|
+
- `operator` — `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`, `exists`
|
|
38
|
+
- `value` — Value to compare against
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- id: check
|
|
42
|
+
type: "@brika/blocks-builtin:condition"
|
|
43
|
+
config:
|
|
44
|
+
field: "temperature"
|
|
45
|
+
operator: "gt"
|
|
46
|
+
value: 25
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Switch
|
|
50
|
+
Multi-way branch based on a value.
|
|
51
|
+
|
|
52
|
+
- **Inputs**: `in` (generic)
|
|
53
|
+
- **Outputs**: `case1`, `case2`, `case3`, `default` (passthrough)
|
|
54
|
+
- **Config**:
|
|
55
|
+
- `field` — Field path to check
|
|
56
|
+
- `case1`, `case2`, `case3` — Values to match
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
- id: switch
|
|
60
|
+
type: "@brika/blocks-builtin:switch"
|
|
61
|
+
config:
|
|
62
|
+
field: "status"
|
|
63
|
+
case1: "active"
|
|
64
|
+
case2: "pending"
|
|
65
|
+
case3: "error"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Delay
|
|
69
|
+
Wait for a duration before continuing.
|
|
70
|
+
|
|
71
|
+
- **Inputs**: `in` (generic)
|
|
72
|
+
- **Outputs**: `out` (passthrough)
|
|
73
|
+
- **Config**:
|
|
74
|
+
- `duration` (duration) — Duration to wait
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
- id: wait
|
|
78
|
+
type: "@brika/blocks-builtin:delay"
|
|
79
|
+
config:
|
|
80
|
+
duration: 5000 # 5 seconds
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Merge
|
|
84
|
+
Wait for multiple inputs before continuing.
|
|
85
|
+
|
|
86
|
+
- **Inputs**: `a`, `b` (generic)
|
|
87
|
+
- **Outputs**: `out` — `{ a: any, b: any }`
|
|
88
|
+
- **Config**: None
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
- id: merge
|
|
92
|
+
type: "@brika/blocks-builtin:merge"
|
|
93
|
+
config: {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Split
|
|
97
|
+
Send data to multiple branches.
|
|
98
|
+
|
|
99
|
+
- **Inputs**: `in` (generic)
|
|
100
|
+
- **Outputs**: `a`, `b` (passthrough)
|
|
101
|
+
- **Config**: None
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
- id: split
|
|
105
|
+
type: "@brika/blocks-builtin:split"
|
|
106
|
+
config: {}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### End
|
|
110
|
+
Terminate a workflow branch.
|
|
111
|
+
|
|
112
|
+
- **Inputs**: `in` (generic)
|
|
113
|
+
- **Outputs**: None
|
|
114
|
+
- **Config**:
|
|
115
|
+
- `status` — `"success"` or `"failure"`
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
- id: end
|
|
119
|
+
type: "@brika/blocks-builtin:end"
|
|
120
|
+
config:
|
|
121
|
+
status: success
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Actions
|
|
125
|
+
|
|
126
|
+
#### HTTP Request
|
|
127
|
+
Make HTTP requests to external APIs.
|
|
128
|
+
|
|
129
|
+
- **Inputs**: `trigger` (generic)
|
|
130
|
+
- **Outputs**: `response`, `error`
|
|
131
|
+
- **Config**:
|
|
132
|
+
- `url` — Request URL
|
|
133
|
+
- `method` — `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
|
|
134
|
+
- `headers` — Request headers object
|
|
135
|
+
- `body` — Request body (for POST/PUT/PATCH)
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
- id: api-call
|
|
139
|
+
type: "@brika/blocks-builtin:http-request"
|
|
140
|
+
config:
|
|
141
|
+
url: "https://api.example.com/data"
|
|
142
|
+
method: GET
|
|
143
|
+
headers:
|
|
144
|
+
Authorization: "Bearer token"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Log
|
|
148
|
+
Log a message with variable interpolation.
|
|
149
|
+
|
|
150
|
+
- **Inputs**: `in` (generic)
|
|
151
|
+
- **Outputs**: `out` (passthrough)
|
|
152
|
+
- **Config**:
|
|
153
|
+
- `message` — Message template with `{{inputs.in.field}}` expressions
|
|
154
|
+
- `level` — `debug`, `info`, `warn`, `error`
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
- id: log
|
|
158
|
+
type: "@brika/blocks-builtin:log"
|
|
159
|
+
config:
|
|
160
|
+
message: "Received: {{inputs.in.value}}"
|
|
161
|
+
level: info
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Data Manipulation
|
|
165
|
+
|
|
166
|
+
#### Transform
|
|
167
|
+
Transform or extract data.
|
|
168
|
+
|
|
169
|
+
- **Inputs**: `in` (generic)
|
|
170
|
+
- **Outputs**: `out` (any)
|
|
171
|
+
- **Config**:
|
|
172
|
+
- `field` — Field to extract (empty for passthrough)
|
|
173
|
+
- `template` — Template to build output object
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
# Extract a field
|
|
177
|
+
- id: extract
|
|
178
|
+
type: "@brika/blocks-builtin:transform"
|
|
179
|
+
config:
|
|
180
|
+
field: "data.temperature"
|
|
181
|
+
|
|
182
|
+
# Build new object
|
|
183
|
+
- id: reshape
|
|
184
|
+
type: "@brika/blocks-builtin:transform"
|
|
185
|
+
config:
|
|
186
|
+
template:
|
|
187
|
+
temp: "data.temperature"
|
|
188
|
+
hum: "data.humidity"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Usage Examples
|
|
192
|
+
|
|
193
|
+
### Simple Clock + Log
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
id: clock-demo
|
|
197
|
+
name: Clock Demo
|
|
198
|
+
enabled: true
|
|
199
|
+
|
|
200
|
+
blocks:
|
|
201
|
+
- id: clock
|
|
202
|
+
type: "@brika/blocks-builtin:clock"
|
|
203
|
+
config:
|
|
204
|
+
interval: 5000
|
|
205
|
+
position: { x: 100, y: 100 }
|
|
206
|
+
|
|
207
|
+
- id: log
|
|
208
|
+
type: "@brika/blocks-builtin:log"
|
|
209
|
+
config:
|
|
210
|
+
message: "Tick #{{inputs.in.count}}"
|
|
211
|
+
level: info
|
|
212
|
+
position: { x: 300, y: 100 }
|
|
213
|
+
|
|
214
|
+
connections:
|
|
215
|
+
- from: clock
|
|
216
|
+
fromPort: tick
|
|
217
|
+
to: log
|
|
218
|
+
toPort: in
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Conditional Flow
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
id: condition-demo
|
|
225
|
+
name: Condition Demo
|
|
226
|
+
enabled: true
|
|
227
|
+
|
|
228
|
+
blocks:
|
|
229
|
+
- id: clock
|
|
230
|
+
type: "@brika/blocks-builtin:clock"
|
|
231
|
+
config:
|
|
232
|
+
interval: 10000
|
|
233
|
+
|
|
234
|
+
- id: condition
|
|
235
|
+
type: "@brika/blocks-builtin:condition"
|
|
236
|
+
config:
|
|
237
|
+
field: "count"
|
|
238
|
+
operator: "gt"
|
|
239
|
+
value: 5
|
|
240
|
+
|
|
241
|
+
- id: high
|
|
242
|
+
type: "@brika/blocks-builtin:log"
|
|
243
|
+
config:
|
|
244
|
+
message: "Count is high: {{inputs.in.count}}"
|
|
245
|
+
level: warn
|
|
246
|
+
|
|
247
|
+
- id: low
|
|
248
|
+
type: "@brika/blocks-builtin:log"
|
|
249
|
+
config:
|
|
250
|
+
message: "Count is low: {{inputs.in.count}}"
|
|
251
|
+
level: debug
|
|
252
|
+
|
|
253
|
+
connections:
|
|
254
|
+
- from: clock
|
|
255
|
+
fromPort: tick
|
|
256
|
+
to: condition
|
|
257
|
+
toPort: in
|
|
258
|
+
- from: condition
|
|
259
|
+
fromPort: then
|
|
260
|
+
to: high
|
|
261
|
+
toPort: in
|
|
262
|
+
- from: condition
|
|
263
|
+
fromPort: else
|
|
264
|
+
to: low
|
|
265
|
+
toPort: in
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Parallel Branches
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
id: parallel-demo
|
|
272
|
+
name: Parallel Demo
|
|
273
|
+
enabled: true
|
|
274
|
+
|
|
275
|
+
blocks:
|
|
276
|
+
- id: clock
|
|
277
|
+
type: "@brika/blocks-builtin:clock"
|
|
278
|
+
config:
|
|
279
|
+
interval: 5000
|
|
280
|
+
|
|
281
|
+
- id: split
|
|
282
|
+
type: "@brika/blocks-builtin:split"
|
|
283
|
+
config: {}
|
|
284
|
+
|
|
285
|
+
- id: fast
|
|
286
|
+
type: "@brika/blocks-builtin:log"
|
|
287
|
+
config:
|
|
288
|
+
message: "Fast path"
|
|
289
|
+
|
|
290
|
+
- id: slow
|
|
291
|
+
type: "@brika/blocks-builtin:delay"
|
|
292
|
+
config:
|
|
293
|
+
duration: 2000
|
|
294
|
+
|
|
295
|
+
- id: slow-log
|
|
296
|
+
type: "@brika/blocks-builtin:log"
|
|
297
|
+
config:
|
|
298
|
+
message: "Slow path (after 2s)"
|
|
299
|
+
|
|
300
|
+
- id: merge
|
|
301
|
+
type: "@brika/blocks-builtin:merge"
|
|
302
|
+
config: {}
|
|
303
|
+
|
|
304
|
+
- id: done
|
|
305
|
+
type: "@brika/blocks-builtin:log"
|
|
306
|
+
config:
|
|
307
|
+
message: "Both paths completed"
|
|
308
|
+
|
|
309
|
+
connections:
|
|
310
|
+
- from: clock
|
|
311
|
+
fromPort: tick
|
|
312
|
+
to: split
|
|
313
|
+
toPort: in
|
|
314
|
+
- from: split
|
|
315
|
+
fromPort: a
|
|
316
|
+
to: fast
|
|
317
|
+
toPort: in
|
|
318
|
+
- from: split
|
|
319
|
+
fromPort: b
|
|
320
|
+
to: slow
|
|
321
|
+
toPort: in
|
|
322
|
+
- from: slow
|
|
323
|
+
fromPort: out
|
|
324
|
+
to: slow-log
|
|
325
|
+
toPort: in
|
|
326
|
+
- from: fast
|
|
327
|
+
fromPort: out
|
|
328
|
+
to: merge
|
|
329
|
+
toPort: a
|
|
330
|
+
- from: slow-log
|
|
331
|
+
fromPort: out
|
|
332
|
+
to: merge
|
|
333
|
+
toPort: b
|
|
334
|
+
- from: merge
|
|
335
|
+
fromPort: out
|
|
336
|
+
to: done
|
|
337
|
+
toPort: in
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Expression Syntax
|
|
341
|
+
|
|
342
|
+
Log blocks support `{{...}}` expressions for variable interpolation:
|
|
343
|
+
|
|
344
|
+
- `{{inputs.in}}` — Raw input data
|
|
345
|
+
- `{{inputs.in.field}}` — Access nested fields
|
|
346
|
+
- `{{config.value}}` — Access config values
|
|
347
|
+
- `{{JSON.stringify(inputs.in)}}` — Serialize to JSON
|
|
348
|
+
|
|
349
|
+
## Installation
|
|
350
|
+
|
|
351
|
+
This plugin is included by default with BRIKA and does not need to be installed separately.
|
package/icon.svg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512" fill="none"
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg" class="">
|
|
3
|
+
<rect id="_r_9_" width="512" height="512" x="0" y="0" rx="0" fill="url(#_r_a_)" stroke="#FFFFFF" stroke-width="0"
|
|
4
|
+
stroke-opacity="100%" paint-order="stroke"></rect>
|
|
5
|
+
<clipPath id="clip">
|
|
6
|
+
<use xlink:href="#_r_9_"></use>
|
|
7
|
+
</clipPath>
|
|
8
|
+
<defs>
|
|
9
|
+
<radialGradient id="_r_a_" cx="50%" cy="50%" r="100%" fx="50%" fy="50%" gradientUnits="objectBoundingBox">
|
|
10
|
+
<stop stop-color="#f95356"></stop>
|
|
11
|
+
<stop offset="1" stop-color="#7e0000"></stop>
|
|
12
|
+
</radialGradient>
|
|
13
|
+
<radialGradient id="_r_b_" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
|
14
|
+
gradientTransform="translate(256) rotate(90) scale(512)">
|
|
15
|
+
<stop stop-color="white"></stop>
|
|
16
|
+
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
|
|
17
|
+
</radialGradient>
|
|
18
|
+
</defs>
|
|
19
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" width="352" height="352" x="80" y="80"
|
|
20
|
+
alignment-baseline="middle" style="color: rgb(255, 213, 213); width: 352px; height: 352px;">
|
|
21
|
+
<path fill="currentColor" fill-rule="evenodd"
|
|
22
|
+
d="M4.136 1H4.865c.401 0 .748 0 1.034.024.301.025.6.08.888.23.411.213.746.548.96.959.149.287.204.587.23.888C8 3.387 8 3.734 8 4.136v7.729c0 .401 0 .748-.024 1.034-.025.301-.08.6-.23.888a2.25 2.25 0 0 1-.959.96c-.287.149-.587.204-.888.23C5.613 15 5.266 15 4.864 15H4.135c-.401 0-.748 0-1.034-.024-.301-.025-.6-.08-.888-.23a2.25 2.25 0 0 1-.96-.959c-.149-.287-.204-.587-.23-.888A13.435 13.435 0 0 1 1 11.864V4.135c0-.401 0-.748.024-1.034.025-.301.08-.6.23-.888a2.25 2.25 0 0 1 .959-.96c.287-.149.587-.204.888-.23C3.387 1 3.734 1 4.136 1Zm-.91 1.519c-.208.017-.284.046-.322.065a.75.75 0 0 0-.32.32c-.02.038-.048.114-.065.321-.018.216-.019.5-.019.94v7.67c0 .44 0 .724.019.94.017.207.046.283.065.32a.75.75 0 0 0 .32.32c.038.02.114.05.321.067.216.017.5.018.94.018h.67c.44 0 .724 0 .94-.018.207-.018.283-.047.32-.066a.75.75 0 0 0 .32-.32c.02-.038.05-.114.066-.321.018-.216.019-.5.019-.94v-7.67c0-.44 0-.724-.019-.94-.017-.207-.046-.283-.065-.32a.75.75 0 0 0-.32-.32c-.038-.02-.114-.05-.321-.066-.216-.018-.5-.019-.94-.019h-.67c-.44 0-.724 0-.94.019ZM11.975 8h.048c.329 0 .613 0 .848.016.247.017.495.054.739.155.551.229.99.667 1.218 1.218.101.244.138.492.155.74.016.234.016.518.016.847v1.048c0 .329 0 .613-.016.848a2.3 2.3 0 0 1-.155.739 2.25 2.25 0 0 1-1.218 1.218 2.3 2.3 0 0 1-.74.155c-.234.016-.518.016-.847.016h-.048c-.329 0-.613 0-.848-.016a2.3 2.3 0 0 1-.739-.155 2.25 2.25 0 0 1-1.218-1.218 2.301 2.301 0 0 1-.155-.74C9 12.638 9 12.354 9 12.025v-1.048c0-.329 0-.613.016-.848.017-.247.054-.495.155-.739a2.25 2.25 0 0 1 1.218-1.218c.244-.101.492-.138.74-.155.234-.016.518-.016.847-.016Zm-.746 1.513a.87.87 0 0 0-.267.044.75.75 0 0 0-.406.406.871.871 0 0 0-.045.267c-.012.178-.012.41-.012.77v1c0 .36 0 .592.012.77a.871.871 0 0 0 .045.267.75.75 0 0 0 .406.406.871.871 0 0 0 .267.045c.178.012.41.012.77.012.36 0 .592 0 .77-.012a.871.871 0 0 0 .267-.045.75.75 0 0 0 .406-.406.871.871 0 0 0 .045-.267c.012-.178.012-.41.012-.77v-1c0-.36 0-.592-.012-.77a.871.871 0 0 0-.045-.267.75.75 0 0 0-.406-.406.87.87 0 0 0-.267-.044C12.592 9.5 12.36 9.5 12 9.5c-.36 0-.592 0-.77.013ZM12 1h-.024c-.329 0-.613 0-.848.016a2.301 2.301 0 0 0-.739.155A2.25 2.25 0 0 0 9.171 2.39a2.302 2.302 0 0 0-.155.74C9 3.362 9 3.646 9 3.975v.048c0 .329 0 .613.016.848.017.247.054.495.155.739.229.551.667.99 1.218 1.218.244.101.492.138.74.155.234.016.518.016.847.016h.048c.329 0 .613 0 .848-.016.247-.017.495-.054.739-.155a2.25 2.25 0 0 0 1.218-1.218c.101-.244.138-.492.155-.74.016-.234.016-.518.016-.847v-.048c0-.329 0-.613-.016-.848a2.301 2.301 0 0 0-.155-.739 2.25 2.25 0 0 0-1.218-1.218 2.301 2.301 0 0 0-.74-.155C12.638 1 12.354 1 12.025 1H12Zm-1.037 1.557a.87.87 0 0 1 .267-.044c.178-.013.41-.013.77-.013.36 0 .592 0 .77.013a.87.87 0 0 1 .267.044.75.75 0 0 1 .406.406.871.871 0 0 1 .045.267c.012.178.012.41.012.77 0 .36 0 .592-.012.77a.871.871 0 0 1-.045.267.75.75 0 0 1-.406.406.87.87 0 0 1-.267.044c-.178.013-.41.013-.77.013-.36 0-.592 0-.77-.013a.87.87 0 0 1-.267-.044.75.75 0 0 1-.406-.406.871.871 0 0 1-.045-.267C10.5 4.592 10.5 4.36 10.5 4c0-.36 0-.592.012-.77a.871.871 0 0 1 .045-.267.75.75 0 0 1 .406-.406Z"
|
|
23
|
+
clip-rule="evenodd"></path>
|
|
24
|
+
</svg>
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Built-in Blocks",
|
|
3
|
+
"description": "Core workflow blocks for BRIKA automations",
|
|
4
|
+
"blocks": {
|
|
5
|
+
"clock": {
|
|
6
|
+
"name": "Clock",
|
|
7
|
+
"description": "Emit periodic ticks at a configurable interval",
|
|
8
|
+
"ports": {
|
|
9
|
+
"tick": {
|
|
10
|
+
"name": "Tick",
|
|
11
|
+
"description": "Emitted on each interval with count and timestamp"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"http-request": {
|
|
16
|
+
"name": "HTTP Request",
|
|
17
|
+
"description": "Make HTTP requests to external APIs",
|
|
18
|
+
"ports": {
|
|
19
|
+
"trigger": {
|
|
20
|
+
"name": "Trigger",
|
|
21
|
+
"description": "Trigger the HTTP request"
|
|
22
|
+
},
|
|
23
|
+
"response": {
|
|
24
|
+
"name": "Response",
|
|
25
|
+
"description": "HTTP response with status, headers, and body"
|
|
26
|
+
},
|
|
27
|
+
"error": {
|
|
28
|
+
"name": "Error",
|
|
29
|
+
"description": "Error details if request fails"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"condition": {
|
|
34
|
+
"name": "Condition",
|
|
35
|
+
"description": "Branch based on a condition",
|
|
36
|
+
"ports": {
|
|
37
|
+
"in": {
|
|
38
|
+
"name": "Input",
|
|
39
|
+
"description": "Value to check"
|
|
40
|
+
},
|
|
41
|
+
"then": {
|
|
42
|
+
"name": "Then",
|
|
43
|
+
"description": "Output when condition is true"
|
|
44
|
+
},
|
|
45
|
+
"else": {
|
|
46
|
+
"name": "Else",
|
|
47
|
+
"description": "Output when condition is false"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"switch": {
|
|
52
|
+
"name": "Switch",
|
|
53
|
+
"description": "Multi-way branch based on value",
|
|
54
|
+
"ports": {
|
|
55
|
+
"in": {
|
|
56
|
+
"name": "Input",
|
|
57
|
+
"description": "Value to check"
|
|
58
|
+
},
|
|
59
|
+
"case1": {
|
|
60
|
+
"name": "Case 1",
|
|
61
|
+
"description": "First case output"
|
|
62
|
+
},
|
|
63
|
+
"case2": {
|
|
64
|
+
"name": "Case 2",
|
|
65
|
+
"description": "Second case output"
|
|
66
|
+
},
|
|
67
|
+
"case3": {
|
|
68
|
+
"name": "Case 3",
|
|
69
|
+
"description": "Third case output"
|
|
70
|
+
},
|
|
71
|
+
"default": {
|
|
72
|
+
"name": "Default",
|
|
73
|
+
"description": "Output when no case matches"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"delay": {
|
|
78
|
+
"name": "Delay",
|
|
79
|
+
"description": "Wait for a specified duration",
|
|
80
|
+
"ports": {
|
|
81
|
+
"in": {
|
|
82
|
+
"name": "Input",
|
|
83
|
+
"description": "Data to delay"
|
|
84
|
+
},
|
|
85
|
+
"out": {
|
|
86
|
+
"name": "Output",
|
|
87
|
+
"description": "Delayed data"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"transform": {
|
|
92
|
+
"name": "Transform",
|
|
93
|
+
"description": "Transform or extract data",
|
|
94
|
+
"ports": {
|
|
95
|
+
"in": {
|
|
96
|
+
"name": "Input",
|
|
97
|
+
"description": "Data to transform"
|
|
98
|
+
},
|
|
99
|
+
"out": {
|
|
100
|
+
"name": "Output",
|
|
101
|
+
"description": "Transformed data"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"log": {
|
|
106
|
+
"name": "Log",
|
|
107
|
+
"description": "Log a message",
|
|
108
|
+
"ports": {
|
|
109
|
+
"in": {
|
|
110
|
+
"name": "Input",
|
|
111
|
+
"description": "Data to log"
|
|
112
|
+
},
|
|
113
|
+
"out": {
|
|
114
|
+
"name": "Output",
|
|
115
|
+
"description": "Passthrough data"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"merge": {
|
|
120
|
+
"name": "Merge",
|
|
121
|
+
"description": "Wait for and merge multiple inputs",
|
|
122
|
+
"ports": {
|
|
123
|
+
"a": {
|
|
124
|
+
"name": "Input A",
|
|
125
|
+
"description": "First input"
|
|
126
|
+
},
|
|
127
|
+
"b": {
|
|
128
|
+
"name": "Input B",
|
|
129
|
+
"description": "Second input"
|
|
130
|
+
},
|
|
131
|
+
"out": {
|
|
132
|
+
"name": "Output",
|
|
133
|
+
"description": "Combined inputs"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"split": {
|
|
138
|
+
"name": "Split",
|
|
139
|
+
"description": "Split execution to parallel branches",
|
|
140
|
+
"ports": {
|
|
141
|
+
"in": {
|
|
142
|
+
"name": "Input",
|
|
143
|
+
"description": "Data to split"
|
|
144
|
+
},
|
|
145
|
+
"a": {
|
|
146
|
+
"name": "Branch A",
|
|
147
|
+
"description": "First branch"
|
|
148
|
+
},
|
|
149
|
+
"b": {
|
|
150
|
+
"name": "Branch B",
|
|
151
|
+
"description": "Second branch"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"end": {
|
|
156
|
+
"name": "End",
|
|
157
|
+
"description": "Terminate the workflow",
|
|
158
|
+
"ports": {
|
|
159
|
+
"in": {
|
|
160
|
+
"name": "Input",
|
|
161
|
+
"description": "Final data"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"fields": {
|
|
167
|
+
"field": "Field path to check",
|
|
168
|
+
"operator": "Comparison operator",
|
|
169
|
+
"value": "Value to compare against",
|
|
170
|
+
"duration": "Wait duration",
|
|
171
|
+
"message": "Message to log",
|
|
172
|
+
"level": "Log level",
|
|
173
|
+
"status": "End status",
|
|
174
|
+
"template": "Output template",
|
|
175
|
+
"interval": "Tick interval in milliseconds",
|
|
176
|
+
"emitOnStart": "Emit an initial tick immediately",
|
|
177
|
+
"url": "Request URL",
|
|
178
|
+
"method": "HTTP method",
|
|
179
|
+
"headers": "Request headers",
|
|
180
|
+
"body": "Request body",
|
|
181
|
+
"timeout": "Request timeout in milliseconds"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Blocs intégrés",
|
|
3
|
+
"description": "Blocs de workflow de base pour les automatisations BRIKA",
|
|
4
|
+
"blocks": {
|
|
5
|
+
"clock": {
|
|
6
|
+
"name": "Horloge",
|
|
7
|
+
"description": "Émettre des ticks périodiques à un intervalle configurable",
|
|
8
|
+
"ports": {
|
|
9
|
+
"tick": {
|
|
10
|
+
"name": "Tick",
|
|
11
|
+
"description": "Émis à chaque intervalle avec compteur et horodatage"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"http-request": {
|
|
16
|
+
"name": "Requête HTTP",
|
|
17
|
+
"description": "Effectuer des requêtes HTTP vers des APIs externes",
|
|
18
|
+
"ports": {
|
|
19
|
+
"trigger": {
|
|
20
|
+
"name": "Déclencheur",
|
|
21
|
+
"description": "Déclencher la requête HTTP"
|
|
22
|
+
},
|
|
23
|
+
"response": {
|
|
24
|
+
"name": "Réponse",
|
|
25
|
+
"description": "Réponse HTTP avec statut, en-têtes et corps"
|
|
26
|
+
},
|
|
27
|
+
"error": {
|
|
28
|
+
"name": "Erreur",
|
|
29
|
+
"description": "Détails de l'erreur si la requête échoue"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"condition": {
|
|
34
|
+
"name": "Condition",
|
|
35
|
+
"description": "Brancher selon une condition",
|
|
36
|
+
"ports": {
|
|
37
|
+
"in": {
|
|
38
|
+
"name": "Entrée",
|
|
39
|
+
"description": "Valeur à vérifier"
|
|
40
|
+
},
|
|
41
|
+
"then": {
|
|
42
|
+
"name": "Alors",
|
|
43
|
+
"description": "Sortie si la condition est vraie"
|
|
44
|
+
},
|
|
45
|
+
"else": {
|
|
46
|
+
"name": "Sinon",
|
|
47
|
+
"description": "Sortie si la condition est fausse"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"switch": {
|
|
52
|
+
"name": "Aiguillage",
|
|
53
|
+
"description": "Branchement multiple selon une valeur",
|
|
54
|
+
"ports": {
|
|
55
|
+
"in": {
|
|
56
|
+
"name": "Entrée",
|
|
57
|
+
"description": "Valeur à vérifier"
|
|
58
|
+
},
|
|
59
|
+
"case1": {
|
|
60
|
+
"name": "Cas 1",
|
|
61
|
+
"description": "Première sortie"
|
|
62
|
+
},
|
|
63
|
+
"case2": {
|
|
64
|
+
"name": "Cas 2",
|
|
65
|
+
"description": "Deuxième sortie"
|
|
66
|
+
},
|
|
67
|
+
"case3": {
|
|
68
|
+
"name": "Cas 3",
|
|
69
|
+
"description": "Troisième sortie"
|
|
70
|
+
},
|
|
71
|
+
"default": {
|
|
72
|
+
"name": "Défaut",
|
|
73
|
+
"description": "Sortie si aucun cas ne correspond"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"delay": {
|
|
78
|
+
"name": "Délai",
|
|
79
|
+
"description": "Attendre une durée spécifiée",
|
|
80
|
+
"ports": {
|
|
81
|
+
"in": {
|
|
82
|
+
"name": "Entrée",
|
|
83
|
+
"description": "Données à retarder"
|
|
84
|
+
},
|
|
85
|
+
"out": {
|
|
86
|
+
"name": "Sortie",
|
|
87
|
+
"description": "Données retardées"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"transform": {
|
|
92
|
+
"name": "Transformer",
|
|
93
|
+
"description": "Transformer ou extraire des données",
|
|
94
|
+
"ports": {
|
|
95
|
+
"in": {
|
|
96
|
+
"name": "Entrée",
|
|
97
|
+
"description": "Données à transformer"
|
|
98
|
+
},
|
|
99
|
+
"out": {
|
|
100
|
+
"name": "Sortie",
|
|
101
|
+
"description": "Données transformées"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"log": {
|
|
106
|
+
"name": "Journal",
|
|
107
|
+
"description": "Enregistrer un message",
|
|
108
|
+
"ports": {
|
|
109
|
+
"in": {
|
|
110
|
+
"name": "Entrée",
|
|
111
|
+
"description": "Données à enregistrer"
|
|
112
|
+
},
|
|
113
|
+
"out": {
|
|
114
|
+
"name": "Sortie",
|
|
115
|
+
"description": "Données passantes"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"merge": {
|
|
120
|
+
"name": "Fusionner",
|
|
121
|
+
"description": "Attendre et fusionner plusieurs entrées",
|
|
122
|
+
"ports": {
|
|
123
|
+
"a": {
|
|
124
|
+
"name": "Entrée A",
|
|
125
|
+
"description": "Première entrée"
|
|
126
|
+
},
|
|
127
|
+
"b": {
|
|
128
|
+
"name": "Entrée B",
|
|
129
|
+
"description": "Deuxième entrée"
|
|
130
|
+
},
|
|
131
|
+
"out": {
|
|
132
|
+
"name": "Sortie",
|
|
133
|
+
"description": "Entrées combinées"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"split": {
|
|
138
|
+
"name": "Diviser",
|
|
139
|
+
"description": "Diviser l'exécution en branches parallèles",
|
|
140
|
+
"ports": {
|
|
141
|
+
"in": {
|
|
142
|
+
"name": "Entrée",
|
|
143
|
+
"description": "Données à diviser"
|
|
144
|
+
},
|
|
145
|
+
"a": {
|
|
146
|
+
"name": "Branche A",
|
|
147
|
+
"description": "Première branche"
|
|
148
|
+
},
|
|
149
|
+
"b": {
|
|
150
|
+
"name": "Branche B",
|
|
151
|
+
"description": "Deuxième branche"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"end": {
|
|
156
|
+
"name": "Fin",
|
|
157
|
+
"description": "Terminer le workflow",
|
|
158
|
+
"ports": {
|
|
159
|
+
"in": {
|
|
160
|
+
"name": "Entrée",
|
|
161
|
+
"description": "Données finales"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"fields": {
|
|
167
|
+
"field": "Chemin du champ à vérifier",
|
|
168
|
+
"operator": "Opérateur de comparaison",
|
|
169
|
+
"value": "Valeur à comparer",
|
|
170
|
+
"duration": "Durée d'attente",
|
|
171
|
+
"message": "Message à enregistrer",
|
|
172
|
+
"level": "Niveau de journal",
|
|
173
|
+
"status": "Statut de fin",
|
|
174
|
+
"template": "Modèle de sortie",
|
|
175
|
+
"interval": "Intervalle en millisecondes",
|
|
176
|
+
"emitOnStart": "Émettre un tick initial immédiatement",
|
|
177
|
+
"url": "URL de la requête",
|
|
178
|
+
"method": "Méthode HTTP",
|
|
179
|
+
"headers": "En-têtes de la requête",
|
|
180
|
+
"body": "Corps de la requête",
|
|
181
|
+
"timeout": "Délai d'expiration en millisecondes"
|
|
182
|
+
}
|
|
183
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.brika.dev/plugin.schema.json",
|
|
3
|
+
"name": "@brika/blocks-builtin",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "Core workflow blocks for BRIKA automations",
|
|
6
|
+
"author": "BRIKA Team",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/maxscharwath/brika/tree/main/plugins/blocks-builtin#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/maxscharwath/brika/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/maxscharwath/brika.git",
|
|
15
|
+
"directory": "plugins/blocks-builtin"
|
|
16
|
+
},
|
|
17
|
+
"icon": "./icon.svg",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"brika",
|
|
20
|
+
"blocks",
|
|
21
|
+
"workflow",
|
|
22
|
+
"automation",
|
|
23
|
+
"condition",
|
|
24
|
+
"delay",
|
|
25
|
+
"home-automation",
|
|
26
|
+
"visual-programming"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"brika": "^0.2.0"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./src/main.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./src/main.ts"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src",
|
|
38
|
+
"locales",
|
|
39
|
+
"icon.svg",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"link": "bun link",
|
|
44
|
+
"tsc": "bunx --biome tsc --noEmit",
|
|
45
|
+
"prepublishOnly": "bun run tsc"
|
|
46
|
+
},
|
|
47
|
+
"blocks": [
|
|
48
|
+
{
|
|
49
|
+
"id": "clock",
|
|
50
|
+
"name": "Clock",
|
|
51
|
+
"description": "Emit periodic ticks on an interval",
|
|
52
|
+
"category": "trigger",
|
|
53
|
+
"icon": "clock",
|
|
54
|
+
"color": "#22c55e"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "http-request",
|
|
58
|
+
"name": "HTTP Request",
|
|
59
|
+
"description": "Make HTTP requests to external APIs",
|
|
60
|
+
"category": "action",
|
|
61
|
+
"icon": "globe",
|
|
62
|
+
"color": "#3b82f6"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "condition",
|
|
66
|
+
"name": "Condition",
|
|
67
|
+
"description": "Branch based on a condition",
|
|
68
|
+
"category": "flow",
|
|
69
|
+
"icon": "git-branch",
|
|
70
|
+
"color": "#f59e0b"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "switch",
|
|
74
|
+
"name": "Switch",
|
|
75
|
+
"description": "Multi-way branch based on a value",
|
|
76
|
+
"category": "flow",
|
|
77
|
+
"icon": "shuffle",
|
|
78
|
+
"color": "#8b5cf6"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "delay",
|
|
82
|
+
"name": "Delay",
|
|
83
|
+
"description": "Wait for a duration before continuing",
|
|
84
|
+
"category": "flow",
|
|
85
|
+
"icon": "timer",
|
|
86
|
+
"color": "#6b7280"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "transform",
|
|
90
|
+
"name": "Transform",
|
|
91
|
+
"description": "Transform or extract data fields",
|
|
92
|
+
"category": "transform",
|
|
93
|
+
"icon": "edit",
|
|
94
|
+
"color": "#ec4899"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "log",
|
|
98
|
+
"name": "Log",
|
|
99
|
+
"description": "Log a message to the console",
|
|
100
|
+
"category": "action",
|
|
101
|
+
"icon": "file-text",
|
|
102
|
+
"color": "#78716c"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "merge",
|
|
106
|
+
"name": "Merge",
|
|
107
|
+
"description": "Wait for multiple inputs before continuing",
|
|
108
|
+
"category": "flow",
|
|
109
|
+
"icon": "git-merge",
|
|
110
|
+
"color": "#06b6d4"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "split",
|
|
114
|
+
"name": "Split",
|
|
115
|
+
"description": "Split into parallel branches",
|
|
116
|
+
"category": "flow",
|
|
117
|
+
"icon": "git-fork",
|
|
118
|
+
"color": "#a855f7"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "end",
|
|
122
|
+
"name": "End",
|
|
123
|
+
"description": "End the workflow branch",
|
|
124
|
+
"category": "action",
|
|
125
|
+
"icon": "square",
|
|
126
|
+
"color": "#dc2626"
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"dependencies": {
|
|
130
|
+
"@brika/sdk": "0.1.1",
|
|
131
|
+
"zod": "^4.3.4"
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Blocks Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides core workflow blocks for BRIKA automations.
|
|
5
|
+
* All blocks use the reactive defineReactiveBlock API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
combine,
|
|
10
|
+
defineReactiveBlock,
|
|
11
|
+
delay as delayOp,
|
|
12
|
+
input,
|
|
13
|
+
interval,
|
|
14
|
+
log,
|
|
15
|
+
map,
|
|
16
|
+
output,
|
|
17
|
+
type Serializable,
|
|
18
|
+
z,
|
|
19
|
+
} from '@brika/sdk';
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Action Block - Call a tool with arguments
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
// HTTP Response schema
|
|
26
|
+
const HttpResponseSchema = z.object({
|
|
27
|
+
status: z.number(),
|
|
28
|
+
statusText: z.string(),
|
|
29
|
+
headers: z.record(z.string(), z.string()),
|
|
30
|
+
body: z.any(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const httpRequest = defineReactiveBlock(
|
|
34
|
+
{
|
|
35
|
+
id: 'http-request',
|
|
36
|
+
name: 'HTTP Request',
|
|
37
|
+
description: 'Make HTTP requests to external APIs',
|
|
38
|
+
category: 'action',
|
|
39
|
+
icon: 'globe',
|
|
40
|
+
color: '#3b82f6',
|
|
41
|
+
inputs: {
|
|
42
|
+
trigger: input(z.generic(), { name: 'Trigger' }),
|
|
43
|
+
},
|
|
44
|
+
outputs: {
|
|
45
|
+
response: output(HttpResponseSchema, { name: 'Response' }),
|
|
46
|
+
error: output(z.object({ message: z.string() }), { name: 'Error' }),
|
|
47
|
+
},
|
|
48
|
+
config: z.object({
|
|
49
|
+
url: z.string().describe('Request URL'),
|
|
50
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).optional().describe('HTTP method'),
|
|
51
|
+
headers: z.record(z.string(), z.string()).optional().describe('Request headers'),
|
|
52
|
+
body: z.string().optional().describe('Request body (for POST/PUT/PATCH)'),
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
({ inputs, outputs, config, log }) => {
|
|
56
|
+
inputs.trigger.on(async () => {
|
|
57
|
+
log('debug', `HTTP ${config.method ?? 'GET'} ${config.url}`);
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(config.url, {
|
|
60
|
+
method: config.method ?? 'GET',
|
|
61
|
+
headers: config.headers,
|
|
62
|
+
body: config.body,
|
|
63
|
+
});
|
|
64
|
+
const body = await res.json().catch(() => res.text());
|
|
65
|
+
outputs.response.emit({
|
|
66
|
+
status: res.status,
|
|
67
|
+
statusText: res.statusText,
|
|
68
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
69
|
+
body,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
log('error', `HTTP request failed: ${err}`);
|
|
73
|
+
outputs.error.emit({ message: String(err) });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
// Condition Block - Branch based on a condition
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
export const condition = defineReactiveBlock(
|
|
84
|
+
{
|
|
85
|
+
id: 'condition',
|
|
86
|
+
name: 'Condition',
|
|
87
|
+
description: 'Branch based on a condition',
|
|
88
|
+
category: 'flow',
|
|
89
|
+
icon: 'git-branch',
|
|
90
|
+
color: '#f59e0b',
|
|
91
|
+
inputs: {
|
|
92
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
93
|
+
},
|
|
94
|
+
outputs: {
|
|
95
|
+
then: output(z.passthrough('in'), { name: 'Then' }),
|
|
96
|
+
else: output(z.passthrough('in'), { name: 'Else' }),
|
|
97
|
+
},
|
|
98
|
+
config: z.object({
|
|
99
|
+
field: z.string().describe('Field path to check (e.g., "value", "data.status")'),
|
|
100
|
+
operator: z
|
|
101
|
+
.enum(['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'contains', 'exists'])
|
|
102
|
+
.describe('Comparison operator'),
|
|
103
|
+
value: z.any().optional().describe('Value to compare against'),
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
({ inputs, outputs, config, log }) => {
|
|
107
|
+
inputs.in.on((data) => {
|
|
108
|
+
const fieldValue = getFieldValue(data, config.field);
|
|
109
|
+
const result = evaluate(fieldValue, config.operator, config.value);
|
|
110
|
+
log('debug', `Condition: ${config.field} ${config.operator} ${config.value} = ${result}`);
|
|
111
|
+
|
|
112
|
+
if (result) {
|
|
113
|
+
outputs.then.emit(data);
|
|
114
|
+
} else {
|
|
115
|
+
outputs.else.emit(data);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
function getFieldValue(data: unknown, path: string): unknown {
|
|
122
|
+
if (data === null || data === undefined) return undefined;
|
|
123
|
+
const parts = path.split('.');
|
|
124
|
+
let current: unknown = data;
|
|
125
|
+
for (const part of parts) {
|
|
126
|
+
if (current === null || current === undefined) return undefined;
|
|
127
|
+
if (typeof current !== 'object') return undefined;
|
|
128
|
+
current = (current as Record<string, unknown>)[part];
|
|
129
|
+
}
|
|
130
|
+
return current;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function evaluate(fieldValue: unknown, operator: string, compareValue: unknown): boolean {
|
|
134
|
+
switch (operator) {
|
|
135
|
+
case 'eq':
|
|
136
|
+
return fieldValue === compareValue;
|
|
137
|
+
case 'neq':
|
|
138
|
+
return fieldValue !== compareValue;
|
|
139
|
+
case 'gt':
|
|
140
|
+
return Number(fieldValue) > Number(compareValue);
|
|
141
|
+
case 'gte':
|
|
142
|
+
return Number(fieldValue) >= Number(compareValue);
|
|
143
|
+
case 'lt':
|
|
144
|
+
return Number(fieldValue) < Number(compareValue);
|
|
145
|
+
case 'lte':
|
|
146
|
+
return Number(fieldValue) <= Number(compareValue);
|
|
147
|
+
case 'contains':
|
|
148
|
+
return String(fieldValue).includes(String(compareValue));
|
|
149
|
+
case 'exists':
|
|
150
|
+
return fieldValue !== undefined && fieldValue !== null;
|
|
151
|
+
default:
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
// Switch Block - Multi-way branch
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
export const switchBlock = defineReactiveBlock(
|
|
161
|
+
{
|
|
162
|
+
id: 'switch',
|
|
163
|
+
name: 'Switch',
|
|
164
|
+
description: 'Multi-way branch based on a value',
|
|
165
|
+
category: 'flow',
|
|
166
|
+
icon: 'shuffle',
|
|
167
|
+
color: '#8b5cf6',
|
|
168
|
+
inputs: {
|
|
169
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
170
|
+
},
|
|
171
|
+
outputs: {
|
|
172
|
+
case1: output(z.passthrough('in'), { name: 'Case 1' }),
|
|
173
|
+
case2: output(z.passthrough('in'), { name: 'Case 2' }),
|
|
174
|
+
case3: output(z.passthrough('in'), { name: 'Case 3' }),
|
|
175
|
+
default: output(z.passthrough('in'), { name: 'Default' }),
|
|
176
|
+
},
|
|
177
|
+
config: z.object({
|
|
178
|
+
field: z.string().describe('Field path to check'),
|
|
179
|
+
case1: z.any().optional().describe('Value for case 1'),
|
|
180
|
+
case2: z.any().optional().describe('Value for case 2'),
|
|
181
|
+
case3: z.any().optional().describe('Value for case 3'),
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
({ inputs, outputs, config, log }) => {
|
|
185
|
+
inputs.in.on((data) => {
|
|
186
|
+
const value = getFieldValue(data, config.field);
|
|
187
|
+
log('debug', `Switch value: ${JSON.stringify(value)}`);
|
|
188
|
+
|
|
189
|
+
if (value === config.case1) {
|
|
190
|
+
outputs.case1.emit(data);
|
|
191
|
+
} else if (value === config.case2) {
|
|
192
|
+
outputs.case2.emit(data);
|
|
193
|
+
} else if (value === config.case3) {
|
|
194
|
+
outputs.case3.emit(data);
|
|
195
|
+
} else {
|
|
196
|
+
outputs.default.emit(data);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
// Delay Block - Wait for a duration
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
export const delay = defineReactiveBlock(
|
|
207
|
+
{
|
|
208
|
+
id: 'delay',
|
|
209
|
+
name: 'Delay',
|
|
210
|
+
description: 'Wait for a duration before continuing',
|
|
211
|
+
category: 'flow',
|
|
212
|
+
icon: 'timer',
|
|
213
|
+
color: '#6b7280',
|
|
214
|
+
inputs: {
|
|
215
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
216
|
+
},
|
|
217
|
+
outputs: {
|
|
218
|
+
out: output(z.passthrough('in'), { name: 'Output' }),
|
|
219
|
+
},
|
|
220
|
+
config: z.object({
|
|
221
|
+
duration: z.duration(undefined, 'Duration to wait'),
|
|
222
|
+
}),
|
|
223
|
+
},
|
|
224
|
+
({ inputs, outputs, config, log }) => {
|
|
225
|
+
log('debug', `Delay configured: ${config.duration}ms`);
|
|
226
|
+
|
|
227
|
+
// Use delay operator to wait before emitting
|
|
228
|
+
inputs.in.pipe(delayOp(config.duration)).to(outputs.out);
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
233
|
+
// Emit Block - Emit an event
|
|
234
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
export const clock = defineReactiveBlock(
|
|
237
|
+
{
|
|
238
|
+
id: 'clock',
|
|
239
|
+
name: 'Clock',
|
|
240
|
+
description: 'Emit periodic ticks on an interval',
|
|
241
|
+
category: 'trigger',
|
|
242
|
+
icon: 'clock',
|
|
243
|
+
color: '#22c55e',
|
|
244
|
+
inputs: {},
|
|
245
|
+
outputs: {
|
|
246
|
+
tick: output(z.object({ count: z.number(), ts: z.number() }), { name: 'Tick' }),
|
|
247
|
+
},
|
|
248
|
+
config: z.object({
|
|
249
|
+
interval: z.duration(undefined, 'Interval between ticks'),
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
({ outputs, config, log, start }) => {
|
|
253
|
+
start(interval(config.interval))
|
|
254
|
+
.pipe(
|
|
255
|
+
map((count) => {
|
|
256
|
+
return { count: count + 1, ts: Date.now() };
|
|
257
|
+
})
|
|
258
|
+
)
|
|
259
|
+
.to(outputs.tick);
|
|
260
|
+
log('info', `Clock started with interval: ${config.interval}ms`);
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
265
|
+
// Transform Block - Transform data
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
export const transform = defineReactiveBlock(
|
|
269
|
+
{
|
|
270
|
+
id: 'transform',
|
|
271
|
+
name: 'Transform',
|
|
272
|
+
description: 'Transform or extract data',
|
|
273
|
+
category: 'transform',
|
|
274
|
+
icon: 'edit',
|
|
275
|
+
color: '#ec4899',
|
|
276
|
+
inputs: {
|
|
277
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
278
|
+
},
|
|
279
|
+
outputs: {
|
|
280
|
+
out: output(z.any(), { name: 'Output' }),
|
|
281
|
+
},
|
|
282
|
+
config: z.object({
|
|
283
|
+
field: z.string().optional().describe('Field to extract (empty for passthrough)'),
|
|
284
|
+
template: z.record(z.string(), z.string()).optional().describe('Template to build output'),
|
|
285
|
+
}),
|
|
286
|
+
},
|
|
287
|
+
({ inputs, outputs, config, log }) => {
|
|
288
|
+
inputs.in
|
|
289
|
+
.pipe(
|
|
290
|
+
map((data) => {
|
|
291
|
+
// If template is provided, build object
|
|
292
|
+
if (config.template) {
|
|
293
|
+
const result: Record<string, unknown> = {};
|
|
294
|
+
for (const [key, path] of Object.entries(config.template)) {
|
|
295
|
+
result[key] = getFieldValue(data, path);
|
|
296
|
+
}
|
|
297
|
+
log('debug', `Transformed with template: ${JSON.stringify(result)}`);
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If field is provided, extract it
|
|
302
|
+
if (config.field) {
|
|
303
|
+
const value = getFieldValue(data, config.field);
|
|
304
|
+
log('debug', `Extracted field ${config.field}: ${JSON.stringify(value)}`);
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Passthrough
|
|
309
|
+
return data;
|
|
310
|
+
})
|
|
311
|
+
)
|
|
312
|
+
.to(outputs.out);
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
317
|
+
// Expression Interpolation
|
|
318
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Interpolate {{inputs.portId.field}} expressions in a template string.
|
|
322
|
+
*
|
|
323
|
+
* @param template Template string with {{...}} placeholders
|
|
324
|
+
* @param context Object containing available data: { inputs: { portId: data }, config: {...} }
|
|
325
|
+
*/
|
|
326
|
+
function interpolate(
|
|
327
|
+
template: string,
|
|
328
|
+
context: { inputs: Record<string, unknown>; config: Record<string, unknown> }
|
|
329
|
+
): string {
|
|
330
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_, expr: string) => {
|
|
331
|
+
const path = expr.trim().split('.');
|
|
332
|
+
let value: unknown = context;
|
|
333
|
+
|
|
334
|
+
for (const key of path) {
|
|
335
|
+
if (value === null || value === undefined) return '';
|
|
336
|
+
if (typeof value !== 'object') return '';
|
|
337
|
+
value = (value as Record<string, unknown>)[key];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (value === undefined || value === null) return '';
|
|
341
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
342
|
+
return String(value);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
347
|
+
// Log Block - Log a message
|
|
348
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
export const logBlock = defineReactiveBlock(
|
|
351
|
+
{
|
|
352
|
+
id: 'log',
|
|
353
|
+
name: 'Log',
|
|
354
|
+
description: 'Log a message with variable interpolation',
|
|
355
|
+
category: 'action',
|
|
356
|
+
icon: 'file-text',
|
|
357
|
+
color: '#78716c',
|
|
358
|
+
inputs: {
|
|
359
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
360
|
+
},
|
|
361
|
+
outputs: {
|
|
362
|
+
out: output(z.passthrough('in'), { name: 'Output' }),
|
|
363
|
+
},
|
|
364
|
+
config: z.object({
|
|
365
|
+
message: z.string().optional().describe('Message template with {{inputs.in.field}} expressions'),
|
|
366
|
+
level: z.enum(['debug', 'info', 'warn', 'error']).default('info').describe('Log level'),
|
|
367
|
+
}),
|
|
368
|
+
},
|
|
369
|
+
({ inputs, outputs, config, log }) => {
|
|
370
|
+
inputs.in.on((data) => {
|
|
371
|
+
const context = {
|
|
372
|
+
inputs: { in: data },
|
|
373
|
+
config: config as Record<string, unknown>,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const message = config.message
|
|
377
|
+
? interpolate(config.message, context)
|
|
378
|
+
: JSON.stringify(data);
|
|
379
|
+
|
|
380
|
+
log(config.level, message);
|
|
381
|
+
outputs.out.emit(data);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
387
|
+
// Merge Block - Wait for multiple inputs (combine)
|
|
388
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
export const merge = defineReactiveBlock(
|
|
391
|
+
{
|
|
392
|
+
id: 'merge',
|
|
393
|
+
name: 'Merge',
|
|
394
|
+
description: 'Wait for both inputs before continuing',
|
|
395
|
+
category: 'flow',
|
|
396
|
+
icon: 'git-merge',
|
|
397
|
+
color: '#06b6d4',
|
|
398
|
+
inputs: {
|
|
399
|
+
a: input(z.generic(), { name: 'Input A' }),
|
|
400
|
+
b: input(z.generic(), { name: 'Input B' }),
|
|
401
|
+
},
|
|
402
|
+
outputs: {
|
|
403
|
+
out: output(
|
|
404
|
+
z.object({
|
|
405
|
+
a: z.generic(),
|
|
406
|
+
b: z.generic(),
|
|
407
|
+
}),
|
|
408
|
+
{ name: 'Output' }
|
|
409
|
+
),
|
|
410
|
+
},
|
|
411
|
+
config: z.object({}),
|
|
412
|
+
},
|
|
413
|
+
({ inputs, outputs, log }) => {
|
|
414
|
+
// Combine waits for both inputs to have values
|
|
415
|
+
combine(inputs.a, inputs.b)
|
|
416
|
+
.pipe(
|
|
417
|
+
map(([a, b]) => {
|
|
418
|
+
log('debug', 'Merged inputs');
|
|
419
|
+
return { a, b };
|
|
420
|
+
})
|
|
421
|
+
)
|
|
422
|
+
.to(outputs.out);
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
427
|
+
// Split Block - Send to multiple outputs
|
|
428
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
429
|
+
|
|
430
|
+
export const split = defineReactiveBlock(
|
|
431
|
+
{
|
|
432
|
+
id: 'split',
|
|
433
|
+
name: 'Split',
|
|
434
|
+
description: 'Send data to multiple branches',
|
|
435
|
+
category: 'flow',
|
|
436
|
+
icon: 'git-fork',
|
|
437
|
+
color: '#a855f7',
|
|
438
|
+
inputs: {
|
|
439
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
440
|
+
},
|
|
441
|
+
outputs: {
|
|
442
|
+
a: output(z.passthrough('in'), { name: 'Branch A' }),
|
|
443
|
+
b: output(z.passthrough('in'), { name: 'Branch B' }),
|
|
444
|
+
},
|
|
445
|
+
config: z.object({}),
|
|
446
|
+
},
|
|
447
|
+
({ inputs, outputs, log }) => {
|
|
448
|
+
inputs.in.on((data) => {
|
|
449
|
+
log('debug', 'Splitting to parallel branches');
|
|
450
|
+
outputs.a.emit(data);
|
|
451
|
+
outputs.b.emit(data);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
457
|
+
// End Block - Terminal block
|
|
458
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
export const end = defineReactiveBlock(
|
|
461
|
+
{
|
|
462
|
+
id: 'end',
|
|
463
|
+
name: 'End',
|
|
464
|
+
description: 'End the workflow branch',
|
|
465
|
+
category: 'flow',
|
|
466
|
+
icon: 'square',
|
|
467
|
+
color: '#dc2626',
|
|
468
|
+
inputs: {
|
|
469
|
+
in: input(z.generic(), { name: 'Input' }),
|
|
470
|
+
},
|
|
471
|
+
outputs: {},
|
|
472
|
+
config: z.object({
|
|
473
|
+
status: z.enum(['success', 'failure']).default('success').describe('End status'),
|
|
474
|
+
}),
|
|
475
|
+
},
|
|
476
|
+
({ inputs, config, log }) => {
|
|
477
|
+
inputs.in.on((data) => {
|
|
478
|
+
log('info', `Workflow ended with status: ${config.status}`);
|
|
479
|
+
log('debug', `Final data: ${JSON.stringify(data)}`);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
log('info', 'Built-in blocks plugin loaded');
|