@directus/api 32.0.1 → 32.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/dist/auth/drivers/oauth2.d.ts +1 -2
- package/dist/auth/drivers/oauth2.js +22 -17
- package/dist/auth/drivers/openid.d.ts +1 -2
- package/dist/auth/drivers/openid.js +18 -13
- package/dist/auth/drivers/saml.js +2 -2
- package/dist/auth/utils/generate-callback-url.d.ts +8 -0
- package/dist/auth/utils/generate-callback-url.js +11 -0
- package/dist/auth/utils/is-login-redirect-allowed.d.ts +8 -0
- package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +16 -8
- package/dist/controllers/extensions.js +6 -0
- package/dist/extensions/lib/installation/manager.js +13 -3
- package/dist/mcp/tools/prompts/flows.md +57 -12
- package/dist/mcp/tools/prompts/operations.md +57 -479
- package/dist/middleware/respond.js +5 -0
- package/dist/services/graphql/schema/parse-query.js +2 -2
- package/dist/services/graphql/utils/filter-replace-m2a.d.ts +7 -3
- package/dist/services/graphql/utils/filter-replace-m2a.js +15 -9
- package/dist/services/import-export.js +6 -4
- package/dist/services/mail/index.d.ts +15 -2
- package/dist/services/mail/index.js +5 -4
- package/dist/services/notifications.js +2 -0
- package/dist/services/tus/data-store.d.ts +1 -1
- package/dist/services/tus/data-store.js +5 -5
- package/dist/utils/validate-query.js +1 -1
- package/package.json +29 -29
- package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
|
@@ -146,309 +146,72 @@ can read existing operations to see if they are using extensions operations. </a
|
|
|
146
146
|
|
|
147
147
|
<workflow_creation>
|
|
148
148
|
|
|
149
|
-
## Workflow Creation Process
|
|
149
|
+
## Workflow Creation Process
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
**Critical Order:** Create flow first → Create operations with null resolve/reject → Link operations via UUIDs → Update
|
|
152
|
+
flow entry point.
|
|
152
153
|
|
|
153
|
-
|
|
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>
|
|
154
|
+
**See `flows` tool for complete workflow example with detailed steps.** </workflow_creation>
|
|
238
155
|
|
|
239
156
|
<positioning_system>
|
|
240
157
|
|
|
241
|
-
## Grid
|
|
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:**
|
|
158
|
+
## Grid Positioning
|
|
315
159
|
|
|
316
|
-
|
|
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
|
-
```
|
|
160
|
+
**Rules:** 14x14 units, spacing every 18 units (example 19/37/55/73). Never (0,0). Start `position_y: 1`.
|
|
330
161
|
|
|
331
|
-
**
|
|
162
|
+
**Patterns:** Linear (19,1)→(37,1)→(55,1). Branching: success (37,1), error (37,19). </positioning_system>
|
|
332
163
|
|
|
333
|
-
|
|
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:**
|
|
164
|
+
<operation_examples> **condition** - Evaluates filter rules
|
|
348
165
|
|
|
349
166
|
```json
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
"options": {
|
|
353
|
-
"collection": "orders",
|
|
354
|
-
"permissions": "$trigger",
|
|
355
|
-
"emitEvents": true,
|
|
356
|
-
"key": "{{ $trigger.payload.id }}",
|
|
357
|
-
"payload": { "status": "processed" }
|
|
358
|
-
}
|
|
359
|
-
}
|
|
167
|
+
{ "type": "condition", "options": { "filter": { "$trigger": { "payload": { "status": { "_eq": "published" } } } } } }
|
|
168
|
+
// Multiple: {"_and": [{"status": {"_eq": "published"}}, {"featured": {"_eq": true}}]}
|
|
360
169
|
```
|
|
361
170
|
|
|
362
|
-
**
|
|
171
|
+
**item-create/read/update/delete** - CRUD operations
|
|
363
172
|
|
|
364
173
|
```json
|
|
365
|
-
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
"permissions": "$full",
|
|
370
|
-
"key": ["{{ read_items[0].id }}"]
|
|
371
|
-
}
|
|
372
|
-
}
|
|
174
|
+
{"type": "item-create", "options": {"collection": "notifications", "permissions": "$trigger", "payload": {"title": "{{ $trigger.payload.title }}"}}}
|
|
175
|
+
{"type": "item-read", "options": {"collection": "products", "query": {"filter": {"status": {"_eq": "active"}}}}}
|
|
176
|
+
{"type": "item-update", "options": {"collection": "orders", "key": "{{ $trigger.payload.id }}", "payload": {"status": "processed"}}}
|
|
177
|
+
{"type": "item-delete", "options": {"collection": "temp_data", "key": ["{{ read_items[0].id }}"]}}
|
|
373
178
|
```
|
|
374
179
|
|
|
375
|
-
|
|
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
|
|
180
|
+
**exec** - Custom JavaScript/TypeScript (sandboxed, no file/network access)
|
|
381
181
|
|
|
382
182
|
```json
|
|
383
183
|
{
|
|
384
184
|
"type": "exec",
|
|
385
185
|
"options": {
|
|
386
|
-
"code": "module.exports = async function(data) {\n
|
|
186
|
+
"code": "module.exports = async function(data) {\n const result = data.$trigger.payload.value * 2;\n return { result, processed: true };\n}"
|
|
387
187
|
}
|
|
388
188
|
}
|
|
389
189
|
```
|
|
390
190
|
|
|
391
|
-
**
|
|
392
|
-
|
|
393
|
-
<mail>
|
|
394
|
-
Send email notifications with optional templates
|
|
191
|
+
**mail** - Send email (markdown/wysiwyg/template)
|
|
395
192
|
|
|
396
193
|
```json
|
|
397
194
|
{
|
|
398
195
|
"type": "mail",
|
|
399
196
|
"options": {
|
|
400
|
-
"to": ["user@example.com"
|
|
197
|
+
"to": ["user@example.com"],
|
|
401
198
|
"subject": "Order Confirmation",
|
|
402
|
-
"
|
|
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
|
|
199
|
+
"body": "Order {{ $trigger.payload.order_id }}"
|
|
407
200
|
}
|
|
408
201
|
}
|
|
202
|
+
// Template: {"type": "template", "template": "welcome-email", "data": {"username": "{{ $trigger.payload.name }}"}}
|
|
409
203
|
```
|
|
410
204
|
|
|
411
|
-
**
|
|
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
|
|
205
|
+
**notification** - In-app notifications
|
|
433
206
|
|
|
434
207
|
```json
|
|
435
208
|
{
|
|
436
209
|
"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
|
-
}
|
|
210
|
+
"options": { "recipient": ["{{ $accountability.user }}"], "subject": "Task Complete", "message": "Export ready" }
|
|
445
211
|
}
|
|
446
212
|
```
|
|
447
213
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
<request>
|
|
451
|
-
Make HTTP requests
|
|
214
|
+
**request** - HTTP requests (headers must be array of objects, body as stringified JSON)
|
|
452
215
|
|
|
453
216
|
```json
|
|
454
217
|
{
|
|
@@ -456,146 +219,57 @@ Make HTTP requests
|
|
|
456
219
|
"options": {
|
|
457
220
|
"method": "POST",
|
|
458
221
|
"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 }}\"}"
|
|
222
|
+
"headers": [{ "header": "Authorization", "value": "Bearer {{ $env.API_TOKEN }}" }],
|
|
223
|
+
"body": "{\"data\": \"{{ process_data }}\"}"
|
|
470
224
|
}
|
|
471
225
|
}
|
|
472
226
|
```
|
|
473
227
|
|
|
474
|
-
**
|
|
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**
|
|
228
|
+
**json-web-token** - Sign/verify/decode JWT
|
|
496
229
|
|
|
497
230
|
```json
|
|
498
231
|
{
|
|
499
232
|
"type": "json-web-token",
|
|
500
233
|
"options": {
|
|
501
|
-
"operation": "sign",
|
|
502
|
-
|
|
503
|
-
// For SIGN operations:
|
|
504
|
-
"payload": {
|
|
505
|
-
"userId": "{{ $trigger.payload.user }}",
|
|
506
|
-
"role": "{{ $trigger.payload.role }}"
|
|
507
|
-
},
|
|
234
|
+
"operation": "sign",
|
|
235
|
+
"payload": { "userId": "{{ $trigger.payload.user }}" },
|
|
508
236
|
"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
|
|
237
|
+
"options": { "expiresIn": "1h" }
|
|
519
238
|
}
|
|
520
239
|
}
|
|
240
|
+
// Verify: {"operation": "verify", "token": "{{ $trigger.payload.token }}", "secret": "{{ $env.JWT_SECRET }}"}
|
|
521
241
|
```
|
|
522
242
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
<other_operations> **Transform JSON:**
|
|
243
|
+
**transform** - Create custom JSON payloads
|
|
526
244
|
|
|
527
245
|
```json
|
|
528
246
|
{
|
|
529
247
|
"type": "transform",
|
|
530
|
-
"options": {
|
|
531
|
-
"json": {
|
|
532
|
-
"combined": {
|
|
533
|
-
"user": "{{ $accountability.user }}",
|
|
534
|
-
"items": "{{ read_items }}",
|
|
535
|
-
"timestamp": "{{ $trigger.timestamp }}"
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
248
|
+
"options": { "json": { "combined": { "user": "{{ $accountability.user }}", "items": "{{ read_items }}" } } }
|
|
539
249
|
}
|
|
540
250
|
```
|
|
541
251
|
|
|
542
|
-
**
|
|
252
|
+
**trigger** - Execute another flow
|
|
543
253
|
|
|
544
254
|
```json
|
|
545
255
|
{
|
|
546
256
|
"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 }
|
|
257
|
+
"options": { "flow": "flow-uuid", "payload": { "data": "{{ transform_result }}" }, "iterationMode": "parallel" }
|
|
562
258
|
}
|
|
563
259
|
```
|
|
564
260
|
|
|
565
|
-
**
|
|
261
|
+
**sleep/log/throw-error** - Utilities
|
|
566
262
|
|
|
567
263
|
```json
|
|
568
|
-
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
264
|
+
{"type": "sleep", "options": {"milliseconds": 5000}}
|
|
265
|
+
{"type": "log", "options": {"message": "Processing {{ $trigger.payload.id }}"}}
|
|
266
|
+
{"type": "throw-error", "options": {"code": "CUSTOM_ERROR", "status": "400", "message": "Invalid data"}}
|
|
572
267
|
```
|
|
573
268
|
|
|
574
|
-
|
|
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
|
|
269
|
+
</operation_examples>
|
|
595
270
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
use specific operation keys like `{{ operation_key }}` for reliable, maintainable flows. </data_chain_variables>
|
|
271
|
+
<data_chain_variables> **Data Chain:** Use `{{ operation_key }}` to access results, `{{ $trigger.payload }}` for trigger
|
|
272
|
+
data. Avoid `{{ $last }}` (breaks when reordered). See `flows` tool for complete syntax. </data_chain_variables>
|
|
599
273
|
|
|
600
274
|
<permission_options> For operations that support permissions:
|
|
601
275
|
|
|
@@ -604,118 +278,22 @@ use specific operation keys like `{{ operation_key }}` for reliable, maintainabl
|
|
|
604
278
|
- `$full` - Use full system permissions
|
|
605
279
|
- `role-uuid` - Use specific role's permissions </permission_options>
|
|
606
280
|
|
|
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
281
|
<common_mistakes>
|
|
675
282
|
|
|
676
|
-
1.
|
|
677
|
-
2.
|
|
678
|
-
3.
|
|
679
|
-
4.
|
|
680
|
-
5.
|
|
681
|
-
6.
|
|
682
|
-
7.
|
|
683
|
-
8.
|
|
684
|
-
9.
|
|
685
|
-
10.
|
|
283
|
+
1. Create flow first, never operations without flow
|
|
284
|
+
2. Use UUIDs in resolve/reject, NOT keys
|
|
285
|
+
3. Create operations before referencing them
|
|
286
|
+
4. No duplicate keys within same flow
|
|
287
|
+
5. Avoid circular resolve/reject references
|
|
288
|
+
6. Set positions (not 0,0)
|
|
289
|
+
7. Use nested objects in filters, NOT dot notation
|
|
290
|
+
8. Request headers as array of objects, body as stringified JSON
|
|
291
|
+
9. Pass native objects in data (except request body)
|
|
292
|
+
10. ALWAYS pass native objects in data (EXCEPTIONS: - request body for `request` operation - code in `exec` operations)
|
|
293
|
+
11. No `$NOW` variable - use exec operation: `return { now: new Date().toISOString() };` </common_mistakes>
|
|
686
294
|
|
|
687
295
|
<troubleshooting>
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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>
|
|
296
|
+
**Invalid foreign key:** Operation UUID doesn't exist. Use UUIDs (36 chars), not keys. Create operations before referencing.
|
|
297
|
+
**Not executing:** Check resolve/reject chain, verify flow.operation set, confirm required options provided.
|
|
298
|
+
**Overlapping (0,0):** Update positions: `{"action": "update", "key": "uuid", "data": {"position_x": 19, "position_y": 1}}`
|
|
299
|
+
</troubleshooting>
|
|
@@ -74,6 +74,11 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
74
74
|
res.set('Content-Type', 'text/csv');
|
|
75
75
|
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'csv'));
|
|
76
76
|
}
|
|
77
|
+
if (req.sanitizedQuery.export === 'csv_utf8') {
|
|
78
|
+
res.attachment(`${filename}.csv`);
|
|
79
|
+
res.set('Content-Type', 'text/csv; charset=utf-8');
|
|
80
|
+
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'csv_utf8'));
|
|
81
|
+
}
|
|
77
82
|
if (req.sanitizedQuery.export === 'yaml') {
|
|
78
83
|
res.attachment(`${filename}.yaml`);
|
|
79
84
|
res.set('Content-Type', 'text/yaml');
|
|
@@ -96,9 +96,9 @@ export async function getQuery(rawQuery, schema, selections, variableValues, acc
|
|
|
96
96
|
query.deep = replaceFuncs(query.deep);
|
|
97
97
|
if (collection) {
|
|
98
98
|
if (query.filter) {
|
|
99
|
-
query.filter = filterReplaceM2A(query.filter, collection, schema);
|
|
99
|
+
query.filter = filterReplaceM2A(query.filter, collection, schema, { aliasMap: query.alias });
|
|
100
100
|
}
|
|
101
|
-
query.deep = filterReplaceM2ADeep(query.deep, collection, schema);
|
|
101
|
+
query.deep = filterReplaceM2ADeep(query.deep, collection, schema, { aliasMap: query.alias });
|
|
102
102
|
}
|
|
103
103
|
validateQuery(query);
|
|
104
104
|
return query;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import type { Filter, NestedDeepQuery, SchemaOverview } from '@directus/types';
|
|
2
|
-
export declare function filterReplaceM2A(filter_arg: Filter, collection: string, schema: SchemaOverview
|
|
3
|
-
|
|
1
|
+
import type { Filter, NestedDeepQuery, Query, SchemaOverview } from '@directus/types';
|
|
2
|
+
export declare function filterReplaceM2A(filter_arg: Filter, collection: string, schema: SchemaOverview, options?: {
|
|
3
|
+
aliasMap?: Query['alias'];
|
|
4
|
+
}): any;
|
|
5
|
+
export declare function filterReplaceM2ADeep(deep_arg: NestedDeepQuery | null | undefined, collection: string, schema: SchemaOverview, options?: {
|
|
6
|
+
aliasMap?: Query['alias'];
|
|
7
|
+
}): any;
|