@pvass24/n8n-nodes-twenty-crm 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.
Files changed (47) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +297 -0
  3. package/dist/credentials/TwentyCrmApi.credentials.d.ts +10 -0
  4. package/dist/credentials/TwentyCrmApi.credentials.js +47 -0
  5. package/dist/credentials/TwentyCrmApi.credentials.js.map +1 -0
  6. package/dist/nodes/TwentyCrm/GenericFunctions.d.ts +7 -0
  7. package/dist/nodes/TwentyCrm/GenericFunctions.js +96 -0
  8. package/dist/nodes/TwentyCrm/GenericFunctions.js.map +1 -0
  9. package/dist/nodes/TwentyCrm/TwentyCrm.node.d.ts +5 -0
  10. package/dist/nodes/TwentyCrm/TwentyCrm.node.js +113 -0
  11. package/dist/nodes/TwentyCrm/TwentyCrm.node.js.map +1 -0
  12. package/dist/nodes/TwentyCrm/TwentyCrm.node.json +17 -0
  13. package/dist/nodes/TwentyCrm/resources/company/company.description.d.ts +3 -0
  14. package/dist/nodes/TwentyCrm/resources/company/company.description.js +296 -0
  15. package/dist/nodes/TwentyCrm/resources/company/company.description.js.map +1 -0
  16. package/dist/nodes/TwentyCrm/resources/company/company.handler.d.ts +2 -0
  17. package/dist/nodes/TwentyCrm/resources/company/company.handler.js +147 -0
  18. package/dist/nodes/TwentyCrm/resources/company/company.handler.js.map +1 -0
  19. package/dist/nodes/TwentyCrm/resources/note/note.description.d.ts +3 -0
  20. package/dist/nodes/TwentyCrm/resources/note/note.description.js +78 -0
  21. package/dist/nodes/TwentyCrm/resources/note/note.description.js.map +1 -0
  22. package/dist/nodes/TwentyCrm/resources/note/note.handler.d.ts +2 -0
  23. package/dist/nodes/TwentyCrm/resources/note/note.handler.js +57 -0
  24. package/dist/nodes/TwentyCrm/resources/note/note.handler.js.map +1 -0
  25. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.description.d.ts +3 -0
  26. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.description.js +102 -0
  27. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.description.js.map +1 -0
  28. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.handler.d.ts +2 -0
  29. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.handler.js +83 -0
  30. package/dist/nodes/TwentyCrm/resources/opportunity/opportunity.handler.js.map +1 -0
  31. package/dist/nodes/TwentyCrm/resources/person/person.description.d.ts +3 -0
  32. package/dist/nodes/TwentyCrm/resources/person/person.description.js +127 -0
  33. package/dist/nodes/TwentyCrm/resources/person/person.description.js.map +1 -0
  34. package/dist/nodes/TwentyCrm/resources/person/person.handler.d.ts +2 -0
  35. package/dist/nodes/TwentyCrm/resources/person/person.handler.js +106 -0
  36. package/dist/nodes/TwentyCrm/resources/person/person.handler.js.map +1 -0
  37. package/dist/nodes/TwentyCrm/resources/task/task.description.d.ts +3 -0
  38. package/dist/nodes/TwentyCrm/resources/task/task.description.js +96 -0
  39. package/dist/nodes/TwentyCrm/resources/task/task.description.js.map +1 -0
  40. package/dist/nodes/TwentyCrm/resources/task/task.handler.d.ts +2 -0
  41. package/dist/nodes/TwentyCrm/resources/task/task.handler.js +79 -0
  42. package/dist/nodes/TwentyCrm/resources/task/task.handler.js.map +1 -0
  43. package/dist/nodes/TwentyCrm/types.d.ts +29 -0
  44. package/dist/nodes/TwentyCrm/types.js +3 -0
  45. package/dist/nodes/TwentyCrm/types.js.map +1 -0
  46. package/dist/tsconfig.tsbuildinfo +1 -0
  47. package/package.json +62 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Vassell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,297 @@
