n8n-nodes-cala 0.2.0 → 0.3.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 +2 -50
- package/dist/credentials/CalaApi.credentials.js +0 -8
- package/dist/nodes/Cala/Cala.node.js +23 -67
- package/package.json +4 -5
- package/dist/credentials/CalaApi.credentials.test.d.ts +0 -1
- package/dist/credentials/CalaApi.credentials.test.js +0 -32
- package/dist/nodes/Cala/Cala.node.test.d.ts +0 -1
- package/dist/nodes/Cala/Cala.node.test.js +0 -128
package/README.md
CHANGED
|
@@ -26,13 +26,9 @@ You need a Cala API key to use this node:
|
|
|
26
26
|
3. In n8n, create new credentials of type **Cala API**
|
|
27
27
|
4. Enter your API key
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Endpoint
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| Operation | Description |
|
|
34
|
-
|-----------|-------------|
|
|
35
|
-
| **Search** | Search verified knowledge using natural language queries |
|
|
31
|
+
The node provides access to the **Knowledge Search** endpoint, which searches trusted knowledge using natural language queries.
|
|
36
32
|
|
|
37
33
|
### Example
|
|
38
34
|
|
|
@@ -73,50 +69,6 @@ You need a Cala API key to use this node:
|
|
|
73
69
|
- [Cala Console](https://console.cala.ai)
|
|
74
70
|
- [n8n Community Nodes Documentation](https://docs.n8n.io/integrations/community-nodes/)
|
|
75
71
|
|
|
76
|
-
## Development
|
|
77
|
-
|
|
78
|
-
### Requirements
|
|
79
|
-
|
|
80
|
-
- Node.js >= 22
|
|
81
|
-
- pnpm >= 10
|
|
82
|
-
|
|
83
|
-
### Project Structure
|
|
84
|
-
|
|
85
|
-
```text
|
|
86
|
-
cala-n8n/
|
|
87
|
-
├── credentials/
|
|
88
|
-
│ └── CalaApi.credentials.ts # API credentials definition
|
|
89
|
-
├── nodes/
|
|
90
|
-
│ └── Cala/
|
|
91
|
-
│ ├── Cala.node.ts # Main node logic
|
|
92
|
-
│ └── cala.svg # Node icon
|
|
93
|
-
├── dist/ # Compiled output
|
|
94
|
-
├── Makefile # Development commands
|
|
95
|
-
├── package.json
|
|
96
|
-
├── tsconfig.json
|
|
97
|
-
└── gulpfile.js
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Quick Start
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
make start # Build + start n8n at http://localhost:5678
|
|
104
|
-
make stop # Stop n8n
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Commands
|
|
108
|
-
|
|
109
|
-
| Command | Description |
|
|
110
|
-
| ------- | ----------- |
|
|
111
|
-
| `make install` | Install dependencies |
|
|
112
|
-
| `make build` | Build the project |
|
|
113
|
-
| `make dev` | Development mode (watch) |
|
|
114
|
-
| `make start` | Start n8n locally |
|
|
115
|
-
| `make stop` | Stop n8n |
|
|
116
|
-
| `make publish` | Publish to npm |
|
|
117
|
-
| `make verify` | Run n8n linter |
|
|
118
|
-
| `make clean` | Remove build artifacts |
|
|
119
|
-
|
|
120
72
|
## License
|
|
121
73
|
|
|
122
74
|
MIT
|
|
@@ -18,14 +18,6 @@ class CalaApi {
|
|
|
18
18
|
required: true,
|
|
19
19
|
description: 'Your Cala API key',
|
|
20
20
|
},
|
|
21
|
-
{
|
|
22
|
-
displayName: 'Base URL',
|
|
23
|
-
name: 'baseUrl',
|
|
24
|
-
type: 'string',
|
|
25
|
-
default: 'https://api.cala.ai',
|
|
26
|
-
required: true,
|
|
27
|
-
description: 'The base URL of the Cala API',
|
|
28
|
-
},
|
|
29
21
|
];
|
|
30
22
|
}
|
|
31
23
|
}
|
|
@@ -9,8 +9,8 @@ class Cala {
|
|
|
9
9
|
icon: 'file:cala.svg',
|
|
10
10
|
group: ['transform'],
|
|
11
11
|
version: 1,
|
|
12
|
-
subtitle: '
|
|
13
|
-
description: 'Search
|
|
12
|
+
subtitle: 'Knowledge Search',
|
|
13
|
+
description: 'Search trusted knowledge with Cala AI',
|
|
14
14
|
defaults: {
|
|
15
15
|
name: 'Cala',
|
|
16
16
|
},
|
|
@@ -23,53 +23,14 @@ class Cala {
|
|
|
23
23
|
},
|
|
24
24
|
],
|
|
25
25
|
properties: [
|
|
26
|
-
{
|
|
27
|
-
displayName: 'Resource',
|
|
28
|
-
name: 'resource',
|
|
29
|
-
type: 'options',
|
|
30
|
-
noDataExpression: true,
|
|
31
|
-
options: [
|
|
32
|
-
{
|
|
33
|
-
name: 'Knowledge',
|
|
34
|
-
value: 'knowledge',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
default: 'knowledge',
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
displayName: 'Operation',
|
|
41
|
-
name: 'operation',
|
|
42
|
-
type: 'options',
|
|
43
|
-
noDataExpression: true,
|
|
44
|
-
displayOptions: {
|
|
45
|
-
show: {
|
|
46
|
-
resource: ['knowledge'],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
options: [
|
|
50
|
-
{
|
|
51
|
-
name: 'Search',
|
|
52
|
-
value: 'search',
|
|
53
|
-
description: 'Search verified knowledge',
|
|
54
|
-
action: 'Search verified knowledge',
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
default: 'search',
|
|
58
|
-
},
|
|
59
26
|
{
|
|
60
27
|
displayName: 'Query',
|
|
61
28
|
name: 'query',
|
|
62
29
|
type: 'string',
|
|
63
30
|
required: true,
|
|
64
|
-
displayOptions: {
|
|
65
|
-
show: {
|
|
66
|
-
resource: ['knowledge'],
|
|
67
|
-
operation: ['search'],
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
31
|
default: '',
|
|
71
|
-
placeholder:
|
|
72
|
-
description: 'The search query to find
|
|
32
|
+
placeholder: "i.e. What were Toyota's total sales in 2023?",
|
|
33
|
+
description: 'The search query to find knowledge',
|
|
73
34
|
},
|
|
74
35
|
],
|
|
75
36
|
};
|
|
@@ -78,33 +39,28 @@ class Cala {
|
|
|
78
39
|
const items = this.getInputData();
|
|
79
40
|
const returnData = [];
|
|
80
41
|
const credentials = await this.getCredentials('calaApi');
|
|
81
|
-
const baseUrl = credentials.baseUrl.replace(/\/$/, '');
|
|
82
42
|
const apiKey = credentials.apiKey;
|
|
83
43
|
for (let i = 0; i < items.length; i++) {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
if (apiKey) {
|
|
92
|
-
headers['X-API-KEY'] = apiKey;
|
|
93
|
-
}
|
|
94
|
-
const response = await this.helpers.httpRequest({
|
|
95
|
-
method: 'POST',
|
|
96
|
-
url: `${baseUrl}/v1/knowledge/search`,
|
|
97
|
-
headers,
|
|
98
|
-
body: {
|
|
99
|
-
input: query,
|
|
100
|
-
},
|
|
101
|
-
json: true,
|
|
102
|
-
});
|
|
103
|
-
returnData.push({
|
|
104
|
-
json: response,
|
|
105
|
-
pairedItem: { item: i },
|
|
106
|
-
});
|
|
44
|
+
const query = this.getNodeParameter('query', i);
|
|
45
|
+
const headers = {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
};
|
|
48
|
+
if (apiKey) {
|
|
49
|
+
headers['X-API-KEY'] = apiKey;
|
|
107
50
|
}
|
|
51
|
+
const response = await this.helpers.httpRequest({
|
|
52
|
+
method: 'POST',
|
|
53
|
+
url: 'https://api.cala.ai/v1/knowledge/search',
|
|
54
|
+
headers,
|
|
55
|
+
body: {
|
|
56
|
+
input: query,
|
|
57
|
+
},
|
|
58
|
+
json: true,
|
|
59
|
+
});
|
|
60
|
+
returnData.push({
|
|
61
|
+
json: response,
|
|
62
|
+
pairedItem: { item: i },
|
|
63
|
+
});
|
|
108
64
|
}
|
|
109
65
|
return [returnData];
|
|
110
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-cala",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "n8n nodes for Cala AI knowledge search",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://
|
|
20
|
+
"url": "https://github.com/cala-ai/cala-n8n-node.git"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://
|
|
22
|
+
"homepage": "https://github.com/cala-ai/cala-n8n-node",
|
|
23
23
|
"bugs": {
|
|
24
|
-
"url": "https://
|
|
24
|
+
"url": "https://github.com/cala-ai/cala-n8n-node/issues"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"eslint": "^9.39.2",
|
|
45
45
|
"gulp": "^5.0.0",
|
|
46
46
|
"jest": "^30.2.0",
|
|
47
|
-
"n8n-workflow": "^1.0.0",
|
|
48
47
|
"ts-jest": "^29.4.6",
|
|
49
48
|
"typescript": "^5.3.0",
|
|
50
49
|
"typescript-eslint": "^8.53.1"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const CalaApi_credentials_1 = require("./CalaApi.credentials");
|
|
4
|
-
describe('CalaApi Credentials', () => {
|
|
5
|
-
let credentials;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
credentials = new CalaApi_credentials_1.CalaApi();
|
|
8
|
-
});
|
|
9
|
-
it('should have correct name', () => {
|
|
10
|
-
expect(credentials.name).toBe('calaApi');
|
|
11
|
-
});
|
|
12
|
-
it('should have correct display name', () => {
|
|
13
|
-
expect(credentials.displayName).toBe('Cala API');
|
|
14
|
-
});
|
|
15
|
-
it('should have documentation URL', () => {
|
|
16
|
-
expect(credentials.documentationUrl).toBe('https://docs.cala.ai');
|
|
17
|
-
});
|
|
18
|
-
it('should have API key property', () => {
|
|
19
|
-
var _a;
|
|
20
|
-
const apiKeyProp = credentials.properties.find(p => p.name === 'apiKey');
|
|
21
|
-
expect(apiKeyProp).toBeDefined();
|
|
22
|
-
expect(apiKeyProp === null || apiKeyProp === void 0 ? void 0 : apiKeyProp.type).toBe('string');
|
|
23
|
-
expect(apiKeyProp === null || apiKeyProp === void 0 ? void 0 : apiKeyProp.required).toBe(true);
|
|
24
|
-
expect((_a = apiKeyProp === null || apiKeyProp === void 0 ? void 0 : apiKeyProp.typeOptions) === null || _a === void 0 ? void 0 : _a.password).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
it('should have base URL property with default value', () => {
|
|
27
|
-
const baseUrlProp = credentials.properties.find(p => p.name === 'baseUrl');
|
|
28
|
-
expect(baseUrlProp).toBeDefined();
|
|
29
|
-
expect(baseUrlProp === null || baseUrlProp === void 0 ? void 0 : baseUrlProp.type).toBe('string');
|
|
30
|
-
expect(baseUrlProp === null || baseUrlProp === void 0 ? void 0 : baseUrlProp.default).toBe('https://api.cala.ai');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const Cala_node_1 = require("./Cala.node");
|
|
4
|
-
describe('Cala Node', () => {
|
|
5
|
-
let node;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
node = new Cala_node_1.Cala();
|
|
8
|
-
});
|
|
9
|
-
describe('execute', () => {
|
|
10
|
-
const createExecutionContext = ({ baseUrl = 'https://api.cala.ai/', apiKey = 'test-key', resource = 'knowledge', operation = 'search', query = 'What is Cala?', response = { content: 'ok' }, } = {}) => {
|
|
11
|
-
const httpRequest = jest.fn(async () => response);
|
|
12
|
-
return {
|
|
13
|
-
getInputData: jest.fn(() => [{ json: { input: 'one' } }]),
|
|
14
|
-
getNodeParameter: jest.fn((name, _index) => {
|
|
15
|
-
if (name === 'resource') {
|
|
16
|
-
return resource;
|
|
17
|
-
}
|
|
18
|
-
if (name === 'operation') {
|
|
19
|
-
return operation;
|
|
20
|
-
}
|
|
21
|
-
if (name === 'query') {
|
|
22
|
-
return query;
|
|
23
|
-
}
|
|
24
|
-
throw new Error(`Unexpected parameter name: ${name}`);
|
|
25
|
-
}),
|
|
26
|
-
getCredentials: jest.fn(async () => ({ baseUrl, apiKey })),
|
|
27
|
-
helpers: {
|
|
28
|
-
httpRequest,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
it('should call Cala API with normalized base URL', async () => {
|
|
33
|
-
const context = createExecutionContext();
|
|
34
|
-
const result = await node.execute.call(context);
|
|
35
|
-
expect(context.helpers.httpRequest).toHaveBeenCalledWith({
|
|
36
|
-
method: 'POST',
|
|
37
|
-
url: 'https://api.cala.ai/v1/knowledge/search',
|
|
38
|
-
headers: {
|
|
39
|
-
'Content-Type': 'application/json',
|
|
40
|
-
'X-API-KEY': 'test-key',
|
|
41
|
-
},
|
|
42
|
-
body: {
|
|
43
|
-
input: 'What is Cala?',
|
|
44
|
-
},
|
|
45
|
-
json: true,
|
|
46
|
-
});
|
|
47
|
-
expect(result).toEqual([[{ json: { content: 'ok' }, pairedItem: { item: 0 } }]]);
|
|
48
|
-
});
|
|
49
|
-
it('should omit API key header when missing', async () => {
|
|
50
|
-
const context = createExecutionContext({ apiKey: '' });
|
|
51
|
-
await node.execute.call(context);
|
|
52
|
-
expect(context.helpers.httpRequest).toHaveBeenCalledWith({
|
|
53
|
-
method: 'POST',
|
|
54
|
-
url: 'https://api.cala.ai/v1/knowledge/search',
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
},
|
|
58
|
-
body: {
|
|
59
|
-
input: 'What is Cala?',
|
|
60
|
-
},
|
|
61
|
-
json: true,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it('should process multiple items', async () => {
|
|
65
|
-
const queries = ['Query 1', 'Query 2', 'Query 3'];
|
|
66
|
-
const httpRequest = jest.fn()
|
|
67
|
-
.mockResolvedValueOnce({ answer: 'Answer 1' })
|
|
68
|
-
.mockResolvedValueOnce({ answer: 'Answer 2' })
|
|
69
|
-
.mockResolvedValueOnce({ answer: 'Answer 3' });
|
|
70
|
-
const context = {
|
|
71
|
-
getInputData: jest.fn(() => queries.map(q => ({ json: { query: q } }))),
|
|
72
|
-
getNodeParameter: jest.fn((name, index) => {
|
|
73
|
-
if (name === 'resource')
|
|
74
|
-
return 'knowledge';
|
|
75
|
-
if (name === 'operation')
|
|
76
|
-
return 'search';
|
|
77
|
-
if (name === 'query')
|
|
78
|
-
return queries[index];
|
|
79
|
-
throw new Error(`Unexpected parameter: ${name}`);
|
|
80
|
-
}),
|
|
81
|
-
getCredentials: jest.fn(async () => ({ baseUrl: 'https://api.cala.ai', apiKey: 'test-key' })),
|
|
82
|
-
helpers: { httpRequest },
|
|
83
|
-
};
|
|
84
|
-
const result = await node.execute.call(context);
|
|
85
|
-
expect(httpRequest).toHaveBeenCalledTimes(3);
|
|
86
|
-
expect(result[0]).toHaveLength(3);
|
|
87
|
-
expect(result[0][0].json).toEqual({ answer: 'Answer 1' });
|
|
88
|
-
expect(result[0][1].json).toEqual({ answer: 'Answer 2' });
|
|
89
|
-
expect(result[0][2].json).toEqual({ answer: 'Answer 3' });
|
|
90
|
-
});
|
|
91
|
-
it('should propagate HTTP errors', async () => {
|
|
92
|
-
const httpRequest = jest.fn().mockRejectedValue(new Error('API Error: 500 Internal Server Error'));
|
|
93
|
-
const context = {
|
|
94
|
-
getInputData: jest.fn(() => [{ json: {} }]),
|
|
95
|
-
getNodeParameter: jest.fn((name) => {
|
|
96
|
-
if (name === 'resource')
|
|
97
|
-
return 'knowledge';
|
|
98
|
-
if (name === 'operation')
|
|
99
|
-
return 'search';
|
|
100
|
-
if (name === 'query')
|
|
101
|
-
return 'test query';
|
|
102
|
-
throw new Error(`Unexpected parameter: ${name}`);
|
|
103
|
-
}),
|
|
104
|
-
getCredentials: jest.fn(async () => ({ baseUrl: 'https://api.cala.ai', apiKey: 'test-key' })),
|
|
105
|
-
helpers: { httpRequest },
|
|
106
|
-
};
|
|
107
|
-
await expect(node.execute.call(context)).rejects.toThrow('API Error: 500 Internal Server Error');
|
|
108
|
-
});
|
|
109
|
-
it('should return empty array for unsupported resource/operation', async () => {
|
|
110
|
-
const httpRequest = jest.fn();
|
|
111
|
-
const context = {
|
|
112
|
-
getInputData: jest.fn(() => [{ json: {} }]),
|
|
113
|
-
getNodeParameter: jest.fn((name) => {
|
|
114
|
-
if (name === 'resource')
|
|
115
|
-
return 'unsupported';
|
|
116
|
-
if (name === 'operation')
|
|
117
|
-
return 'unknown';
|
|
118
|
-
throw new Error(`Unexpected parameter: ${name}`);
|
|
119
|
-
}),
|
|
120
|
-
getCredentials: jest.fn(async () => ({ baseUrl: 'https://api.cala.ai', apiKey: 'test-key' })),
|
|
121
|
-
helpers: { httpRequest },
|
|
122
|
-
};
|
|
123
|
-
const result = await node.execute.call(context);
|
|
124
|
-
expect(httpRequest).not.toHaveBeenCalled();
|
|
125
|
-
expect(result).toEqual([[]]);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|