@lilremark/n8n-nodes-twenty-dynamic 2.2.10

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 (64) hide show
  1. package/LICENSE.md +19 -0
  2. package/README.md +290 -0
  3. package/dist/credentials/TwentyApi.credentials.d.ts +8 -0
  4. package/dist/credentials/TwentyApi.credentials.js +49 -0
  5. package/dist/credentials/TwentyApi.credentials.js.map +1 -0
  6. package/dist/nodes/Twenty/ComplexFieldDetection.d.ts +8 -0
  7. package/dist/nodes/Twenty/ComplexFieldDetection.js +27 -0
  8. package/dist/nodes/Twenty/ComplexFieldDetection.js.map +1 -0
  9. package/dist/nodes/Twenty/FieldParameters.d.ts +11 -0
  10. package/dist/nodes/Twenty/FieldParameters.js +211 -0
  11. package/dist/nodes/Twenty/FieldParameters.js.map +1 -0
  12. package/dist/nodes/Twenty/FieldTransformation.d.ts +30 -0
  13. package/dist/nodes/Twenty/FieldTransformation.js +168 -0
  14. package/dist/nodes/Twenty/FieldTransformation.js.map +1 -0
  15. package/dist/nodes/Twenty/Twenty.node.d.ts +22 -0
  16. package/dist/nodes/Twenty/Twenty.node.js +1629 -0
  17. package/dist/nodes/Twenty/Twenty.node.js.map +1 -0
  18. package/dist/nodes/Twenty/Twenty.node.json +30 -0
  19. package/dist/nodes/Twenty/TwentyApi.client.d.ts +82 -0
  20. package/dist/nodes/Twenty/TwentyApi.client.js +654 -0
  21. package/dist/nodes/Twenty/TwentyApi.client.js.map +1 -0
  22. package/dist/nodes/Twenty/introspection/fieldIntrospection.d.ts +15 -0
  23. package/dist/nodes/Twenty/introspection/fieldIntrospection.js +135 -0
  24. package/dist/nodes/Twenty/introspection/fieldIntrospection.js.map +1 -0
  25. package/dist/nodes/Twenty/operations/create.operation.d.ts +7 -0
  26. package/dist/nodes/Twenty/operations/create.operation.js +20 -0
  27. package/dist/nodes/Twenty/operations/create.operation.js.map +1 -0
  28. package/dist/nodes/Twenty/operations/createMany.operation.d.ts +11 -0
  29. package/dist/nodes/Twenty/operations/createMany.operation.js +72 -0
  30. package/dist/nodes/Twenty/operations/createMany.operation.js.map +1 -0
  31. package/dist/nodes/Twenty/operations/delete.operation.d.ts +7 -0
  32. package/dist/nodes/Twenty/operations/delete.operation.js +18 -0
  33. package/dist/nodes/Twenty/operations/delete.operation.js.map +1 -0
  34. package/dist/nodes/Twenty/operations/deleteMany.operation.d.ts +7 -0
  35. package/dist/nodes/Twenty/operations/deleteMany.operation.js +39 -0
  36. package/dist/nodes/Twenty/operations/deleteMany.operation.js.map +1 -0
  37. package/dist/nodes/Twenty/operations/get.operation.d.ts +7 -0
  38. package/dist/nodes/Twenty/operations/get.operation.js +25 -0
  39. package/dist/nodes/Twenty/operations/get.operation.js.map +1 -0
  40. package/dist/nodes/Twenty/operations/getMany.operation.d.ts +8 -0
  41. package/dist/nodes/Twenty/operations/getMany.operation.js +37 -0
  42. package/dist/nodes/Twenty/operations/getMany.operation.js.map +1 -0
  43. package/dist/nodes/Twenty/operations/index.d.ts +11 -0
  44. package/dist/nodes/Twenty/operations/index.js +26 -0
  45. package/dist/nodes/Twenty/operations/index.js.map +1 -0
  46. package/dist/nodes/Twenty/operations/list.operation.d.ts +7 -0
  47. package/dist/nodes/Twenty/operations/list.operation.js +25 -0
  48. package/dist/nodes/Twenty/operations/list.operation.js.map +1 -0
  49. package/dist/nodes/Twenty/operations/update.operation.d.ts +7 -0
  50. package/dist/nodes/Twenty/operations/update.operation.js +21 -0
  51. package/dist/nodes/Twenty/operations/update.operation.js.map +1 -0
  52. package/dist/nodes/Twenty/operations/updateMany.operation.d.ts +19 -0
  53. package/dist/nodes/Twenty/operations/updateMany.operation.js +75 -0
  54. package/dist/nodes/Twenty/operations/updateMany.operation.js.map +1 -0
  55. package/dist/nodes/Twenty/operations/upsert.operation.d.ts +12 -0
  56. package/dist/nodes/Twenty/operations/upsert.operation.js +122 -0
  57. package/dist/nodes/Twenty/operations/upsert.operation.js.map +1 -0
  58. package/dist/nodes/Twenty/operations/upsertMany.operation.d.ts +13 -0
  59. package/dist/nodes/Twenty/operations/upsertMany.operation.js +120 -0
  60. package/dist/nodes/Twenty/operations/upsertMany.operation.js.map +1 -0
  61. package/dist/nodes/Twenty/twenty.svg +12 -0
  62. package/dist/package.json +81 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -0
  64. package/package.json +81 -0