1
+ # n8n-nodes-twenty-crm
2
+
3
+ [![npm version](https://img.shields.io/npm/v/n8n-nodes-twenty-crm.svg)](https://www.npmjs.com/package/n8n-nodes-twenty-crm)
4
+ [![npm downloads](https://img.shields.io/npm/dm/n8n-nodes-twenty-crm.svg)](https://www.npmjs.com/package/n8n-nodes-twenty-crm)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![n8n community node](https://img.shields.io/badge/n8n-community%20node-ff6d5a)](https://docs.n8n.io/integrations/community-nodes/)
7
+
8
+ > The production-grade n8n community node for [Twenty CRM](https://twenty.com) — the open-source alternative to Salesforce.
9
+
10
+ Full CRUD operations with **cursor pagination**, **advanced filtering**, **composite field handling**, and **zero runtime dependencies**.
11
+
12
+ ---
13
+
14
+ ## Why This Node?
15
+
16
+ | Feature | n8n-nodes-twenty-crm | Other Twenty nodes |
17
+ |---------|---------------------|-------------------|
18
+ | Pagination (Return All) | **Full cursor-based** | None (max 60 results) |
19
+ | Filtering | **Built-in filter UI** | Manual query strings |
20
+ | Create/Update bodies | **Properly structured** | Broken (bugs #5, #6) |
21
+ | Error messages | **Actionable & clear** | Raw JSON dumps |
22
+ | Runtime dependencies | **Zero** | Has runtime deps |
23
+ | n8n verification eligible | **Yes** | No |
24
+ | AI Agent compatible | **Yes** (`usableAsTool`) | No |
25
+ | Custom field support | **Via Additional Fields** | Not supported |
26
+ | Test coverage | **24 tests (unit + integration)** | None |
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ### In n8n (Recommended)
33
+
34
+ 1. Go to **Settings** → **Community Nodes**
35
+ 2. Select **Install a community node**
36
+ 3. Enter: `n8n-nodes-twenty-crm`
37
+ 4. Click **Install**
38
+
39
+ ### Via npm (Self-hosted)
40
+
41
+ ```bash
42
+ cd ~/.n8n/custom
43
+ npm install n8n-nodes-twenty-crm
44
+ ```
45
+
46
+ Then restart n8n.
47
+
48
+ ---
49
+
50
+ ## Setup
51
+
52
+ 1. In your Twenty CRM instance, go to **Settings** → **Playground**
53
+ 2. Generate an API key
54
+ 3. In n8n, create new credentials:
55
+ - **Credential Type**: Twenty CRM API
56
+ - **Server URL**: Your Twenty instance URL (e.g. `https://crm.yourdomain.com`)
57
+ - **API Key**: Paste the key from step 2
58
+
59
+ The credential test will automatically verify connectivity.
60
+
61
+ ---
62
+
63
+ ## Resources & Operations
64
+
65
+ ### Company
66
+
67
+ | Operation | Description |
68
+ |-----------|-------------|
69
+ | **Create** | Create a company with name, domain, address, LinkedIn, revenue |
70
+ | **Get** | Retrieve a company by ID |
71
+ | **Get Many** | List companies with filters, ordering, and pagination |
72
+ | **Update** | Update any company field |
73
+ | **Delete** | Soft-delete a company |
74
+
75
+ ### Person (Contact)
76
+
77
+ | Operation | Description |
78
+ |-----------|-------------|
79
+ | **Create** | Create a contact with name, email, phone, job title, company link |
80
+ | **Get** | Retrieve a person by ID |
81
+ | **Get Many** | List people with filters on name, email, company |
82
+ | **Update** | Update any person field |
83
+ | **Delete** | Soft-delete a person |
84
+
85
+ ### Opportunity (Deal)
86
+
87
+ | Operation | Description |
88
+ |-----------|-------------|
89
+ | **Create** | Create a deal with stage, amount, close date, company/contact links |
90
+ | **Get** | Retrieve an opportunity by ID |
91
+ | **Get Many** | List opportunities filtered by stage, company, date |
92
+ | **Update** | Update stage, amount, or any field |
93
+ | **Delete** | Soft-delete an opportunity |
94
+
95
+ ### Note
96
+
97
+ | Operation | Description |
98
+ |-----------|-------------|
99
+ | **Create** | Create a note with title and body |
100
+ | **Get** | Retrieve a note by ID |
101
+ | **Get Many** | List all notes with pagination |
102
+ | **Update** | Update title or body |
103
+ | **Delete** | Soft-delete a note |
104
+
105
+ ### Task
106
+
107
+ | Operation | Description |
108
+ |-----------|-------------|
109
+ | **Create** | Create a task with title, body, due date, status, assignee |
110
+ | **Get** | Retrieve a task by ID |
111
+ | **Get Many** | List tasks filtered by status |
112
+ | **Update** | Update status, assignee, due date, or any field |
113
+ | **Delete** | Soft-delete a task |
114
+
115
+ ---
116
+
117
+ ## Usage Examples
118
+
119
+ ### Create a Company
120
+
121
+ ```json
122
+ {
123
+ "resource": "company",
124
+ "operation": "create",
125
+ "name": "Acme Corp",
126
+ "additionalFields": {
127
+ "domainUrl": "https://acme.com",
128
+ "addressCity": "San Francisco",
129
+ "addressState": "CA",
130
+ "addressCountry": "US",
131
+ "annualRevenueMicros": 5000000000,
132
+ "currencyCode": "USD"
133
+ }
134
+ }
135
+ ```
136
+
137
+ ### List People by Company
138
+
139
+ ```json
140
+ {
141
+ "resource": "person",
142
+ "operation": "getAll",
143
+ "returnAll": false,
144
+ "limit": 20,
145
+ "filters": {
146
+ "companyId": "your-company-uuid"
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Update Opportunity Stage
152
+
153
+ ```json
154
+ {
155
+ "resource": "opportunity",
156
+ "operation": "update",
157
+ "opportunityId": "your-opportunity-uuid",
158
+ "updateFields": {
159
+ "stage": "CLOSED_WON",
160
+ "amountMicros": 25000000000,
161
+ "currencyCode": "USD"
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Search Companies by Name
167
+
168
+ ```json
169
+ {
170
+ "resource": "company",
171
+ "operation": "getAll",
172
+ "returnAll": false,
173
+ "limit": 10,
174
+ "filters": {
175
+ "nameContains": "tech"
176
+ },
177
+ "orderBy": "createdAt[DescNullsLast]"
178
+ }
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Filtering
184
+
185
+ The node provides a user-friendly filter UI that translates to Twenty's powerful filter syntax:
186
+
187
+ | Filter Type | Example | Twenty Syntax |
188
+ |-------------|---------|---------------|
189
+ | Substring match | Name Contains: "tech" | `name[ilike]:"%tech%"` |
190
+ | Date range | Created After: 2024-01-01 | `createdAt[gte]:"2024-01-01"` |
191
+ | Exact match | Company ID: uuid | `companyId[eq]:"uuid"` |
192
+ | Stage filter | Stage: CLOSED_WON | `stage[eq]:"CLOSED_WON"` |
193
+
194
+ All filters are combined with AND logic. For advanced queries, use the Twenty API directly.
195
+
196
+ ---
197
+
198
+ ## Pagination
199
+
200
+ - **Return All = false**: Returns up to `limit` results (max 60 per page)
201
+ - **Return All = true**: Automatically paginates through all results using cursor-based pagination
202
+
203
+ The node handles cursor tokens internally — you never need to manage `starting_after` or `endCursor` manually.
204
+
205
+ ---
206
+
207
+ ## AI Agent Support
208
+
209
+ This node has `usableAsTool: true`, making it available as a tool in n8n's AI Agent workflows. An LLM can:
210
+
211
+ - Search for companies or contacts
212
+ - Create tasks and notes
213
+ - Update opportunity stages
214
+ - Query pipeline data
215
+
216
+ ---
217
+
218
+ ## Compatibility
219
+
220
+ | Requirement | Version |
221
+ |-------------|---------|
222
+ | n8n | >= 1.0.0 |
223
+ | Node.js | >= 18 |
224
+ | Twenty CRM | >= 0.30 (REST API v1) |
225
+
226
+ Works with both [Twenty Cloud](https://twenty.com) and self-hosted instances.
227
+
228
+ ---
229
+
230
+ ## Development
231
+
232
+ ```bash
233
+ git clone https://github.com/pvass24/n8n-nodes-twenty-crm.git
234
+ cd n8n-nodes-twenty-crm
235
+ npm install
236
+ npm run build
237
+
238
+ # Run unit tests
239
+ npm test
240
+
241
+ # Run integration tests (requires .env.test with API credentials)
242
+ npm run test:integration
243
+ ```
244
+
245
+ ### Testing Setup
246
+
247
+ Create `.env.test` in the project root:
248
+
249
+ ```env
250
+ TWENTY_API_URL=https://your-twenty-instance.com/rest
251
+ TWENTY_API_KEY=your-api-key-here
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Contributing
257
+
258
+ Contributions are welcome! Please:
259
+
260
+ 1. Fork this repository
261
+ 2. Create a feature branch (`git checkout -b feat/new-resource`)
262
+ 3. Write tests for your changes
263
+ 4. Ensure `npm test` and `npm run build` pass
264
+ 5. Submit a Pull Request
265
+
266
+ ### Adding a New Resource
267
+
268
+ 1. Create `nodes/TwentyCrm/resources/{resource}/{resource}.description.ts`
269
+ 2. Create `nodes/TwentyCrm/resources/{resource}/{resource}.handler.ts`
270
+ 3. Import and register in `TwentyCrm.node.ts`
271
+ 4. Add integration tests
272
+
273
+ ---
274
+
275
+ ## Roadmap
276
+
277
+ - [ ] Polling Trigger node (new companies, updated deals, etc.)
278
+ - [ ] Webhook Trigger (when Twenty adds webhook support)
279
+ - [ ] Custom Objects support
280
+ - [ ] Custom Fields dynamic loading
281
+ - [ ] Batch operations (create/update many)
282
+ - [ ] File attachments
283
+
284
+ ---
285
+
286
+ ## License
287
+
288
+ [MIT](LICENSE.md) — built by [Patrick Vassell](https://iampjv.co)
289
+
290
+ ---
291
+
292
+ ## Links
293
+
294
+ - [Twenty CRM](https://twenty.com) — The open-source CRM
295
+ - [Twenty REST API Docs](https://twenty.com/developers/rest-api)
296
+ - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/)
297
+ - [Report Issues](https://github.com/pvass24/n8n-nodes-twenty-crm/issues)
@@ -0,0 +1,10 @@
1
+ import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties, Icon } from 'n8n-workflow';
2
+ export declare class TwentyCrmApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ icon: Icon;
6
+ documentationUrl: string;
7
+ properties: INodeProperties[];
8
+ authenticate: IAuthenticateGeneric;
9
+ test: ICredentialTestRequest;
10
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwentyCrmApi = void 0;
4
+ class TwentyCrmApi {
5
+ constructor() {
6
+ this.name = 'twentyCrmApi';
7
+ this.displayName = 'Twenty CRM API';
8
+ this.icon = 'file:../icons/twenty.svg';
9
+ this.documentationUrl = 'https://twenty.com/developers/rest-api';
10
+ this.properties = [
11
+ {
12
+ displayName: 'Server URL',
13
+ name: 'serverUrl',
14
+ type: 'string',
15
+ default: 'https://api.twenty.com',
16
+ placeholder: 'https://crm.yourdomain.com',
17
+ description: 'The base URL of your Twenty CRM instance (no trailing slash)',
18
+ },
19
+ {
20
+ displayName: 'API Key',
21
+ name: 'apiKey',
22
+ type: 'string',
23
+ typeOptions: { password: true },
24
+ default: '',
25
+ description: 'Generate in Settings → Playground in your Twenty workspace',
26
+ },
27
+ ];
28
+ this.authenticate = {
29
+ type: 'generic',
30
+ properties: {
31
+ headers: {
32
+ Authorization: '=Bearer {{$credentials.apiKey}}',
33
+ },
34
+ },
35
+ };
36
+ this.test = {
37
+ request: {
38
+ baseURL: '={{$credentials.serverUrl}}',
39
+ url: '/rest/companies',
40
+ method: 'GET',
41
+ qs: { limit: '1' },
42
+ },
43
+ };
44
+ }
45
+ }
46
+ exports.TwentyCrmApi = TwentyCrmApi;
47
+ //# sourceMappingURL=TwentyCrmApi.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwentyCrmApi.credentials.js","sourceRoot":"","sources":["../../credentials/TwentyCrmApi.credentials.ts"],"names":[],"mappings":";;;AAQA,MAAa,YAAY;IAAzB;QACC,SAAI,GAAG,cAAc,CAAC;QAEtB,gBAAW,GAAG,gBAAgB,CAAC;QAE/B,SAAI,GAAS,0BAA0B,CAAC;QAExC,qBAAgB,GAAG,wCAAwC,CAAC;QAE5D,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,wBAAwB;gBACjC,WAAW,EAAE,4BAA4B;gBACzC,WAAW,EAAE,8DAA8D;aAC3E;YACD;gBACC,WAAW,EAAE,SAAS;gBACtB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,4DAA4D;aACzE;SACD,CAAC;QAEF,iBAAY,GAAyB;YACpC,IAAI,EAAE,SAAS;YACf,UAAU,EAAE;gBACX,OAAO,EAAE;oBACR,aAAa,EAAE,iCAAiC;iBAChD;aACD;SACD,CAAC;QAEF,SAAI,GAA2B;YAC9B,OAAO,EAAE;gBACR,OAAO,EAAE,6BAA6B;gBACtC,GAAG,EAAE,iBAAiB;gBACtB,MAAM,EAAE,KAAK;gBACb,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;aAClB;SACD,CAAC;IACH,CAAC;CAAA;AA7CD,oCA6CC"}
@@ -0,0 +1,7 @@
1
+ import type { IDataObject, IExecuteFunctions, IHttpRequestMethods, ILoadOptionsFunctions } from 'n8n-workflow';
2
+ import type { ITwentyFilter } from './types';
3
+ export declare function buildFilterString(filters: ITwentyFilter[]): string;
4
+ export declare function buildRequestUrl(serverUrl: string, resource: string, id?: string): string;
5
+ export declare function extractDataFromResponse(response: IDataObject, key: string): unknown;
6
+ export declare function twentyApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, resource: string, body?: IDataObject, query?: IDataObject, id?: string): Promise<IDataObject>;
7
+ export declare function twentyApiRequestAllItems(this: IExecuteFunctions, resource: string, query?: IDataObject, limit?: number): Promise<IDataObject[]>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFilterString = buildFilterString;
4
+ exports.buildRequestUrl = buildRequestUrl;
5
+ exports.extractDataFromResponse = extractDataFromResponse;
6
+ exports.twentyApiRequest = twentyApiRequest;
7
+ exports.twentyApiRequestAllItems = twentyApiRequestAllItems;
8
+ const n8n_workflow_1 = require("n8n-workflow");
9
+ const NUMERIC_RE = /^\d+(\.\d+)?$/;
10
+ const BOOLEAN_RE = /^(true|false)$/;
11
+ const NULL_RE = /^NULL$/;
12
+ const ARRAY_RE = /^\[.*\]$/;
13
+ function buildFilterString(filters) {
14
+ if (filters.length === 0)
15
+ return '';
16
+ return filters
17
+ .map((f) => {
18
+ const value = formatFilterValue(f.value, f.comparator);
19
+ return `${f.field}[${f.comparator}]:${value}`;
20
+ })
21
+ .join(',');
22
+ }
23
+ function formatFilterValue(value, comparator) {
24
+ if (NULL_RE.test(value))
25
+ return value;
26
+ if (BOOLEAN_RE.test(value))
27
+ return value;
28
+ if (NUMERIC_RE.test(value))
29
+ return value;
30
+ if (ARRAY_RE.test(value))
31
+ return value;
32
+ if (comparator === 'is')
33
+ return value;
34
+ return `"${value}"`;
35
+ }
36
+ function buildRequestUrl(serverUrl, resource, id) {
37
+ const base = serverUrl.replace(/\/$/, '');
38
+ const path = id ? `/rest/${resource}/${id}` : `/rest/${resource}`;
39
+ return `${base}${path}`;
40
+ }
41
+ function extractDataFromResponse(response, key) {
42
+ if (response.error || response.statusCode) {
43
+ const messages = response.messages || [response.error];
44
+ throw new Error(messages.join('; '));
45
+ }
46
+ const data = response.data;
47
+ if (!data) {
48
+ throw new Error('Response missing data field');
49
+ }
50
+ return data[key];
51
+ }
52
+ async function twentyApiRequest(method, resource, body, query, id) {
53
+ const credentials = await this.getCredentials('twentyCrmApi');
54
+ const serverUrl = credentials.serverUrl.replace(/\/$/, '');
55
+ const options = {
56
+ method,
57
+ url: buildRequestUrl(serverUrl, resource, id),
58
+ json: true,
59
+ body: body && Object.keys(body).length > 0 ? body : undefined,
60
+ qs: query && Object.keys(query).length > 0 ? query : undefined,
61
+ };
62
+ try {
63
+ return (await this.helpers.httpRequestWithAuthentication.call(this, 'twentyCrmApi', options));
64
+ }
65
+ catch (error) {
66
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
67
+ }
68
+ }
69
+ async function twentyApiRequestAllItems(resource, query = {}, limit) {
70
+ const results = [];
71
+ let hasNextPage = true;
72
+ query.limit = limit ? Math.min(limit, 60) : 60;
73
+ while (hasNextPage) {
74
+ const response = await twentyApiRequest.call(this, 'GET', resource, undefined, query);
75
+ const pageInfo = response.pageInfo;
76
+ const data = response.data;
77
+ if (data) {
78
+ const items = data[resource];
79
+ if (items) {
80
+ results.push(...items);
81
+ }
82
+ }
83
+ if (limit && results.length >= limit) {
84
+ return results.slice(0, limit);
85
+ }
86
+ if ((pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage) && pageInfo.endCursor) {
87
+ query.starting_after = pageInfo.endCursor;
88
+ hasNextPage = true;
89
+ }
90
+ else {
91
+ hasNextPage = false;
92
+ }
93
+ }
94
+ return results;
95
+ }
96
+ //# sourceMappingURL=GenericFunctions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenericFunctions.js","sourceRoot":"","sources":["../../../nodes/TwentyCrm/GenericFunctions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class TwentyCrm implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwentyCrm = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const company_description_1 = require("./resources/company/company.description");
6
+ const company_handler_1 = require("./resources/company/company.handler");
7
+ const person_description_1 = require("./resources/person/person.description");
8
+ const person_handler_1 = require("./resources/person/person.handler");
9
+ const opportunity_description_1 = require("./resources/opportunity/opportunity.description");
10
+ const opportunity_handler_1 = require("./resources/opportunity/opportunity.handler");
11
+ const note_description_1 = require("./resources/note/note.description");
12
+ const note_handler_1 = require("./resources/note/note.handler");
13
+ const task_description_1 = require("./resources/task/task.description");
14
+ const task_handler_1 = require("./resources/task/task.handler");
15
+ class TwentyCrm {
16
+ constructor() {
17
+ this.description = {
18
+ displayName: 'Twenty CRM',
19
+ name: 'twentyCrm',
20
+ icon: 'file:../../icons/twenty.svg',
21
+ group: ['transform'],
22
+ version: 1,
23
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
24
+ description: 'Interact with Twenty CRM API',
25
+ defaults: {
26
+ name: 'Twenty CRM',
27
+ },
28
+ usableAsTool: true,
29
+ inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
30
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
31
+ credentials: [
32
+ {
33
+ name: 'twentyCrmApi',
34
+ required: true,
35
+ },
36
+ ],
37
+ properties: [
38
+ {
39
+ displayName: 'Resource',
40
+ name: 'resource',
41
+ type: 'options',
42
+ noDataExpression: true,
43
+ options: [
44
+ { name: 'Company', value: 'company' },
45
+ { name: 'Note', value: 'note' },
46
+ { name: 'Opportunity', value: 'opportunity' },
47
+ { name: 'Person', value: 'person' },
48
+ { name: 'Task', value: 'task' },
49
+ ],
50
+ default: 'company',
51
+ },
52
+ ...company_description_1.companyOperations,
53
+ ...company_description_1.companyFields,
54
+ ...person_description_1.personOperations,
55
+ ...person_description_1.personFields,
56
+ ...note_description_1.noteOperations,
57
+ ...note_description_1.noteFields,
58
+ ...opportunity_description_1.opportunityOperations,
59
+ ...opportunity_description_1.opportunityFields,
60
+ ...task_description_1.taskOperations,
61
+ ...task_description_1.taskFields,
62
+ ],
63
+ };
64
+ }
65
+ async execute() {
66
+ const items = this.getInputData();
67
+ const returnData = [];
68
+ const resource = this.getNodeParameter('resource', 0);
69
+ const operation = this.getNodeParameter('operation', 0);
70
+ for (let i = 0; i < items.length; i++) {
71
+ try {
72
+ let result;
73
+ switch (resource) {
74
+ case 'company':
75
+ result = await company_handler_1.handleCompanyOperation.call(this, operation, i);
76
+ break;
77
+ case 'person':
78
+ result = await person_handler_1.handlePersonOperation.call(this, operation, i);
79
+ break;
80
+ case 'opportunity':
81
+ result = await opportunity_handler_1.handleOpportunityOperation.call(this, operation, i);
82
+ break;
83
+ case 'note':
84
+ result = await note_handler_1.handleNoteOperation.call(this, operation, i);
85
+ break;
86
+ case 'task':
87
+ result = await task_handler_1.handleTaskOperation.call(this, operation, i);
88
+ break;
89
+ default:
90
+ throw new Error(`Unknown resource: ${resource}`);
91
+ }
92
+ if (Array.isArray(result)) {
93
+ for (const item of result) {
94
+ returnData.push({ json: item });
95
+ }
96
+ }
97
+ else {
98
+ returnData.push({ json: result });
99
+ }
100
+ }
101
+ catch (error) {
102
+ if (this.continueOnFail()) {
103
+ returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
104
+ continue;
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ return [returnData];
110
+ }
111
+ }
112
+ exports.TwentyCrm = TwentyCrm;
113
+ //# sourceMappingURL=TwentyCrm.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwentyCrm.node.js","sourceRoot":"","sources":["../../../nodes/TwentyCrm/TwentyCrm.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ {
2
+ "node": "n8n-nodes-base.twentyCrm",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Sales"],
6
+ "resources": {
7
+ "primaryDocumentation": [
8
+ {
9
+ "url": "https://twenty.com/developers/rest-api"
10
+ }
11
+ ]
12
+ },
13
+ "alias": ["CRM", "Twenty", "Sales", "Contacts", "Deals", "Pipeline"],
14
+ "subcategories": {
15
+ "Sales": ["Contact Management", "Deal Tracking"]
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ export declare const companyOperations: INodeProperties[];
3
+ export declare const companyFields: INodeProperties[];