Zum Hauptinhalt springen

Custom Nodes entwickeln

Lernen Sie, wie Sie eigene Nodes für Exeoflow entwickeln.

Node-Grundlagen

Ein Node ist eine wiederverwendbare Komponente, die eine spezifische Aufgabe in einem Workflow ausführt.

Node-Struktur

Jeder Node besteht aus:

  • Metadata: Beschreibung, Name, Icon
  • Properties: Konfigurierbare Eigenschaften
  • Execute-Methode: Hauptlogik des Nodes
  • Inputs/Outputs: Datenein- und -ausgänge

Einfacher Node

Hier ist ein Beispiel für einen einfachen Node:

import { INodeType, INodeTypeDescription, IExecuteFunctions } from '@/types';

export class MyCustomNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Mein Custom Node',
name: 'myCustomNode',
icon: 'file:mycustomnode.svg',
group: ['transform'],
version: 1,
description: 'Führt eine benutzerdefinierte Operation aus',
defaults: {
name: 'Mein Custom Node',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Text',
name: 'text',
type: 'string',
default: '',
placeholder: 'Text eingeben',
description: 'Der zu verarbeitende Text',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Großbuchstaben',
value: 'uppercase',
},
{
name: 'Kleinbuchstaben',
value: 'lowercase',
},
],
default: 'uppercase',
description: 'Die auszuführende Operation',
},
],
};

async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const returnData = [];

for (let i = 0; i < items.length; i++) {
const text = this.getNodeParameter('text', i) as string;
const operation = this.getNodeParameter('operation', i) as string;

let result: string;

if (operation === 'uppercase') {
result = text.toUpperCase();
} else {
result = text.toLowerCase();
}

returnData.push({
json: {
result,
originalText: text,
operation,
},
});
}

return [returnData];
}
}

Node-Properties

Property-Typen

Exeoflow unterstützt verschiedene Property-Typen:

String

{
displayName: 'Text',
name: 'text',
type: 'string',
default: '',
description: 'Ein Textfeld',
}

Number

{
displayName: 'Anzahl',
name: 'count',
type: 'number',
default: 0,
description: 'Eine Zahl',
}

Boolean

{
displayName: 'Aktiviert',
name: 'enabled',
type: 'boolean',
default: false,
description: 'Aktivieren/Deaktivieren',
}

Options (Dropdown)

{
displayName: 'Modus',
name: 'mode',
type: 'options',
options: [
{ name: 'Modus A', value: 'a' },
{ name: 'Modus B', value: 'b' },
],
default: 'a',
}

Collection (Gruppierte Properties)

{
displayName: 'Erweiterte Optionen',
name: 'advanced',
type: 'collection',
placeholder: 'Option hinzufügen',
default: {},
options: [
{
displayName: 'Timeout',
name: 'timeout',
type: 'number',
default: 5000,
},
],
}

Daten verarbeiten

Input-Daten abrufen

const items = this.getInputData();

for (let i = 0; i < items.length; i++) {
const item = items[i];
const data = item.json;

// Verarbeiten Sie die Daten
}

Parameter abrufen

const text = this.getNodeParameter('text', i) as string;
const count = this.getNodeParameter('count', i) as number;
const enabled = this.getNodeParameter('enabled', i) as boolean;

Output-Daten zurückgeben

return [
[
{
json: {
result: 'Ergebnis',
status: 'success',
},
},
],
];

HTTP-Requests

Für HTTP-Requests verwenden Sie die Helper-Funktion:

async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const returnData = [];

for (let i = 0; i < items.length; i++) {
const url = this.getNodeParameter('url', i) as string;

const response = await this.helpers.request({
method: 'GET',
url,
json: true,
});

returnData.push({
json: response,
});
}

return [returnData];
}

Fehlerbehandlung

async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const returnData = [];

for (let i = 0; i < items.length; i++) {
try {
// Ihre Logik hier

returnData.push({
json: { success: true },
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}

return [returnData];
}

Credentials

Für Nodes, die Authentifizierung benötigen:

Credential-Definition

// credentials/MyServiceApi.credentials.ts
import { ICredentialType, INodeProperties } from '@/types';

export class MyServiceApi implements ICredentialType {
name = 'myServiceApi';
displayName = 'My Service API';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
}

Credentials verwenden

const credentials = await this.getCredentials('myServiceApi');
const apiKey = credentials.apiKey as string;

const response = await this.helpers.request({
method: 'GET',
url: 'https://api.example.com/data',
headers: {
'Authorization': `Bearer ${apiKey}`,
},
json: true,
});

Node registrieren

Registrieren Sie Ihren Node in src/nodes/index.ts:

import { MyCustomNode } from './MyCustomNode/MyCustomNode.node';

export const nodeTypes = {
'myCustomNode': new MyCustomNode(),
// ... andere Nodes
};

Testing

Erstellen Sie Tests für Ihren Node:

// tests/nodes/MyCustomNode.test.ts
import { MyCustomNode } from '@/nodes/MyCustomNode/MyCustomNode.node';

describe('MyCustomNode', () => {
it('should convert text to uppercase', async () => {
const node = new MyCustomNode();

const executeFunctions = {
getInputData: () => [{ json: {} }],
getNodeParameter: (name: string) => {
if (name === 'text') return 'hello';
if (name === 'operation') return 'uppercase';
},
};

const result = await node.execute.call(executeFunctions);

expect(result[0][0].json.result).toBe('HELLO');
});
});

Best Practices

  • Verwenden Sie aussagekräftige Namen und Beschreibungen
  • Implementieren Sie umfassende Fehlerbehandlung
  • Dokumentieren Sie alle Properties
  • Schreiben Sie Tests für Ihre Nodes
  • Validieren Sie Eingabedaten
  • Verwenden Sie TypeScript-Typen
  • Folgen Sie den Coding-Standards

Nächste Schritte