@directus/api 30.0.0 → 31.0.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.
Files changed (83) hide show
  1. package/dist/app.js +5 -0
  2. package/dist/auth/drivers/oauth2.js +17 -3
  3. package/dist/auth/drivers/openid.js +17 -3
  4. package/dist/controllers/mcp.d.ts +2 -0
  5. package/dist/controllers/mcp.js +33 -0
  6. package/dist/controllers/users.js +17 -7
  7. package/dist/controllers/versions.js +3 -2
  8. package/dist/database/errors/dialects/mssql.d.ts +1 -1
  9. package/dist/database/errors/dialects/mssql.js +18 -10
  10. package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
  11. package/dist/database/migrations/20250813A-add-mcp.js +18 -0
  12. package/dist/database/run-ast/README.md +46 -0
  13. package/dist/mcp/define.d.ts +2 -0
  14. package/dist/mcp/define.js +3 -0
  15. package/dist/mcp/index.d.ts +1 -0
  16. package/dist/mcp/index.js +1 -0
  17. package/dist/mcp/schema.d.ts +485 -0
  18. package/dist/mcp/schema.js +219 -0
  19. package/dist/mcp/server.d.ts +97 -0
  20. package/dist/mcp/server.js +310 -0
  21. package/dist/mcp/tools/assets.d.ts +3 -0
  22. package/dist/mcp/tools/assets.js +54 -0
  23. package/dist/mcp/tools/collections.d.ts +84 -0
  24. package/dist/mcp/tools/collections.js +90 -0
  25. package/dist/mcp/tools/fields.d.ts +101 -0
  26. package/dist/mcp/tools/fields.js +157 -0
  27. package/dist/mcp/tools/files.d.ts +235 -0
  28. package/dist/mcp/tools/files.js +103 -0
  29. package/dist/mcp/tools/flows.d.ts +323 -0
  30. package/dist/mcp/tools/flows.js +85 -0
  31. package/dist/mcp/tools/folders.d.ts +95 -0
  32. package/dist/mcp/tools/folders.js +96 -0
  33. package/dist/mcp/tools/index.d.ts +15 -0
  34. package/dist/mcp/tools/index.js +29 -0
  35. package/dist/mcp/tools/items.d.ts +87 -0
  36. package/dist/mcp/tools/items.js +141 -0
  37. package/dist/mcp/tools/operations.d.ts +171 -0
  38. package/dist/mcp/tools/operations.js +77 -0
  39. package/dist/mcp/tools/prompts/assets.md +8 -0
  40. package/dist/mcp/tools/prompts/collections.md +336 -0
  41. package/dist/mcp/tools/prompts/fields.md +521 -0
  42. package/dist/mcp/tools/prompts/files.md +180 -0
  43. package/dist/mcp/tools/prompts/flows.md +495 -0
  44. package/dist/mcp/tools/prompts/folders.md +34 -0
  45. package/dist/mcp/tools/prompts/index.d.ts +16 -0
  46. package/dist/mcp/tools/prompts/index.js +19 -0
  47. package/dist/mcp/tools/prompts/items.md +317 -0
  48. package/dist/mcp/tools/prompts/operations.md +721 -0
  49. package/dist/mcp/tools/prompts/relations.md +386 -0
  50. package/dist/mcp/tools/prompts/schema.md +130 -0
  51. package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
  52. package/dist/mcp/tools/prompts/system-prompt.md +44 -0
  53. package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
  54. package/dist/mcp/tools/relations.d.ts +73 -0
  55. package/dist/mcp/tools/relations.js +93 -0
  56. package/dist/mcp/tools/schema.d.ts +54 -0
  57. package/dist/mcp/tools/schema.js +317 -0
  58. package/dist/mcp/tools/system.d.ts +3 -0
  59. package/dist/mcp/tools/system.js +22 -0
  60. package/dist/mcp/tools/trigger-flow.d.ts +8 -0
  61. package/dist/mcp/tools/trigger-flow.js +48 -0
  62. package/dist/mcp/transport.d.ts +13 -0
  63. package/dist/mcp/transport.js +18 -0
  64. package/dist/mcp/types.d.ts +56 -0
  65. package/dist/mcp/types.js +1 -0
  66. package/dist/services/authentication.js +36 -0
  67. package/dist/services/fields.js +4 -4
  68. package/dist/services/items.js +14 -4
  69. package/dist/services/payload.d.ts +7 -3
  70. package/dist/services/payload.js +26 -12
  71. package/dist/services/server.js +1 -0
  72. package/dist/services/tfa.d.ts +1 -1
  73. package/dist/services/tfa.js +20 -5
  74. package/dist/services/versions.d.ts +6 -4
  75. package/dist/services/versions.js +84 -25
  76. package/dist/types/auth.d.ts +2 -1
  77. package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
  78. package/dist/utils/versioning/deep-map-with-schema.js +81 -0
  79. package/dist/utils/versioning/handle-version.d.ts +2 -2
  80. package/dist/utils/versioning/handle-version.js +47 -43
  81. package/dist/utils/versioning/split-recursive.d.ts +4 -0
  82. package/dist/utils/versioning/split-recursive.js +27 -0
  83. package/package.json +30 -29