@@ -0,0 +1,1629 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Twenty = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const TwentyApi_client_1 = require("./TwentyApi.client");
6
+ const FieldTransformation_1 = require("./FieldTransformation");
7
+ const operations_1 = require("./operations");
8
+ function buildSmartFilter(searchQuery, resource) {
9
+ const isAdvancedSyntax = /\w+(\.\w+)?\[.+\]:/.test(searchQuery);
10
+ if (isAdvancedSyntax) {
11
+ return searchQuery;
12
+ }
13
+ const searchValue = searchQuery.trim();
14
+ const escapedValue = searchValue.replace(/"/g, '\\"');
15
+ if (resource === 'person') {
16
+ return `or(name.firstName[ilike]:"%${escapedValue}%",name.lastName[ilike]:"%${escapedValue}%")`;
17
+ }
18
+ return `name[ilike]:"%${escapedValue}%"`;
19
+ }
20
+ class Twenty {
21
+ constructor() {
22
+ this.description = {
23
+ displayName: 'Twenty CRM - Dynamic',
24
+ name: 'twenty',
25
+ icon: 'file:twenty.svg',
26
+ group: ['transform'],
27
+ version: 1,
28
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
29
+ description: 'Interact with Twenty CRM - supporting standard and custom data models - dynamically adapts to your database schema.',
30
+ defaults: {
31
+ name: 'Twenty CRM - Dynamic',
32
+ },
33
+ inputs: ['main'],
34
+ outputs: ['main'],
35
+ credentials: [
36
+ {
37
+ name: 'twentyApi',
38
+ required: true,
39
+ },
40
+ ],
41
+ properties: [
42
+ {
43
+ displayName: 'Database Group',
44
+ name: 'resourceGroup',
45
+ type: 'options',
46
+ noDataExpression: true,
47
+ options: [
48
+ {
49
+ name: 'All Databases',
50
+ value: 'all',
51
+ description: 'Show all available databases in your Twenty CRM workspace',
52
+ },
53
+ {
54
+ name: 'Custom Databases',
55
+ value: 'custom',
56
+ description: 'Your user-created custom databases with your own data models',
57
+ },
58
+ {
59
+ name: 'Standard Databases',
60
+ value: 'standard',
61
+ description: 'Your core Twenty CRM databases (Company, Person, Opportunity, etc.)',
62
+ },
63
+ {
64
+ name: 'System Databases',
65
+ value: 'system',
66
+ description: 'Hidden Twenty system databases not normally accessible to users - Advanced use only',
67
+ },
68
+ ],
69
+ default: 'standard',
70
+ required: true,
71
+ description: 'Filter databases by group to narrow down the selection',
72
+ },
73
+ {
74
+ displayName: 'Database Name or ID',
75
+ name: 'resource',
76
+ type: 'options',
77
+ noDataExpression: true,
78
+ typeOptions: {
79
+ loadOptionsMethod: 'getResources',
80
+ loadOptionsDependsOn: ['resourceGroup'],
81
+ },
82
+ default: '',
83
+ required: true,
84
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
85
+ },
86
+ {
87
+ displayName: 'Operation',
88
+ name: 'operation',
89
+ type: 'options',
90
+ noDataExpression: true,
91
+ options: [
92
+ {
93
+ name: 'Create',
94
+ value: 'create',
95
+ description: 'Create a new record',
96
+ action: 'Create a record',
97
+ },
98
+ {
99
+ name: 'Create Many',
100
+ value: 'createMany',
101
+ description: 'Create multiple new records at once',
102
+ action: 'Create many records',
103
+ },
104
+ {
105
+ name: 'Create or Update',
106
+ value: 'upsert',
107
+ description: 'Create a new record, or update the current one if it already exists (upsert)',
108
+ action: 'Create or update a record',
109
+ },
110
+ {
111
+ name: 'Create or Update Many',
112
+ value: 'upsertMany',
113
+ description: 'Create or update multiple records at once',
114
+ action: 'Create or update many records',
115
+ },
116
+ {
117
+ name: 'Delete',
118
+ value: 'delete',
119
+ description: 'Delete a record by ID',
120
+ action: 'Delete a record',
121
+ },
122
+ {
123
+ name: 'Delete Many',
124
+ value: 'deleteMany',
125
+ description: 'Delete multiple records by IDs',
126
+ action: 'Delete many records',
127
+ },
128
+ {
129
+ name: 'Get',
130
+ value: 'get',
131
+ description: 'Retrieve a single record by ID',
132
+ action: 'Get a record',
133
+ },
134
+ {
135
+ name: 'Get Many',
136
+ value: 'getMany',
137
+ description: 'Retrieve multiple records by IDs',
138
+ action: 'Get many records',
139
+ },
140
+ {
141
+ name: 'List/Search',
142
+ value: 'findMany',
143
+ description: 'Get multiple records with optional filters',
144
+ action: 'List records',
145
+ },
146
+ {
147
+ name: 'Update',
148
+ value: 'update',
149
+ description: 'Update an existing record',
150
+ action: 'Update a record',
151
+ },
152
+ {
153
+ name: 'Update Many',
154
+ value: 'updateMany',
155
+ description: 'Update multiple records at once',
156
+ action: 'Update many records',
157
+ },
158
+ ],
159
+ default: 'create',
160
+ required: true,
161
+ },
162
+ {
163
+ displayName: 'Record',
164
+ name: 'recordId',
165
+ type: 'resourceLocator',
166
+ default: { mode: 'list', value: '' },
167
+ required: true,
168
+ displayOptions: {
169
+ show: {
170
+ operation: ['get'],
171
+ },
172
+ },
173
+ modes: [
174
+ {
175
+ displayName: 'From List',
176
+ name: 'list',
177
+ type: 'list',
178
+ placeholder: 'Select a record...',
179
+ typeOptions: {
180
+ searchListMethod: 'getRecordsForDatabase',
181
+ searchable: true,
182
+ },
183
+ },
184
+ {
185
+ displayName: 'By URL',
186
+ name: 'url',
187
+ type: 'string',
188
+ placeholder: 'https://app.twenty.com/objects/companies/123e4567-e89b-12d3-a456-426614174000',
189
+ validation: [
190
+ {
191
+ type: 'regex',
192
+ properties: {
193
+ regex: 'https?://.*?/objects/[^/]+/([a-f0-9-]{36})',
194
+ errorMessage: 'Not a valid Twenty record URL',
195
+ },
196
+ },
197
+ ],
198
+ extractValue: {
199
+ type: 'regex',
200
+ regex: 'https?://.*?/objects/[^/]+/([a-f0-9-]{36})',
201
+ },
202
+ },
203
+ {
204
+ displayName: 'By ID',
205
+ name: 'id',
206
+ type: 'string',
207
+ placeholder: 'e.g., 123e4567-e89b-12d3-a456-426614174000',
208
+ validation: [
209
+ {
210
+ type: 'regex',
211
+ properties: {
212
+ regex: '^[a-f0-9-]{36}$',
213
+ errorMessage: 'Not a valid UUID',
214
+ },
215
+ },
216
+ ],
217
+ },
218
+ ],
219
+ description: 'The record to retrieve from the selected database',
220
+ },
221
+ {
222
+ displayName: 'Record',
223
+ name: 'recordIdDelete',
224
+ type: 'resourceLocator',
225
+ default: { mode: 'list', value: '' },
226
+ required: true,
227
+ displayOptions: {
228
+ show: {
229
+ operation: ['delete'],
230
+ },
231
+ },
232
+ description: 'The record to delete from the selected database. ⚠️ Delete operations are permanent and cannot be undone.',
233
+ modes: [
234
+ {
235
+ displayName: 'From List',
236
+ name: 'list',
237
+ type: 'list',
238
+ hint: 'Select a record from the dropdown list',
239
+ typeOptions: {
240
+ searchListMethod: 'getRecordsForDatabase',
241
+ searchable: true,
242
+ searchFilterRequired: false,
243
+ },
244
+ },
245
+ {
246
+ displayName: 'By URL',
247
+ name: 'url',
248
+ type: 'string',
249
+ hint: 'Paste the record URL from Twenty CRM',
250
+ placeholder: 'https://app.twenty.com/objects/people/123e4567-e89b-12d3-a456-426614174000',
251
+ validation: [
252
+ {
253
+ type: 'regex',
254
+ properties: {
255
+ regex: 'https?://.*?/objects/[^/]+/[a-f0-9-]{36}',
256
+ errorMessage: 'Not a valid Twenty CRM record URL',
257
+ },
258
+ },
259
+ ],
260
+ },
261
+ {
262
+ displayName: 'By ID',
263
+ name: 'id',
264
+ type: 'string',
265
+ hint: 'Enter the record UUID directly',
266
+ placeholder: '123e4567-e89b-12d3-a456-426614174000',
267
+ validation: [
268
+ {
269
+ type: 'regex',
270
+ properties: {
271
+ regex: '^[a-f0-9-]{36}$',
272
+ errorMessage: 'Not a valid UUID',
273
+ },
274
+ },
275
+ ],
276
+ },
277
+ ],
278
+ },
279
+ {
280
+ displayName: 'Record',
281
+ name: 'recordIdUpdate',
282
+ type: 'resourceLocator',
283
+ default: { mode: 'list', value: '' },
284
+ required: true,
285
+ displayOptions: {
286
+ show: {
287
+ operation: ['update'],
288
+ },
289
+ },
290
+ description: 'The record to update in the selected database',
291
+ modes: [
292
+ {
293
+ displayName: 'From List',
294
+ name: 'list',
295
+ type: 'list',
296
+ hint: 'Select a record from the dropdown list',
297
+ typeOptions: {
298
+ searchListMethod: 'getRecordsForDatabase',
299
+ searchable: true,
300
+ searchFilterRequired: false,
301
+ },
302
+ },
303
+ {
304
+ displayName: 'By URL',
305
+ name: 'url',
306
+ type: 'string',
307
+ hint: 'Paste the record URL from Twenty CRM',
308
+ placeholder: 'https://app.twenty.com/objects/people/123e4567-e89b-12d3-a456-426614174000',
309
+ validation: [
310
+ {
311
+ type: 'regex',
312
+ properties: {
313
+ regex: 'https?://.*?/objects/[^/]+/[a-f0-9-]{36}',
314
+ errorMessage: 'Not a valid Twenty CRM record URL',
315
+ },
316
+ },
317
+ ],
318
+ },
319
+ {
320
+ displayName: 'By ID',
321
+ name: 'id',
322
+ type: 'string',
323
+ hint: 'Enter the record UUID directly',
324
+ placeholder: '123e4567-e89b-12d3-a456-426614174000',
325
+ validation: [
326
+ {
327
+ type: 'regex',
328
+ properties: {
329
+ regex: '^[a-f0-9-]{36}$',
330
+ errorMessage: 'Not a valid UUID',
331
+ },
332
+ },
333
+ ],
334
+ },
335
+ ],
336
+ },
337
+ {
338
+ displayName: 'Match By',
339
+ name: 'upsertMode',
340
+ type: 'options',
341
+ displayOptions: {
342
+ show: {
343
+ operation: ['upsert'],
344
+ },
345
+ },
346
+ options: [
347
+ {
348
+ name: 'Record ID',
349
+ value: 'id',
350
+ description: 'Match by record UUID (if you already know the ID)',
351
+ },
352
+ {
353
+ name: 'Unique Field',
354
+ value: 'field',
355
+ description: 'Match by a unique field (e.g., email for people, domain for companies)',
356
+ },
357
+ ],
358
+ default: 'field',
359
+ description: 'How to determine if a record already exists',
360
+ },
361
+ {
362
+ displayName: 'Record',
363
+ name: 'recordIdUpsert',
364
+ type: 'resourceLocator',
365
+ default: { mode: 'list', value: '' },
366
+ required: true,
367
+ displayOptions: {
368
+ show: {
369
+ operation: ['upsert'],
370
+ upsertMode: ['id'],
371
+ },
372
+ },
373
+ description: 'The record to update if it exists. If not found, a new record will be created with the provided fields.',
374
+ modes: [
375
+ {
376
+ displayName: 'From List',
377
+ name: 'list',
378
+ type: 'list',
379
+ hint: 'Select a record from the dropdown list',
380
+ typeOptions: {
381
+ searchListMethod: 'getRecordsForDatabase',
382
+ searchable: true,
383
+ searchFilterRequired: false,
384
+ },
385
+ },
386
+ {
387
+ displayName: 'By URL',
388
+ name: 'url',
389
+ type: 'string',
390
+ hint: 'Paste the record URL from Twenty CRM',
391
+ placeholder: 'https://app.twenty.com/objects/people/123e4567-e89b-12d3-a456-426614174000',
392
+ validation: [
393
+ {
394
+ type: 'regex',
395
+ properties: {
396
+ regex: 'https?://.*?/objects/[^/]+/[a-f0-9-]{36}',
397
+ errorMessage: 'Not a valid Twenty CRM record URL',
398
+ },
399
+ },
400
+ ],
401
+ },
402
+ {
403
+ displayName: 'By ID',
404
+ name: 'id',
405
+ type: 'string',
406
+ hint: 'Enter the record UUID directly',
407
+ placeholder: '123e4567-e89b-12d3-a456-426614174000',
408
+ validation: [
409
+ {
410
+ type: 'regex',
411
+ properties: {
412
+ regex: '^[a-f0-9-]{36}$',
413
+ errorMessage: 'Not a valid UUID',
414
+ },
415
+ },
416
+ ],
417
+ },
418
+ ],
419
+ },
420
+ {
421
+ displayName: 'Match Field Name or ID',
422
+ name: 'upsertMatchField',
423
+ type: 'options',
424
+ typeOptions: {
425
+ loadOptionsMethod: 'getFieldsForResource',
426
+ },
427
+ displayOptions: {
428
+ show: {
429
+ operation: ['upsert'],
430
+ upsertMode: ['field'],
431
+ },
432
+ },
433
+ default: '',
434
+ required: true,
435
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
436
+ placeholder: 'Select a unique field (e.g., email)',
437
+ },
438
+ {
439
+ displayName: 'Match Value',
440
+ name: 'upsertMatchValue',
441
+ type: 'string',
442
+ displayOptions: {
443
+ show: {
444
+ operation: ['upsert'],
445
+ upsertMode: ['field'],
446
+ },
447
+ },
448
+ default: '',
449
+ required: true,
450
+ description: 'The value to search for in the match field',
451
+ placeholder: 'e.g., john@example.com',
452
+ },
453
+ {
454
+ displayName: 'Input Data',
455
+ name: 'bulkData',
456
+ type: 'json',
457
+ displayOptions: {
458
+ show: {
459
+ operation: ['createMany', 'getMany', 'updateMany', 'deleteMany', 'upsertMany'],
460
+ },
461
+ },
462
+ default: '[]',
463
+ required: true,
464
+ description: 'Array of data for bulk operation. Format varies by operation - see documentation.',
465
+ placeholder: '[{"field1": "value1"}, {"field1": "value2"}]',
466
+ hint: 'Provide array of objects. For Create Many: array of field objects. For Get/Delete Many: array of IDs. For Update/Upsert Many: array of objects with id/matchValue and fields.',
467
+ },
468
+ {
469
+ displayName: 'Match Field Name or ID',
470
+ name: 'upsertManyMatchField',
471
+ type: 'options',
472
+ typeOptions: {
473
+ loadOptionsMethod: 'getFieldsForResource',
474
+ },
475
+ displayOptions: {
476
+ show: {
477
+ operation: ['upsertMany'],
478
+ },
479
+ },
480
+ default: '',
481
+ required: true,
482
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
483
+ placeholder: 'Select a unique field (e.g., email)',
484
+ hint: 'The unique field to match records on. Each item in Input Data should have a matchValue for this field.',
485
+ },
486
+ {
487
+ displayName: 'Fields',
488
+ name: 'fields',
489
+ type: 'fixedCollection',
490
+ typeOptions: {
491
+ multipleValues: true,
492
+ },
493
+ displayOptions: {
494
+ show: {
495
+ operation: ['create', 'update', 'upsert'],
496
+ },
497
+ },
498
+ default: {},
499
+ placeholder: 'Add Field',
500
+ description: 'The fields to set on the record',
501
+ options: [
502
+ {
503
+ name: 'field',
504
+ displayName: 'Field',
505
+ values: [
506
+ {
507
+ displayName: 'Field Name or ID',
508
+ name: 'fieldName',
509
+ type: 'options',
510
+ typeOptions: {
511
+ loadOptionsMethod: 'getFieldsForResource',
512
+ },
513
+ default: '',
514
+ description: 'The name of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
515
+ },
516
+ {
517
+ displayName: 'Field Type',
518
+ name: 'fieldType',
519
+ type: 'hidden',
520
+ default: '={{$parameter["&fieldName"].split("|")[1]}}',
521
+ description: 'Auto-detected field type from Twenty CRM schema. Extracted from fieldName value.',
522
+ },
523
+ {
524
+ displayName: 'Boolean Value',
525
+ name: 'fieldBooleanValue',
526
+ type: 'options',
527
+ options: [
528
+ {
529
+ name: 'True',
530
+ value: true,
531
+ },
532
+ {
533
+ name: 'False',
534
+ value: false,
535
+ },
536
+ ],
537
+ displayOptions: {
538
+ show: {
539
+ fieldType: ['boolean'],
540
+ },
541
+ },
542
+ default: false,
543
+ description: 'Select True or False',
544
+ },
545
+ {
546
+ displayName: 'Value',
547
+ name: 'fieldValue',
548
+ type: 'string',
549
+ displayOptions: {
550
+ show: {
551
+ fieldType: ['simple'],
552
+ },
553
+ },
554
+ default: '',
555
+ description: 'The value to set for this field',
556
+ placeholder: 'Enter value',
557
+ },
558
+ {
559
+ displayName: 'First Name',
560
+ name: 'firstName',
561
+ type: 'string',
562
+ displayOptions: {
563
+ show: {
564
+ fieldType: ['fullName'],
565
+ },
566
+ },
567
+ default: '',
568
+ description: 'First name / given name',
569
+ placeholder: 'John',
570
+ },
571
+ {
572
+ displayName: 'Last Name',
573
+ name: 'lastName',
574
+ type: 'string',
575
+ displayOptions: {
576
+ show: {
577
+ fieldType: ['fullName'],
578
+ },
579
+ },
580
+ default: '',
581
+ description: 'Last name / family name',
582
+ placeholder: 'Doe',
583
+ },
584
+ {
585
+ displayName: 'URL',
586
+ name: 'primaryLinkUrl',
587
+ type: 'string',
588
+ displayOptions: {
589
+ show: {
590
+ fieldType: ['link'],
591
+ },
592
+ },
593
+ default: '',
594
+ description: 'The complete URL',
595
+ placeholder: 'https://example.com',
596
+ },
597
+ {
598
+ displayName: 'Label',
599
+ name: 'primaryLinkLabel',
600
+ type: 'string',
601
+ displayOptions: {
602
+ show: {
603
+ fieldType: ['link'],
604
+ },
605
+ },
606
+ default: '',
607
+ description: 'Display label for the URL',
608
+ placeholder: 'example.com',
609
+ },
610
+ {
611
+ displayName: 'Amount',
612
+ name: 'currencyAmount',
613
+ type: 'number',
614
+ displayOptions: {
615
+ show: {
616
+ fieldType: ['currency'],
617
+ },
618
+ },
619
+ default: 0,
620
+ description: 'Amount in your currency (will be converted to micros automatically)',
621
+ placeholder: '100000',
622
+ },
623
+ {
624
+ displayName: 'Currency Code',
625
+ name: 'currencyCode',
626
+ type: 'options',
627
+ displayOptions: {
628
+ show: {
629
+ fieldType: ['currency'],
630
+ },
631
+ },
632
+ options: [
633
+ { name: 'Australian Dollar (AUD)', value: 'AUD' },
634
+ { name: 'British Pound (GBP)', value: 'GBP' },
635
+ { name: 'Canadian Dollar (CAD)', value: 'CAD' },
636
+ { name: 'Chinese Yuan (CNY)', value: 'CNY' },
637
+ { name: 'Euro (EUR)', value: 'EUR' },
638
+ { name: 'Japanese Yen (JPY)', value: 'JPY' },
639
+ { name: 'Swiss Franc (CHF)', value: 'CHF' },
640
+ { name: 'US Dollar (USD)', value: 'USD' },
641
+ ],
642
+ default: 'USD',
643
+ description: 'Three-letter currency code',
644
+ },
645
+ {
646
+ displayName: 'Street Address 1',
647
+ name: 'addressStreet1',
648
+ type: 'string',
649
+ displayOptions: {
650
+ show: {
651
+ fieldType: ['address'],
652
+ },
653
+ },
654
+ default: '',
655
+ description: 'Primary street address',
656
+ placeholder: '123 Main Street',
657
+ },
658
+ {
659
+ displayName: 'Street Address 2',
660
+ name: 'addressStreet2',
661
+ type: 'string',
662
+ displayOptions: {
663
+ show: {
664
+ fieldType: ['address'],
665
+ },
666
+ },
667
+ default: '',
668
+ description: 'Apartment, suite, unit, etc. (optional).',
669
+ placeholder: 'Suite 100',
670
+ },
671
+ {
672
+ displayName: 'City',
673
+ name: 'addressCity',
674
+ type: 'string',
675
+ displayOptions: {
676
+ show: {
677
+ fieldType: ['address'],
678
+ },
679
+ },
680
+ default: '',
681
+ description: 'City or locality',
682
+ placeholder: 'New York',
683
+ },
684
+ {
685
+ displayName: 'Postal Code',
686
+ name: 'addressPostcode',
687
+ type: 'string',
688
+ displayOptions: {
689
+ show: {
690
+ fieldType: ['address'],
691
+ },
692
+ },
693
+ default: '',
694
+ description: 'ZIP or postal code',
695
+ placeholder: '10001',
696
+ },
697
+ {
698
+ displayName: 'State / Province',
699
+ name: 'addressState',
700
+ type: 'string',
701
+ displayOptions: {
702
+ show: {
703
+ fieldType: ['address'],
704
+ },
705
+ },
706
+ default: '',
707
+ description: 'State, province, or region',
708
+ placeholder: 'NY',
709
+ },
710
+ {
711
+ displayName: 'Country',
712
+ name: 'addressCountry',
713
+ type: 'string',
714
+ displayOptions: {
715
+ show: {
716
+ fieldType: ['address'],
717
+ },
718
+ },
719
+ default: '',
720
+ description: 'Country name',
721
+ placeholder: 'United States',
722
+ },
723
+ {
724
+ displayName: 'Latitude',
725
+ name: 'addressLat',
726
+ type: 'number',
727
+ displayOptions: {
728
+ show: {
729
+ fieldType: ['address'],
730
+ },
731
+ },
732
+ default: undefined,
733
+ description: 'Geographic latitude (optional)',
734
+ placeholder: '40.7128',
735
+ },
736
+ {
737
+ displayName: 'Longitude',
738
+ name: 'addressLng',
739
+ type: 'number',
740
+ displayOptions: {
741
+ show: {
742
+ fieldType: ['address'],
743
+ },
744
+ },
745
+ default: undefined,
746
+ description: 'Geographic longitude (optional)',
747
+ placeholder: '-74.0060',
748
+ },
749
+ {
750
+ displayName: 'Primary Email',
751
+ name: 'primaryEmail',
752
+ type: 'string',
753
+ displayOptions: {
754
+ show: {
755
+ fieldType: ['emails'],
756
+ },
757
+ },
758
+ default: '',
759
+ description: 'Primary email address',
760
+ placeholder: 'john@example.com',
761
+ },
762
+ {
763
+ displayName: 'Primary Phone Number',
764
+ name: 'primaryPhoneNumber',
765
+ type: 'string',
766
+ displayOptions: {
767
+ show: {
768
+ fieldType: ['phones'],
769
+ },
770
+ },
771
+ default: '',
772
+ placeholder: '+1-555-0123',
773
+ },
774
+ {
775
+ displayName: 'Country Code',
776
+ name: 'primaryPhoneCountryCode',
777
+ type: 'string',
778
+ displayOptions: {
779
+ show: {
780
+ fieldType: ['phones'],
781
+ },
782
+ },
783
+ default: '',
784
+ description: 'Two-letter country code (ISO 3166-1 alpha-2)',
785
+ placeholder: 'US',
786
+ },
787
+ {
788
+ displayName: 'Calling Code',
789
+ name: 'primaryPhoneCallingCode',
790
+ type: 'string',
791
+ displayOptions: {
792
+ show: {
793
+ fieldType: ['phones'],
794
+ },
795
+ },
796
+ default: '',
797
+ description: 'International calling code with plus sign',
798
+ placeholder: '+1',
799
+ },
800
+ {
801
+ displayName: 'Value Name or ID',
802
+ name: 'fieldSelectValue',
803
+ type: 'options',
804
+ typeOptions: {
805
+ loadOptionsMethod: 'getOptionsForSelectField',
806
+ loadOptionsDependsOn: ['fieldName'],
807
+ },
808
+ displayOptions: {
809
+ show: {
810
+ fieldType: ['select'],
811
+ },
812
+ },
813
+ default: '',
814
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
815
+ },
816
+ {
817
+ displayName: 'Values Names or IDs',
818
+ name: 'fieldMultiSelectValue',
819
+ type: 'multiOptions',
820
+ typeOptions: {
821
+ loadOptionsMethod: 'getOptionsForSelectField',
822
+ loadOptionsDependsOn: ['fieldName'],
823
+ },
824
+ displayOptions: {
825
+ show: {
826
+ fieldType: ['multiSelect'],
827
+ },
828
+ },
829
+ default: [],
830
+ description: 'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
831
+ },
832
+ {
833
+ displayName: 'Markdown',
834
+ name: 'markdown',
835
+ type: 'string',
836
+ typeOptions: { rows: 4 },
837
+ displayOptions: {
838
+ show: {
839
+ fieldType: ['richText'],
840
+ },
841
+ },
842
+ default: '',
843
+ description: 'Content in Markdown format',
844
+ placeholder: '## Heading\n\nYour content here...',
845
+ },
846
+ ],
847
+ },
848
+ ],
849
+ },
850
+ {
851
+ displayName: 'Limit',
852
+ name: 'limit',
853
+ type: 'number',
854
+ typeOptions: {
855
+ minValue: 1,
856
+ },
857
+ displayOptions: {
858
+ show: {
859
+ operation: ['findMany'],
860
+ },
861
+ },
862
+ default: 50,
863
+ description: 'Max number of results to return',
864
+ },
865
+ {
866
+ displayName: 'Search Query',
867
+ name: 'searchQuery',
868
+ type: 'string',
869
+ displayOptions: {
870
+ show: {
871
+ operation: ['findMany'],
872
+ },
873
+ },
874
+ default: '',
875
+ placeholder: 'Type a search term or use advanced syntax...',
876
+ description: 'Search records by name. Just type a word (e.g., "google") to search by name. For Person database, searches both first and last name automatically. For advanced filtering, use Twenty filter syntax: field[operator]:value (e.g., createdAt[gte]:"2024-01-01"). Operators: eq, neq, like, ilike, gt, gte, lt, lte, in, startsWith, is. Combine with and()/or()/not(). Leave empty to return all records.',
877
+ },
878
+ {
879
+ displayName: 'Order By Field Name or ID',
880
+ name: 'orderByField',
881
+ type: 'options',
882
+ typeOptions: {
883
+ loadOptionsMethod: 'getFieldsForOrderBy',
884
+ },
885
+ displayOptions: {
886
+ show: {
887
+ operation: ['findMany'],
888
+ },
889
+ },
890
+ default: '',
891
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
892
+ },
893
+ {
894
+ displayName: 'Order Direction',
895
+ name: 'orderByDirection',
896
+ type: 'options',
897
+ displayOptions: {
898
+ show: {
899
+ operation: ['findMany'],
900
+ },
901
+ },
902
+ options: [
903
+ {
904
+ name: 'Ascending (Nulls First)',
905
+ value: 'AscNullsFirst',
906
+ },
907
+ {
908
+ name: 'Ascending (Nulls Last)',
909
+ value: 'AscNullsLast',
910
+ },
911
+ {
912
+ name: 'Descending (Nulls First)',
913
+ value: 'DescNullsFirst',
914
+ },
915
+ {
916
+ name: 'Descending (Nulls Last)',
917
+ value: 'DescNullsLast',
918
+ },
919
+ ],
920
+ default: 'AscNullsFirst',
921
+ description: 'Sort direction for results',
922
+ },
923
+ ],
924
+ };
925
+ this.methods = {
926
+ loadOptions: {
927
+ async getResources() {
928
+ try {
929
+ let resourceGroup = 'all';
930
+ try {
931
+ resourceGroup = this.getCurrentNodeParameter('resourceGroup');
932
+ }
933
+ catch {
934
+ resourceGroup = 'all';
935
+ }
936
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, false);
937
+ let filteredObjects = schema.objects;
938
+ switch (resourceGroup) {
939
+ case 'all':
940
+ filteredObjects = schema.objects;
941
+ break;
942
+ case 'standard':
943
+ filteredObjects = schema.objects.filter(obj => obj.isCustom === false && obj.isSystem === false && obj.isActive === true);
944
+ break;
945
+ case 'system':
946
+ filteredObjects = schema.objects.filter(obj => obj.isSystem === true && obj.isCustom === false);
947
+ break;
948
+ case 'custom':
949
+ filteredObjects = schema.objects.filter(obj => obj.isCustom === true);
950
+ break;
951
+ default:
952
+ filteredObjects = schema.objects;
953
+ }
954
+ const options = filteredObjects.map((obj) => ({
955
+ name: obj.labelSingular,
956
+ value: obj.nameSingular,
957
+ description: obj.isCustom ? '(Custom Database)' : '(Standard Database)',
958
+ }));
959
+ options.sort((a, b) => {
960
+ var _a, _b;
961
+ const aIsCustom = ((_a = a.description) === null || _a === void 0 ? void 0 : _a.includes('Custom')) || false;
962
+ const bIsCustom = ((_b = b.description) === null || _b === void 0 ? void 0 : _b.includes('Custom')) || false;
963
+ if (aIsCustom === bIsCustom) {
964
+ return a.name.localeCompare(b.name);
965
+ }
966
+ return aIsCustom ? 1 : -1;
967
+ });
968
+ return options;
969
+ }
970
+ catch (error) {
971
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load databases from Twenty CRM. Please check your credentials and connection. Error: ${error.message}`);
972
+ }
973
+ },
974
+ async getFieldsForResource() {
975
+ try {
976
+ const resource = this.getCurrentNodeParameter('resource');
977
+ if (!resource) {
978
+ return [];
979
+ }
980
+ let operation = '';
981
+ try {
982
+ operation = this.getCurrentNodeParameter('operation');
983
+ }
984
+ catch {
985
+ }
986
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, false);
987
+ const objectMeta = schema.objects.find((obj) => obj.nameSingular === resource);
988
+ const metadataFields = (objectMeta === null || objectMeta === void 0 ? void 0 : objectMeta.fields) || [];
989
+ const graphqlFields = await TwentyApi_client_1.getDataSchemaForObject.call(this, resource);
990
+ const fieldMap = new Map();
991
+ graphqlFields.forEach((field) => {
992
+ fieldMap.set(field.name, {
993
+ ...field,
994
+ source: 'graphql',
995
+ });
996
+ });
997
+ metadataFields.forEach((field) => {
998
+ fieldMap.set(field.name, {
999
+ ...field,
1000
+ source: 'metadata',
1001
+ });
1002
+ });
1003
+ const allFields = Array.from(fieldMap.values());
1004
+ const EXCLUDED_FIELD_TYPES = new Set([
1005
+ 'RELATION', 'MORPH_RELATION',
1006
+ 'ACTOR',
1007
+ 'TS_VECTOR',
1008
+ 'POSITION',
1009
+ 'FILES',
1010
+ ]);
1011
+ const isCreateOrUpdate = operation === 'create' || operation === 'update';
1012
+ const filteredFields = allFields.filter((field) => {
1013
+ var _a;
1014
+ if (field.isActive === false)
1015
+ return false;
1016
+ if (EXCLUDED_FIELD_TYPES.has(field.type))
1017
+ return false;
1018
+ if ((_a = field.type) === null || _a === void 0 ? void 0 : _a.endsWith('Connection'))
1019
+ return false;
1020
+ if (isCreateOrUpdate)
1021
+ return field.isWritable;
1022
+ return true;
1023
+ });
1024
+ const mapTwentyTypeToN8nType = (twentyType) => {
1025
+ const typeMap = {
1026
+ 'SELECT': 'select',
1027
+ 'MULTI_SELECT': 'multiSelect',
1028
+ 'FULL_NAME': 'fullName',
1029
+ 'LINKS': 'link',
1030
+ 'CURRENCY': 'currency',
1031
+ 'ADDRESS': 'address',
1032
+ 'FullName': 'fullName',
1033
+ 'Links': 'link',
1034
+ 'Currency': 'currency',
1035
+ 'Address': 'address',
1036
+ 'EMAILS': 'emails',
1037
+ 'PHONES': 'phones',
1038
+ 'RICH_TEXT': 'richText',
1039
+ 'BOOLEAN': 'boolean',
1040
+ 'TEXT': 'simple',
1041
+ 'NUMBER': 'simple',
1042
+ 'DATE_TIME': 'simple',
1043
+ 'DATE': 'simple',
1044
+ 'UUID': 'simple',
1045
+ 'RAW_JSON': 'simple',
1046
+ 'RELATION': 'relation',
1047
+ 'MORPH_RELATION': 'relation',
1048
+ 'ACTOR': 'simple',
1049
+ 'POSITION': 'simple',
1050
+ 'TS_VECTOR': 'simple',
1051
+ 'FILES': 'simple',
1052
+ };
1053
+ return typeMap[twentyType] || 'simple';
1054
+ };
1055
+ const mapGraphQLTypeToN8nType = (graphqlType) => {
1056
+ if (graphqlType.startsWith('LIST<') && graphqlType.includes('Enum')) {
1057
+ return 'multiSelect';
1058
+ }
1059
+ if (graphqlType.includes('Enum')) {
1060
+ return 'select';
1061
+ }
1062
+ return mapTwentyTypeToN8nType(graphqlType);
1063
+ };
1064
+ const options = filteredFields.map((field) => {
1065
+ const n8nType = field.source === 'metadata'
1066
+ ? mapTwentyTypeToN8nType(field.type)
1067
+ : mapGraphQLTypeToN8nType(field.type);
1068
+ return {
1069
+ name: (0, TwentyApi_client_1.getCleanFieldLabel)(field.label, field.name),
1070
+ value: `${field.name}|${n8nType}`,
1071
+ description: field.type,
1072
+ };
1073
+ });
1074
+ options.sort((a, b) => {
1075
+ const aValue = String(a.value);
1076
+ const bValue = String(b.value);
1077
+ if (aValue.startsWith('name|'))
1078
+ return -1;
1079
+ if (bValue.startsWith('name|'))
1080
+ return 1;
1081
+ const aIsStandard = ['id', 'createdAt', 'updatedAt', 'deletedAt'].some((f) => aValue.includes(f));
1082
+ const bIsStandard = ['id', 'createdAt', 'updatedAt', 'deletedAt'].some((f) => bValue.includes(f));
1083
+ if (aIsStandard === bIsStandard) {
1084
+ return a.name.localeCompare(b.name);
1085
+ }
1086
+ return aIsStandard ? -1 : 1;
1087
+ });
1088
+ return options;
1089
+ }
1090
+ catch (error) {
1091
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load fields for resource: ${error.message}`);
1092
+ }
1093
+ },
1094
+ async getFieldsForOrderBy() {
1095
+ try {
1096
+ const resource = this.getCurrentNodeParameter('resource');
1097
+ if (!resource) {
1098
+ return [{ name: '(No Sorting)', value: '' }];
1099
+ }
1100
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, false);
1101
+ const objectMeta = schema.objects.find((obj) => obj.nameSingular === resource);
1102
+ const metadataFields = (objectMeta === null || objectMeta === void 0 ? void 0 : objectMeta.fields) || [];
1103
+ const graphqlFields = await TwentyApi_client_1.getDataSchemaForObject.call(this, resource);
1104
+ const fieldMap = new Map();
1105
+ graphqlFields.forEach((f) => fieldMap.set(f.name, { ...f, source: 'graphql' }));
1106
+ metadataFields.forEach((f) => fieldMap.set(f.name, { ...f, source: 'metadata' }));
1107
+ const allFields = Array.from(fieldMap.values());
1108
+ const sortableTypes = ['TEXT', 'NUMBER', 'BOOLEAN', 'UUID', 'DATE_TIME', 'DATE', 'SELECT', 'PHONE', 'EMAIL', 'RAW_JSON'];
1109
+ const sortableFields = allFields.filter((f) => {
1110
+ if (f.isActive === false)
1111
+ return false;
1112
+ if (sortableTypes.includes(f.type))
1113
+ return true;
1114
+ if (f.type.includes('Connection'))
1115
+ return false;
1116
+ if (['String', 'Int', 'Float', 'Boolean', 'DateTime', 'Date', 'UUID', 'ID'].includes(f.type))
1117
+ return true;
1118
+ return false;
1119
+ });
1120
+ const options = [
1121
+ { name: '(No Sorting)', value: '' },
1122
+ ];
1123
+ sortableFields.forEach((field) => {
1124
+ options.push({
1125
+ name: (0, TwentyApi_client_1.getCleanFieldLabel)(field.label, field.name),
1126
+ value: field.name,
1127
+ description: field.type,
1128
+ });
1129
+ });
1130
+ options.sort((a, b) => {
1131
+ if (a.value === '')
1132
+ return -1;
1133
+ if (b.value === '')
1134
+ return 1;
1135
+ return a.name.localeCompare(b.name);
1136
+ });
1137
+ return options;
1138
+ }
1139
+ catch (error) {
1140
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load sortable fields: ${error.message}`);
1141
+ }
1142
+ },
1143
+ async getOptionsForSelectField() {
1144
+ var _a, _b;
1145
+ try {
1146
+ const resource = this.getCurrentNodeParameter('resource');
1147
+ if (!resource) {
1148
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No resource selected');
1149
+ }
1150
+ let fieldNameWithType;
1151
+ try {
1152
+ fieldNameWithType = this.getCurrentNodeParameter('&fieldName');
1153
+ }
1154
+ catch {
1155
+ return [];
1156
+ }
1157
+ if (!fieldNameWithType) {
1158
+ return [];
1159
+ }
1160
+ const parts = fieldNameWithType.split('|');
1161
+ if (parts.length !== 2) {
1162
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid field format: ${fieldNameWithType}`);
1163
+ }
1164
+ const [fieldName, fieldType] = parts;
1165
+ if (!['select', 'multiSelect'].includes(fieldType)) {
1166
+ return [];
1167
+ }
1168
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, false);
1169
+ const objectMeta = schema.objects.find((obj) => obj.nameSingular === resource);
1170
+ if (objectMeta === null || objectMeta === void 0 ? void 0 : objectMeta.fields) {
1171
+ const metadataField = objectMeta.fields.find((f) => f.name === fieldName);
1172
+ if ((metadataField === null || metadataField === void 0 ? void 0 : metadataField.options) && metadataField.options.length > 0) {
1173
+ const sortedOptions = [...metadataField.options].sort((a, b) => a.position - b.position);
1174
+ return sortedOptions.map(opt => ({
1175
+ name: opt.label,
1176
+ value: opt.value,
1177
+ description: opt.color ? `Color: ${opt.color}` : undefined,
1178
+ }));
1179
+ }
1180
+ }
1181
+ const typeName = resource.charAt(0).toUpperCase() + resource.slice(1);
1182
+ const graphqlSchema = await TwentyApi_client_1.queryGraphQLType.call(this, typeName);
1183
+ if ((_a = graphqlSchema.__type) === null || _a === void 0 ? void 0 : _a.fields) {
1184
+ const graphqlField = graphqlSchema.__type.fields.find((f) => f.name === fieldName);
1185
+ if (graphqlField) {
1186
+ let enumTypeName = null;
1187
+ if (graphqlField.type.kind === 'ENUM') {
1188
+ enumTypeName = graphqlField.type.name;
1189
+ }
1190
+ else if (graphqlField.type.kind === 'LIST' && ((_b = graphqlField.type.ofType) === null || _b === void 0 ? void 0 : _b.kind) === 'ENUM') {
1191
+ enumTypeName = graphqlField.type.ofType.name;
1192
+ }
1193
+ if (enumTypeName) {
1194
+ const enumValues = await TwentyApi_client_1.queryEnumValues.call(this, enumTypeName);
1195
+ return enumValues.map(ev => ({
1196
+ name: ev.label,
1197
+ value: ev.name,
1198
+ }));
1199
+ }
1200
+ }
1201
+ }
1202
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No options found for field "${fieldName}" (type: ${fieldType}). This field may not be a SELECT or MULTI_SELECT type, or the field data is not available.`);
1203
+ }
1204
+ catch (error) {
1205
+ if (error instanceof n8n_workflow_1.NodeOperationError) {
1206
+ throw error;
1207
+ }
1208
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error fetching options: ${error.message}`);
1209
+ }
1210
+ },
1211
+ },
1212
+ listSearch: {
1213
+ async getRecordsForDatabase(filter) {
1214
+ var _a;
1215
+ try {
1216
+ const resource = this.getCurrentNodeParameter('resource');
1217
+ if (!resource) {
1218
+ return { results: [] };
1219
+ }
1220
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, true);
1221
+ const objectMetadata = schema.objects.find((obj) => obj.nameSingular === resource);
1222
+ if (!objectMetadata) {
1223
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Database "${resource}" not found in schema`);
1224
+ }
1225
+ const isPerson = resource === 'person';
1226
+ const nameFieldQuery = isPerson
1227
+ ? `name {
1228
+ firstName
1229
+ lastName
1230
+ }`
1231
+ : 'name';
1232
+ const fieldsToQuery = ['id', nameFieldQuery];
1233
+ const pluralName = objectMetadata.namePlural;
1234
+ const hasFilter = filter && filter.trim() !== '';
1235
+ let filterClause = '';
1236
+ if (hasFilter) {
1237
+ if (isPerson) {
1238
+ filterClause = ', filter: { or: [ { name: { firstName: { ilike: $searchPattern } } }, { name: { lastName: { ilike: $searchPattern } } } ] }';
1239
+ }
1240
+ else {
1241
+ filterClause = ', filter: { name: { ilike: $searchPattern } }';
1242
+ }
1243
+ }
1244
+ const query = `
1245
+ query List${objectMetadata.labelPlural.replace(/\s+/g, '')}($limit: Int!${hasFilter ? ', $searchPattern: String!' : ''}) {
1246
+ ${pluralName}(first: $limit${filterClause}) {
1247
+ edges {
1248
+ node {
1249
+ ${fieldsToQuery.join('\n ')}
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+ `;
1255
+ const variables = {
1256
+ limit: 100,
1257
+ };
1258
+ if (hasFilter) {
1259
+ variables.searchPattern = `%${filter}%`;
1260
+ }
1261
+ const response = await TwentyApi_client_1.twentyApiRequest.call(this, 'graphql', query, variables);
1262
+ const edges = ((_a = response[pluralName]) === null || _a === void 0 ? void 0 : _a.edges) || [];
1263
+ if (edges.length === 0) {
1264
+ return {
1265
+ results: [
1266
+ {
1267
+ name: `No ${objectMetadata.labelPlural} Found`,
1268
+ value: '',
1269
+ },
1270
+ ],
1271
+ };
1272
+ }
1273
+ const results = edges.map((edge) => {
1274
+ const record = edge.node;
1275
+ let displayValue;
1276
+ if (isPerson && record.name && typeof record.name === 'object') {
1277
+ const firstName = record.name.firstName || '';
1278
+ const lastName = record.name.lastName || '';
1279
+ displayValue = `${firstName} ${lastName}`.trim() || record.id;
1280
+ }
1281
+ else {
1282
+ displayValue = record.name || record.id;
1283
+ }
1284
+ return {
1285
+ name: displayValue,
1286
+ value: record.id,
1287
+ url: `https://app.twenty.com/objects/${objectMetadata.namePlural}/${record.id}`,
1288
+ };
1289
+ });
1290
+ return { results };
1291
+ }
1292
+ catch (error) {
1293
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load records from Twenty CRM. Error: ${error.message}`);
1294
+ }
1295
+ },
1296
+ },
1297
+ };
1298
+ }
1299
+ async execute() {
1300
+ var _a, _b, _c, _d, _e;
1301
+ const items = this.getInputData();
1302
+ const returnData = [];
1303
+ const operation = this.getNodeParameter('operation', 0);
1304
+ const resource = this.getNodeParameter('resource', 0);
1305
+ const schema = await TwentyApi_client_1.getCachedSchema.call(this, true);
1306
+ const objectMetadata = schema.objects.find((obj) => obj.nameSingular === resource);
1307
+ if (!objectMetadata) {
1308
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Object "${resource}" not found in schema`);
1309
+ }
1310
+ for (let i = 0; i < items.length; i++) {
1311
+ try {
1312
+ if (operation === 'create') {
1313
+ const fieldsParam = this.getNodeParameter('fields', i, {});
1314
+ const fieldsData = (0, FieldTransformation_1.transformFieldsData)(fieldsParam.field || [], resource);
1315
+ const { query, variables } = await TwentyApi_client_1.buildCreateMutation.call(this, resource, fieldsData, objectMetadata);
1316
+ const response = await TwentyApi_client_1.twentyApiRequest.call(this, 'graphql', query, variables);
1317
+ const operationName = `create${resource.charAt(0).toUpperCase() + resource.slice(1)}`;
1318
+ const createdRecord = response[operationName];
1319
+ returnData.push({
1320
+ json: createdRecord,
1321
+ pairedItem: { item: i },
1322
+ });
1323
+ }
1324
+ else if (operation === 'createMany') {
1325
+ const bulkDataParam = this.getNodeParameter('bulkData', i);
1326
+ const recordsData = JSON.parse(bulkDataParam);
1327
+ if (!Array.isArray(recordsData)) {
1328
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input Data must be an array of objects');
1329
+ }
1330
+ const transformedRecords = recordsData.map(record => (0, FieldTransformation_1.transformFieldsData)(Object.entries(record).map(([key, value]) => ({
1331
+ fieldName: key,
1332
+ fieldValue: value,
1333
+ })), resource));
1334
+ const results = await (0, operations_1.executeCreateMany)(this, resource, transformedRecords, objectMetadata);
1335
+ results.forEach((result) => {
1336
+ returnData.push({
1337
+ json: result.success ? result.record : { error: result.error, index: result.index },
1338
+ pairedItem: { item: i },
1339
+ });
1340
+ });
1341
+ }
1342
+ else if (operation === 'get') {
1343
+ const recordIdParam = this.getNodeParameter('recordId', i);
1344
+ let recordId;
1345
+ if (typeof recordIdParam === 'string') {
1346
+ recordId = recordIdParam;
1347
+ }
1348
+ else if (recordIdParam && typeof recordIdParam === 'object' && recordIdParam.value) {
1349
+ if (recordIdParam.mode === 'url') {
1350
+ const urlMatch = recordIdParam.value.match(/https?:\/\/.*?\/objects\/[^\/]+\/([a-f0-9-]{36})/i);
1351
+ if (!urlMatch) {
1352
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not extract record ID from URL: ${recordIdParam.value}`);
1353
+ }
1354
+ recordId = urlMatch[1];
1355
+ }
1356
+ else {
1357
+ recordId = recordIdParam.value;
1358
+ }
1359
+ }
1360
+ else {
1361
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No record ID provided');
1362
+ }
1363
+ const pluralName = objectMetadata.namePlural;
1364
+ const restPath = `/${pluralName}/${recordId}`;
1365
+ try {
1366
+ const response = await TwentyApi_client_1.twentyRestApiRequest.call(this, 'GET', restPath);
1367
+ const record = (_a = response.data) === null || _a === void 0 ? void 0 : _a[resource];
1368
+ if (!record) {
1369
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Record with ID "${recordId}" not found`);
1370
+ }
1371
+ returnData.push({
1372
+ json: record,
1373
+ pairedItem: { item: i },
1374
+ });
1375
+ }
1376
+ catch (error) {
1377
+ if (error.message.includes('Record not found')) {
1378
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Record with ID "${recordId}" not found`);
1379
+ }
1380
+ throw error;
1381
+ }
1382
+ }
1383
+ else if (operation === 'getMany') {
1384
+ const bulkDataParam = this.getNodeParameter('bulkData', i);
1385
+ const recordIds = JSON.parse(bulkDataParam);
1386
+ if (!Array.isArray(recordIds)) {
1387
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input Data must be an array of record IDs');
1388
+ }
1389
+ const results = await (0, operations_1.executeGetMany)(this, resource, recordIds, objectMetadata);
1390
+ results.forEach((result) => {
1391
+ returnData.push({
1392
+ json: result.success ? result.record : { error: result.error, id: result.id, index: result.index },
1393
+ pairedItem: { item: i },
1394
+ });
1395
+ });
1396
+ }
1397
+ else if (operation === 'update') {
1398
+ const recordIdParam = this.getNodeParameter('recordIdUpdate', i);
1399
+ let recordId;
1400
+ if (typeof recordIdParam === 'string') {
1401
+ recordId = recordIdParam;
1402
+ }
1403
+ else if (recordIdParam && typeof recordIdParam === 'object' && recordIdParam.value) {
1404
+ if (recordIdParam.mode === 'url') {
1405
+ const urlMatch = recordIdParam.value.match(/https?:\/\/.*?\/objects\/[^\/]+\/([a-f0-9-]{36})/i);
1406
+ if (!urlMatch) {
1407
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not extract record ID from URL: ${recordIdParam.value}`);
1408
+ }
1409
+ recordId = urlMatch[1];
1410
+ }
1411
+ else {
1412
+ recordId = recordIdParam.value;
1413
+ }
1414
+ }
1415
+ else {
1416
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No record ID provided');
1417
+ }
1418
+ const fieldsParam = this.getNodeParameter('fields', i, {});
1419
+ const fieldsData = (0, FieldTransformation_1.transformFieldsData)(fieldsParam.field || [], resource);
1420
+ const { query, variables } = await TwentyApi_client_1.buildUpdateMutation.call(this, resource, recordId, fieldsData, objectMetadata);
1421
+ const response = await TwentyApi_client_1.twentyApiRequest.call(this, 'graphql', query, variables);
1422
+ const operationName = `update${resource.charAt(0).toUpperCase() + resource.slice(1)}`;
1423
+ const updatedRecord = response[operationName];
1424
+ returnData.push({
1425
+ json: updatedRecord,
1426
+ pairedItem: { item: i },
1427
+ });
1428
+ }
1429
+ else if (operation === 'updateMany') {
1430
+ const bulkDataParam = this.getNodeParameter('bulkData', i);
1431
+ const updatesData = JSON.parse(bulkDataParam);
1432
+ if (!Array.isArray(updatesData)) {
1433
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input Data must be an array of objects with id and fields');
1434
+ }
1435
+ const transformedUpdates = updatesData.map(update => ({
1436
+ id: update.id,
1437
+ fieldsData: (0, FieldTransformation_1.transformFieldsData)(Object.entries(update.fields || {}).map(([key, value]) => ({
1438
+ fieldName: key,
1439
+ fieldValue: value,
1440
+ })), resource),
1441
+ }));
1442
+ const results = await (0, operations_1.executeUpdateMany)(this, resource, transformedUpdates, objectMetadata);
1443
+ results.forEach((result) => {
1444
+ returnData.push({
1445
+ json: result.success ? result.record : { error: result.error, id: result.id, index: result.index },
1446
+ pairedItem: { item: i },
1447
+ });
1448
+ });
1449
+ }
1450
+ else if (operation === 'upsert') {
1451
+ const upsertMode = this.getNodeParameter('upsertMode', i, 'field');
1452
+ const fieldsParam = this.getNodeParameter('fields', i, {});
1453
+ const fieldsData = (0, FieldTransformation_1.transformFieldsData)(fieldsParam.field || [], resource);
1454
+ const options = {};
1455
+ if (upsertMode === 'id') {
1456
+ options.recordIdParam = this.getNodeParameter('recordIdUpsert', i);
1457
+ }
1458
+ else {
1459
+ const matchFieldParam = this.getNodeParameter('upsertMatchField', i);
1460
+ const matchValue = this.getNodeParameter('upsertMatchValue', i);
1461
+ options.matchField = matchFieldParam.split('|')[0];
1462
+ options.matchValue = matchValue;
1463
+ }
1464
+ const { record, action } = await (0, operations_1.executeUpsert)(this, upsertMode, resource, fieldsData, objectMetadata, options);
1465
+ returnData.push({
1466
+ json: { ...record, __upsertAction: action },
1467
+ pairedItem: { item: i },
1468
+ });
1469
+ }
1470
+ else if (operation === 'upsertMany') {
1471
+ const bulkDataParam = this.getNodeParameter('bulkData', i);
1472
+ const upsertData = JSON.parse(bulkDataParam);
1473
+ if (!Array.isArray(upsertData)) {
1474
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input Data must be an array of objects with matchValue and fields');
1475
+ }
1476
+ const matchFieldParam = this.getNodeParameter('upsertManyMatchField', i);
1477
+ const matchField = matchFieldParam.split('|')[0];
1478
+ const transformedUpserts = upsertData.map(item => ({
1479
+ matchValue: item.matchValue,
1480
+ fieldsData: (0, FieldTransformation_1.transformFieldsData)(Object.entries(item.fields || {}).map(([key, value]) => ({
1481
+ fieldName: key,
1482
+ fieldValue: value,
1483
+ })), resource),
1484
+ }));
1485
+ const results = await (0, operations_1.executeUpsertMany)(this, resource, 'field', transformedUpserts, objectMetadata, { matchField });
1486
+ results.forEach((result) => {
1487
+ returnData.push({
1488
+ json: result.success
1489
+ ? { ...result.record, __upsertAction: result.action }
1490
+ : { error: result.error, index: result.index },
1491
+ pairedItem: { item: i },
1492
+ });
1493
+ });
1494
+ }
1495
+ else if (operation === 'delete') {
1496
+ const recordIdParam = this.getNodeParameter('recordIdDelete', i);
1497
+ let recordId;
1498
+ if (typeof recordIdParam === 'string') {
1499
+ recordId = recordIdParam;
1500
+ }
1501
+ else if (recordIdParam && typeof recordIdParam === 'object' && recordIdParam.value) {
1502
+ if (recordIdParam.mode === 'url') {
1503
+ const urlMatch = recordIdParam.value.match(/https?:\/\/.*?\/objects\/[^\/]+\/([a-f0-9-]{36})/i);
1504
+ if (!urlMatch) {
1505
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not extract record ID from URL: ${recordIdParam.value}`);
1506
+ }
1507
+ recordId = urlMatch[1];
1508
+ }
1509
+ else {
1510
+ recordId = recordIdParam.value;
1511
+ }
1512
+ }
1513
+ else {
1514
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No record ID provided');
1515
+ }
1516
+ const pluralName = objectMetadata.namePlural;
1517
+ const restPath = `/${pluralName}/${recordId}`;
1518
+ try {
1519
+ const response = await TwentyApi_client_1.twentyRestApiRequest.call(this, 'DELETE', restPath);
1520
+ let deletedRecord;
1521
+ if (response.data) {
1522
+ deletedRecord = response.data[resource] || response.data[objectMetadata.nameSingular] || response.data;
1523
+ }
1524
+ else {
1525
+ deletedRecord = response;
1526
+ }
1527
+ const resultId = (deletedRecord === null || deletedRecord === void 0 ? void 0 : deletedRecord.id) || recordId;
1528
+ returnData.push({
1529
+ json: { success: true, id: resultId, deletedRecord },
1530
+ pairedItem: { item: i },
1531
+ });
1532
+ }
1533
+ catch (error) {
1534
+ if (error.message && error.message.includes('Record not found')) {
1535
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Record with ID "${recordId}" not found`);
1536
+ }
1537
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to delete record with ID "${recordId}": ${error.message || error}`);
1538
+ }
1539
+ }
1540
+ else if (operation === 'deleteMany') {
1541
+ const bulkDataParam = this.getNodeParameter('bulkData', i);
1542
+ const recordIds = JSON.parse(bulkDataParam);
1543
+ if (!Array.isArray(recordIds)) {
1544
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input Data must be an array of record IDs');
1545
+ }
1546
+ const results = await (0, operations_1.executeDeleteMany)(this, resource, recordIds, objectMetadata);
1547
+ results.forEach((result) => {
1548
+ returnData.push({
1549
+ json: result.success
1550
+ ? { success: true, id: result.id }
1551
+ : { error: result.error, id: result.id, index: result.index },
1552
+ pairedItem: { item: i },
1553
+ });
1554
+ });
1555
+ }
1556
+ else if (operation === 'findMany') {
1557
+ const limit = this.getNodeParameter('limit', i);
1558
+ const searchQuery = this.getNodeParameter('searchQuery', i, '');
1559
+ const orderByField = this.getNodeParameter('orderByField', i, '');
1560
+ const orderByDirection = this.getNodeParameter('orderByDirection', i, 'AscNullsFirst');
1561
+ const pluralName = objectMetadata.namePlural;
1562
+ const staticParts = [];
1563
+ if (searchQuery) {
1564
+ const filter = buildSmartFilter(searchQuery, resource);
1565
+ staticParts.push(`filter=${encodeURIComponent(filter)}`);
1566
+ }
1567
+ if (orderByField) {
1568
+ staticParts.push(`order_by=${orderByField}[${orderByDirection}]`);
1569
+ }
1570
+ let collected = 0;
1571
+ let cursor;
1572
+ let hasNextPage = true;
1573
+ try {
1574
+ while (hasNextPage && (!limit || collected < limit)) {
1575
+ const remaining = limit ? limit - collected : TwentyApi_client_1.TWENTY_REST_MAX_PAGE_SIZE;
1576
+ const pageSize = Math.min(remaining, TwentyApi_client_1.TWENTY_REST_MAX_PAGE_SIZE);
1577
+ const queryParts = [...staticParts, `limit=${pageSize}`];
1578
+ if (cursor) {
1579
+ queryParts.push(`starting_after=${encodeURIComponent(cursor)}`);
1580
+ }
1581
+ const restPath = `/${pluralName}?${queryParts.join('&')}`;
1582
+ const response = await TwentyApi_client_1.twentyRestApiRequest.call(this, 'GET', restPath);
1583
+ const data = response.data;
1584
+ if (!data)
1585
+ break;
1586
+ const records = data[pluralName];
1587
+ if (!records)
1588
+ break;
1589
+ const recordsArray = Array.isArray(records)
1590
+ ? records
1591
+ : (_c = (_b = records.edges) === null || _b === void 0 ? void 0 : _b.map((edge) => edge.node)) !== null && _c !== void 0 ? _c : [];
1592
+ for (const record of recordsArray) {
1593
+ returnData.push({
1594
+ json: record,
1595
+ pairedItem: { item: i },
1596
+ });
1597
+ collected++;
1598
+ }
1599
+ const pageInfo = (_d = data.pageInfo) !== null && _d !== void 0 ? _d : records.pageInfo;
1600
+ hasNextPage = (pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.hasNextPage) === true;
1601
+ cursor = pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.endCursor;
1602
+ if (!cursor || recordsArray.length === 0)
1603
+ break;
1604
+ }
1605
+ }
1606
+ catch (error) {
1607
+ if ((_e = error.message) === null || _e === void 0 ? void 0 : _e.includes('not found')) {
1608
+ continue;
1609
+ }
1610
+ throw error;
1611
+ }
1612
+ }
1613
+ }
1614
+ catch (error) {
1615
+ if (this.continueOnFail()) {
1616
+ returnData.push({
1617
+ json: { error: error.message },
1618
+ pairedItem: { item: i },
1619
+ });
1620
+ continue;
1621
+ }
1622
+ throw error;
1623
+ }
1624
+ }
1625
+ return [returnData];
1626
+ }
1627
+ }
1628
+ exports.Twenty = Twenty;
1629
+ //# sourceMappingURL=Twenty.node.js.map