@blucca/n8n-check 0.0.0-stage → 0.1.6

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 (70) hide show
  1. package/Dockerfile +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +319 -2
  4. package/bin/n8n-check.mjs +62 -0
  5. package/examples/agregado-enrichment/README.md +68 -0
  6. package/examples/agregado-enrichment/n8n-synthetic +11 -0
  7. package/examples/agregado-enrichment/observed-results.json +806 -0
  8. package/examples/agregado-enrichment/prepare.mjs +39 -0
  9. package/examples/identity-projection/README.md +39 -0
  10. package/examples/identity-projection/case.json +33 -0
  11. package/examples/identity-projection/observed-results.json +117 -0
  12. package/examples/identity-projection/workflow.json +71 -0
  13. package/examples/identity-projection/wrong-id.json +71 -0
  14. package/examples/morsof-invoice-history/README.md +77 -0
  15. package/examples/morsof-invoice-history/UPSTREAM-LICENSE +21 -0
  16. package/examples/morsof-invoice-history/n8n-datatable.cjs +28 -0
  17. package/examples/morsof-invoice-history/observed-results.json +113 -0
  18. package/examples/morsof-invoice-history/prepare.mjs +57 -0
  19. package/examples/morsof-invoice-history/replay.mjs +35 -0
  20. package/examples/morsof-invoice-history/run.sh +19 -0
  21. package/examples/object-body/broken.json +74 -0
  22. package/examples/object-body/case.json +81 -0
  23. package/examples/object-body/fixed.json +74 -0
  24. package/examples/object-body/observed-results.json +449 -0
  25. package/examples/object-body/retry-case.json +112 -0
  26. package/examples/object-body/retry.json +77 -0
  27. package/examples/propfind-body/README.md +75 -0
  28. package/examples/propfind-body/case.json +51 -0
  29. package/examples/propfind-body/no-body-control.json +96 -0
  30. package/examples/propfind-body/observed-results.json +307 -0
  31. package/examples/propfind-body/workflow.json +96 -0
  32. package/examples/raw-response/README.md +71 -0
  33. package/examples/raw-response/file-case.json +54 -0
  34. package/examples/raw-response/json-body.json +58 -0
  35. package/examples/raw-response/json-case.json +52 -0
  36. package/examples/raw-response/observed-results.json +383 -0
  37. package/examples/raw-response/raw-file.json +88 -0
  38. package/examples/raw-response/raw-json.json +58 -0
  39. package/examples/raw-response/raw-text.json +58 -0
  40. package/examples/raw-response/text-case.json +50 -0
  41. package/examples/retry-isolation/README.md +71 -0
  42. package/examples/retry-isolation/direct-case.json +112 -0
  43. package/examples/retry-isolation/direct.json +101 -0
  44. package/examples/retry-isolation/editor-template.json +168 -0
  45. package/examples/retry-isolation/http-batching-case.json +112 -0
  46. package/examples/retry-isolation/http-batching.json +107 -0
  47. package/examples/retry-isolation/loop-case.json +117 -0
  48. package/examples/retry-isolation/loop.json +133 -0
  49. package/examples/retry-isolation/observed-results.json +155 -0
  50. package/examples/silent-filter/README.md +57 -0
  51. package/examples/silent-filter/broken.json +96 -0
  52. package/examples/silent-filter/case.json +120 -0
  53. package/examples/silent-filter/fixed.json +96 -0
  54. package/examples/silent-filter/observed-results.json +273 -0
  55. package/examples/stop-after/README.md +66 -0
  56. package/examples/stop-after/broken.json +134 -0
  57. package/examples/stop-after/case.json +34 -0
  58. package/examples/stop-after/fixed.json +134 -0
  59. package/examples/stop-after/observed-results.json +162 -0
  60. package/examples/version-matrix/README.md +65 -0
  61. package/examples/version-matrix/observed-results.json +307 -0
  62. package/examples/version-matrix/upgrade-check.yml +32 -0
  63. package/package.json +41 -4
  64. package/src/action.mjs +108 -0
  65. package/src/case-draft.mjs +163 -0
  66. package/src/mock.mjs +72 -0
  67. package/src/report.mjs +73 -0
  68. package/src/runner.mjs +111 -0
  69. package/src/version.mjs +3 -0
  70. package/src/workflow.mjs +140 -0