@@ -0,0 +1,721 @@
1
+ Perform CRUD on Directus Operations within Flows. Operations are individual actions that execute sequentially in a flow,
2
+ processing and transforming data through the data chain.
3
+
4
+ <key_concepts>
5
+
6
+ - **Operations** are the building blocks of flows
7
+ - Each operation has a unique `key` that identifies it in the data chain
8
+ - Operations connect via `resolve` (success) and `reject` (failure) paths
9
+ - Data from each operation is stored under its key in the data chain </key_concepts>
10
+
11
+ <uuid_vs_keys>
12
+
13
+ ## UUID vs Keys - Critical Distinction
14
+
15
+ **UUIDs** (System identifiers): `"abc-123-def-456"`
16
+
17
+ - Use in: `resolve`, `reject`, `flow`, operation `key` field in CRUD
18
+ - Generated automatically when operations are created
19
+ - Required for connecting operations
20
+
21
+ **Keys** (Human-readable names): `"send_email"`, `"check_status"`
22
+
23
+ - Use in: Data chain variables `{{ operation_key }}`
24
+ - You define these when creating operations
25
+ - Used to access operation results in subsequent operations </uuid_vs_keys>
26
+
27
+ <critical_syntax>
28
+
29
+ ## Critical Syntax Rules - MEMORIZE THESE
30
+
31
+ **Condition Filters**: Use nested objects, NEVER dot notation
32
+
33
+ - ❌ `"$trigger.payload.status"`
34
+ - ✅ `{"$trigger": {"payload": {"status": {"_eq": "published"}}}}`
35
+
36
+ **Request Headers**: Array of objects, NOT simple objects
37
+
38
+ - ❌ `{"Authorization": "Bearer token"}`
39
+ - ✅ `[{"header": "Authorization", "value": "Bearer token"}]`
40
+
41
+ **Request Body**: Stringified JSON, NOT native objects
42
+
43
+ - ❌ `"body": {"data": "value"}`
44
+ - ✅ `"body": "{\"data\": \"value\"}"`
45
+
46
+ **Data Chain Variables**: Use operation keys, avoid `$last`
47
+
48
+ - ❌ `{{ $last }}` (breaks when reordered)
49
+ - ✅ `{{ operation_key }}` (reliable) </critical_syntax>
50
+
51
+ <required_fields>
52
+
53
+ ## Required Fields Summary
54
+
55
+ **All Operations:**
56
+
57
+ - `flow` - UUID of parent flow
58
+ - `key` - Unique operation identifier
59
+ - `type` - Operation type
60
+ - `position_x`, `position_y` - Grid coordinates
61
+ - `resolve`, `reject` - Next operation UUIDs (null initially) </required_fields>
62
+
63
+ <available_operations> Core operations available in Directus:
64
+
65
+ - **condition** - Evaluate filter rules to determine execution path
66
+ - **exec** - Execute custom JavaScript/TypeScript code in sandboxed environment
67
+ - **item-create** - Create items in a collection
68
+ - **item-read** - Retrieve items from a collection
69
+ - **item-update** - Update existing items in a collection
70
+ - **item-delete** - Remove items from a collection
71
+ - **json-web-token** - Sign, verify, or decode JWT tokens
72
+ - **log** - Output debug messages to console
73
+ - **mail** - Send email notifications with templates
74
+ - **notification** - Send in-app notifications to users
75
+ - **request** - Make HTTP requests to external services
76
+ - **sleep** - Delay execution for specified time
77
+ - **throw-error** - Throw custom errors to stop flow execution
78
+ - **transform** - Create custom JSON payloads
79
+ - **trigger** - Execute another flow with iteration modes
80
+
81
+ If user has installed extensions from the Directus Marketplace, there may be more operations available than this. You
82
+ can read existing operations to see if they are using extensions operations. </available_operations>
83
+
84
+ <crud_actions>
85
+
86
+ ### `read` - List Flow Operations
87
+
88
+ ```json
89
+ {
90
+ "action": "read",
91
+ "query": {
92
+ "fields": ["id", "name", "key", "type", "flow", "resolve", "reject"],
93
+ "filter": { "flow": { "_eq": "flow-uuid" } },
94
+ "sort": ["position_x", "position_y"]
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### `create` - Add Operation to Flow
100
+
101
+ ```json
102
+ {
103
+ "action": "create",
104
+ "data": {
105
+ "flow": "flow-uuid", // Required: Flow this operation belongs to
106
+ "key": "notify_user", // Required: Unique key for this operation
107
+ "type": "notification", // Required: Operation type
108
+ "name": "Send Notification", // Optional: Display name
109
+ "position_x": 19, // Required: Grid X position (use 19, 37, 55, 73...)
110
+ "position_y": 1, // Required: Grid Y position (use 1, 19, 37...)
111
+ "options": {
112
+ // Optional: Type-specific configuration (default: {})
113
+ // Configuration based on operation type
114
+ },
115
+ "resolve": null, // Required: UUID of next operation on success (null initially)
116
+ "reject": null // Required: UUID of next operation on failure (null initially)
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### `update` - Modify Operation
122
+
123
+ ```json
124
+ {
125
+ "action": "update",
126
+ "key": "operation-uuid",
127
+ "data": {
128
+ "options": {
129
+ // Updated configuration
130
+ },
131
+ "resolve": "operation-uuid-here"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### `delete` - Remove Operation
137
+
138
+ ```json
139
+ {
140
+ "action": "delete",
141
+ "key": "operation-uuid"
142
+ }
143
+ ```
144
+
145
+ </crud_actions>
146
+
147
+ <workflow_creation>
148
+
149
+ ## Workflow Creation Process - **ESSENTIAL READING**
150
+
151
+ **⚠️ CRITICAL**: Follow this exact order or operations will fail
152
+
153
+ <workflow_steps>
154
+
155
+ ### Step-by-Step Process:
156
+
157
+ 1. **Create the flow** using the `flows` tool
158
+ 2. **Create all operations** with null resolve/reject initially
159
+ 3. **Link operations together** using the UUIDs returned from step 2
160
+ 4. **Update the flow** to set the first operation as the entry point
161
+
162
+ ### Why This Order Matters:
163
+
164
+ - Operations must exist before they can be referenced in resolve/reject fields
165
+ - UUIDs are only available after operations are created
166
+ - The flow needs at least one operation created before setting its entry point </workflow_steps>
167
+
168
+ <workflow_example>
169
+
170
+ ### Complete Workflow Example:
171
+
172
+ ```json
173
+ // Step 1: Create the flow first (using flows tool)
174
+ {
175
+ "action": "create",
176
+ "data": {
177
+ "name": "Email on Post Published",
178
+ "trigger": "event",
179
+ "options": {
180
+ "type": "action",
181
+ "scope": ["items.create"],
182
+ "collections": ["posts"]
183
+ }
184
+ }
185
+ }
186
+ // Returns: {"id": "flow-uuid-123", ...}
187
+
188
+ // Step 2: Create operations with null connections initially
189
+ {"action": "create", "data": {
190
+ "flow": "flow-uuid-123",
191
+ "key": "check_status",
192
+ "type": "condition",
193
+ "position_x": 19, // First operation position
194
+ "position_y": 1,
195
+ "options": {
196
+ "filter": {
197
+ "$trigger": {
198
+ "payload": {
199
+ "status": {"_eq": "published"}
200
+ }
201
+ }
202
+ }
203
+ },
204
+ "resolve": null, // Set to null initially
205
+ "reject": null // Set to null initially
206
+ }}
207
+ // Returns: {"id": "condition-uuid-456", ...}
208
+
209
+ {"action": "create", "data": {
210
+ "flow": "flow-uuid-123",
211
+ "key": "send_email",
212
+ "type": "mail",
213
+ "position_x": 37, // Second operation position
214
+ "position_y": 1,
215
+ "options": {
216
+ "to": ["admin@example.com"],
217
+ "subject": "New post published",
218
+ "body": "Post '{{$trigger.payload.title}}' was published"
219
+ },
220
+ "resolve": null,
221
+ "reject": null
222
+ }}
223
+ // Returns: {"id": "email-uuid-789", ...}
224
+
225
+ // Step 3: Connect operations using UUIDs (NOT keys)
226
+ {"action": "update", "key": "condition-uuid-456", "data": {
227
+ "resolve": "email-uuid-789", // Use UUID from step 2
228
+ "reject": null // No error handling operation
229
+ }}
230
+
231
+ // Step 4: Update flow to set first operation (using flows tool)
232
+ {"action": "update", "key": "flow-uuid-123", "data": {
233
+ "operation": "condition-uuid-456" // First operation UUID
234
+ }}
235
+ ```
236
+
237
+ </workflow_example> </workflow_creation>
238
+
239
+ <positioning_system>
240
+
241
+ ## Grid-Based Positioning - **ALWAYS SET POSITIONS**
242
+
243
+ **Grid Rules:**
244
+
245
+ - Each operation: 14x14 grid units
246
+ - Standard spacing: 18 units (19, 37, 55, 73...)
247
+ - Vertical start: `position_y: 1`
248
+ - Never use (0,0) - operations will overlap
249
+
250
+ **Common Patterns:**
251
+
252
+ ```json
253
+ // Linear flow
254
+ {"position_x": 19, "position_y": 1} // First
255
+ {"position_x": 37, "position_y": 1} // Second
256
+ {"position_x": 55, "position_y": 1} // Third
257
+
258
+ // Branching (success/error)
259
+ {"position_x": 19, "position_y": 1} // Main
260
+ {"position_x": 37, "position_y": 1} // Success (same row)
261
+ {"position_x": 37, "position_y": 19} // Error (lower row)
262
+ ```
263
+
264
+ </positioning_system>
265
+
266
+ <operation_examples> <condition> Evaluates filter rules to determine path
267
+
268
+ ```json
269
+ {
270
+ "type": "condition",
271
+ "options": {
272
+ "filter": {
273
+ "$trigger": {
274
+ "payload": {
275
+ "status": { "_eq": "published" }
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ <filter_examples>
284
+
285
+ ```json
286
+ // Check if field exists
287
+ {
288
+ "filter": {
289
+ "$trigger": {
290
+ "payload": {
291
+ "website": {"_nnull": true}
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ // Multiple conditions (AND) - CORRECTED SYNTAX
298
+ {
299
+ "filter": {
300
+ "$trigger": {
301
+ "payload": {
302
+ "_and": [
303
+ {"status": {"_eq": "published"}},
304
+ {"featured": {"_eq": true}}
305
+ ]
306
+ }
307
+ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ </filter_examples> </condition>
313
+
314
+ <item_operations> **Create Items:**
315
+
316
+ ```json
317
+ {
318
+ "type": "item-create",
319
+ "options": {
320
+ "collection": "notifications",
321
+ "permissions": "$trigger",
322
+ "emitEvents": true,
323
+ "payload": {
324
+ "title": "{{ $trigger.payload.title }}",
325
+ "user": "{{ $accountability.user }}"
326
+ }
327
+ }
328
+ }
329
+ ```
330
+
331
+ **Read Items:**
332
+
333
+ ```json
334
+ {
335
+ "type": "item-read",
336
+ "options": {
337
+ "collection": "products",
338
+ "permissions": "$full",
339
+ "query": {
340
+ "filter": { "status": { "_eq": "active" } },
341
+ "limit": 10
342
+ }
343
+ }
344
+ }
345
+ ```
346
+
347
+ **Update Items:**
348
+
349
+ ```json
350
+ {
351
+ "type": "item-update",
352
+ "options": {
353
+ "collection": "orders",
354
+ "permissions": "$trigger",
355
+ "emitEvents": true,
356
+ "key": "{{ $trigger.payload.id }}",
357
+ "payload": { "status": "processed" }
358
+ }
359
+ }
360
+ ```
361
+
362
+ **Delete Items:**
363
+
364
+ ```json
365
+ {
366
+ "type": "item-delete",
367
+ "options": {
368
+ "collection": "temp_data",
369
+ "permissions": "$full",
370
+ "key": ["{{ read_items[0].id }}"]
371
+ }
372
+ }
373
+ ```
374
+
375
+ </item_operations>
376
+
377
+ <exec>
378
+ Execute custom JavaScript/TypeScript in isolated sandbox
379
+
380
+ **⚠️ SECURITY WARNING**: Scripts run sandboxed with NO file system or network access
381
+
382
+ ```json
383
+ {
384
+ "type": "exec",
385
+ "options": {
386
+ "code": "module.exports = async function(data) {\n // Validate input\n if (!data.$trigger.payload.value) {\n throw new Error('Missing required value');\n }\n \n // Process data\n const result = data.$trigger.payload.value * 2;\n \n // Return must be valid JSON\n return {\n result: result,\n processed: true\n };\n}"
387
+ }
388
+ }
389
+ ```
390
+
391
+ **Common Use Cases**: Data transformation, calculations, complex logic, formatting, extracting nested values </exec>
392
+
393
+ <mail>
394
+ Send email notifications with optional templates
395
+
396
+ ```json
397
+ {
398
+ "type": "mail",
399
+ "options": {
400
+ "to": ["user@example.com", "{{ $trigger.payload.email }}"],
401
+ "subject": "Order Confirmation",
402
+ "type": "markdown", // "markdown" (default), "wysiwyg", or "template"
403
+ "body": "Your order {{ $trigger.payload.order_id }} has been confirmed.",
404
+ "cc": ["cc@example.com"], // Optional
405
+ "bcc": ["bcc@example.com"], // Optional
406
+ "replyTo": ["reply@example.com"] // Optional
407
+ }
408
+ }
409
+ ```
410
+
411
+ **Template Mode:**
412
+
413
+ ```json
414
+ {
415
+ "type": "mail",
416
+ "options": {
417
+ "to": ["{{ $trigger.payload.email }}"],
418
+ "subject": "Welcome!",
419
+ "type": "template",
420
+ "template": "welcome-email", // Template name (default: "base")
421
+ "data": {
422
+ "username": "{{ $trigger.payload.name }}",
423
+ "activation_url": "https://example.com/activate/{{ $trigger.payload.token }}"
424
+ }
425
+ }
426
+ }
427
+ ```
428
+
429
+ </mail>
430
+
431
+ <notification>
432
+ Send in-app notifications to users
433
+
434
+ ```json
435
+ {
436
+ "type": "notification",
437
+ "options": {
438
+ "recipient": ["{{ $accountability.user }}"], // User ID(s) to notify
439
+ "subject": "Task Complete",
440
+ "message": "Your export is ready for download",
441
+ "permissions": "$trigger",
442
+ "collection": "exports", // Optional: Related collection
443
+ "item": "{{ create_export.id }}" // Optional: Related item ID
444
+ }
445
+ }
446
+ ```
447
+
448
+ </notification>
449
+
450
+ <request>
451
+ Make HTTP requests
452
+
453
+ ```json
454
+ {
455
+ "type": "request",
456
+ "options": {
457
+ "method": "POST",
458
+ "url": "https://api.example.com/webhook",
459
+ "headers": [
460
+ {
461
+ "header": "Authorization",
462
+ "value": "Bearer {{ $env.API_TOKEN }}"
463
+ },
464
+ {
465
+ "header": "Content-Type",
466
+ "value": "application/json"
467
+ }
468
+ ],
469
+ "body": "{\"data\": \"{{ process_data }}\", \"timestamp\": \"{{ $trigger.timestamp }}\"}"
470
+ }
471
+ }
472
+ ```
473
+
474
+ **Real Example (Netlify Deploy Hook)**:
475
+
476
+ ```json
477
+ {
478
+ "type": "request",
479
+ "options": {
480
+ "method": "POST",
481
+ "url": "https://api.netlify.com/build_hooks/your-hook-id",
482
+ "headers": [
483
+ {
484
+ "header": "User-Agent",
485
+ "value": "Directus-Flow/1.0"
486
+ }
487
+ ],
488
+ "body": "{\"trigger\": \"content_updated\", \"item_id\": \"{{ $trigger.payload.id }}\"}"
489
+ }
490
+ }
491
+ ```
492
+
493
+ </request>
494
+
495
+ <json_web_token> Sign, verify, or decode JWT tokens - **CONSOLIDATED EXAMPLE**
496
+
497
+ ```json
498
+ {
499
+ "type": "json-web-token",
500
+ "options": {
501
+ "operation": "sign", // "sign", "verify", or "decode"
502
+
503
+ // For SIGN operations:
504
+ "payload": {
505
+ "userId": "{{ $trigger.payload.user }}",
506
+ "role": "{{ $trigger.payload.role }}"
507
+ },
508
+ "secret": "{{ $env.JWT_SECRET }}",
509
+ "options": {
510
+ "expiresIn": "1h",
511
+ "algorithm": "HS256"
512
+ },
513
+
514
+ // For VERIFY/DECODE operations:
515
+ "token": "{{ $trigger.payload.token }}"
516
+ // "secret": "{{ $env.JWT_SECRET }}", // Required for verify, not for decode
517
+ // "options": {"algorithms": ["HS256"]}, // For verify
518
+ // "options": {"complete": true} // For decode
519
+ }
520
+ }
521
+ ```
522
+
523
+ </json_web_token>
524
+
525
+ <other_operations> **Transform JSON:**
526
+
527
+ ```json
528
+ {
529
+ "type": "transform",
530
+ "options": {
531
+ "json": {
532
+ "combined": {
533
+ "user": "{{ $accountability.user }}",
534
+ "items": "{{ read_items }}",
535
+ "timestamp": "{{ $trigger.timestamp }}"
536
+ }
537
+ }
538
+ }
539
+ }
540
+ ```
541
+
542
+ **Trigger Flow:**
543
+
544
+ ```json
545
+ {
546
+ "type": "trigger",
547
+ "options": {
548
+ "flow": "other-flow-uuid",
549
+ "payload": { "data": "{{ transform_result }}" },
550
+ "iterationMode": "parallel", // "parallel", "serial", "batch"
551
+ "batchSize": 10 // Only for batch mode
552
+ }
553
+ }
554
+ ```
555
+
556
+ **Sleep:**
557
+
558
+ ```json
559
+ {
560
+ "type": "sleep",
561
+ "options": { "milliseconds": 5000 }
562
+ }
563
+ ```
564
+
565
+ **Log:**
566
+
567
+ ```json
568
+ {
569
+ "type": "log",
570
+ "options": { "message": "Processing item: {{ $trigger.payload.id }}" }
571
+ }
572
+ ```
573
+
574
+ **Throw Error:**
575
+
576
+ ```json
577
+ {
578
+ "type": "throw-error",
579
+ "options": {
580
+ "code": "CUSTOM_ERROR",
581
+ "status": "400",
582
+ "message": "Invalid data: {{ $trigger.payload.error_details }}"
583
+ }
584
+ }
585
+ ```
586
+
587
+ </other_operations> </operation_examples>
588
+
589
+ <data_chain_variables> Use `{{ variable }}` syntax to access data:
590
+
591
+ - `{{ $trigger.payload }}` - Trigger data
592
+ - `{{ $accountability.user }}` - User context
593
+ - `{{ operation_key }}` - Result from specific operation (recommended)
594
+ - `{{ operation_key.field }}` - Specific field from operation result
595
+
596
+ **⚠️ Avoid `$last`:** While `{{ $last }}` references the previous operation's result, avoid using it in production
597
+ flows. If you reorder operations, `$last` will reference a different operation, potentially breaking your flow. Always
598
+ use specific operation keys like `{{ operation_key }}` for reliable, maintainable flows. </data_chain_variables>
599
+
600
+ <permission_options> For operations that support permissions:
601
+
602
+ - `$trigger` - Use permissions from the triggering context (default)
603
+ - `$public` - Use public role permissions
604
+ - `$full` - Use full system permissions
605
+ - `role-uuid` - Use specific role's permissions </permission_options>
606
+
607
+ <real_world_patterns> <data_processing_pipeline>
608
+
609
+ ### Data Processing Pipeline
610
+
611
+ Read → Transform → Update pattern:
612
+
613
+ ```json
614
+ // 1. Read with relations
615
+ {
616
+ "flow": "flow-uuid", "key": "invoice", "type": "item-read",
617
+ "position_x": 19, "position_y": 1,
618
+ "options": {
619
+ "collection": "os_invoices",
620
+ "key": ["{{$trigger.payload.invoice}}"],
621
+ "query": {"fields": ["*", "line_items.*", "payments.*"]}
622
+ },
623
+ "resolve": "calc-operation-uuid"
624
+ }
625
+ // 2. Calculate totals
626
+ {
627
+ "flow": "flow-uuid", "key": "calculations", "type": "exec",
628
+ "position_x": 37, "position_y": 1,
629
+ "options": {
630
+ "code": "module.exports = async function(data) {\n const invoice = data.invoice;\n const subtotal = invoice.line_items.reduce((sum, item) => sum + (item.price * item.quantity), 0);\n const tax = subtotal * 0.08;\n return { subtotal, tax, total: subtotal + tax };\n}"
631
+ },
632
+ "resolve": "update-operation-uuid"
633
+ }
634
+ // 3. Update with results
635
+ {
636
+ "flow": "flow-uuid", "key": "update_invoice", "type": "item-update",
637
+ "position_x": 55, "position_y": 1,
638
+ "options": {
639
+ "collection": "os_invoices",
640
+ "payload": "{{calculations}}",
641
+ "key": ["{{$trigger.payload.invoice}}"]
642
+ }
643
+ }
644
+ ```
645
+
646
+ </data_processing_pipeline>
647
+
648
+ <error_handling_branching>
649
+
650
+ ### Error Handling with Branching
651
+
652
+ ```json
653
+ // Main operation with error handling
654
+ {
655
+ "flow": "flow-uuid", "key": "main_operation", "type": "request",
656
+ "position_x": 19, "position_y": 1,
657
+ "resolve": "success-operation-uuid",
658
+ "reject": "error-operation-uuid"
659
+ }
660
+ // Success path
661
+ {
662
+ "flow": "flow-uuid", "key": "success_notification", "type": "notification",
663
+ "position_x": 37, "position_y": 1
664
+ }
665
+ // Error path (lower row)
666
+ {
667
+ "flow": "flow-uuid", "key": "error_log", "type": "log",
668
+ "position_x": 37, "position_y": 19
669
+ }
670
+ ```
671
+
672
+ </error_handling_branching> </real_world_patterns>
673
+
674
+ <common_mistakes>
675
+
676
+ 1. **DO NOT** create operations without a flow - create flow first
677
+ 2. **DO NOT** use operation keys in resolve/reject - use UUIDs (see <workflow_example> above)
678
+ 3. **DO NOT** try to reference operations that do not exist yet
679
+ 4. **DO NOT** use duplicate keys within the same flow
680
+ 5. **DO NOT** create circular references in resolve/reject paths
681
+ 6. **DO NOT** forget to handle both success and failure paths
682
+ 7. **DO NOT** pass stringified JSON - use native objects (except request body)
683
+ 8. **DO NOT** leave operations at default position (0,0) - see <positioning_system> above
684
+ 9. **DO NOT** use dot notation in condition filters - see <critical_syntax> above
685
+ 10. **DO NOT** use wrong format for request operations - see <critical_syntax> above </common_mistakes>
686
+
687
+ <troubleshooting>
688
+ <invalid_foreign_key>
689
+ ### "Invalid foreign key" Errors
690
+
691
+ This typically means you're trying to reference an operation that doesn't exist:
692
+
693
+ - Verify the operation UUID exists by reading operations for the flow
694
+ - Check that you're using UUIDs (36 characters) not keys (short names)
695
+ - Ensure operations are created before being referenced </invalid_foreign_key>
696
+
697
+ <operation_not_executing>
698
+
699
+ ### Operation Not Executing
700
+
701
+ - Check the resolve/reject chain for breaks
702
+ - Verify the first operation is set as the flow's `operation` field
703
+ - Confirm all required operation options are provided </operation_not_executing>
704
+
705
+ <overlapping_operations>
706
+
707
+ ### Overlapping Operations in Visual Editor
708
+
709
+ If operations appear stacked at (0,0) in the flow editor:
710
+
711
+ ```json
712
+ // Fix by updating each operation's position
713
+ {"action": "update", "key": "operation-uuid", "data": {
714
+ "position_x": 19, "position_y": 1
715
+ }}
716
+ {"action": "update", "key": "other-operation-uuid", "data": {
717
+ "position_x": 37, "position_y": 1
718
+ }}
719
+ ```
720
+
721
+ </overlapping_operations> </troubleshooting>