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
- Architektur - Verstehen Sie die Architektur
- Beitragen - Zum Projekt beitragen