n8n-nodes-connector-engine 1.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.
- package/README.md +94 -0
- package/dist/credentials/ConnectorEngineApi.credentials.js +30 -0
- package/dist/nodes/ConnectorEngine.node.js +250 -0
- package/dist/services/CapabilityService.js +34 -0
- package/dist/services/FieldMapper.js +80 -0
- package/dist/services/SchemaService.js +58 -0
- package/dist/services/TransportService.js +73 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# n8n-nodes-connector-engine
|
|
2
|
+
|
|
3
|
+
Universal **Connector Engine** node for n8n. Connects to your ERP Integration Execution API and runs connector capabilities (resources and operations) with schema-driven input fields.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- **Node.js 18+** (for building the package)
|
|
8
|
+
- n8n instance (cloud or self-hosted)
|
|
9
|
+
- Connector Engine API (base URL + API key from your ERP Integration app)
|
|
10
|
+
|
|
11
|
+
## Build (required before install from path)
|
|
12
|
+
|
|
13
|
+
From the package directory:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm run build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This produces the `dist/` folder that n8n loads.
|
|
21
|
+
|
|
22
|
+
## Install in n8n
|
|
23
|
+
|
|
24
|
+
### Option A: From zip (no build, ready to install)
|
|
25
|
+
|
|
26
|
+
Use the pre-built zip so you don’t need Node 18 or to run `npm run build`.
|
|
27
|
+
|
|
28
|
+
1. Get **n8n-nodes-connector-engine.zip** (from the repo or run `npm run zip` in the package folder to create it).
|
|
29
|
+
2. Unzip it into a folder, e.g.:
|
|
30
|
+
```bash
|
|
31
|
+
mkdir -p n8n-nodes-connector-engine
|
|
32
|
+
cd n8n-nodes-connector-engine
|
|
33
|
+
unzip /path/to/n8n-nodes-connector-engine.zip
|
|
34
|
+
```
|
|
35
|
+
You should see `package.json`, `README.md`, and `dist/` in that folder.
|
|
36
|
+
3. On the machine where n8n runs, install from that folder:
|
|
37
|
+
```bash
|
|
38
|
+
mkdir -p ~/.n8n/nodes
|
|
39
|
+
cd ~/.n8n/nodes
|
|
40
|
+
npm install /path/to/n8n-nodes-connector-engine
|
|
41
|
+
```
|
|
42
|
+
4. Restart n8n. The **Connector Engine** node and **Connector Engine API** credential will be available.
|
|
43
|
+
|
|
44
|
+
### Option B: From n8n UI (if published to npm)
|
|
45
|
+
|
|
46
|
+
1. In n8n: **Settings** → **Community nodes** → **Install**.
|
|
47
|
+
2. Enter: `n8n-nodes-connector-engine`.
|
|
48
|
+
3. Install and restart if prompted.
|
|
49
|
+
|
|
50
|
+
### Option C: Install from local path (self-hosted)
|
|
51
|
+
|
|
52
|
+
1. Build the package (see above) so `dist/` exists.
|
|
53
|
+
2. On the machine where n8n runs, install the package into n8n’s nodes directory:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mkdir -p ~/.n8n/nodes
|
|
57
|
+
cd ~/.n8n/nodes
|
|
58
|
+
npm install /path/to/n8n-nodes-connector-engine
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. Restart n8n.
|
|
62
|
+
|
|
63
|
+
### Option D: Docker / queue mode
|
|
64
|
+
|
|
65
|
+
1. Build the package locally (Node 18+) so `dist/` exists.
|
|
66
|
+
2. Copy the built package (or the repo) into the container and install:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
docker exec -it n8n sh
|
|
70
|
+
mkdir -p ~/.n8n/nodes
|
|
71
|
+
cd ~/.n8n/nodes
|
|
72
|
+
npm install /path/to/n8n-nodes-connector-engine
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Restart the n8n container.
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
1. Add a **Connector Engine** node to your workflow.
|
|
80
|
+
2. Create a **Connector Engine API** credential:
|
|
81
|
+
- **Base URL**: your Execution API base URL (e.g. `https://your-app.com/api` or `http://localhost/api`).
|
|
82
|
+
- **API Key**: an API key from your ERP Integration app (API Keys page).
|
|
83
|
+
3. In the node, choose **Connector** → **Resource** → **Operation** (loaded from your API).
|
|
84
|
+
4. Add **Fields** (key/value) as required by the operation; keys come from the capability schema.
|
|
85
|
+
5. Run the workflow; the node calls `POST /execute` and returns the result data.
|
|
86
|
+
|
|
87
|
+
## Credentials
|
|
88
|
+
|
|
89
|
+
- **Name:** Connector Engine API
|
|
90
|
+
- **Fields:** Base URL (required), API Key (required, password).
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorEngineApi = void 0;
|
|
4
|
+
class ConnectorEngineApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'connectorEngineApi';
|
|
7
|
+
this.displayName = 'Connector Engine API';
|
|
8
|
+
this.documentationUrl = 'https://github.com/your-org/n8n-nodes-connector-engine';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Base URL',
|
|
12
|
+
name: 'baseUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
required: true,
|
|
16
|
+
placeholder: 'https://api.example.com',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
displayName: 'API Key',
|
|
20
|
+
name: 'apiKey',
|
|
21
|
+
type: 'string',
|
|
22
|
+
typeOptions: { password: true },
|
|
23
|
+
default: '',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ConnectorEngineApi = ConnectorEngineApi;
|
|
30
|
+
exports.default = ConnectorEngineApi;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorEngine = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const CapabilityService_1 = require("../services/CapabilityService");
|
|
6
|
+
const FieldMapper_1 = require("../services/FieldMapper");
|
|
7
|
+
const SchemaService_1 = require("../services/SchemaService");
|
|
8
|
+
const TransportService_1 = require("../services/TransportService");
|
|
9
|
+
function getCredentials() {
|
|
10
|
+
const creds = this.getCredentials('connectorEngineApi');
|
|
11
|
+
if (!creds || !creds.baseUrl || !creds.apiKey) {
|
|
12
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Connector Engine API credentials (base URL and API key) are required.');
|
|
13
|
+
}
|
|
14
|
+
return { baseUrl: String(creds.baseUrl).replace(/\/$/, ''), apiKey: String(creds.apiKey) };
|
|
15
|
+
}
|
|
16
|
+
class ConnectorEngine {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.description = {
|
|
19
|
+
displayName: 'Connector Engine',
|
|
20
|
+
name: 'connectorEngine',
|
|
21
|
+
group: ['transform'],
|
|
22
|
+
version: 1,
|
|
23
|
+
subtitle: '={{ $parameter["resource"] + "." + $parameter["operation"] }}',
|
|
24
|
+
description: 'Execute connector capabilities via schema-driven API',
|
|
25
|
+
defaults: { name: 'Connector Engine' },
|
|
26
|
+
inputs: ['main'],
|
|
27
|
+
outputs: ['main'],
|
|
28
|
+
credentials: [{ name: 'connectorEngineApi', required: true }],
|
|
29
|
+
properties: [
|
|
30
|
+
{
|
|
31
|
+
displayName: 'Connector',
|
|
32
|
+
name: 'connectorId',
|
|
33
|
+
type: 'options',
|
|
34
|
+
required: true,
|
|
35
|
+
default: '',
|
|
36
|
+
typeOptions: { loadOptionsMethod: 'getConnectors' },
|
|
37
|
+
description: 'Connector instance to use',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
displayName: 'Resource',
|
|
41
|
+
name: 'resource',
|
|
42
|
+
type: 'options',
|
|
43
|
+
required: true,
|
|
44
|
+
default: '',
|
|
45
|
+
typeOptions: {
|
|
46
|
+
loadOptionsMethod: 'getResources',
|
|
47
|
+
loadOptionsDependsOn: ['connectorId'],
|
|
48
|
+
},
|
|
49
|
+
description: 'Resource (e.g. customer, order)',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
displayName: 'Operation',
|
|
53
|
+
name: 'operation',
|
|
54
|
+
type: 'options',
|
|
55
|
+
required: true,
|
|
56
|
+
default: '',
|
|
57
|
+
typeOptions: {
|
|
58
|
+
loadOptionsMethod: 'getOperations',
|
|
59
|
+
loadOptionsDependsOn: ['connectorId', 'resource'],
|
|
60
|
+
},
|
|
61
|
+
description: 'Operation to perform',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
displayName: 'Fields',
|
|
65
|
+
name: 'fields',
|
|
66
|
+
type: 'fixedCollection',
|
|
67
|
+
typeOptions: { multipleValues: true },
|
|
68
|
+
default: {},
|
|
69
|
+
placeholder: 'Add field',
|
|
70
|
+
description: 'Input fields for the capability',
|
|
71
|
+
options: [
|
|
72
|
+
{
|
|
73
|
+
name: 'values',
|
|
74
|
+
displayName: 'Field',
|
|
75
|
+
values: [
|
|
76
|
+
{
|
|
77
|
+
displayName: 'Key',
|
|
78
|
+
name: 'key',
|
|
79
|
+
type: 'options',
|
|
80
|
+
default: '',
|
|
81
|
+
typeOptions: {
|
|
82
|
+
loadOptionsMethod: 'getFieldKeys',
|
|
83
|
+
loadOptionsDependsOn: ['connectorId', 'resource', 'operation'],
|
|
84
|
+
},
|
|
85
|
+
description: 'Field name from schema',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
displayName: 'Value',
|
|
89
|
+
name: 'value',
|
|
90
|
+
type: 'string',
|
|
91
|
+
default: '',
|
|
92
|
+
typeOptions: {
|
|
93
|
+
loadOptionsMethod: 'getFieldValueOptions',
|
|
94
|
+
loadOptionsDependsOn: ['connectorId', 'resource', 'operation', 'fields'],
|
|
95
|
+
},
|
|
96
|
+
description: 'Value for the field',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
methods: {
|
|
104
|
+
loadOptions: {
|
|
105
|
+
async getConnectors() {
|
|
106
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
107
|
+
const transport = new TransportService_1.TransportService();
|
|
108
|
+
const response = await transport.getConnectors(baseUrl, apiKey);
|
|
109
|
+
const data = response && response.data;
|
|
110
|
+
if (!Array.isArray(data))
|
|
111
|
+
return [];
|
|
112
|
+
return data.map((c) => ({ name: `${c.name} (${c.type})`, value: c.id }));
|
|
113
|
+
},
|
|
114
|
+
async getResources() {
|
|
115
|
+
const connectorId = this.getCurrentNodeParameter('connectorId');
|
|
116
|
+
if (!connectorId)
|
|
117
|
+
return [];
|
|
118
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
119
|
+
const transport = new TransportService_1.TransportService();
|
|
120
|
+
const capabilityService = new CapabilityService_1.CapabilityService(transport);
|
|
121
|
+
const structure = await capabilityService.getStructuredCapabilities(baseUrl, apiKey, connectorId);
|
|
122
|
+
return structure.resources.map((r) => ({ name: r, value: r }));
|
|
123
|
+
},
|
|
124
|
+
async getOperations() {
|
|
125
|
+
const connectorId = this.getCurrentNodeParameter('connectorId');
|
|
126
|
+
const resource = this.getCurrentNodeParameter('resource');
|
|
127
|
+
if (!connectorId || !resource)
|
|
128
|
+
return [];
|
|
129
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
130
|
+
const transport = new TransportService_1.TransportService();
|
|
131
|
+
const capabilityService = new CapabilityService_1.CapabilityService(transport);
|
|
132
|
+
const structure = await capabilityService.getStructuredCapabilities(baseUrl, apiKey, connectorId);
|
|
133
|
+
const ops = structure.operationsByResource[resource];
|
|
134
|
+
if (!Array.isArray(ops))
|
|
135
|
+
return [];
|
|
136
|
+
return ops.map((o) => ({ name: o, value: o }));
|
|
137
|
+
},
|
|
138
|
+
async getFieldKeys() {
|
|
139
|
+
const connectorId = this.getCurrentNodeParameter('connectorId');
|
|
140
|
+
const resource = this.getCurrentNodeParameter('resource');
|
|
141
|
+
const operation = this.getCurrentNodeParameter('operation');
|
|
142
|
+
if (!connectorId || !resource || !operation)
|
|
143
|
+
return [];
|
|
144
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
145
|
+
const transport = new TransportService_1.TransportService();
|
|
146
|
+
const context = {
|
|
147
|
+
getWorkflowStaticData: this.getWorkflowStaticData ? this.getWorkflowStaticData.bind(this) : () => ({ get: () => undefined, set: () => {} }),
|
|
148
|
+
};
|
|
149
|
+
const schemaService = new SchemaService_1.SchemaService(transport, context);
|
|
150
|
+
const schema = await schemaService.getInputDto(baseUrl, apiKey, connectorId, `${resource}.${operation}`);
|
|
151
|
+
const fieldMapper = new FieldMapper_1.FieldMapper();
|
|
152
|
+
return fieldMapper.schemaToKeyOptions(schema);
|
|
153
|
+
},
|
|
154
|
+
async getFieldValueOptions() {
|
|
155
|
+
const connectorId = this.getCurrentNodeParameter('connectorId');
|
|
156
|
+
const resource = this.getCurrentNodeParameter('resource');
|
|
157
|
+
const operation = this.getCurrentNodeParameter('operation');
|
|
158
|
+
if (!connectorId || !resource || !operation)
|
|
159
|
+
return [];
|
|
160
|
+
let key;
|
|
161
|
+
try {
|
|
162
|
+
key = this.getCurrentNodeParameter('key');
|
|
163
|
+
}
|
|
164
|
+
catch (_a) {
|
|
165
|
+
const fields = this.getNodeParameter('fields');
|
|
166
|
+
const values = fields && fields.values;
|
|
167
|
+
key = Array.isArray(values) && values.length > 0 ? (values[values.length - 1] && values[values.length - 1].key) : undefined;
|
|
168
|
+
}
|
|
169
|
+
if (!key)
|
|
170
|
+
return [];
|
|
171
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
172
|
+
const transport = new TransportService_1.TransportService();
|
|
173
|
+
const context = {
|
|
174
|
+
getWorkflowStaticData: this.getWorkflowStaticData ? this.getWorkflowStaticData.bind(this) : () => ({ get: () => undefined, set: () => {} }),
|
|
175
|
+
};
|
|
176
|
+
const schemaService = new SchemaService_1.SchemaService(transport, context);
|
|
177
|
+
const schema = await schemaService.getInputDto(baseUrl, apiKey, connectorId, `${resource}.${operation}`);
|
|
178
|
+
const fieldMapper = new FieldMapper_1.FieldMapper();
|
|
179
|
+
return fieldMapper.schemaToValueOptionsForKey(schema, key);
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async execute() {
|
|
186
|
+
const items = this.getInputData();
|
|
187
|
+
const returnData = [];
|
|
188
|
+
const transport = new TransportService_1.TransportService();
|
|
189
|
+
const fieldMapper = new FieldMapper_1.FieldMapper();
|
|
190
|
+
const { baseUrl, apiKey } = getCredentials.call(this);
|
|
191
|
+
const context = {
|
|
192
|
+
getWorkflowStaticData: this.getWorkflowStaticData ? this.getWorkflowStaticData.bind(this) : () => ({ get: () => undefined, set: () => {} }),
|
|
193
|
+
};
|
|
194
|
+
const schemaService = new SchemaService_1.SchemaService(transport, context);
|
|
195
|
+
for (let i = 0; i < items.length; i++) {
|
|
196
|
+
try {
|
|
197
|
+
const connectorId = this.getNodeParameter('connectorId', i);
|
|
198
|
+
const resource = this.getNodeParameter('resource', i);
|
|
199
|
+
const operation = this.getNodeParameter('operation', i);
|
|
200
|
+
const fieldsParam = this.getNodeParameter('fields', i);
|
|
201
|
+
if (!connectorId || !resource || !operation) {
|
|
202
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Connector, Resource, and Operation are required.', { itemIndex: i });
|
|
203
|
+
}
|
|
204
|
+
const capabilityKey = `${resource}.${operation}`;
|
|
205
|
+
const schema = await schemaService.getInputDto(baseUrl, apiKey, connectorId, capabilityKey);
|
|
206
|
+
const fieldsEntries = Array.isArray(fieldsParam && fieldsParam.values) ? fieldsParam.values : [];
|
|
207
|
+
const input = fieldMapper.fieldsCollectionToInput(fieldsEntries, schema);
|
|
208
|
+
let response;
|
|
209
|
+
try {
|
|
210
|
+
response = await transport.execute(baseUrl, apiKey, connectorId, capabilityKey, input);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
if (err instanceof TransportService_1.TransportError) {
|
|
214
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), err, {
|
|
215
|
+
message: err.message,
|
|
216
|
+
httpCode: err.httpCode,
|
|
217
|
+
description: err.details != null ? String(err.details) : undefined,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), err);
|
|
221
|
+
}
|
|
222
|
+
if (response && response.success === false) {
|
|
223
|
+
const msg = (response.error && response.error.message) || 'Execution failed';
|
|
224
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), msg, {
|
|
225
|
+
itemIndex: i,
|
|
226
|
+
description: response.error && response.error.code,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const data = response && response.success === true ? response.data : response;
|
|
230
|
+
returnData.push({
|
|
231
|
+
json: typeof data === 'object' && data !== null ? data : { data },
|
|
232
|
+
pairedItem: { item: i },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
if (this.continueOnFail()) {
|
|
237
|
+
returnData.push({
|
|
238
|
+
json: { error: err.message },
|
|
239
|
+
pairedItem: { item: i },
|
|
240
|
+
});
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return [returnData];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
exports.ConnectorEngine = ConnectorEngine;
|
|
250
|
+
exports.default = ConnectorEngine;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CapabilityService = void 0;
|
|
4
|
+
class CapabilityService {
|
|
5
|
+
constructor(transport) {
|
|
6
|
+
this.transport = transport;
|
|
7
|
+
}
|
|
8
|
+
async getStructuredCapabilities(baseUrl, apiKey, connectorId) {
|
|
9
|
+
const list = await this.transport.getCapabilities(baseUrl, apiKey, connectorId);
|
|
10
|
+
const resourceSet = new Set();
|
|
11
|
+
const operationsByResource = {};
|
|
12
|
+
for (const item of list) {
|
|
13
|
+
const key = typeof item === 'object' && item !== null && 'key' in item ? String(item.key) : String(item);
|
|
14
|
+
const dot = key.indexOf('.');
|
|
15
|
+
const resource = dot >= 0 ? key.slice(0, dot) : key;
|
|
16
|
+
const operation = dot >= 0 ? key.slice(dot + 1) : key;
|
|
17
|
+
if (!resource || !operation)
|
|
18
|
+
continue;
|
|
19
|
+
resourceSet.add(resource);
|
|
20
|
+
if (!operationsByResource[resource]) {
|
|
21
|
+
operationsByResource[resource] = [];
|
|
22
|
+
}
|
|
23
|
+
const ops = operationsByResource[resource];
|
|
24
|
+
if (!ops.includes(operation)) {
|
|
25
|
+
ops.push(operation);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
resources: Array.from(resourceSet).sort(),
|
|
30
|
+
operationsByResource,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.CapabilityService = CapabilityService;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FieldMapper = void 0;
|
|
4
|
+
class FieldMapper {
|
|
5
|
+
schemaToKeyOptions(schema) {
|
|
6
|
+
return Object.keys(schema).map((key) => ({ name: key, value: key }));
|
|
7
|
+
}
|
|
8
|
+
schemaToValueOptionsForKey(schema, key) {
|
|
9
|
+
const desc = schema[key];
|
|
10
|
+
if (!desc)
|
|
11
|
+
return [];
|
|
12
|
+
if (Array.isArray(desc.enum) && desc.enum.length > 0) {
|
|
13
|
+
return desc.enum.map((v) => ({ name: String(v), value: String(v) }));
|
|
14
|
+
}
|
|
15
|
+
if (desc.type === 'boolean') {
|
|
16
|
+
return [
|
|
17
|
+
{ name: 'True', value: 'true' },
|
|
18
|
+
{ name: 'False', value: 'false' },
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
fieldsCollectionToInput(fieldsEntries, schema) {
|
|
24
|
+
const input = {};
|
|
25
|
+
for (const entry of fieldsEntries) {
|
|
26
|
+
const key = entry && entry.key;
|
|
27
|
+
if (key === undefined || key === null || key === '')
|
|
28
|
+
continue;
|
|
29
|
+
const descriptor = schema[key];
|
|
30
|
+
let raw = entry && entry.value;
|
|
31
|
+
if (raw === undefined || (typeof raw === 'string' && raw.trim() === '')) {
|
|
32
|
+
if (descriptor && descriptor.required) {
|
|
33
|
+
input[key] = descriptor.default !== undefined && descriptor.default !== null ? descriptor.default : '';
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (descriptor) {
|
|
38
|
+
input[key] = this.coerceValue(raw, descriptor);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
input[key] = raw;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return input;
|
|
45
|
+
}
|
|
46
|
+
coerceValue(raw, descriptor) {
|
|
47
|
+
if (raw === undefined || raw === null) {
|
|
48
|
+
return descriptor.default;
|
|
49
|
+
}
|
|
50
|
+
const type = descriptor.type || 'string';
|
|
51
|
+
if (type === 'boolean') {
|
|
52
|
+
if (typeof raw === 'boolean')
|
|
53
|
+
return raw;
|
|
54
|
+
const s = String(raw).toLowerCase();
|
|
55
|
+
if (s === 'true' || s === '1')
|
|
56
|
+
return true;
|
|
57
|
+
if (s === 'false' || s === '0')
|
|
58
|
+
return false;
|
|
59
|
+
return Boolean(raw);
|
|
60
|
+
}
|
|
61
|
+
if (type === 'number' || type === 'integer') {
|
|
62
|
+
if (typeof raw === 'number' && !Number.isNaN(raw))
|
|
63
|
+
return type === 'integer' ? Math.floor(raw) : raw;
|
|
64
|
+
const n = Number(raw);
|
|
65
|
+
return Number.isNaN(n) ? descriptor.default : (type === 'integer' ? Math.floor(n) : n);
|
|
66
|
+
}
|
|
67
|
+
if (type === 'object' || type === 'array') {
|
|
68
|
+
if (typeof raw === 'object')
|
|
69
|
+
return raw;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(String(raw));
|
|
72
|
+
}
|
|
73
|
+
catch (_a) {
|
|
74
|
+
return descriptor.default !== undefined && descriptor.default !== null ? descriptor.default : (type === 'array' ? [] : {});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return String(raw);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.FieldMapper = FieldMapper;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaService = void 0;
|
|
4
|
+
const PRIMITIVE_TYPES = ['string', 'number', 'integer', 'boolean', 'object', 'array'];
|
|
5
|
+
function normalizePropertySchema(value, required) {
|
|
6
|
+
if (value === undefined || value === null)
|
|
7
|
+
return null;
|
|
8
|
+
let type = 'string';
|
|
9
|
+
let enumValues;
|
|
10
|
+
let defaultVal;
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
const t = value.toLowerCase();
|
|
13
|
+
type = PRIMITIVE_TYPES.includes(t) ? t : 'string';
|
|
14
|
+
}
|
|
15
|
+
else if (typeof value === 'object' && value !== null && 'type' in value) {
|
|
16
|
+
const v = value;
|
|
17
|
+
type = PRIMITIVE_TYPES.includes(v.type) ? v.type : 'string';
|
|
18
|
+
if (Array.isArray(v.enum))
|
|
19
|
+
enumValues = v.enum.map(String);
|
|
20
|
+
defaultVal = v.default;
|
|
21
|
+
}
|
|
22
|
+
return { type, enum: enumValues, default: defaultVal, required };
|
|
23
|
+
}
|
|
24
|
+
class SchemaService {
|
|
25
|
+
constructor(transport, context) {
|
|
26
|
+
this.transport = transport;
|
|
27
|
+
this.context = context;
|
|
28
|
+
}
|
|
29
|
+
async getInputDto(baseUrl, apiKey, connectorId, capabilityKey) {
|
|
30
|
+
const cacheKey = `schema:${connectorId}:${capabilityKey}`;
|
|
31
|
+
const staticData = this.context.getWorkflowStaticData('node');
|
|
32
|
+
const cached = staticData.get(cacheKey);
|
|
33
|
+
if (cached && typeof cached === 'object') {
|
|
34
|
+
return cached;
|
|
35
|
+
}
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await this.transport.getCapabilitySchema(baseUrl, apiKey, connectorId, capabilityKey);
|
|
39
|
+
}
|
|
40
|
+
catch (_a) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
const inputDto = response && response.input_dto;
|
|
44
|
+
if (!inputDto || typeof inputDto !== 'object' || !inputDto.properties) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const requiredSet = new Set(Array.isArray(inputDto.required) ? inputDto.required : []);
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const [propName, propSchema] of Object.entries(inputDto.properties)) {
|
|
50
|
+
const normalized = normalizePropertySchema(propSchema, requiredSet.has(propName));
|
|
51
|
+
if (normalized)
|
|
52
|
+
result[propName] = normalized;
|
|
53
|
+
}
|
|
54
|
+
staticData.set(cacheKey, result);
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.SchemaService = SchemaService;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransportService = exports.TransportError = void 0;
|
|
4
|
+
class TransportError extends Error {
|
|
5
|
+
constructor(message, httpCode, code, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.httpCode = httpCode;
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = 'TransportError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.TransportError = TransportError;
|
|
14
|
+
class TransportService {
|
|
15
|
+
buildUrl(baseUrl, path) {
|
|
16
|
+
const base = baseUrl.replace(/\/$/, '');
|
|
17
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
18
|
+
return `${base}${p}`;
|
|
19
|
+
}
|
|
20
|
+
async request(baseUrl, apiKey, path, options = {}) {
|
|
21
|
+
const url = this.buildUrl(baseUrl, path);
|
|
22
|
+
const headers = Object.assign({
|
|
23
|
+
'X-API-KEY': apiKey,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
}, options.headers || {});
|
|
26
|
+
const response = await fetch(url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, headers), options.headers || {}) }));
|
|
27
|
+
const text = await response.text();
|
|
28
|
+
let body = null;
|
|
29
|
+
try {
|
|
30
|
+
body = text ? JSON.parse(text) : null;
|
|
31
|
+
}
|
|
32
|
+
catch (_a) {
|
|
33
|
+
body = text;
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const err = body && typeof body === 'object' && 'error' in body ? body.error : null;
|
|
37
|
+
const message = err && typeof err === 'object' && typeof err.message === 'string' ? err.message : `Request failed with status ${response.status}`;
|
|
38
|
+
const code = err && typeof err === 'object' && typeof err.code === 'string' ? err.code : undefined;
|
|
39
|
+
const details = err && typeof err === 'object' && 'details' in err ? err.details : undefined;
|
|
40
|
+
throw new TransportError(message, response.status, code, details);
|
|
41
|
+
}
|
|
42
|
+
return body;
|
|
43
|
+
}
|
|
44
|
+
async getConnectors(baseUrl, apiKey) {
|
|
45
|
+
return this.request(baseUrl, apiKey, '/execute/connectors', { method: 'GET' });
|
|
46
|
+
}
|
|
47
|
+
async getCapabilities(baseUrl, apiKey, connectorId) {
|
|
48
|
+
const path = '/execute/capabilities';
|
|
49
|
+
const body = { connector_id: connectorId };
|
|
50
|
+
const result = await this.request(baseUrl, apiKey, path, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
});
|
|
54
|
+
return Array.isArray(result) ? result : [];
|
|
55
|
+
}
|
|
56
|
+
async getCapabilitySchema(baseUrl, apiKey, connectorId, capabilityKey) {
|
|
57
|
+
return this.request(baseUrl, apiKey, '/execute/capability-schema', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
body: JSON.stringify({ connector_id: connectorId, key: capabilityKey }),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async execute(baseUrl, apiKey, connectorId, capabilityKey, input) {
|
|
63
|
+
return this.request(baseUrl, apiKey, '/execute', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
connector_id: connectorId,
|
|
67
|
+
capability_key: capabilityKey,
|
|
68
|
+
input,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.TransportService = TransportService;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-connector-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal Connector Engine node for n8n - schema-driven, backend-driven capabilities",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"connector-engine",
|
|
9
|
+
"erp",
|
|
10
|
+
"integration"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://github.com/your-org/n8n-nodes-connector-engine#readme",
|
|
14
|
+
"author": {},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/your-org/n8n-nodes-connector-engine.git"
|
|
18
|
+
},
|
|
19
|
+
"main": "dist/index.js",
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"zip": "zip -r n8n-nodes-connector-engine.zip package.json README.md dist"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"*.d.ts"
|
|
28
|
+
],
|
|
29
|
+
"n8n": {
|
|
30
|
+
"n8nNodesApiVersion": 1,
|
|
31
|
+
"credentials": [
|
|
32
|
+
"dist/credentials/ConnectorEngineApi.credentials.js"
|
|
33
|
+
],
|
|
34
|
+
"nodes": [
|
|
35
|
+
"dist/nodes/ConnectorEngine.node.js"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.10.0",
|
|
40
|
+
"n8n-workflow": "*",
|
|
41
|
+
"typescript": "~5.3.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"n8n-workflow": "*"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|