@hustleops/n8n-nodes-hustleops 0.1.5

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.
@@ -0,0 +1,629 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CORE_RESOURCE_OPTIONS = exports.CORE_RESOURCE_DEFINITIONS = void 0;
4
+ exports.getCoreResourceDefinition = getCoreResourceDefinition;
5
+ exports.sanitizeDtoBody = sanitizeDtoBody;
6
+ exports.buildGenericSearchRequest = buildGenericSearchRequest;
7
+ exports.buildSearchRequest = buildSearchRequest;
8
+ const n8n_workflow_1 = require("n8n-workflow");
9
+ const GenericFunctions_1 = require("./GenericFunctions");
10
+ const SEARCH_REQUEST_FIELDS = new Set(['filter', 'pagination', 'excludeIds']);
11
+ const SEARCH_PAGINATION_FIELDS = new Set(['page', 'pageSize', 'sortBy', 'sortOrder']);
12
+ const SEARCH_OPERATORS = new Set([
13
+ 'eq',
14
+ 'neq',
15
+ 'contains',
16
+ 'startsWith',
17
+ 'in',
18
+ 'notIn',
19
+ 'gt',
20
+ 'gte',
21
+ 'lt',
22
+ 'lte',
23
+ 'isNull',
24
+ 'isNotNull',
25
+ ]);
26
+ const SEARCH_GROUP_OPERATORS = new Set(['AND', 'OR']);
27
+ const MAX_SEARCH_GROUPS = 20;
28
+ const MAX_SEARCH_CONDITIONS = 50;
29
+ const MAX_SEARCH_DEPTH = 4;
30
+ const MAX_PAGE = 10000;
31
+ const MAX_PAGE_SIZE = 100;
32
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
33
+ const ALERT_SOURCE_PATTERN = /^[a-zA-Z0-9:\-_]+$/;
34
+ const SEVERITY_VALUES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];
35
+ const TLP_VALUES = ['RED', 'AMBER_STRICT', 'AMBER', 'GREEN', 'CLEAR'];
36
+ const THREAT_LEVEL_VALUES = ['MALICIOUS', 'SUSPICIOUS', 'UNKNOWN', 'BENIGN'];
37
+ const CRITICALITY_VALUES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
38
+ const RESOURCE_DEFINITION_NODE = {
39
+ id: 'hustleops-resource-definitions',
40
+ name: 'HustleOps',
41
+ type: 'hustleOps',
42
+ typeVersion: 1,
43
+ position: [0, 0],
44
+ parameters: {},
45
+ };
46
+ const alertSearchFields = [
47
+ 'seq',
48
+ 'name',
49
+ 'description',
50
+ 'summary',
51
+ 'severity',
52
+ 'tlp',
53
+ 'source',
54
+ 'type',
55
+ 'sourceRef',
56
+ 'alertRefUrl',
57
+ 'status',
58
+ 'stage',
59
+ 'detectedAt',
60
+ 'closedAt',
61
+ 'closedById',
62
+ 'assigneeId',
63
+ 'incidentId',
64
+ 'createdById',
65
+ 'startedAt',
66
+ 'startedById',
67
+ 'createdAt',
68
+ 'updatedAt',
69
+ 'tags',
70
+ ];
71
+ const incidentSearchFields = [
72
+ 'seq',
73
+ 'name',
74
+ 'description',
75
+ 'summary',
76
+ 'severity',
77
+ 'tlp',
78
+ 'status',
79
+ 'stage',
80
+ 'category',
81
+ 'closedAt',
82
+ 'closedById',
83
+ 'assigneeId',
84
+ 'createdById',
85
+ 'startedAt',
86
+ 'startedById',
87
+ 'detectedAt',
88
+ 'createdAt',
89
+ 'updatedAt',
90
+ 'tags',
91
+ ];
92
+ const observableSearchFields = [
93
+ 'seq',
94
+ 'value',
95
+ 'description',
96
+ 'type',
97
+ 'threatLevel',
98
+ 'tlp',
99
+ 'criticality',
100
+ 'firstSeen',
101
+ 'lastSeen',
102
+ 'createdById',
103
+ 'createdAt',
104
+ 'updatedAt',
105
+ 'tags',
106
+ ];
107
+ const knowledgeSearchFields = [
108
+ 'seq',
109
+ 'value',
110
+ 'description',
111
+ 'type',
112
+ 'tlp',
113
+ 'createdById',
114
+ 'updatedById',
115
+ 'createdAt',
116
+ 'updatedAt',
117
+ 'tags',
118
+ ];
119
+ exports.CORE_RESOURCE_DEFINITIONS = {
120
+ alert: {
121
+ resource: 'alert',
122
+ displayName: 'Alert',
123
+ description: 'Security alert created by a source system or analyst',
124
+ entityType: 'ALERT',
125
+ path: '/alerts',
126
+ defaultSortBy: 'detectedAt',
127
+ defaultSortOrder: 'desc',
128
+ searchFields: alertSearchFields,
129
+ sortFields: [...alertSearchFields, 'displayId'],
130
+ createFields: [
131
+ 'name',
132
+ 'description',
133
+ 'severity',
134
+ 'tlp',
135
+ 'source',
136
+ 'type',
137
+ 'sourceRef',
138
+ 'alertRefUrl',
139
+ 'status',
140
+ 'detectedAt',
141
+ 'incidentId',
142
+ 'assigneeId',
143
+ 'tags',
144
+ ],
145
+ updateFields: [
146
+ 'name',
147
+ 'description',
148
+ 'severity',
149
+ 'tlp',
150
+ 'source',
151
+ 'type',
152
+ 'sourceRef',
153
+ 'alertRefUrl',
154
+ 'status',
155
+ 'detectedAt',
156
+ 'incidentId',
157
+ 'assigneeId',
158
+ 'summary',
159
+ 'version',
160
+ ],
161
+ requiredCreateFields: [
162
+ 'name',
163
+ 'description',
164
+ 'severity',
165
+ 'tlp',
166
+ 'source',
167
+ 'type',
168
+ 'sourceRef',
169
+ 'detectedAt',
170
+ ],
171
+ fieldSpecs: {
172
+ name: { name: 'name', type: 'string', requiredForCreate: true, maxLength: 200 },
173
+ description: {
174
+ name: 'description',
175
+ type: 'string',
176
+ requiredForCreate: true,
177
+ maxLength: 15000,
178
+ },
179
+ severity: {
180
+ name: 'severity',
181
+ type: 'enum',
182
+ requiredForCreate: true,
183
+ allowedValues: SEVERITY_VALUES,
184
+ },
185
+ tlp: {
186
+ name: 'tlp',
187
+ type: 'enum',
188
+ requiredForCreate: true,
189
+ allowedValues: TLP_VALUES,
190
+ },
191
+ source: {
192
+ name: 'source',
193
+ type: 'string',
194
+ requiredForCreate: true,
195
+ nonEmpty: true,
196
+ maxLength: 50,
197
+ pattern: ALERT_SOURCE_PATTERN,
198
+ patternDescription: 'may only contain letters, digits, colons, hyphens, and underscores',
199
+ },
200
+ type: {
201
+ name: 'type',
202
+ type: 'string',
203
+ requiredForCreate: true,
204
+ nonEmpty: true,
205
+ picklistDomain: 'alertType',
206
+ description: 'Must match an active alertType picklist value in HustleOps, such as authentication, endpoint, email, network, dns, user_activity, or other on a default instance.',
207
+ },
208
+ sourceRef: {
209
+ name: 'sourceRef',
210
+ type: 'string',
211
+ requiredForCreate: true,
212
+ nonEmpty: true,
213
+ maxLength: 255,
214
+ },
215
+ alertRefUrl: { name: 'alertRefUrl', type: 'url', maxLength: 2048 },
216
+ status: {
217
+ name: 'status',
218
+ type: 'string',
219
+ nonEmpty: true,
220
+ picklistDomain: 'alertStatus',
221
+ description: 'Must match an active alertStatus picklist value in HustleOps when provided.',
222
+ },
223
+ detectedAt: { name: 'detectedAt', type: 'date-time', requiredForCreate: true },
224
+ incidentId: { name: 'incidentId', type: 'uuid' },
225
+ assigneeId: { name: 'assigneeId', type: 'uuid' },
226
+ summary: { name: 'summary', type: 'string' },
227
+ version: { name: 'version', type: 'number' },
228
+ tags: { name: 'tags', type: 'tags' },
229
+ },
230
+ },
231
+ incident: {
232
+ resource: 'incident',
233
+ displayName: 'Incident',
234
+ description: 'Incident case that groups investigation and response work',
235
+ entityType: 'INCIDENT',
236
+ path: '/incidents',
237
+ defaultSortBy: 'createdAt',
238
+ defaultSortOrder: 'desc',
239
+ searchFields: incidentSearchFields,
240
+ sortFields: [...incidentSearchFields, 'displayId'],
241
+ createFields: [
242
+ 'name',
243
+ 'description',
244
+ 'severity',
245
+ 'tlp',
246
+ 'status',
247
+ 'category',
248
+ 'assigneeId',
249
+ 'tags',
250
+ ],
251
+ updateFields: [
252
+ 'name',
253
+ 'description',
254
+ 'severity',
255
+ 'tlp',
256
+ 'status',
257
+ 'category',
258
+ 'assigneeId',
259
+ 'detectedAt',
260
+ 'closedAt',
261
+ 'summary',
262
+ 'version',
263
+ ],
264
+ requiredCreateFields: ['name', 'description', 'severity', 'tlp', 'category'],
265
+ fieldSpecs: {
266
+ name: { name: 'name', type: 'string', requiredForCreate: true, maxLength: 200 },
267
+ description: {
268
+ name: 'description',
269
+ type: 'string',
270
+ requiredForCreate: true,
271
+ maxLength: 15000,
272
+ },
273
+ severity: {
274
+ name: 'severity',
275
+ type: 'enum',
276
+ requiredForCreate: true,
277
+ allowedValues: SEVERITY_VALUES,
278
+ },
279
+ tlp: {
280
+ name: 'tlp',
281
+ type: 'enum',
282
+ requiredForCreate: true,
283
+ allowedValues: TLP_VALUES,
284
+ },
285
+ status: {
286
+ name: 'status',
287
+ type: 'string',
288
+ nonEmpty: true,
289
+ picklistDomain: 'incidentStatus',
290
+ description: 'Must match an active incidentStatus picklist value in HustleOps when provided.',
291
+ },
292
+ category: {
293
+ name: 'category',
294
+ type: 'string',
295
+ requiredForCreate: true,
296
+ nonEmpty: true,
297
+ picklistDomain: 'incidentCategory',
298
+ description: 'Must match an active incidentCategory picklist value in HustleOps.',
299
+ },
300
+ assigneeId: { name: 'assigneeId', type: 'uuid' },
301
+ detectedAt: { name: 'detectedAt', type: 'date-time' },
302
+ closedAt: { name: 'closedAt', type: 'date-time' },
303
+ summary: { name: 'summary', type: 'string' },
304
+ version: { name: 'version', type: 'number' },
305
+ tags: { name: 'tags', type: 'tags' },
306
+ },
307
+ },
308
+ observable: {
309
+ resource: 'observable',
310
+ displayName: 'Observable',
311
+ description: 'Indicator or artifact observed during detection or investigation',
312
+ entityType: 'OBSERVABLE',
313
+ path: '/observables',
314
+ defaultSortBy: 'lastSeen',
315
+ defaultSortOrder: 'desc',
316
+ searchFields: observableSearchFields,
317
+ sortFields: [...observableSearchFields, 'displayId'],
318
+ createFields: [
319
+ 'value',
320
+ 'description',
321
+ 'type',
322
+ 'threatLevel',
323
+ 'tlp',
324
+ 'criticality',
325
+ 'firstSeen',
326
+ 'lastSeen',
327
+ 'tags',
328
+ ],
329
+ updateFields: [
330
+ 'value',
331
+ 'description',
332
+ 'type',
333
+ 'threatLevel',
334
+ 'tlp',
335
+ 'criticality',
336
+ 'firstSeen',
337
+ 'lastSeen',
338
+ 'version',
339
+ ],
340
+ requiredCreateFields: ['value', 'type', 'threatLevel', 'tlp', 'firstSeen', 'lastSeen'],
341
+ fieldSpecs: {
342
+ value: { name: 'value', type: 'string', requiredForCreate: true, maxLength: 2048 },
343
+ description: { name: 'description', type: 'string', maxLength: 5000 },
344
+ type: {
345
+ name: 'type',
346
+ type: 'string',
347
+ requiredForCreate: true,
348
+ nonEmpty: true,
349
+ picklistDomain: 'observableType',
350
+ description: 'Must match an active observableType picklist value in HustleOps.',
351
+ },
352
+ threatLevel: {
353
+ name: 'threatLevel',
354
+ type: 'enum',
355
+ requiredForCreate: true,
356
+ allowedValues: THREAT_LEVEL_VALUES,
357
+ picklistDomain: 'threatLevel',
358
+ picklistValueTransform: 'uppercase',
359
+ },
360
+ tlp: {
361
+ name: 'tlp',
362
+ type: 'enum',
363
+ requiredForCreate: true,
364
+ allowedValues: TLP_VALUES,
365
+ },
366
+ criticality: {
367
+ name: 'criticality',
368
+ type: 'enum',
369
+ allowedValues: CRITICALITY_VALUES,
370
+ picklistDomain: 'criticality',
371
+ picklistValueTransform: 'uppercase',
372
+ },
373
+ firstSeen: { name: 'firstSeen', type: 'date-time', requiredForCreate: true },
374
+ lastSeen: { name: 'lastSeen', type: 'date-time', requiredForCreate: true },
375
+ version: { name: 'version', type: 'number' },
376
+ tags: { name: 'tags', type: 'tags' },
377
+ },
378
+ },
379
+ knowledge: {
380
+ resource: 'knowledge',
381
+ displayName: 'Knowledge',
382
+ description: 'Reusable knowledge, runbook, or note entry',
383
+ entityType: 'KNOWLEDGE',
384
+ path: '/knowledge',
385
+ defaultSortBy: 'createdAt',
386
+ defaultSortOrder: 'desc',
387
+ searchFields: knowledgeSearchFields,
388
+ sortFields: [...knowledgeSearchFields, 'displayId'],
389
+ createFields: ['value', 'description', 'type', 'tlp', 'tags'],
390
+ updateFields: ['value', 'description', 'type', 'tlp', 'version'],
391
+ requiredCreateFields: ['value', 'type', 'tlp'],
392
+ fieldSpecs: {
393
+ value: {
394
+ name: 'value',
395
+ type: 'string',
396
+ requiredForCreate: true,
397
+ nonEmpty: true,
398
+ maxLength: 500,
399
+ },
400
+ description: { name: 'description', type: 'string', maxLength: 15000 },
401
+ type: {
402
+ name: 'type',
403
+ type: 'string',
404
+ requiredForCreate: true,
405
+ nonEmpty: true,
406
+ picklistDomain: 'knowledgeType',
407
+ description: 'Must match an active knowledgeType picklist value in HustleOps.',
408
+ },
409
+ tlp: {
410
+ name: 'tlp',
411
+ type: 'enum',
412
+ requiredForCreate: true,
413
+ allowedValues: TLP_VALUES,
414
+ },
415
+ version: { name: 'version', type: 'number' },
416
+ tags: { name: 'tags', type: 'tags' },
417
+ },
418
+ },
419
+ };
420
+ exports.CORE_RESOURCE_OPTIONS = Object.values(exports.CORE_RESOURCE_DEFINITIONS).map((definition) => ({
421
+ name: definition.displayName,
422
+ value: definition.resource,
423
+ description: definition.description,
424
+ }));
425
+ function assertSupportedFields(definition, operationLabel, body, allowedFields) {
426
+ for (const field of Object.keys(body)) {
427
+ if (!allowedFields.includes(field)) {
428
+ throw new Error(`Unsupported ${definition.displayName} ${operationLabel} field: ${field}`);
429
+ }
430
+ }
431
+ }
432
+ function validateDtoField(definition, field, value) {
433
+ var _a;
434
+ const spec = definition.fieldSpecs[field];
435
+ if (!spec || value === undefined || value === null) {
436
+ return;
437
+ }
438
+ if (spec.type === 'string' && typeof value !== 'string') {
439
+ throw new Error(`${definition.displayName} field ${field} must be a string.`);
440
+ }
441
+ if (spec.type === 'number' && typeof value !== 'number') {
442
+ throw new Error(`${definition.displayName} field ${field} must be a number.`);
443
+ }
444
+ if (spec.type === 'uuid' && (typeof value !== 'string' || !UUID_PATTERN.test(value))) {
445
+ throw new Error(`${definition.displayName} field ${field} must be a UUID.`);
446
+ }
447
+ if (spec.type === 'date-time' && (typeof value !== 'string' || Number.isNaN(Date.parse(value)))) {
448
+ throw new Error(`${definition.displayName} field ${field} must be an ISO date-time string.`);
449
+ }
450
+ if (spec.type === 'url' && typeof value !== 'string') {
451
+ throw new Error(`${definition.displayName} field ${field} must be a string.`);
452
+ }
453
+ if (spec.type === 'url') {
454
+ try {
455
+ const url = new URL(value);
456
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
457
+ throw new Error('Unsupported protocol.');
458
+ }
459
+ }
460
+ catch {
461
+ throw new n8n_workflow_1.NodeOperationError(RESOURCE_DEFINITION_NODE, `${definition.displayName} field ${field} must be a valid HTTP or HTTPS URL.`);
462
+ }
463
+ }
464
+ if (spec.type === 'enum' && spec.allowedValues && !spec.allowedValues.includes(value)) {
465
+ throw new Error(`${definition.displayName} field ${field} must be one of: ${spec.allowedValues.join(', ')}.`);
466
+ }
467
+ if (spec.type === 'tags' &&
468
+ (!Array.isArray(value) || value.some((tag) => typeof tag !== 'string'))) {
469
+ throw new Error(`${definition.displayName} field ${field} must be an array of tag names.`);
470
+ }
471
+ if (typeof value !== 'string') {
472
+ return;
473
+ }
474
+ if (spec.nonEmpty && value === '') {
475
+ throw new Error(`${definition.displayName} field ${field} cannot be empty when provided.`);
476
+ }
477
+ if (spec.maxLength !== undefined && value.length > spec.maxLength) {
478
+ throw new Error(`${definition.displayName} field ${field} cannot exceed ${spec.maxLength} characters.`);
479
+ }
480
+ if (spec.pattern && !spec.pattern.test(value)) {
481
+ throw new Error(`${definition.displayName} field ${field} ${(_a = spec.patternDescription) !== null && _a !== void 0 ? _a : 'has an invalid format'}.`);
482
+ }
483
+ }
484
+ function assertRequiredCreateFields(definition, body) {
485
+ for (const field of definition.requiredCreateFields) {
486
+ if (body[field] === undefined || body[field] === null || body[field] === '') {
487
+ throw new Error(`Missing required ${definition.displayName} create field: ${field}`);
488
+ }
489
+ }
490
+ }
491
+ function getCoreResourceDefinition(resource) {
492
+ const definition = exports.CORE_RESOURCE_DEFINITIONS[resource];
493
+ if (!definition) {
494
+ throw new Error(`Unsupported HustleOps resource: ${resource}`);
495
+ }
496
+ return definition;
497
+ }
498
+ function sanitizeDtoBody(definition, operation, body) {
499
+ const allowedFields = operation === 'create' ? definition.createFields : definition.updateFields;
500
+ assertSupportedFields(definition, operation, body, allowedFields);
501
+ const compacted = (0, GenericFunctions_1.compactObject)(body);
502
+ if (operation === 'create') {
503
+ assertRequiredCreateFields(definition, compacted);
504
+ }
505
+ else if (Object.keys(compacted).length === 0) {
506
+ throw new Error(`${definition.displayName} update body must include at least one supported field.`);
507
+ }
508
+ for (const [field, value] of Object.entries(compacted)) {
509
+ validateDtoField(definition, field, value);
510
+ }
511
+ return compacted;
512
+ }
513
+ function assertAllowedKeys(definition, label, value, allowedKeys) {
514
+ for (const key of Object.keys(value)) {
515
+ if (!allowedKeys.has(key)) {
516
+ throw new Error(`Unsupported ${definition.displayName} ${label} field: ${key}`);
517
+ }
518
+ }
519
+ }
520
+ function validateExcludeIds(definition, value) {
521
+ if (value === undefined) {
522
+ return;
523
+ }
524
+ if (!Array.isArray(value) ||
525
+ value.some((id) => typeof id !== 'string' || !UUID_PATTERN.test(id))) {
526
+ throw new Error(`${definition.displayName} search excludeIds must contain valid UUIDs.`);
527
+ }
528
+ }
529
+ function validateSearchFilter(definition, value, depth = 0, state = { groups: 0, conditions: 0 }) {
530
+ if (value === undefined || value === null) {
531
+ return;
532
+ }
533
+ if (depth > MAX_SEARCH_DEPTH) {
534
+ throw new Error(`${definition.displayName} search filter is nested too deeply.`);
535
+ }
536
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
537
+ throw new Error(`${definition.displayName} search filter must be an object.`);
538
+ }
539
+ const objectValue = value;
540
+ if (objectValue.operator !== undefined &&
541
+ !SEARCH_GROUP_OPERATORS.has(objectValue.operator)) {
542
+ throw new Error(`${definition.displayName} search filter operator must be AND or OR.`);
543
+ }
544
+ if (objectValue.groups !== undefined && !Array.isArray(objectValue.groups)) {
545
+ throw new Error(`${definition.displayName} search filter groups must be an array.`);
546
+ }
547
+ if (objectValue.conditions !== undefined && !Array.isArray(objectValue.conditions)) {
548
+ throw new Error(`${definition.displayName} search filter conditions must be an array.`);
549
+ }
550
+ const groups = Array.isArray(objectValue.groups) ? objectValue.groups : [];
551
+ if (depth === 0 && groups.length === 0) {
552
+ throw new Error(`${definition.displayName} search filter must contain at least one group.`);
553
+ }
554
+ state.groups += groups.length;
555
+ if (state.groups > MAX_SEARCH_GROUPS) {
556
+ throw new Error(`${definition.displayName} search filter cannot contain more than ${MAX_SEARCH_GROUPS} groups.`);
557
+ }
558
+ const conditions = Array.isArray(objectValue.conditions) ? objectValue.conditions : [];
559
+ if (depth > 0 && conditions.length === 0 && groups.length === 0) {
560
+ throw new Error(`${definition.displayName} search filter groups must contain at least one condition.`);
561
+ }
562
+ state.conditions += conditions.length;
563
+ if (state.conditions > MAX_SEARCH_CONDITIONS) {
564
+ throw new Error(`${definition.displayName} search filter cannot contain more than ${MAX_SEARCH_CONDITIONS} conditions.`);
565
+ }
566
+ for (const condition of conditions) {
567
+ if (!condition || typeof condition !== 'object' || Array.isArray(condition)) {
568
+ throw new Error(`${definition.displayName} search condition must be an object.`);
569
+ }
570
+ const conditionObject = condition;
571
+ if (typeof conditionObject.field !== 'string' ||
572
+ !definition.searchFields.includes(conditionObject.field)) {
573
+ throw new Error(`Unsupported ${definition.displayName} search field: ${conditionObject.field}`);
574
+ }
575
+ if (!SEARCH_OPERATORS.has(conditionObject.operator)) {
576
+ throw new Error(`Unsupported ${definition.displayName} search operator: ${conditionObject.operator}`);
577
+ }
578
+ if ((conditionObject.operator === 'in' || conditionObject.operator === 'notIn') &&
579
+ !Array.isArray(conditionObject.value)) {
580
+ throw new Error(`${definition.displayName} search operator ${conditionObject.operator} requires an array value.`);
581
+ }
582
+ if (conditionObject.operator !== 'isNull' &&
583
+ conditionObject.operator !== 'isNotNull' &&
584
+ !Object.prototype.hasOwnProperty.call(conditionObject, 'value')) {
585
+ throw new Error(`${definition.displayName} search condition value is required for operator ${conditionObject.operator}.`);
586
+ }
587
+ }
588
+ for (const group of groups) {
589
+ validateSearchFilter(definition, group, depth + 1, state);
590
+ }
591
+ }
592
+ function buildGenericSearchRequest(definition, input) {
593
+ var _a;
594
+ assertAllowedKeys(definition, 'search request', input, SEARCH_REQUEST_FIELDS);
595
+ const pagination = ((_a = input.pagination) !== null && _a !== void 0 ? _a : {});
596
+ assertAllowedKeys(definition, 'search pagination', pagination, SEARCH_PAGINATION_FIELDS);
597
+ const sortBy = typeof pagination.sortBy === 'string' && pagination.sortBy !== ''
598
+ ? pagination.sortBy
599
+ : definition.defaultSortBy;
600
+ const sortOrder = pagination.sortOrder === 'asc' || pagination.sortOrder === 'desc'
601
+ ? pagination.sortOrder
602
+ : definition.defaultSortOrder;
603
+ if (!definition.sortFields.includes(sortBy)) {
604
+ throw new Error(`Unsupported ${definition.displayName} search sort field: ${sortBy}`);
605
+ }
606
+ const page = typeof pagination.page === 'number' ? pagination.page : 1;
607
+ const pageSize = typeof pagination.pageSize === 'number' ? pagination.pageSize : 25;
608
+ if (!Number.isInteger(page) || page < 1 || page > MAX_PAGE) {
609
+ throw new Error(`${definition.displayName} search pagination.page must be between 1 and ${MAX_PAGE}.`);
610
+ }
611
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > MAX_PAGE_SIZE) {
612
+ throw new Error(`${definition.displayName} search pagination.pageSize must be between 1 and ${MAX_PAGE_SIZE}.`);
613
+ }
614
+ validateSearchFilter(definition, input.filter);
615
+ validateExcludeIds(definition, input.excludeIds);
616
+ return (0, GenericFunctions_1.compactObject)({
617
+ ...input,
618
+ pagination: {
619
+ ...pagination,
620
+ page,
621
+ pageSize,
622
+ sortBy,
623
+ sortOrder,
624
+ },
625
+ });
626
+ }
627
+ function buildSearchRequest(definition, input) {
628
+ return buildGenericSearchRequest(definition, input);
629
+ }
@@ -0,0 +1,7 @@
1
+ import type { CoreResource, DtoOperation } from './resourceDefinitions';
2
+ export declare const CORE_WRITE_OPERATIONS: DtoOperation[];
3
+ export declare function toPascalCase(value: string): string;
4
+ export declare function fieldDisplayName(field: string): string;
5
+ export declare function structuredFieldParameterName(resource: CoreResource, operation: DtoOperation, field: string): string;
6
+ export declare function createAdditionalFieldsParameterName(resource: CoreResource): string;
7
+ export declare function updateFieldsParameterName(resource: CoreResource): string;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CORE_WRITE_OPERATIONS = void 0;
4
+ exports.toPascalCase = toPascalCase;
5
+ exports.fieldDisplayName = fieldDisplayName;
6
+ exports.structuredFieldParameterName = structuredFieldParameterName;
7
+ exports.createAdditionalFieldsParameterName = createAdditionalFieldsParameterName;
8
+ exports.updateFieldsParameterName = updateFieldsParameterName;
9
+ exports.CORE_WRITE_OPERATIONS = ['create', 'update'];
10
+ function toPascalCase(value) {
11
+ return value.replace(/(^|[-_\s]+)([a-z0-9])/g, (_match, _separator, character) => character.toUpperCase());
12
+ }
13
+ function fieldDisplayName(field) {
14
+ return field
15
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
16
+ .replace(/\b\w/g, (character) => character.toUpperCase())
17
+ .replace(/\bId\b/g, 'ID')
18
+ .replace(/\bUrl\b/g, 'URL')
19
+ .replace(/\bTlp\b/g, 'TLP');
20
+ }
21
+ function structuredFieldParameterName(resource, operation, field) {
22
+ return `${resource}${toPascalCase(operation)}${toPascalCase(field)}`;
23
+ }
24
+ function createAdditionalFieldsParameterName(resource) {
25
+ return `${resource}CreateAdditionalFields`;
26
+ }
27
+ function updateFieldsParameterName(resource) {
28
+ return `${resource}UpdateFields`;
29
+ }
@@ -0,0 +1,15 @@
1
+ import type { IDataObject, IExecuteFunctions, INodePropertyOptions } from 'n8n-workflow';
2
+ export type TagOperation = 'list' | 'search' | 'create' | 'updateColor' | 'bulkUpdateColor' | 'delete' | 'bulkDelete';
3
+ export type EntityTagOperation = 'setTags' | 'addTags' | 'removeTag';
4
+ type ValidationContext = Pick<IExecuteFunctions, 'getNode'>;
5
+ export declare const TAG_RESOURCE_OPTION: INodePropertyOptions;
6
+ export declare const TAG_OPERATION_OPTIONS: INodePropertyOptions[];
7
+ export declare const ENTITY_TAG_OPERATION_OPTIONS: INodePropertyOptions[];
8
+ type ParseTagValuesOptions = {
9
+ allowEmpty?: boolean;
10
+ emptyMessage?: string;
11
+ };
12
+ export declare function parseTagValues(context: ValidationContext, value: unknown, label: string, itemIndex: number, options?: ParseTagValuesOptions): string[];
13
+ export declare function parseEntityTagValues(context: ValidationContext, value: unknown, operationLabel: 'Set Tags' | 'Add Tags', itemIndex: number): string[];
14
+ export declare function sanitizeTagBody(context: ValidationContext, operation: TagOperation, body: IDataObject, itemIndex: number): IDataObject;
15
+ export {};