@@ -0,0 +1,57 @@
1
+ import {readFile,writeFile,mkdir} from 'node:fs/promises';
2
+ import {createHash} from 'node:crypto';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {join,dirname} from 'node:path';
5
+ const here=dirname(fileURLToPath(import.meta.url));
6
+ const sources={fixed:['3d8c61ee5b0e37e7e0052f53e7839db011b9c0de','b29612d4206a557129f0f97b7ca6619419033bff7940bc2366370fd20838e180'],broken:['adb2d3c3aa7534bcce53d955938e24c9f061b315','43bb3d16f4e1bf10a01cbb75b257e7bdb8e536d2aaedb94f1d05464137dda23a']};
7
+ const cloned=x=>JSON.parse(JSON.stringify(x));
8
+ const save=async(n,x)=>writeFile(join(here,n),JSON.stringify(x,null,2)+'\n');
9
+ const fixtureName='Start Manual Test';
10
+ const edge=n=>({node:n,type:'main',index:0});
11
+ function code(w,name,js){w.nodes.push({id:'contract-'+name.replaceAll(' ','-'),name,type:'n8n-nodes-base.code',typeVersion:2,position:[2000,2000],parameters:{mode:'runOnceForAllItems',jsCode:js}});}
12
+ function then(w,from,to){w.connections[from]??={main:[[]]};w.connections[from].main[0]??=[];w.connections[from].main[0].push(edge(to));}
13
+ const projectCode=`return $input.all().map(({json:v})=>({json:{invoiceId:v.invoiceId,customerId:v.customerId,email:v.email,classification:v.classification,stage:v.reminderStage,outputPath:v.outputPath,actionReason:v.actionReason,historyRecordId:v.historyRecordId??null,draftCustomerId:v.draft?.recipient?.customerId??null,draftInvoiceId:v.draft?.invoice?.invoiceId??null}}));`;
14
+ for(const [kind,[sha,hash]] of Object.entries(sources)){
15
+ let bytes;try{bytes=await readFile(join(here,kind+'.source.json'));}catch{const r=await fetch(`https://raw.githubusercontent.com/Morsoflab/n8n-automation-templates/${sha}/templates/invoice-payment-follow-up/invoice-payment-follow-up.json`,{signal:AbortSignal.timeout(30000)});if(!r.ok)throw Error('download '+r.status);bytes=Buffer.from(await r.arrayBuffer());await writeFile(join(here,kind+'.source.json'),bytes);}
16
+ if(createHash('sha256').update(bytes).digest('hex')!==hash)throw Error('Source hash '+kind);
17
+ const w=JSON.parse(bytes);
18
+ const node=n=>w.nodes.find(x=>x.name===n);
19
+ node('Load Fictional Invoice Samples').parameters.jsCode=`return $('${fixtureName}').first().json.invoices.map(json=>({json}));`;
20
+ const config=node(kind==='fixed'?'Validate Workflow Configuration':'Validate Normalize and Classify');
21
+ config.parameters.jsCode=config.parameters.jsCode.replace("const EVALUATION_DATE = '';","const EVALUATION_DATE = '2030-06-20';");
22
+ for(const name of ['Ready for Review','Already Prepared','No Action Required','Manual Review','Invalid Record']){code(w,'Contract '+name,projectCode);then(w,name,'Contract '+name);}
23
+ code(w,'Contract Writes',`return $input.all().map(({json:r})=>({json:{invoiceId:r.invoice_id,stage:r.reminder_stage,customerId:r.customer_id,email:r.recipient_email,amount:r.outstanding_amount,id:r.id}}));`);
24
+ then(w,'Store Reminder Preparation','Contract Writes');
25
+ if(kind==='fixed'){
26
+ // Fixture-only history setup. Every operation uses the real Data Table node.
27
+ w.connections['Create or Reuse Reminder History'].main[0]=[edge('Fixture History Rows')];
28
+ code(w,'Fixture History Rows',`const rows=$('${fixtureName}').first().json.history??[];return rows.length?rows.map(json=>({json})):[{json:{emptyHistory:true}}];`);
29
+ const branch=cloned(node('Invalid Record?'));branch.id='fixture-history-empty';branch.name='Fixture Empty History?';branch.parameters.conditions.conditions[0]={id:'empty-history',leftValue:'={{ $json.emptyHistory === true }}',rightValue:true,operator:{type:'boolean',operation:'true',singleValue:true}};w.nodes.push(branch);
30
+ const insert=cloned(node('Store Reminder Preparation'));insert.id='fixture-seed-history';insert.name='Fixture Seed History';delete insert.onError;
31
+ insert.parameters.columns.value=Object.fromEntries(Object.keys(insert.parameters.columns.value).map(k=>[k,'={{ $json.'+k+' }}']));w.nodes.push(insert);
32
+ code(w,'Fixture Seed Complete','return [{json:{}}];');
33
+ w.connections['Fixture History Rows']={main:[[edge('Fixture Empty History?')]]};
34
+ w.connections['Fixture Empty History?']={main:[[edge('Load Reminder History')],[edge('Fixture Seed History')]]};
35
+ w.connections['Fixture Seed History']={main:[[edge('Fixture Seed Complete')]]};
36
+ w.connections['Fixture Seed Complete']={main:[[edge('Load Reminder History')]]};
37
+ }
38
+ await save(kind+'.workflow.json',w);
39
+ }
40
+ const invoice=(id,dueDate='2030-06-22',extra={})=>({invoiceId:id,customer:{id:'CUST-'+id,name:'Fictional '+id,email:id.toLowerCase()+'@fixture.example',phone:''},currency:'EUR',totalAmount:100,paidAmount:0,outstandingAmount:100,dueDate,paymentStatus:'unpaid',disputed:false,updatedAt:'2030-06-19T10:00:00.000Z',...extra});
41
+ const A=invoice('INV-A'),B=invoice('INV-B','2030-06-20'),C=invoice('INV-C','2030-06-12'),P=invoice('INV-P','2030-06-15',{paymentStatus:'paid',paidAmount:100,outstandingAmount:0});
42
+ const stage=x=>x.paymentStatus==='paid'?'':x.dueDate==='2030-06-22'?'upcoming_3_days':x.dueDate==='2030-06-20'?'due_today':'overdue_7_days';
43
+ const classification=x=>x.paymentStatus==='paid'?'paid':stage(x)==='upcoming_3_days'?'upcoming':stage(x)==='due_today'?'due':'overdue';
44
+ const expected=(x,path='ready_for_review',id=null,reason=path==='already_prepared'?'reminder_stage_already_prepared':path==='no_action_required'?'invoice_paid':'reminder_stage_eligible')=>({invoiceId:x.invoiceId,customerId:x.customer.id,email:x.customer.email,classification:classification(x),stage:stage(x),outputPath:path,actionReason:reason,historyRecordId:id,draftCustomerId:path==='no_action_required'?null:x.customer.id,draftInvoiceId:path==='no_action_required'?null:x.invoiceId});
45
+ const history=(x,prepared_at='2030-06-19T09:00:00.000Z')=>({invoice_id:x.invoiceId,reminder_stage:stage(x),classification:classification(x),customer_id:x.customer.id,recipient_email:x.customer.email,recipient_phone:'',currency:'EUR',outstanding_amount:100,due_date:x.dueDate,review_status:'pending_manual_approval',draft_subject:'Payment reminder review: invoice '+x.invoiceId,prepared_at,source_updated_at:x.updatedAt});
46
+ const write=(x,id)=>({invoiceId:x.invoiceId,stage:stage(x),customerId:x.customer.id,email:x.customer.email,amount:x.outstandingAmount,id});
47
+ const specs=[
48
+ ['two-eligible',[A,B],[],[expected(A,'ready_for_review',1),expected(B,'ready_for_review',2)],[],[],[write(A,1),write(B,2)]],
49
+ ['eligible-paid',[A,P],[],[expected(A,'ready_for_review',1)],[],[expected(P,'no_action_required')],[write(A,1)]],
50
+ ['existing-new',[A,B],[history(A)],[expected(B,'ready_for_review',2)],[expected(A,'already_prepared',1)],[],[write(B,2)]],
51
+ ['duplicate',[A,A],[],[expected(A,'ready_for_review',1),expected(A,'already_prepared',1,'duplicate_in_execution')],[],[],[write(A,1)]],
52
+ ['reordered',[B,A],[history(A)],[expected(B,'ready_for_review',2)],[expected(A,'already_prepared',1)],[],[write(B,2)]],
53
+ ['empty-history',[A,B,C],[],[expected(A,'ready_for_review',1),expected(B,'ready_for_review',2),expected(C,'ready_for_review',3)],[],[],[write(A,1),write(B,2),write(C,3)]],
54
+ ['multiple-existing',[A,B,C],[history(A,'2030-06-18T09:00:00.000Z'),history(A),history(B)],[expected(C,'ready_for_review',4)],[expected(A,'already_prepared',2),expected(B,'already_prepared',3)],[],[write(C,4)]],
55
+ ];
56
+ for(const[name,invoices,rows,ready,already,paid,writes]of specs){await save(name+'.case.json',{version:1,name:'Morsof invoice history: '+name,input:{node:fixtureName,items:[{invoices,history:rows}]},mocks:[],assertions:[{node:'Contract Ready for Review',equals:ready},{node:'Contract Already Prepared',equals:already},{node:'Contract No Action Required',equals:paid},{node:'Contract Manual Review',equals:[]},{node:'Contract Invalid Record',equals:[]},{node:'Contract Writes',equals:writes}]});}
57
+ await save('manifest.json',{sources,configuration:{EVALUATION_DATE:'2030-06-20'},cases:specs.map(x=>x[0]),adaptations:['Replace fictional sample generator with case JSON input.','Set documented EVALUATION_DATE test override.','Append read-only terminal output projections.','For fixed graph, insert fixture-only Data Table seed branch before original Load Reminder History.'],runtime:'n8n 2.41.7 with DataTableModule initialized for execute CLI'});
@@ -0,0 +1,35 @@
1
+ // Run the same imported workflow twice against one isolated SQLite database.
2
+ // The first pass uses n8n-check; the second uses its execution/assertion functions.
3
+ import {readFile,writeFile,mkdir} from 'node:fs/promises';
4
+ import {join,resolve,dirname} from 'node:path';
5
+ import {fileURLToPath,pathToFileURL} from 'node:url';
6
+ import {DatabaseSync} from 'node:sqlite';
7
+ const here=dirname(fileURLToPath(import.meta.url));
8
+ const root=resolve(process.argv[2]??'./n8n-check');
9
+ const {runCase,runtimeEnv,command}=await import(pathToFileURL(join(root,'src/runner.mjs')));
10
+ const {executionFrom,executionChecks,junit}=await import(pathToFileURL(join(root,'src/report.mjs')));
11
+ const out=resolve(process.argv[3]??join(here,'results/replay'));
12
+ const runtime=join(here,'n8n-datatable.cjs');
13
+ const first=await runCase({workflowFile:join(here,'fixed.workflow.json'),caseFile:join(here,'two-eligible.case.json'),n8n:runtime,out:join(out,'first')});
14
+ if(first.exitCode!==0)throw Error('Initial preparation failed: '+JSON.stringify(first));
15
+ const home=join(first.runDirectory,'n8n-home');
16
+ const dbFile=join(home,'.n8n/database.sqlite');
17
+ function rows(){const db=new DatabaseSync(dbFile,{readOnly:true});const table=db.prepare("SELECT id FROM data_table WHERE name='invoice_reminder_history'").get();if(!table)throw Error('Missing history table');const data=db.prepare('SELECT * FROM "data_table_user_'+table.id+'" ORDER BY id').all();db.close();return data;}
18
+ const before=rows();
19
+ const env=runtimeEnv(home);env.N8N_ENCRYPTION_KEY=JSON.parse(await readFile(join(home,'.n8n/config'),'utf8')).encryptionKey;
20
+ const workflow=JSON.parse(await readFile(join(first.runDirectory,'workflow.json'),'utf8'));
21
+ const start=Date.now();
22
+ const raw=await command(runtime,['execute','--id='+workflow.id,'--rawOutput'],env,60000);
23
+ await mkdir(join(out,'second'),{recursive:true});
24
+ await writeFile(join(out,'second/execute.stdout.log'),raw.stdout);await writeFile(join(out,'second/execute.stderr.log'),raw.stderr);
25
+ const execution=executionFrom(raw.stdout+'\n'+raw.stderr);if(execution)await writeFile(join(out,'second/execution.json'),JSON.stringify(execution,null,2));
26
+ const original=JSON.parse(await readFile(join(here,'two-eligible.case.json'),'utf8'));
27
+ const assertions=original.assertions.map(a=>a.node==='Contract Ready for Review'||a.node==='Contract Writes'?{...a,equals:[]}:a.node==='Contract Already Prepared'?{...a,equals:original.assertions[0].equals.map(v=>({...v,outputPath:'already_prepared',actionReason:'reminder_stage_already_prepared'}))}:a);
28
+ const after=rows();const checks=executionChecks(execution,raw,assertions);
29
+ checks.push({name:'Stored rows unchanged across replay',passed:JSON.stringify(before)===JSON.stringify(after),expected:before,actual:after});
30
+ const previousTimes=execution?.data.resultData.runData['Already Prepared']?.flatMap(run=>run.data.main[0].map(item=>({id:item.json.historyRecordId,previousPreparedAt:item.json.previousPreparedAt})))??[];
31
+ const storedTimes=before.map(row=>({id:row.id,previousPreparedAt:new Date(row.prepared_at.replace(' ', 'T') + 'Z').toISOString()}));
32
+ checks.push({name:'Replay keeps original preparation timestamps',passed:JSON.stringify(previousTimes)===JSON.stringify(storedTimes),expected:storedTimes,actual:previousTimes});
33
+ const passed=checks.every(c=>c.passed);const report={schemaVersion:1,runnerVersion:first.runnerVersion,name:'Morsof unchanged replay against persistent Data Table',status:passed?'passed':'failed',checks,requests:[],network:first.network,n8nVersion:first.n8nVersion,runDirectory:join(out,'second'),durationMs:Date.now()-start,exitCode:passed?0:1};
34
+ await writeFile(join(out,'second/report.json'),JSON.stringify(report,null,2)+'\n');await writeFile(join(out,'second/junit.xml'),junit(report));
35
+ process.stdout.write(JSON.stringify({status:report.status,checks:checks.length,rows:after.length,report:join(out,'second/report.json')})+'\n');process.exitCode=report.exitCode;
@@ -0,0 +1,19 @@
1
+ #!/bin/sh
2
+ # Invoke inside a loopback-only network namespace; put n8n 2.41.7 on PATH.
3
+ set -eu
4
+ PACK=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
5
+ CHECK=$(CDPATH= cd -- "${1:?Usage: run.sh /path/to/n8n-check [results-dir]}" && pwd)
6
+ OUT=${2:-"$PACK/results"}
7
+ for CASE in two-eligible eligible-paid existing-new duplicate reordered empty-history multiple-existing; do
8
+ node "$CHECK/bin/n8n-check.mjs" "$PACK/fixed.workflow.json" "$PACK/$CASE.case.json" \
9
+ --n8n "$PACK/n8n-datatable.cjs" --out "$OUT/$CASE"
10
+ done
11
+ if node "$CHECK/bin/n8n-check.mjs" "$PACK/broken.workflow.json" "$PACK/two-eligible.case.json" \
12
+ --n8n "$PACK/n8n-datatable.cjs" --out "$OUT/broken"; then
13
+ echo 'Historical regression unexpectedly passed.' >&2
14
+ exit 1
15
+ else
16
+ CODE=$?
17
+ [ "$CODE" -eq 1 ] || exit "$CODE"
18
+ fi
19
+ node "$PACK/replay.mjs" "$CHECK" "$OUT/replay"
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "Object body example",
3
+ "nodes": [
4
+ {
5
+ "id": "start",
6
+ "name": "Start",
7
+ "type": "n8n-nodes-base.manualTrigger",
8
+ "typeVersion": 1,
9
+ "position": [
10
+ -400,
11
+ 0
12
+ ],
13
+ "parameters": {}
14
+ },
15
+ {
16
+ "id": "fixture",
17
+ "name": "Fixture",
18
+ "type": "n8n-nodes-base.noOp",
19
+ "typeVersion": 1,
20
+ "position": [
21
+ -200,
22
+ 0
23
+ ],
24
+ "parameters": {}
25
+ },
26
+ {
27
+ "id": "render",
28
+ "name": "Render",
29
+ "type": "n8n-nodes-base.httpRequest",
30
+ "typeVersion": 4.2,
31
+ "position": [
32
+ 0,
33
+ 0
34
+ ],
35
+ "parameters": {
36
+ "method": "POST",
37
+ "url": "https://api.example.com/renders",
38
+ "sendBody": true,
39
+ "specifyBody": "json",
40
+ "jsonBody": "={ \"shortId\": {{ $json.shortId }}, \"renderOptions\": {{ $json.styling }} }",
41
+ "options": {
42
+ "timeout": 10000
43
+ }
44
+ }
45
+ }
46
+ ],
47
+ "connections": {
48
+ "Start": {
49
+ "main": [
50
+ [
51
+ {
52
+ "node": "Fixture",
53
+ "type": "main",
54
+ "index": 0
55
+ }
56
+ ]
57
+ ]
58
+ },
59
+ "Fixture": {
60
+ "main": [
61
+ [
62
+ {
63
+ "node": "Render",
64
+ "type": "main",
65
+ "index": 0
66
+ }
67
+ ]
68
+ ]
69
+ }
70
+ },
71
+ "settings": {
72
+ "executionOrder": "v1"
73
+ }
74
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Two render requests preserve object options",
4
+ "input": {
5
+ "node": "Fixture",
6
+ "items": [
7
+ {
8
+ "shortId": 42,
9
+ "styling": {
10
+ "color": "#112233",
11
+ "fontSize": 31
12
+ }
13
+ },
14
+ {
15
+ "shortId": 43,
16
+ "styling": {
17
+ "color": "#aabbcc",
18
+ "fontSize": 47
19
+ }
20
+ }
21
+ ]
22
+ },
23
+ "mocks": [
24
+ {
25
+ "node": "Render",
26
+ "url": "/renders",
27
+ "routes": [
28
+ {
29
+ "method": "POST",
30
+ "path": "/renders",
31
+ "responses": [
32
+ {
33
+ "status": 200,
34
+ "json": {
35
+ "id": "render-42"
36
+ }
37
+ },
38
+ {
39
+ "status": 200,
40
+ "json": {
41
+ "id": "render-43"
42
+ }
43
+ }
44
+ ],
45
+ "expect": {
46
+ "count": 2,
47
+ "bodies": [
48
+ {
49
+ "shortId": 42,
50
+ "renderOptions": {
51
+ "color": "#112233",
52
+ "fontSize": 31
53
+ }
54
+ },
55
+ {
56
+ "shortId": 43,
57
+ "renderOptions": {
58
+ "color": "#aabbcc",
59
+ "fontSize": 47
60
+ }
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ ],
68
+ "assertions": [
69
+ {
70
+ "node": "Render",
71
+ "equals": [
72
+ {
73
+ "id": "render-42"
74
+ },
75
+ {
76
+ "id": "render-43"
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "Object body example",
3
+ "nodes": [
4
+ {
5
+ "id": "start",
6
+ "name": "Start",
7
+ "type": "n8n-nodes-base.manualTrigger",
8
+ "typeVersion": 1,
9
+ "position": [
10
+ -400,
11
+ 0
12
+ ],
13
+ "parameters": {}
14
+ },
15
+ {
16
+ "id": "fixture",
17
+ "name": "Fixture",
18
+ "type": "n8n-nodes-base.noOp",
19
+ "typeVersion": 1,
20
+ "position": [
21
+ -200,
22
+ 0
23
+ ],
24
+ "parameters": {}
25
+ },
26
+ {
27
+ "id": "render",
28
+ "name": "Render",
29
+ "type": "n8n-nodes-base.httpRequest",
30
+ "typeVersion": 4.2,
31
+ "position": [
32
+ 0,
33
+ 0
34
+ ],
35
+ "parameters": {
36
+ "method": "POST",
37
+ "url": "https://api.example.com/renders",
38
+ "sendBody": true,
39
+ "specifyBody": "json",
40
+ "jsonBody": "={{ { shortId: $json.shortId, renderOptions: $json.styling } }}",
41
+ "options": {
42
+ "timeout": 10000
43
+ }
44
+ }
45
+ }
46
+ ],
47
+ "connections": {
48
+ "Start": {
49
+ "main": [
50
+ [
51
+ {
52
+ "node": "Fixture",
53
+ "type": "main",
54
+ "index": 0
55
+ }
56
+ ]
57
+ ]
58
+ },
59
+ "Fixture": {
60
+ "main": [
61
+ [
62
+ {
63
+ "node": "Render",
64
+ "type": "main",
65
+ "index": 0
66
+ }
67
+ ]
68
+ ]
69
+ }
70
+ },
71
+ "settings": {
72
+ "executionOrder": "v1"
73
+ }
74
+ }