@unstable-dev/unmeshed-mcp 0.1.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 +64 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +11 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.js +97 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +31 -0
- package/dist/get-docs.d.ts +8 -0
- package/dist/get-docs.js +64 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +203 -0
- package/knowledge/README.md +16 -0
- package/knowledge/SKILL.md +359 -0
- package/knowledge/assets/patterns.md +637 -0
- package/knowledge/execution/debugging-guide.md +18 -0
- package/knowledge/execution/process-run.schema.md +24 -0
- package/knowledge/execution/step-run.schema.md +21 -0
- package/knowledge/process-definition.schema.md +36 -0
- package/knowledge/references/integrations.md +914 -0
- package/knowledge/references/steps-knowledge.md +834 -0
- package/knowledge/step-definition.schema.md +140 -0
- package/knowledge/step-output-paths.md +45 -0
- package/knowledge/steps/DECISION_ENGINE.md +248 -0
- package/knowledge/steps/DEPENDSON.md +296 -0
- package/knowledge/steps/EXIT.md +220 -0
- package/knowledge/steps/FAIL.md +198 -0
- package/knowledge/steps/FLOW_GATEWAY.md +405 -0
- package/knowledge/steps/FOREACH.md +250 -0
- package/knowledge/steps/HTTP.md +183 -0
- package/knowledge/steps/JAVASCRIPT.md +192 -0
- package/knowledge/steps/JQ.md +189 -0
- package/knowledge/steps/LIST.md +279 -0
- package/knowledge/steps/NOOP.md +165 -0
- package/knowledge/steps/PARALLEL.md +366 -0
- package/knowledge/steps/PYTHON.md +206 -0
- package/knowledge/steps/SEND_RESPONSE.md +301 -0
- package/knowledge/steps/SQLITE.md +301 -0
- package/knowledge/steps/SUB_PROCESS.md +296 -0
- package/knowledge/steps/SWITCH.md +369 -0
- package/knowledge/steps/UPDATE_STEP.md +257 -0
- package/knowledge/steps/WAIT.md +218 -0
- package/knowledge/steps/WHILE.md +328 -0
- package/knowledge/steps/WORKER.md +233 -0
- package/knowledge/system-prompt.md +274 -0
- package/package.json +39 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
# Unmeshed Step Types — Full Reference
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## JAVASCRIPT
|
|
6
|
+
|
|
7
|
+
Executes an inline JavaScript transformation. Input/output data mapping between steps.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"type": "JAVASCRIPT",
|
|
12
|
+
"input": {
|
|
13
|
+
"script": "(steps, context) => {\n // your logic here\n return { result: value };\n}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Rules:**
|
|
19
|
+
- Script signature is always `(steps, context) => { ... }`
|
|
20
|
+
- Access previous step output via `steps.<ref>.output.result.<field>`
|
|
21
|
+
- Access workflow input via `context.input.<field>`
|
|
22
|
+
- Access workflow/process metadata via `context.id`
|
|
23
|
+
- Must `return` an object — this becomes `output.result`
|
|
24
|
+
- Use `steps.__self.id` for current step ID
|
|
25
|
+
- Use `steps.__self.startTime` for the step's start timestamp (milliseconds) — useful in WAIT steps
|
|
26
|
+
- Use `steps.__self.executionList` — array of past execution attempts for this step (populated when an error policy retries). `steps.__self.executionList.length` = number of attempts so far.
|
|
27
|
+
- `preExecutionScript` in configuration can be used for setup (rare)
|
|
28
|
+
- To throw a test failure: `throw Error("Testcase failed")` — this causes the step and workflow to fail
|
|
29
|
+
|
|
30
|
+
**Accessing a prior step's INPUT** (not output) from a later step:
|
|
31
|
+
```javascript
|
|
32
|
+
steps.<ref>.input.body.content.<field> // HTTP step request body field
|
|
33
|
+
steps.<ref>.input.<field> // any step input field
|
|
34
|
+
```
|
|
35
|
+
**Accessing a prior JS/Python step's OUTPUT** from another step:
|
|
36
|
+
```javascript
|
|
37
|
+
steps.<ref>.output.result.<field>
|
|
38
|
+
```
|
|
39
|
+
**Accessing error output from an optional failed step:**
|
|
40
|
+
```javascript
|
|
41
|
+
const errorMessage = steps.my_optional_step.output.error;
|
|
42
|
+
if (errorMessage.includes("some error text")) { ... }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Return `__statePut` to update workflow state:**
|
|
46
|
+
```javascript
|
|
47
|
+
return {
|
|
48
|
+
"__statePut": { "count": (context.state.count || 0) + 1 }
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Deep equality helper** (no JSON.stringify, handles nested objects):
|
|
53
|
+
```javascript
|
|
54
|
+
function deepEqual(obj1, obj2) {
|
|
55
|
+
if (obj1 === obj2) return true;
|
|
56
|
+
if (typeof obj1 !== 'object' || obj1 === null ||
|
|
57
|
+
typeof obj2 !== 'object' || obj2 === null) return false;
|
|
58
|
+
const keys1 = Object.keys(obj1);
|
|
59
|
+
const keys2 = Object.keys(obj2);
|
|
60
|
+
if (keys1.length !== keys2.length) return false;
|
|
61
|
+
for (let key of keys1) {
|
|
62
|
+
if (!keys2.includes(key)) return false;
|
|
63
|
+
if (!deepEqual(obj1[key], obj2[key])) return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## PYTHON
|
|
72
|
+
|
|
73
|
+
Executes inline Python for heavier processing (file parsing, binary ops, filesystem access).
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"type": "PYTHON",
|
|
78
|
+
"input": {
|
|
79
|
+
"script": "def main(steps, context):\n # your logic\n return { 'key': 'value' }"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Rules:**
|
|
85
|
+
- Entry point is always `def main(steps, context):`
|
|
86
|
+
- `steps` and `context` are dicts — use `.get()` for safe access
|
|
87
|
+
- Files available at `/app/files/<location>/<filename>`
|
|
88
|
+
- Must return a dict
|
|
89
|
+
- Standard Python libraries available (os, re, zlib, json, base64, mimetypes, etc.)
|
|
90
|
+
- Output is under `steps.<ref>.output.result.<field>`
|
|
91
|
+
- Use `steps["<ref>"].output.result["<field>"]` when referring to output from another Python or JavaScript step
|
|
92
|
+
- Use `steps["__self"]["id"]` for current step ID inside Python
|
|
93
|
+
- Use `context.get("id")` for the workflow process/run ID
|
|
94
|
+
|
|
95
|
+
**File system patterns:**
|
|
96
|
+
```python
|
|
97
|
+
import os, base64, mimetypes
|
|
98
|
+
|
|
99
|
+
def main(steps, context):
|
|
100
|
+
base_dir = "/app/files/my-folder"
|
|
101
|
+
attachments = []
|
|
102
|
+
for filename in os.listdir(base_dir):
|
|
103
|
+
path = os.path.join(base_dir, filename)
|
|
104
|
+
if os.path.isfile(path):
|
|
105
|
+
with open(path, "rb") as f:
|
|
106
|
+
raw = f.read()
|
|
107
|
+
encoded = base64.b64encode(raw).decode("utf-8")
|
|
108
|
+
mime, _ = mimetypes.guess_type(filename)
|
|
109
|
+
attachments.append({
|
|
110
|
+
"name": filename,
|
|
111
|
+
"contentType": mime or "application/octet-stream",
|
|
112
|
+
"type": "CONTENT_BYTES",
|
|
113
|
+
"contentBase64": encoded
|
|
114
|
+
})
|
|
115
|
+
return {"attachments": attachments}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## preExecutionScript
|
|
121
|
+
|
|
122
|
+
`preExecutionScript` is supported for Python and JavaScript steps. It runs before the step executes and can override the step definition input at runtime.
|
|
123
|
+
|
|
124
|
+
Required fields:
|
|
125
|
+
- `preExecutionScript`: the actual script to execute
|
|
126
|
+
- `constructInputFromScript`: set to `true` to enable script-driven input construction
|
|
127
|
+
- `scriptLanguage`: `JAVASCRIPT` or `PYTHON` (`JAVASCRIPT` is the default when omitted)
|
|
128
|
+
|
|
129
|
+
The step schema fields are: `preExecutionScript`, `constructInputFromScript`, and `scriptLanguage`.
|
|
130
|
+
|
|
131
|
+
When enabled, the script runs before the step and its returned keys are merged into the step definition. Any returned values override the actual step configuration during runtime.
|
|
132
|
+
|
|
133
|
+
Example (JavaScript):
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"type": "HTTP",
|
|
137
|
+
"preExecutionScript": "(steps, context) => { return {\n url: `https://api.example.com/users/${steps.create_user.output.result.id}`,\n method: 'GET'\n }; }",
|
|
138
|
+
"constructInputFromScript": true,
|
|
139
|
+
"scriptLanguage": "JAVASCRIPT",
|
|
140
|
+
"input": {
|
|
141
|
+
"method": "GET",
|
|
142
|
+
"url": "https://placeholder.example.com",
|
|
143
|
+
"headers": {
|
|
144
|
+
"Accept": "application/json"
|
|
145
|
+
},
|
|
146
|
+
"params": {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Example (Python):
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"type": "HTTP",
|
|
155
|
+
"preExecutionScript": "def main(steps, context):\n return {\n 'url': f'https://api.example.com/users/{steps[\"create_user\"].output.result[\"id\"]}',\n 'method': 'GET'\n }\n",
|
|
156
|
+
"constructInputFromScript": true,
|
|
157
|
+
"scriptLanguage": "PYTHON",
|
|
158
|
+
"input": {
|
|
159
|
+
"method": "GET",
|
|
160
|
+
"url": "https://placeholder.example.com",
|
|
161
|
+
"headers": {
|
|
162
|
+
"Accept": "application/json"
|
|
163
|
+
},
|
|
164
|
+
"params": {}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Note:
|
|
170
|
+
- `preExecutionScript` must return an object
|
|
171
|
+
- returned fields override the step configuration
|
|
172
|
+
- the default `scriptLanguage` is `JAVASCRIPT`
|
|
173
|
+
|
|
174
|
+
## NOOP
|
|
175
|
+
|
|
176
|
+
A pass-through step that holds static configuration/constants and exposes them as output.
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"type": "NOOP",
|
|
181
|
+
"input": {
|
|
182
|
+
"anyKey": "anyStaticValue",
|
|
183
|
+
"nestedObject": { "field": "value" },
|
|
184
|
+
"arrayField": ["item1", "item2"]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Rules:**
|
|
190
|
+
- No computation — purely a data carrier
|
|
191
|
+
- All `input` fields become directly accessible as `steps.<ref>.output.<field>`
|
|
192
|
+
(note: NOT wrapped in `.result` — access directly)
|
|
193
|
+
- Ideal for job definitions, config objects, feature flags
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## INTEGRATION
|
|
198
|
+
|
|
199
|
+
Calls an external service. The `type` field in `input` determines which integration.
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"type": "INTEGRATION",
|
|
204
|
+
"input": {
|
|
205
|
+
"type": "<integration-type>",
|
|
206
|
+
"name": "<connection-name>",
|
|
207
|
+
"publishProperties": { /* integration-specific config */ },
|
|
208
|
+
"messageBody": { /* payload / prompts / operation */ }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
See `integrations.md` for full per-integration schemas.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## HTTP (native step — not INTEGRATION)
|
|
218
|
+
|
|
219
|
+
A dedicated HTTP step type (distinct from the `http` INTEGRATION). Used for direct HTTP calls with optional polling/repeat support.
|
|
220
|
+
|
|
221
|
+
### GET request
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"type": "HTTP",
|
|
225
|
+
"input": {
|
|
226
|
+
"method": "GET",
|
|
227
|
+
"url": "{{variables.dev_server}}/api/resource/{{steps.create.output.response.id}}",
|
|
228
|
+
"headers": {
|
|
229
|
+
"Content-Type": "application/json",
|
|
230
|
+
"Accept": "application/json",
|
|
231
|
+
"Authorization": "Bearer {{secrets.api_token}}"
|
|
232
|
+
},
|
|
233
|
+
"params": { "key": "value" },
|
|
234
|
+
"repeatUntilEnabled": null,
|
|
235
|
+
"repeatUntilCondition": { "script": "(steps, context) => { return false; }" },
|
|
236
|
+
"repeatIntervalSeconds": null,
|
|
237
|
+
"maxRepeatCount": null,
|
|
238
|
+
"includeFullResponseString": false,
|
|
239
|
+
"noEncode": false,
|
|
240
|
+
"extraLongTimeouts": false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### POST / PUT request (with body)
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"type": "HTTP",
|
|
249
|
+
"input": {
|
|
250
|
+
"method": "POST",
|
|
251
|
+
"url": "{{variables.dev_server}}/api/resource",
|
|
252
|
+
"headers": { "Content-Type": "application/json", "Authorization": "Bearer {{secrets.api_token}}" },
|
|
253
|
+
"params": {},
|
|
254
|
+
"body": {
|
|
255
|
+
"type": "json",
|
|
256
|
+
"content": {
|
|
257
|
+
"title": "foo",
|
|
258
|
+
"body": "{{ steps.prev_step.output.response.someField }}",
|
|
259
|
+
"userId": 1,
|
|
260
|
+
"fullObject": "{{ steps.prev_step.output.response }}"
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"repeatUntilEnabled": null,
|
|
264
|
+
"repeatUntilCondition": { "script": "(steps, context) => { return false; }" },
|
|
265
|
+
"repeatIntervalSeconds": null,
|
|
266
|
+
"maxRepeatCount": null,
|
|
267
|
+
"includeFullResponseString": false,
|
|
268
|
+
"noEncode": false,
|
|
269
|
+
"extraLongTimeouts": false
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### DELETE request
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"input": {
|
|
278
|
+
"method": "DELETE",
|
|
279
|
+
"url": "{{variables.dev_server}}/api/resource",
|
|
280
|
+
"params": { "id": "{{ steps.create.output.response.id }}" },
|
|
281
|
+
"body": { "type": "json", "content": {} }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Polling (repeat until condition)
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"input": {
|
|
290
|
+
"method": "GET",
|
|
291
|
+
"url": "https://api.example.com/status",
|
|
292
|
+
"params": { "count": "{{ context.state.count }}" },
|
|
293
|
+
"repeatUntilEnabled": true,
|
|
294
|
+
"repeatUntilCondition": {
|
|
295
|
+
"script": "(steps, context) => { return steps.__self.output?.response?.status === 'done'; }"
|
|
296
|
+
},
|
|
297
|
+
"repeatIntervalSeconds": 2,
|
|
298
|
+
"maxRepeatCount": 10,
|
|
299
|
+
"includeFullResponseString": false,
|
|
300
|
+
"noEncode": false,
|
|
301
|
+
"extraLongTimeouts": false
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Rules:**
|
|
307
|
+
- `type` is `"HTTP"` (uppercase, not `"INTEGRATION"`)
|
|
308
|
+
- `ref` naming: `new_http_<n>`, `http_<n>`, or a descriptive name like `fetch_all`, `create_new`
|
|
309
|
+
- Output: `steps.<ref>.output.response.<field>` (response body fields), `steps.<ref>.output.statusCode` (integer)
|
|
310
|
+
- Access a prior step's INPUT from a later step: `steps.<ref>.input.body.content.<field>` (real pattern — useful for assertions)
|
|
311
|
+
- `body`: for POST/PUT/DELETE — structure is `{ "type": "json", "content": { ... } }`. GET requests omit `body`.
|
|
312
|
+
- `body.content` can include `{{...}}` template references to inject previous step outputs
|
|
313
|
+
- `params`: query string key-value pairs (always include even if empty `{}`)
|
|
314
|
+
- `{{variables.<name>}}`: inject environment/namespace variables (e.g. server URLs)
|
|
315
|
+
- `{{secrets.<name>}}`: inject secrets (e.g. API tokens) — never hardcode credentials
|
|
316
|
+
- URL path segments can include `{{steps.<ref>.output.response.<field>}}` directly in the URL string
|
|
317
|
+
- `repeatUntilEnabled: true` + `repeatIntervalSeconds` + `maxRepeatCount` — polling mode; stops when condition returns `true`
|
|
318
|
+
- `repeatUntilCondition.script`: use optional chaining (`?.`) for safety since early polls may have no response yet
|
|
319
|
+
- When NOT polling: set `repeatUntilEnabled: null`, `repeatIntervalSeconds: null`, `maxRepeatCount: null`
|
|
320
|
+
- `includeFullResponseString: false` — set `true` only when you need the raw response string
|
|
321
|
+
- `extraLongTimeouts: false` — set `true` for very slow endpoints
|
|
322
|
+
- Collect FOREACH loop results: `steps["http[0]"].output`, `steps["http[1]"].output`, etc.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## SQLITE
|
|
327
|
+
|
|
328
|
+
A native SQLITE step that executes a SQL statement against a managed SQLite store in the current namespace.
|
|
329
|
+
|
|
330
|
+
Example:
|
|
331
|
+
```json
|
|
332
|
+
{
|
|
333
|
+
"orgId": 1,
|
|
334
|
+
"namespace": "automated_tests",
|
|
335
|
+
"name": "update",
|
|
336
|
+
"type": "SQLITE",
|
|
337
|
+
"ref": "update",
|
|
338
|
+
"optional": false,
|
|
339
|
+
"createdBy": "system",
|
|
340
|
+
"updatedBy": "system",
|
|
341
|
+
"description": null,
|
|
342
|
+
"label": null,
|
|
343
|
+
"created": 1765971852407,
|
|
344
|
+
"updated": 1765971852407,
|
|
345
|
+
"configuration": {
|
|
346
|
+
"errorPolicyName": null,
|
|
347
|
+
"useCache": false,
|
|
348
|
+
"cacheKey": null,
|
|
349
|
+
"cacheTimeoutSeconds": 0,
|
|
350
|
+
"stream": false,
|
|
351
|
+
"streamAllStatuses": false,
|
|
352
|
+
"preExecutionScript": null,
|
|
353
|
+
"constructInputFromScript": false,
|
|
354
|
+
"scriptLanguage": null,
|
|
355
|
+
"jqTransformer": null,
|
|
356
|
+
"rateLimitMaxRequests": 0,
|
|
357
|
+
"rateLimitWindowSeconds": 0
|
|
358
|
+
},
|
|
359
|
+
"children": [],
|
|
360
|
+
"input": {
|
|
361
|
+
"storeName": "automated-test-db",
|
|
362
|
+
"sql": "update sample_table set name = :#newName where name = 'Alice' or name = :secondName;",
|
|
363
|
+
"parameters": {
|
|
364
|
+
"newName": "Bob",
|
|
365
|
+
"secondName": "Alex"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"output": null
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Rules:**
|
|
373
|
+
- `type` must be `SQLITE`
|
|
374
|
+
- `storeName` specifies the SQLite store name in the current namespace
|
|
375
|
+
- `sql` contains the SQL statement to execute
|
|
376
|
+
- `parameters` is optional, and is used for parameterized SQL values
|
|
377
|
+
- `preExecutionScript`, `constructInputFromScript`, and `scriptLanguage` behave the same way as other step types
|
|
378
|
+
- SQL results are returned in `steps.<ref>.output` if the step is configured to expose output
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## WORKER
|
|
383
|
+
|
|
384
|
+
Executes code in external worker processes using the Unmeshed Polyglot SDK. Workers run outside the main Unmeshed engine and can be implemented in Java, Python, Go, TypeScript/NodeJs or other languages.
|
|
385
|
+
|
|
386
|
+
Example:
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"orgId": 1,
|
|
390
|
+
"namespace": "automated_tests",
|
|
391
|
+
"name": "not_exist_x",
|
|
392
|
+
"type": "WORKER",
|
|
393
|
+
"ref": "worker_1",
|
|
394
|
+
"optional": false,
|
|
395
|
+
"createdBy": "system",
|
|
396
|
+
"updatedBy": "system",
|
|
397
|
+
"description": null,
|
|
398
|
+
"label": null,
|
|
399
|
+
"created": 1776247373914,
|
|
400
|
+
"updated": 1776247373914,
|
|
401
|
+
"configuration": {
|
|
402
|
+
"errorPolicyName": null,
|
|
403
|
+
"useCache": false,
|
|
404
|
+
"cacheKey": null,
|
|
405
|
+
"cacheTimeoutSeconds": 0,
|
|
406
|
+
"stream": false,
|
|
407
|
+
"streamAllStatuses": false,
|
|
408
|
+
"preExecutionScript": null,
|
|
409
|
+
"constructInputFromScript": false,
|
|
410
|
+
"scriptLanguage": null,
|
|
411
|
+
"jqTransformer": null,
|
|
412
|
+
"rateLimitMaxRequests": 0,
|
|
413
|
+
"rateLimitWindowSeconds": 0
|
|
414
|
+
},
|
|
415
|
+
"children": [],
|
|
416
|
+
"input": {},
|
|
417
|
+
"output": null
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Rules:**
|
|
422
|
+
- `type` must be `WORKER`
|
|
423
|
+
- `name` specifies the worker queue name that the SDK worker is registered to
|
|
424
|
+
- `input` contains the data passed to the worker function
|
|
425
|
+
- Workers run asynchronously outside the main engine using the Unmeshed SDK
|
|
426
|
+
- Worker implementations can be in Java (Spring Boot), Python, or other supported languages
|
|
427
|
+
- Worker output is returned in `steps.<ref>.output` when the worker completes
|
|
428
|
+
- `preExecutionScript`, `constructInputFromScript`, and `scriptLanguage` behave the same way as other step types
|
|
429
|
+
- Workers must be registered and running for the step to execute successfully
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## WAIT
|
|
434
|
+
|
|
435
|
+
Pauses workflow execution until a specified time.
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"type": "WAIT",
|
|
440
|
+
"input": {
|
|
441
|
+
"script": "(steps, context) => {\n return {\n \"waitUntil\": steps.__self.startTime + (25 * 1000)\n };\n}"
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Rules:**
|
|
447
|
+
- `input.script` is a JS function returning `{ waitUntil: <timestamp_ms> }`
|
|
448
|
+
- `steps.__self.startTime` — start time of this WAIT instance in milliseconds (use for relative waits)
|
|
449
|
+
- `steps.__self.start` — alternative property (also milliseconds)
|
|
450
|
+
- Inside a WHILE loop body: use the WHILE step's `.updated` timestamp as the loop iteration reference:
|
|
451
|
+
```javascript
|
|
452
|
+
(steps, context) => {
|
|
453
|
+
const lastIterationTime = steps.my_while_step.updated;
|
|
454
|
+
return { "waitUntil": lastIterationTime + 1000 };
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
- Inside FOREACH loops: use `steps.__self.executionList?.at(-1)?.scheduled` as reference
|
|
458
|
+
- `waitUntil` is an absolute epoch timestamp in milliseconds
|
|
459
|
+
- `ref` naming: `wait_<n>` or `wait`
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## FOREACH
|
|
464
|
+
|
|
465
|
+
Iterates over an array, executing its child steps for each element.
|
|
466
|
+
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"type": "FOREACH",
|
|
470
|
+
"children": [
|
|
471
|
+
{ /* child step(s) — usually a LIST */ }
|
|
472
|
+
],
|
|
473
|
+
"input": {
|
|
474
|
+
"inputArray": [1, 2, 3, 4, 5],
|
|
475
|
+
"concurrency": 1
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Rules:**
|
|
481
|
+
- `inputArray`: the array to iterate over — can be static or a `{{...}}` template reference
|
|
482
|
+
- `concurrency`: how many iterations run in parallel — `1` = sequential, `N` = N at a time
|
|
483
|
+
- Children execute for each element in the array
|
|
484
|
+
- Access the current iteration value inside child scripts via `context.input` or loop context
|
|
485
|
+
- After the loop, access individual iteration outputs: `steps["<ref>[0]"].output`, `steps["<ref>[1]"].output`, etc.
|
|
486
|
+
- `ref` naming: use a descriptive name like `foreachstep` or `foreach_<n>`
|
|
487
|
+
- Children are typically a single `LIST` containing the loop body steps
|
|
488
|
+
|
|
489
|
+
**Collecting FOREACH results in JAVASCRIPT:**
|
|
490
|
+
```javascript
|
|
491
|
+
(steps, context) => {
|
|
492
|
+
const allOutputs = [];
|
|
493
|
+
allOutputs.push(steps["http[0]"].output);
|
|
494
|
+
allOutputs.push(steps["http[1]"].output);
|
|
495
|
+
// ... etc
|
|
496
|
+
return allOutputs;
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## WHILE
|
|
503
|
+
|
|
504
|
+
Loops while a condition is true, executing its child steps each iteration.
|
|
505
|
+
|
|
506
|
+
```json
|
|
507
|
+
{
|
|
508
|
+
"type": "WHILE",
|
|
509
|
+
"children": [
|
|
510
|
+
{
|
|
511
|
+
"type": "LIST",
|
|
512
|
+
"ref": "loop_body",
|
|
513
|
+
"children": [
|
|
514
|
+
{ /* WAIT step (throttle iterations) */ },
|
|
515
|
+
{ /* JAVASCRIPT step (do work + update state) */ }
|
|
516
|
+
],
|
|
517
|
+
"input": {}
|
|
518
|
+
}
|
|
519
|
+
],
|
|
520
|
+
"input": {
|
|
521
|
+
"script": "(steps, context) => {\n return whileloop.iteration < 5;\n}"
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Rules:**
|
|
527
|
+
- `input.script` is a JS function returning a boolean — loop continues while `true`
|
|
528
|
+
- `whileloop.iteration` — built-in counter starting at 0, increments each iteration
|
|
529
|
+
- Child is typically a single `LIST` containing the loop body
|
|
530
|
+
- Always include a `WAIT` step inside the loop body to prevent tight-loop runaway
|
|
531
|
+
- Use `context.state.<key>` to read mutable state that persists across iterations
|
|
532
|
+
- Use `__statePut` in a JAVASCRIPT step to write to state (see context.state below)
|
|
533
|
+
- `steps.<while_ref>.updated` — timestamp of the last loop iteration (use in WAIT as base time)
|
|
534
|
+
- `ref` naming: `new_while_<n>` or `while_<n>`
|
|
535
|
+
|
|
536
|
+
**Loop body with state update:**
|
|
537
|
+
```javascript
|
|
538
|
+
// WAIT step — throttle using last iteration time
|
|
539
|
+
(steps, context) => {
|
|
540
|
+
return { "waitUntil": steps.my_while_step.updated + 1000 };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// JAVASCRIPT step — increment counter in state
|
|
544
|
+
(steps, context) => {
|
|
545
|
+
return {
|
|
546
|
+
"__statePut": {
|
|
547
|
+
"count": (context.state.count || 0) + 1
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## context.state and __statePut
|
|
556
|
+
|
|
557
|
+
Workflows support mutable in-run state accessible across all steps via `context.state`.
|
|
558
|
+
|
|
559
|
+
**Reading state:**
|
|
560
|
+
```javascript
|
|
561
|
+
(steps, context) => {
|
|
562
|
+
const count = context.state.count || 0;
|
|
563
|
+
const lastId = context.state.lastProcessedId;
|
|
564
|
+
// ...
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Writing state** (return `__statePut` from any JAVASCRIPT step):
|
|
569
|
+
```javascript
|
|
570
|
+
(steps, context) => {
|
|
571
|
+
return {
|
|
572
|
+
"__statePut": {
|
|
573
|
+
"count": (context.state.count || 0) + 1,
|
|
574
|
+
"lastProcessedId": someId
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Rules:**
|
|
581
|
+
- `context.state` is a plain object, readable in any step script
|
|
582
|
+
- To write: return `{ "__statePut": { key: value } }` from a JAVASCRIPT step
|
|
583
|
+
- `__statePut` merges into existing state (not a full replace)
|
|
584
|
+
- State is scoped to the current workflow run — not persisted across runs (use PERSISTED_STATE for that)
|
|
585
|
+
- Common use: loop counters, accumulating results, tracking IDs across iterations
|
|
586
|
+
- Also accessible in HTTP step `params`: `"count": "{{ context.state.count }}"`
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## {{variables.*}} and {{secrets.*}}
|
|
591
|
+
|
|
592
|
+
Template placeholders available in HTTP step URLs, headers, params, and body content.
|
|
593
|
+
|
|
594
|
+
```json
|
|
595
|
+
{
|
|
596
|
+
"url": "{{variables.dev_server}}/api/resource",
|
|
597
|
+
"headers": {
|
|
598
|
+
"Authorization": "Bearer {{secrets.unmeshed_test_token}}"
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Rules:**
|
|
604
|
+
- `{{variables.<name>}}` — namespace/environment variables (e.g. base URLs, config values)
|
|
605
|
+
- `{{secrets.<name>}}` — secret values (API tokens, passwords) — never hardcode these
|
|
606
|
+
- Available in HTTP step `url`, `headers`, `params`, and `body.content`
|
|
607
|
+
- Also available as `{{steps.<ref>.output.response.<field>}}` template refs in the same fields
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## DECISION_ENGINE
|
|
612
|
+
|
|
613
|
+
Routes based on a named decision table (rule engine).
|
|
614
|
+
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"type": "DECISION_ENGINE",
|
|
618
|
+
"input": {
|
|
619
|
+
"decisionRuleStrategy": "FIRST_MATCH",
|
|
620
|
+
"decisionTable": "<table-name>",
|
|
621
|
+
"decisionContext": {
|
|
622
|
+
"FieldName": "{{ steps.<ref>.output.results.<field> }}"
|
|
623
|
+
},
|
|
624
|
+
"decisionTableVersion": "",
|
|
625
|
+
"decisionOutputColumns": ["OutputCol1", "OutputCol2"]
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**Rules:**
|
|
631
|
+
- `decisionTable` must reference a table configured in Unmeshed
|
|
632
|
+
- `decisionRuleStrategy`: `FIRST_MATCH` (most common) or `ALL_MATCH`
|
|
633
|
+
- `decisionContext` keys must match column names in the decision table
|
|
634
|
+
- Context values can be `{{...}}` template expressions or empty string `""` for optional fields
|
|
635
|
+
- Output columns define what fields are returned from the matched rule
|
|
636
|
+
- Decision output accessed via `steps.<ref>.output.result.<ColumnName>` or `steps.<ref>.output.results.<ColumnName>` — defensively check both
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## SWITCH
|
|
641
|
+
|
|
642
|
+
Branches the workflow based on script logic. Must have `children` array with branch steps.
|
|
643
|
+
|
|
644
|
+
```json
|
|
645
|
+
{
|
|
646
|
+
"type": "SWITCH",
|
|
647
|
+
"children": [
|
|
648
|
+
{ /* branch step — any type: LIST, INTEGRATION, NOOP, EXIT, JAVASCRIPT */ },
|
|
649
|
+
{ /* another branch */ }
|
|
650
|
+
],
|
|
651
|
+
"input": {
|
|
652
|
+
"script": "(steps, context) => {\n if (condition) return 'branch-a';\n return 'branch-b';\n}",
|
|
653
|
+
"responseMapping": [
|
|
654
|
+
{ "targetRef": "<ref-of-branch-a>", "value": "branch-a" },
|
|
655
|
+
{ "targetRef": "<ref-of-branch-b>", "value": "branch-b" },
|
|
656
|
+
{ "targetRef": "<ref-of-default>", "defaultBranch": true }
|
|
657
|
+
]
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Rules:**
|
|
663
|
+
- Script must return a string matching one of the `value` fields in `responseMapping`
|
|
664
|
+
- `defaultBranch: true` entry is required — points to the fallback branch ref
|
|
665
|
+
- Children can be **any step type** — `LIST`, `INTEGRATION`, `NOOP`, `EXIT`, `JAVASCRIPT`
|
|
666
|
+
- Only the matched branch executes
|
|
667
|
+
- The `ref` of each child must match the `targetRef` in `responseMapping`
|
|
668
|
+
- A SWITCH branch child that is a single step does NOT need to be wrapped in a LIST
|
|
669
|
+
- Real-world pattern: check for API errors (e.g. rate limiting) and branch to a skip path vs main path
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## LIST
|
|
674
|
+
|
|
675
|
+
A sequential container of steps. Used as a branch body inside SWITCH or PARALLEL, or as a standalone grouping.
|
|
676
|
+
|
|
677
|
+
```json
|
|
678
|
+
{
|
|
679
|
+
"type": "LIST",
|
|
680
|
+
"children": [
|
|
681
|
+
{ /* step 1 */ },
|
|
682
|
+
{ /* step 2 */ },
|
|
683
|
+
{ /* step 3 */ }
|
|
684
|
+
],
|
|
685
|
+
"input": {},
|
|
686
|
+
"output": null
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
**Rules:**
|
|
691
|
+
- Steps in `children` execute in order
|
|
692
|
+
- Used as a named branch inside SWITCH (`ref` must match SWITCH `responseMapping`)
|
|
693
|
+
- Used as a branch inside PARALLEL (each LIST runs concurrently with others)
|
|
694
|
+
- Can contain any step types including nested SWITCH, PARALLEL, WAIT, FOREACH
|
|
695
|
+
- `input` is always `{}` for LIST
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## PARALLEL
|
|
700
|
+
|
|
701
|
+
Executes multiple branches simultaneously. Children are typically LIST steps (each containing multiple steps), not just SUB_PROCESS.
|
|
702
|
+
|
|
703
|
+
```json
|
|
704
|
+
{
|
|
705
|
+
"type": "PARALLEL",
|
|
706
|
+
"children": [
|
|
707
|
+
{
|
|
708
|
+
"type": "LIST",
|
|
709
|
+
"ref": "branch_1",
|
|
710
|
+
"children": [ /* steps */ ],
|
|
711
|
+
"input": {}
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
"type": "LIST",
|
|
715
|
+
"ref": "branch_2",
|
|
716
|
+
"children": [ /* steps */ ],
|
|
717
|
+
"input": {}
|
|
718
|
+
}
|
|
719
|
+
],
|
|
720
|
+
"input": {
|
|
721
|
+
"failIfAnyBranchFails": true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Rules:**
|
|
727
|
+
- All children execute concurrently
|
|
728
|
+
- `failIfAnyBranchFails: true` — workflow fails if any branch fails (strict mode)
|
|
729
|
+
- `failIfAnyBranchFails: false` — continue even if branches fail (lenient mode)
|
|
730
|
+
- Children are **LIST steps** (for multi-step branches) or **SUB_PROCESS steps** (for sub-workflow fan-out)
|
|
731
|
+
- Each LIST branch has its own `ref` and `children`
|
|
732
|
+
- Steps inside different PARALLEL branches can reference each other's outputs after the PARALLEL completes
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## SUB_PROCESS
|
|
737
|
+
|
|
738
|
+
Invokes another named Unmeshed workflow.
|
|
739
|
+
|
|
740
|
+
```json
|
|
741
|
+
{
|
|
742
|
+
"type": "SUB_PROCESS",
|
|
743
|
+
"input": {
|
|
744
|
+
"processName": "<workflow-name>",
|
|
745
|
+
"waitForCompletion": true,
|
|
746
|
+
"input": { /* optional input to pass */ }
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**Rules:**
|
|
752
|
+
- `processName` must match the `name` of another workflow in the same org
|
|
753
|
+
- `waitForCompletion: true` — waits for sub-process to finish before continuing
|
|
754
|
+
- `waitForCompletion: false` — fire-and-forget
|
|
755
|
+
- `input` is optional; omit or pass `{}` if no input needed
|
|
756
|
+
- Common pattern: SUB_PROCESS followed by WAIT to allow async side effects (e.g. emails) to propagate before assertions
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## PERSISTED_STATE
|
|
761
|
+
|
|
762
|
+
Reads or writes persistent key-value state scoped to the org/namespace.
|
|
763
|
+
|
|
764
|
+
```json
|
|
765
|
+
{
|
|
766
|
+
"type": "PERSISTED_STATE",
|
|
767
|
+
"input": {
|
|
768
|
+
"operation": "READ",
|
|
769
|
+
"name": "<state-key>",
|
|
770
|
+
"path": "$"
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
For UPSERT (write):
|
|
776
|
+
```json
|
|
777
|
+
{
|
|
778
|
+
"type": "PERSISTED_STATE",
|
|
779
|
+
"input": {
|
|
780
|
+
"operation": "UPSERT",
|
|
781
|
+
"name": "<state-key>",
|
|
782
|
+
"path": "$",
|
|
783
|
+
"value": "<value-to-store>"
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**Rules:**
|
|
789
|
+
- `operation`: `"READ"` or `"UPSERT"`
|
|
790
|
+
- `name`: the key — can be a static string or a `{{...}}` template expression
|
|
791
|
+
- `path`: use `"$"` for root; supports JSONPath for nested access
|
|
792
|
+
- `value`: only for UPSERT — the value to persist
|
|
793
|
+
- READ output: `steps.<ref>.output.result.value` — returns `null` if key doesn't exist
|
|
794
|
+
- Common pattern: READ → SWITCH on whether result is null → UPSERT after creating resource
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
## EXIT
|
|
799
|
+
|
|
800
|
+
Terminates the workflow immediately with a given status.
|
|
801
|
+
|
|
802
|
+
```json
|
|
803
|
+
{
|
|
804
|
+
"type": "EXIT",
|
|
805
|
+
"input": {
|
|
806
|
+
"message": "Exit message",
|
|
807
|
+
"status": "COMPLETED"
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**Rules:**
|
|
813
|
+
- `status`: `"COMPLETED"` or `"FAILED"`
|
|
814
|
+
- Used inside SWITCH branches to short-circuit the workflow
|
|
815
|
+
- Common pattern: gate check via PERSISTED_STATE → SWITCH → EXIT if flag is false
|
|
816
|
+
- Can also be used mid-workflow as a temporary stub (e.g. `"message": "Exiting temporarily"`)
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## Optional Steps
|
|
821
|
+
|
|
822
|
+
Any step can be marked `"optional": true` to allow the workflow to continue even if that step fails.
|
|
823
|
+
|
|
824
|
+
```json
|
|
825
|
+
{
|
|
826
|
+
"optional": true,
|
|
827
|
+
...
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
**Rules:**
|
|
832
|
+
- Default is `false` — step failure stops the workflow
|
|
833
|
+
- Set `optional: true` for steps that may legitimately fail (e.g. external API calls that might be rate-limited, best-effort cleanup steps)
|
|
834
|
+
- Real-world use: `get_all_tables` in an Airtable workflow marked optional so the SWITCH after it can check for rate-limit errors gracefully
|