@awesomate/hosting-mcp 0.12.0 → 0.13.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 (58) hide show
  1. package/dist/index.js +204 -8
  2. package/package.json +2 -2
  3. package/skill/awesomate-app-builder/SKILL.md +5 -1
  4. package/skill/awesomate-credentials/SKILL.md +1 -1
  5. package/skill/awesomate-hosting/SKILL.md +50 -159
  6. package/skill/awesomate-hosting/references/connect-troubleshooting.md +77 -0
  7. package/skill/awesomate-hosting/references/multi-account.md +33 -0
  8. package/skill/awesomate-hosting/references/rest-fallback.md +30 -0
  9. package/skill/awesomate-hosting/scripts/bootstrap.mjs +27 -7
  10. package/skill/awesomate-n8n/SKILL.md +153 -147
  11. package/skill/awesomate-n8n/evals/ai-agent-build/graders/grader.md +31 -0
  12. package/skill/awesomate-n8n/evals/ai-agent-build/prompt.md +1 -0
  13. package/skill/awesomate-n8n/evals/build-form-email/graders/grader.md +30 -0
  14. package/skill/awesomate-n8n/evals/build-form-email/prompt.md +1 -0
  15. package/skill/awesomate-n8n/evals/datatable-dedupe/graders/grader.md +32 -0
  16. package/skill/awesomate-n8n/evals/datatable-dedupe/prompt.md +2 -0
  17. package/skill/awesomate-n8n/evals/diagnose-failure/graders/grader.md +27 -0
  18. package/skill/awesomate-n8n/evals/diagnose-failure/prompt.md +2 -0
  19. package/skill/awesomate-n8n/evals/essentials-upsell/graders/grader.md +27 -0
  20. package/skill/awesomate-n8n/evals/essentials-upsell/prompt.md +2 -0
  21. package/skill/awesomate-n8n/evals/live-change-promote/graders/grader.md +35 -0
  22. package/skill/awesomate-n8n/evals/live-change-promote/prompt.md +2 -0
  23. package/skill/awesomate-n8n/evals/possibilities-grounded/graders/grader.md +28 -0
  24. package/skill/awesomate-n8n/evals/possibilities-grounded/prompt.md +1 -0
  25. package/skill/awesomate-n8n/evals/validated-not-done/graders/grader.md +27 -0
  26. package/skill/awesomate-n8n/evals/validated-not-done/prompt.md +2 -0
  27. package/skill/awesomate-n8n/evals/vars-not-env/graders/grader.md +26 -0
  28. package/skill/awesomate-n8n/evals/vars-not-env/prompt.md +2 -0
  29. package/skill/awesomate-n8n/evals/webhook-body-fix/graders/grader.md +26 -0
  30. package/skill/awesomate-n8n/evals/webhook-body-fix/prompt.md +3 -0
  31. package/skill/awesomate-n8n/references/ai-agents.md +135 -0
  32. package/skill/awesomate-n8n/references/datatables.md +105 -0
  33. package/skill/awesomate-n8n/references/{node-recipes.md → platform-notes.md} +56 -7
  34. package/skill/awesomate-n8n/references/possibilities.md +83 -0
  35. package/skill/awesomate-n8n/references/testing-policy.md +115 -0
  36. package/skill/awesomate-n8n/references/troubleshooting.md +69 -0
  37. package/skill/awesomate-n8n/references/upgrade-loop.md +98 -0
  38. package/skill/awesomate-n8n/references/vendor/MANIFEST.json +26 -0
  39. package/skill/awesomate-n8n/references/vendor/code-node/BUILTIN_FUNCTIONS.md +779 -0
  40. package/skill/awesomate-n8n/references/vendor/code-node/COMMON_PATTERNS.md +1123 -0
  41. package/skill/awesomate-n8n/references/vendor/code-node/DATA_ACCESS.md +797 -0
  42. package/skill/awesomate-n8n/references/vendor/code-node/ERROR_PATTERNS.md +776 -0
  43. package/skill/awesomate-n8n/references/vendor/code-node/SKILL.md +703 -0
  44. package/skill/awesomate-n8n/references/vendor/expressions/COMMON_MISTAKES.md +406 -0
  45. package/skill/awesomate-n8n/references/vendor/expressions/EXAMPLES.md +496 -0
  46. package/skill/awesomate-n8n/references/vendor/expressions/SKILL.md +525 -0
  47. package/skill/awesomate-n8n/references/vendor/node-configuration/DEPENDENCIES.md +743 -0
  48. package/skill/awesomate-n8n/references/vendor/node-configuration/OPERATION_PATTERNS.md +926 -0
  49. package/skill/awesomate-n8n/references/vendor/node-configuration/SKILL.md +583 -0
  50. package/skill/awesomate-n8n/references/vendor/validation/ERROR_CATALOG.md +781 -0
  51. package/skill/awesomate-n8n/references/vendor/validation/FALSE_POSITIVES.md +695 -0
  52. package/skill/awesomate-n8n/references/vendor/validation/SKILL.md +414 -0
  53. package/skill/awesomate-n8n/references/vendor/workflow-patterns/SKILL.md +413 -0
  54. package/skill/awesomate-n8n/references/vendor/workflow-patterns/ai_agent_workflow.md +797 -0
  55. package/skill/awesomate-n8n/references/vendor/workflow-patterns/database_operations.md +798 -0
  56. package/skill/awesomate-n8n/references/vendor/workflow-patterns/http_api_integration.md +747 -0
  57. package/skill/awesomate-n8n/references/vendor/workflow-patterns/scheduled_tasks.md +786 -0
  58. package/skill/awesomate-n8n/references/vendor/workflow-patterns/webhook_processing.md +558 -0
@@ -0,0 +1,1123 @@
1
+ <!--
2
+ VENDORED from n8n-builder@d293559 (n8n-code-javascript/COMMON_PATTERNS.md) — DO NOT EDIT HERE.
3
+ Edit the source in ~/Projects/n8n-builder, then re-run:
4
+ node mcp/scripts/sync-n8n-references.mjs --write
5
+
6
+ Awesomate platform overrides — where this file conflicts with
7
+ ../platform-notes.md, platform-notes wins:
8
+ - $env is BLOCKED fleet-wide → use {{ $vars.key || 'fallback' }}
9
+ - Task runners are ON → no $helpers in Code nodes; use HTTP Request nodes
10
+ - Webhook payloads live at $json.body
11
+ - saveExecutionProgress must stay false
12
+ -->
13
+
14
+ # Common Patterns - JavaScript Code Node
15
+
16
+ Production-tested patterns for n8n Code nodes. These patterns are proven in real workflows.
17
+
18
+ ---
19
+
20
+ ## Overview
21
+
22
+ This guide covers the 10 most useful Code node patterns for n8n workflows. Each pattern includes:
23
+ - **Use Case**: When to use this pattern
24
+ - **Key Techniques**: Important coding techniques demonstrated
25
+ - **Complete Example**: Working code you can adapt
26
+ - **Variations**: Common modifications
27
+
28
+ **Pattern Categories:**
29
+ - Data Aggregation (Patterns 1, 5, 10)
30
+ - Content Processing (Patterns 2, 3)
31
+ - Data Validation & Comparison (Patterns 4)
32
+ - Data Transformation (Patterns 5, 6, 7)
33
+ - Output Formatting (Pattern 8)
34
+ - Filtering & Ranking (Pattern 9)
35
+
36
+ ---
37
+
38
+ ## Pattern 1: Multi-Source Data Aggregation
39
+
40
+ **Use Case**: Combining data from multiple APIs, RSS feeds, webhooks, or databases
41
+
42
+ **When to use:**
43
+ - Collecting data from multiple services
44
+ - Normalizing different API response formats
45
+ - Merging data sources into unified structure
46
+ - Building aggregated reports
47
+
48
+ **Key Techniques**: Loop iteration, conditional parsing, data normalization
49
+
50
+ ### Complete Example
51
+
52
+ ```javascript
53
+ // Process and structure data collected from multiple sources
54
+ const allItems = $input.all();
55
+ let processedArticles = [];
56
+
57
+ // Handle different source formats
58
+ for (const item of allItems) {
59
+ const sourceName = item.json.name || 'Unknown';
60
+ const sourceData = item.json;
61
+
62
+ // Parse source-specific structure - Hacker News format
63
+ if (sourceName === 'Hacker News' && sourceData.hits) {
64
+ for (const hit of sourceData.hits) {
65
+ processedArticles.push({
66
+ title: hit.title,
67
+ url: hit.url,
68
+ summary: hit.story_text || 'No summary',
69
+ source: 'Hacker News',
70
+ score: hit.points || 0,
71
+ fetchedAt: new Date().toISOString()
72
+ });
73
+ }
74
+ }
75
+
76
+ // Parse source-specific structure - Reddit format
77
+ else if (sourceName === 'Reddit' && sourceData.data?.children) {
78
+ for (const post of sourceData.data.children) {
79
+ processedArticles.push({
80
+ title: post.data.title,
81
+ url: post.data.url,
82
+ summary: post.data.selftext || 'No summary',
83
+ source: 'Reddit',
84
+ score: post.data.score || 0,
85
+ fetchedAt: new Date().toISOString()
86
+ });
87
+ }
88
+ }
89
+
90
+ // Parse source-specific structure - RSS feed format
91
+ else if (sourceName === 'RSS' && sourceData.items) {
92
+ for (const rssItem of sourceData.items) {
93
+ processedArticles.push({
94
+ title: rssItem.title,
95
+ url: rssItem.link,
96
+ summary: rssItem.description || 'No summary',
97
+ source: 'RSS Feed',
98
+ score: 0,
99
+ fetchedAt: new Date().toISOString()
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ // Sort by score (highest first)
106
+ processedArticles.sort((a, b) => b.score - a.score);
107
+
108
+ return processedArticles.map(article => ({json: article}));
109
+ ```
110
+
111
+ ### Variations
112
+
113
+ ```javascript
114
+ // Variation 1: Add source weighting
115
+ for (const article of processedArticles) {
116
+ const weights = {
117
+ 'Hacker News': 1.5,
118
+ 'Reddit': 1.0,
119
+ 'RSS Feed': 0.8
120
+ };
121
+
122
+ article.weightedScore = article.score * (weights[article.source] || 1.0);
123
+ }
124
+
125
+ // Variation 2: Filter by minimum score
126
+ processedArticles = processedArticles.filter(article => article.score >= 10);
127
+
128
+ // Variation 3: Deduplicate by URL
129
+ const seen = new Set();
130
+ processedArticles = processedArticles.filter(article => {
131
+ if (seen.has(article.url)) {
132
+ return false;
133
+ }
134
+ seen.add(article.url);
135
+ return true;
136
+ });
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Pattern 2: Regex Filtering & Pattern Matching
142
+
143
+ **Use Case**: Content analysis, keyword extraction, mention tracking, text parsing
144
+
145
+ **When to use:**
146
+ - Extracting mentions or tags from text
147
+ - Finding patterns in unstructured data
148
+ - Counting keyword occurrences
149
+ - Validating formats (emails, phone numbers)
150
+
151
+ **Key Techniques**: Regex matching, object aggregation, sorting/ranking
152
+
153
+ ### Complete Example
154
+
155
+ ```javascript
156
+ // Extract and track mentions using regex patterns
157
+ const etfPattern = /\b([A-Z]{2,5})\b/g;
158
+ const knownETFs = ['VOO', 'VTI', 'VT', 'SCHD', 'QYLD', 'VXUS', 'SPY', 'QQQ'];
159
+
160
+ const etfMentions = {};
161
+
162
+ for (const item of $input.all()) {
163
+ const data = item.json.data;
164
+
165
+ // Skip if no data or children
166
+ if (!data?.children) continue;
167
+
168
+ for (const post of data.children) {
169
+ // Combine title and body text
170
+ const title = post.data.title || '';
171
+ const body = post.data.selftext || '';
172
+ const combinedText = (title + ' ' + body).toUpperCase();
173
+
174
+ // Find all matches
175
+ const matches = combinedText.match(etfPattern);
176
+
177
+ if (matches) {
178
+ for (const match of matches) {
179
+ // Only count known ETFs
180
+ if (knownETFs.includes(match)) {
181
+ if (!etfMentions[match]) {
182
+ etfMentions[match] = {
183
+ count: 0,
184
+ totalScore: 0,
185
+ posts: []
186
+ };
187
+ }
188
+
189
+ etfMentions[match].count++;
190
+ etfMentions[match].totalScore += post.data.score || 0;
191
+ etfMentions[match].posts.push({
192
+ title: post.data.title,
193
+ url: post.data.url,
194
+ score: post.data.score
195
+ });
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ // Convert to array and sort by mention count
203
+ return Object.entries(etfMentions)
204
+ .map(([etf, data]) => ({
205
+ json: {
206
+ etf,
207
+ mentions: data.count,
208
+ totalScore: data.totalScore,
209
+ averageScore: data.totalScore / data.count,
210
+ topPosts: data.posts
211
+ .sort((a, b) => b.score - a.score)
212
+ .slice(0, 3)
213
+ }
214
+ }))
215
+ .sort((a, b) => b.json.mentions - a.json.mentions);
216
+ ```
217
+
218
+ ### Variations
219
+
220
+ ```javascript
221
+ // Variation 1: Email extraction
222
+ const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
223
+ const emails = text.match(emailPattern) || [];
224
+
225
+ // Variation 2: Phone number extraction
226
+ const phonePattern = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g;
227
+ const phones = text.match(phonePattern) || [];
228
+
229
+ // Variation 3: Hashtag extraction
230
+ const hashtagPattern = /#(\w+)/g;
231
+ const hashtags = [];
232
+ let match;
233
+ while ((match = hashtagPattern.exec(text)) !== null) {
234
+ hashtags.push(match[1]);
235
+ }
236
+
237
+ // Variation 4: URL extraction
238
+ const urlPattern = /https?:\/\/[^\s]+/g;
239
+ const urls = text.match(urlPattern) || [];
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Pattern 3: Markdown Parsing & Structured Data Extraction
245
+
246
+ **Use Case**: Parsing formatted text, extracting structured fields, content transformation
247
+
248
+ **When to use:**
249
+ - Parsing markdown or HTML
250
+ - Extracting data from structured text
251
+ - Converting formatted content to JSON
252
+ - Processing documentation or articles
253
+
254
+ **Key Techniques**: Regex grouping, helper functions, data normalization, while loops for iteration
255
+
256
+ ### Complete Example
257
+
258
+ ```javascript
259
+ // Parse markdown and extract structured information
260
+ const markdown = $input.first().json.data.markdown;
261
+ const adRegex = /##\s*(.*?)\n(.*?)(?=\n##|\n---|$)/gs;
262
+
263
+ const ads = [];
264
+ let match;
265
+
266
+ // Helper function to parse time strings to minutes
267
+ function parseTimeToMinutes(timeStr) {
268
+ if (!timeStr) return 999999; // Sort unparseable times last
269
+
270
+ const hourMatch = timeStr.match(/(\d+)\s*hour/);
271
+ const dayMatch = timeStr.match(/(\d+)\s*day/);
272
+ const minMatch = timeStr.match(/(\d+)\s*min/);
273
+
274
+ let totalMinutes = 0;
275
+ if (dayMatch) totalMinutes += parseInt(dayMatch[1]) * 1440; // 24 * 60
276
+ if (hourMatch) totalMinutes += parseInt(hourMatch[1]) * 60;
277
+ if (minMatch) totalMinutes += parseInt(minMatch[1]);
278
+
279
+ return totalMinutes;
280
+ }
281
+
282
+ // Extract all job postings from markdown
283
+ while ((match = adRegex.exec(markdown)) !== null) {
284
+ const title = match[1]?.trim() || 'No title';
285
+ const content = match[2]?.trim() || '';
286
+
287
+ // Extract structured fields from content
288
+ const districtMatch = content.match(/\*\*District:\*\*\s*(.*?)(?:\n|$)/);
289
+ const salaryMatch = content.match(/\*\*Salary:\*\*\s*(.*?)(?:\n|$)/);
290
+ const timeMatch = content.match(/Posted:\s*(.*?)\*/);
291
+
292
+ ads.push({
293
+ title: title,
294
+ district: districtMatch?.[1].trim() || 'Unknown',
295
+ salary: salaryMatch?.[1].trim() || 'Not specified',
296
+ postedTimeAgo: timeMatch?.[1] || 'Unknown',
297
+ timeInMinutes: parseTimeToMinutes(timeMatch?.[1]),
298
+ fullContent: content,
299
+ extractedAt: new Date().toISOString()
300
+ });
301
+ }
302
+
303
+ // Sort by recency (posted time)
304
+ ads.sort((a, b) => a.timeInMinutes - b.timeInMinutes);
305
+
306
+ return ads.map(ad => ({json: ad}));
307
+ ```
308
+
309
+ ### Variations
310
+
311
+ ```javascript
312
+ // Variation 1: Parse HTML table to JSON
313
+ const tableRegex = /<tr>(.*?)<\/tr>/gs;
314
+ const cellRegex = /<td>(.*?)<\/td>/g;
315
+
316
+ const rows = [];
317
+ let tableMatch;
318
+
319
+ while ((tableMatch = tableRegex.exec(htmlTable)) !== null) {
320
+ const cells = [];
321
+ let cellMatch;
322
+
323
+ while ((cellMatch = cellRegex.exec(tableMatch[1])) !== null) {
324
+ cells.push(cellMatch[1].trim());
325
+ }
326
+
327
+ if (cells.length > 0) {
328
+ rows.push(cells);
329
+ }
330
+ }
331
+
332
+ // Variation 2: Extract code blocks from markdown
333
+ const codeBlockRegex = /```(\w+)?\n(.*?)```/gs;
334
+ const codeBlocks = [];
335
+
336
+ while ((match = codeBlockRegex.exec(markdown)) !== null) {
337
+ codeBlocks.push({
338
+ language: match[1] || 'plain',
339
+ code: match[2].trim()
340
+ });
341
+ }
342
+
343
+ // Variation 3: Parse YAML frontmatter
344
+ const frontmatterRegex = /^---\n(.*?)\n---/s;
345
+ const frontmatterMatch = content.match(frontmatterRegex);
346
+
347
+ if (frontmatterMatch) {
348
+ const yamlLines = frontmatterMatch[1].split('\n');
349
+ const metadata = {};
350
+
351
+ for (const line of yamlLines) {
352
+ const [key, ...valueParts] = line.split(':');
353
+ if (key && valueParts.length > 0) {
354
+ metadata[key.trim()] = valueParts.join(':').trim();
355
+ }
356
+ }
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Pattern 4: JSON Comparison & Validation
363
+
364
+ **Use Case**: Workflow versioning, configuration validation, change detection, data integrity
365
+
366
+ **When to use:**
367
+ - Comparing two versions of data
368
+ - Detecting changes in configurations
369
+ - Validating data consistency
370
+ - Checking for differences
371
+
372
+ **Key Techniques**: JSON ordering, base64 decoding, deep comparison, object manipulation
373
+
374
+ ### Complete Example
375
+
376
+ ```javascript
377
+ // Compare and validate JSON objects from different sources
378
+ const orderJsonKeys = (jsonObj) => {
379
+ const ordered = {};
380
+ Object.keys(jsonObj).sort().forEach(key => {
381
+ ordered[key] = jsonObj[key];
382
+ });
383
+ return ordered;
384
+ };
385
+
386
+ const allItems = $input.all();
387
+
388
+ // Assume first item is base64-encoded original, second is current
389
+ const origWorkflow = JSON.parse(
390
+ Buffer.from(allItems[0].json.content, 'base64').toString()
391
+ );
392
+ const currentWorkflow = allItems[1].json;
393
+
394
+ // Order keys for consistent comparison
395
+ const orderedOriginal = orderJsonKeys(origWorkflow);
396
+ const orderedCurrent = orderJsonKeys(currentWorkflow);
397
+
398
+ // Deep comparison
399
+ const isSame = JSON.stringify(orderedOriginal) === JSON.stringify(orderedCurrent);
400
+
401
+ // Find differences
402
+ const differences = [];
403
+ for (const key of Object.keys(orderedOriginal)) {
404
+ if (JSON.stringify(orderedOriginal[key]) !== JSON.stringify(orderedCurrent[key])) {
405
+ differences.push({
406
+ field: key,
407
+ original: orderedOriginal[key],
408
+ current: orderedCurrent[key]
409
+ });
410
+ }
411
+ }
412
+
413
+ // Check for new keys
414
+ for (const key of Object.keys(orderedCurrent)) {
415
+ if (!(key in orderedOriginal)) {
416
+ differences.push({
417
+ field: key,
418
+ original: null,
419
+ current: orderedCurrent[key],
420
+ status: 'new'
421
+ });
422
+ }
423
+ }
424
+
425
+ return [{
426
+ json: {
427
+ identical: isSame,
428
+ differenceCount: differences.length,
429
+ differences: differences,
430
+ original: orderedOriginal,
431
+ current: orderedCurrent,
432
+ comparedAt: new Date().toISOString()
433
+ }
434
+ }];
435
+ ```
436
+
437
+ ### Variations
438
+
439
+ ```javascript
440
+ // Variation 1: Simple equality check
441
+ const isEqual = JSON.stringify(obj1) === JSON.stringify(obj2);
442
+
443
+ // Variation 2: Deep diff with detailed changes
444
+ function deepDiff(obj1, obj2, path = '') {
445
+ const changes = [];
446
+
447
+ for (const key in obj1) {
448
+ const currentPath = path ? `${path}.${key}` : key;
449
+
450
+ if (!(key in obj2)) {
451
+ changes.push({type: 'removed', path: currentPath, value: obj1[key]});
452
+ } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
453
+ changes.push(...deepDiff(obj1[key], obj2[key], currentPath));
454
+ } else if (obj1[key] !== obj2[key]) {
455
+ changes.push({
456
+ type: 'modified',
457
+ path: currentPath,
458
+ from: obj1[key],
459
+ to: obj2[key]
460
+ });
461
+ }
462
+ }
463
+
464
+ for (const key in obj2) {
465
+ if (!(key in obj1)) {
466
+ const currentPath = path ? `${path}.${key}` : key;
467
+ changes.push({type: 'added', path: currentPath, value: obj2[key]});
468
+ }
469
+ }
470
+
471
+ return changes;
472
+ }
473
+
474
+ // Variation 3: Schema validation
475
+ function validateSchema(data, schema) {
476
+ const errors = [];
477
+
478
+ for (const field of schema.required || []) {
479
+ if (!(field in data)) {
480
+ errors.push(`Missing required field: ${field}`);
481
+ }
482
+ }
483
+
484
+ for (const [field, type] of Object.entries(schema.types || {})) {
485
+ if (field in data && typeof data[field] !== type) {
486
+ errors.push(`Field ${field} should be ${type}, got ${typeof data[field]}`);
487
+ }
488
+ }
489
+
490
+ return {
491
+ valid: errors.length === 0,
492
+ errors
493
+ };
494
+ }
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Pattern 5: CRM Data Transformation
500
+
501
+ **Use Case**: Lead enrichment, data normalization, API preparation, form data processing
502
+
503
+ **When to use:**
504
+ - Processing form submissions
505
+ - Preparing data for CRM APIs
506
+ - Normalizing contact information
507
+ - Enriching lead data
508
+
509
+ **Key Techniques**: Object destructuring, data mapping, format conversion, field splitting
510
+
511
+ ### Complete Example
512
+
513
+ ```javascript
514
+ // Transform form data into CRM-compatible format
515
+ const item = $input.all()[0];
516
+ const {
517
+ name,
518
+ email,
519
+ phone,
520
+ company,
521
+ course_interest,
522
+ message,
523
+ timestamp
524
+ } = item.json;
525
+
526
+ // Split name into first and last
527
+ const nameParts = name.split(' ');
528
+ const firstName = nameParts[0] || '';
529
+ const lastName = nameParts.slice(1).join(' ') || 'Unknown';
530
+
531
+ // Format phone number
532
+ const cleanPhone = phone.replace(/[^\d]/g, ''); // Remove non-digits
533
+
534
+ // Build CRM data structure
535
+ const crmData = {
536
+ data: {
537
+ type: 'Contact',
538
+ attributes: {
539
+ first_name: firstName,
540
+ last_name: lastName,
541
+ email1: email,
542
+ phone_work: cleanPhone,
543
+ account_name: company,
544
+ description: `Course Interest: ${course_interest}\n\nMessage: ${message}\n\nSubmitted: ${timestamp}`,
545
+ lead_source: 'Website Form',
546
+ status: 'New'
547
+ }
548
+ },
549
+ metadata: {
550
+ original_submission: timestamp,
551
+ processed_at: new Date().toISOString()
552
+ }
553
+ };
554
+
555
+ return [{
556
+ json: {
557
+ ...item.json,
558
+ crmData,
559
+ processed: true
560
+ }
561
+ }];
562
+ ```
563
+
564
+ ### Variations
565
+
566
+ ```javascript
567
+ // Variation 1: Multiple contact processing
568
+ const contacts = $input.all();
569
+
570
+ return contacts.map(item => {
571
+ const data = item.json;
572
+ const [firstName, ...lastNameParts] = data.name.split(' ');
573
+
574
+ return {
575
+ json: {
576
+ firstName,
577
+ lastName: lastNameParts.join(' ') || 'Unknown',
578
+ email: data.email.toLowerCase(),
579
+ phone: data.phone.replace(/[^\d]/g, ''),
580
+ tags: [data.source, data.interest_level].filter(Boolean)
581
+ }
582
+ };
583
+ });
584
+
585
+ // Variation 2: Field validation and normalization
586
+ function normalizePContact(raw) {
587
+ return {
588
+ first_name: raw.firstName?.trim() || '',
589
+ last_name: raw.lastName?.trim() || 'Unknown',
590
+ email: raw.email?.toLowerCase().trim() || '',
591
+ phone: raw.phone?.replace(/[^\d]/g, '') || '',
592
+ company: raw.company?.trim() || 'Unknown',
593
+ title: raw.title?.trim() || '',
594
+ valid: Boolean(raw.email && raw.firstName)
595
+ };
596
+ }
597
+
598
+ // Variation 3: Lead scoring
599
+ function calculateLeadScore(data) {
600
+ let score = 0;
601
+
602
+ if (data.email) score += 10;
603
+ if (data.phone) score += 10;
604
+ if (data.company) score += 15;
605
+ if (data.title?.toLowerCase().includes('director')) score += 20;
606
+ if (data.title?.toLowerCase().includes('manager')) score += 15;
607
+ if (data.message?.length > 100) score += 10;
608
+
609
+ return score;
610
+ }
611
+ ```
612
+
613
+ ---
614
+
615
+ ## Pattern 6: Release Information Processing
616
+
617
+ **Use Case**: Version management, changelog parsing, release notes generation, GitHub API processing
618
+
619
+ **When to use:**
620
+ - Processing GitHub releases
621
+ - Filtering stable versions
622
+ - Generating changelog summaries
623
+ - Extracting version information
624
+
625
+ **Key Techniques**: Array filtering, conditional field extraction, date formatting, string manipulation
626
+
627
+ ### Complete Example
628
+
629
+ ```javascript
630
+ // Extract and filter stable releases from GitHub API
631
+ const allReleases = $input.first().json;
632
+
633
+ const stableReleases = allReleases
634
+ .filter(release => !release.prerelease && !release.draft)
635
+ .slice(0, 10)
636
+ .map(release => {
637
+ // Extract highlights section from changelog
638
+ const body = release.body || '';
639
+ let highlights = 'No highlights available';
640
+
641
+ if (body.includes('## Highlights:')) {
642
+ highlights = body.split('## Highlights:')[1]?.split('##')[0]?.trim();
643
+ } else {
644
+ // Fallback to first 500 chars
645
+ highlights = body.substring(0, 500) + '...';
646
+ }
647
+
648
+ return {
649
+ tag: release.tag_name,
650
+ name: release.name,
651
+ published: release.published_at,
652
+ publishedDate: new Date(release.published_at).toLocaleDateString(),
653
+ author: release.author.login,
654
+ url: release.html_url,
655
+ changelog: body,
656
+ highlights: highlights,
657
+ assetCount: release.assets.length,
658
+ assets: release.assets.map(asset => ({
659
+ name: asset.name,
660
+ size: asset.size,
661
+ downloadCount: asset.download_count,
662
+ downloadUrl: asset.browser_download_url
663
+ }))
664
+ };
665
+ });
666
+
667
+ return stableReleases.map(release => ({json: release}));
668
+ ```
669
+
670
+ ### Variations
671
+
672
+ ```javascript
673
+ // Variation 1: Version comparison
674
+ function compareVersions(v1, v2) {
675
+ const parts1 = v1.replace('v', '').split('.').map(Number);
676
+ const parts2 = v2.replace('v', '').split('.').map(Number);
677
+
678
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
679
+ const num1 = parts1[i] || 0;
680
+ const num2 = parts2[i] || 0;
681
+
682
+ if (num1 > num2) return 1;
683
+ if (num1 < num2) return -1;
684
+ }
685
+
686
+ return 0;
687
+ }
688
+
689
+ // Variation 2: Breaking change detection
690
+ function hasBreakingChanges(changelog) {
691
+ const breakingKeywords = [
692
+ 'BREAKING CHANGE',
693
+ 'breaking change',
694
+ 'BC:',
695
+ '💥'
696
+ ];
697
+
698
+ return breakingKeywords.some(keyword => changelog.includes(keyword));
699
+ }
700
+
701
+ // Variation 3: Extract version numbers
702
+ const versionPattern = /v?(\d+)\.(\d+)\.(\d+)/;
703
+ const match = tagName.match(versionPattern);
704
+
705
+ if (match) {
706
+ const [_, major, minor, patch] = match;
707
+ const version = {major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch)};
708
+ }
709
+ ```
710
+
711
+ ---
712
+
713
+ ## Pattern 7: Array Transformation with Context
714
+
715
+ **Use Case**: Quick data transformation, field mapping, adding computed fields
716
+
717
+ **When to use:**
718
+ - Transforming arrays with additional context
719
+ - Adding calculated fields
720
+ - Simplifying complex objects
721
+ - Pluralization logic
722
+
723
+ **Key Techniques**: Array methods chaining, ternary operators, computed properties
724
+
725
+ ### Complete Example
726
+
727
+ ```javascript
728
+ // Transform releases with contextual information
729
+ const releases = $input.first().json
730
+ .filter(release => !release.prerelease && !release.draft)
731
+ .slice(0, 10)
732
+ .map(release => ({
733
+ version: release.tag_name,
734
+ assetCount: release.assets.length,
735
+ assetsCountText: release.assets.length === 1 ? 'file' : 'files',
736
+ downloadUrl: release.html_url,
737
+ isRecent: new Date(release.published_at) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
738
+ age: Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000)),
739
+ ageText: `${Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000))} days ago`
740
+ }));
741
+
742
+ return releases.map(release => ({json: release}));
743
+ ```
744
+
745
+ ### Variations
746
+
747
+ ```javascript
748
+ // Variation 1: Add ranking
749
+ const items = $input.all()
750
+ .sort((a, b) => b.json.score - a.json.score)
751
+ .map((item, index) => ({
752
+ json: {
753
+ ...item.json,
754
+ rank: index + 1,
755
+ medal: index < 3 ? ['🥇', '🥈', '🥉'][index] : ''
756
+ }
757
+ }));
758
+
759
+ // Variation 2: Add percentage calculations
760
+ const total = $input.all().reduce((sum, item) => sum + item.json.value, 0);
761
+
762
+ const itemsWithPercentage = $input.all().map(item => ({
763
+ json: {
764
+ ...item.json,
765
+ percentage: ((item.json.value / total) * 100).toFixed(2) + '%'
766
+ }
767
+ }));
768
+
769
+ // Variation 3: Add category labels
770
+ const categorize = (value) => {
771
+ if (value > 100) return 'High';
772
+ if (value > 50) return 'Medium';
773
+ return 'Low';
774
+ };
775
+
776
+ const categorized = $input.all().map(item => ({
777
+ json: {
778
+ ...item.json,
779
+ category: categorize(item.json.value)
780
+ }
781
+ }));
782
+ ```
783
+
784
+ ---
785
+
786
+ ## Pattern 8: Slack Block Kit Formatting
787
+
788
+ **Use Case**: Chat notifications, rich message formatting, interactive messages
789
+
790
+ **When to use:**
791
+ - Sending formatted Slack messages
792
+ - Creating interactive notifications
793
+ - Building rich content for chat platforms
794
+ - Status reports and alerts
795
+
796
+ **Key Techniques**: Template literals, nested objects, Block Kit syntax, date formatting
797
+
798
+ ### Complete Example
799
+
800
+ ```javascript
801
+ // Create Slack-formatted message with structured blocks
802
+ const date = new Date().toISOString().split('T')[0];
803
+ const data = $input.first().json;
804
+
805
+ return [{
806
+ json: {
807
+ text: `Daily Report - ${date}`, // Fallback text
808
+ blocks: [
809
+ {
810
+ type: "header",
811
+ text: {
812
+ type: "plain_text",
813
+ text: `📊 Daily Security Report - ${date}`
814
+ }
815
+ },
816
+ {
817
+ type: "section",
818
+ text: {
819
+ type: "mrkdwn",
820
+ text: `*Status:* ${data.status === 'ok' ? '✅ All Clear' : '⚠️ Issues Detected'}\n*Alerts:* ${data.alertCount || 0}\n*Updated:* ${new Date().toLocaleString()}`
821
+ }
822
+ },
823
+ {
824
+ type: "divider"
825
+ },
826
+ {
827
+ type: "section",
828
+ fields: [
829
+ {
830
+ type: "mrkdwn",
831
+ text: `*Failed Logins:*\n${data.failedLogins || 0}`
832
+ },
833
+ {
834
+ type: "mrkdwn",
835
+ text: `*API Errors:*\n${data.apiErrors || 0}`
836
+ },
837
+ {
838
+ type: "mrkdwn",
839
+ text: `*Uptime:*\n${data.uptime || '100%'}`
840
+ },
841
+ {
842
+ type: "mrkdwn",
843
+ text: `*Response Time:*\n${data.avgResponseTime || 'N/A'}ms`
844
+ }
845
+ ]
846
+ },
847
+ {
848
+ type: "context",
849
+ elements: [{
850
+ type: "mrkdwn",
851
+ text: `Report generated automatically by n8n workflow`
852
+ }]
853
+ }
854
+ ]
855
+ }
856
+ }];
857
+ ```
858
+
859
+ ### Variations
860
+
861
+ ```javascript
862
+ // Variation 1: Interactive buttons
863
+ const blocksWithButtons = [
864
+ {
865
+ type: "section",
866
+ text: {
867
+ type: "mrkdwn",
868
+ text: "Would you like to approve this request?"
869
+ },
870
+ accessory: {
871
+ type: "button",
872
+ text: {
873
+ type: "plain_text",
874
+ text: "Approve"
875
+ },
876
+ style: "primary",
877
+ value: "approve",
878
+ action_id: "approve_button"
879
+ }
880
+ }
881
+ ];
882
+
883
+ // Variation 2: List formatting
884
+ const items = ['Item 1', 'Item 2', 'Item 3'];
885
+ const formattedList = items.map((item, i) => `${i + 1}. ${item}`).join('\n');
886
+
887
+ // Variation 3: Status indicators
888
+ function getStatusEmoji(status) {
889
+ const statusMap = {
890
+ 'success': '✅',
891
+ 'warning': '⚠️',
892
+ 'error': '❌',
893
+ 'info': 'ℹ️'
894
+ };
895
+
896
+ return statusMap[status] || '•';
897
+ }
898
+
899
+ // Variation 4: Truncate long messages
900
+ function truncate(text, maxLength = 3000) {
901
+ if (text.length <= maxLength) return text;
902
+ return text.substring(0, maxLength - 3) + '...';
903
+ }
904
+ ```
905
+
906
+ ---
907
+
908
+ ## Pattern 9: Top N Filtering & Ranking
909
+
910
+ **Use Case**: RAG pipelines, ranking algorithms, result filtering, leaderboards
911
+
912
+ **When to use:**
913
+ - Getting top results by score
914
+ - Filtering best/worst performers
915
+ - Building leaderboards
916
+ - Relevance ranking
917
+
918
+ **Key Techniques**: Sorting, slicing, null coalescing, score calculations
919
+
920
+ ### Complete Example
921
+
922
+ ```javascript
923
+ // Filter and rank by similarity score, return top results
924
+ const ragResponse = $input.item.json;
925
+ const chunks = ragResponse.chunks || [];
926
+
927
+ // Sort by similarity (highest first)
928
+ const topChunks = chunks
929
+ .sort((a, b) => (b.similarity || 0) - (a.similarity || 0))
930
+ .slice(0, 6);
931
+
932
+ return [{
933
+ json: {
934
+ query: ragResponse.query,
935
+ topChunks: topChunks,
936
+ count: topChunks.length,
937
+ maxSimilarity: topChunks[0]?.similarity || 0,
938
+ minSimilarity: topChunks[topChunks.length - 1]?.similarity || 0,
939
+ averageSimilarity: topChunks.reduce((sum, chunk) => sum + (chunk.similarity || 0), 0) / topChunks.length
940
+ }
941
+ }];
942
+ ```
943
+
944
+ ### Variations
945
+
946
+ ```javascript
947
+ // Variation 1: Top N with minimum threshold
948
+ const threshold = 0.7;
949
+ const topItems = $input.all()
950
+ .filter(item => item.json.score >= threshold)
951
+ .sort((a, b) => b.json.score - a.json.score)
952
+ .slice(0, 10);
953
+
954
+ // Variation 2: Bottom N (worst performers)
955
+ const bottomItems = $input.all()
956
+ .sort((a, b) => a.json.score - b.json.score) // Ascending
957
+ .slice(0, 5);
958
+
959
+ // Variation 3: Top N by multiple criteria
960
+ const ranked = $input.all()
961
+ .map(item => ({
962
+ ...item,
963
+ compositeScore: (item.json.relevance * 0.6) + (item.json.recency * 0.4)
964
+ }))
965
+ .sort((a, b) => b.compositeScore - a.compositeScore)
966
+ .slice(0, 10);
967
+
968
+ // Variation 4: Percentile filtering
969
+ const allScores = $input.all().map(item => item.json.score).sort((a, b) => b - a);
970
+ const percentile95 = allScores[Math.floor(allScores.length * 0.05)];
971
+
972
+ const topPercentile = $input.all().filter(item => item.json.score >= percentile95);
973
+ ```
974
+
975
+ ---
976
+
977
+ ## Pattern 10: String Aggregation & Reporting
978
+
979
+ **Use Case**: Report generation, log aggregation, content concatenation, summary creation
980
+
981
+ **When to use:**
982
+ - Combining multiple text outputs
983
+ - Generating reports from data
984
+ - Aggregating logs or messages
985
+ - Creating formatted summaries
986
+
987
+ **Key Techniques**: Array joining, string concatenation, template literals, timestamp handling
988
+
989
+ ### Complete Example
990
+
991
+ ```javascript
992
+ // Aggregate multiple text inputs into formatted report
993
+ const allItems = $input.all();
994
+
995
+ // Collect all messages
996
+ const messages = allItems.map(item => item.json.message);
997
+
998
+ // Build report
999
+ const header = `🎯 **Daily Summary Report**\n📅 ${new Date().toLocaleString()}\n📊 Total Items: ${messages.length}\n\n`;
1000
+ const divider = '\n\n---\n\n';
1001
+ const footer = `\n\n---\n\n✅ Report generated at ${new Date().toISOString()}`;
1002
+
1003
+ const finalReport = header + messages.join(divider) + footer;
1004
+
1005
+ return [{
1006
+ json: {
1007
+ report: finalReport,
1008
+ messageCount: messages.length,
1009
+ generatedAt: new Date().toISOString(),
1010
+ reportLength: finalReport.length
1011
+ }
1012
+ }];
1013
+ ```
1014
+
1015
+ ### Variations
1016
+
1017
+ ```javascript
1018
+ // Variation 1: Numbered list
1019
+ const numberedReport = allItems
1020
+ .map((item, index) => `${index + 1}. ${item.json.title}\n ${item.json.description}`)
1021
+ .join('\n\n');
1022
+
1023
+ // Variation 2: Markdown table
1024
+ const headers = '| Name | Status | Score |\n|------|--------|-------|\n';
1025
+ const rows = allItems
1026
+ .map(item => `| ${item.json.name} | ${item.json.status} | ${item.json.score} |`)
1027
+ .join('\n');
1028
+
1029
+ const table = headers + rows;
1030
+
1031
+ // Variation 3: HTML report
1032
+ const htmlReport = `
1033
+ <!DOCTYPE html>
1034
+ <html>
1035
+ <head><title>Report</title></head>
1036
+ <body>
1037
+ <h1>Report - ${new Date().toLocaleDateString()}</h1>
1038
+ <ul>
1039
+ ${allItems.map(item => `<li>${item.json.title}: ${item.json.value}</li>`).join('\n ')}
1040
+ </ul>
1041
+ </body>
1042
+ </html>
1043
+ `;
1044
+
1045
+ // Variation 4: JSON summary
1046
+ const summary = {
1047
+ generated: new Date().toISOString(),
1048
+ totalItems: allItems.length,
1049
+ items: allItems.map(item => item.json),
1050
+ statistics: {
1051
+ total: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0),
1052
+ average: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0) / allItems.length,
1053
+ max: Math.max(...allItems.map(item => item.json.value || 0)),
1054
+ min: Math.min(...allItems.map(item => item.json.value || 0))
1055
+ }
1056
+ };
1057
+ ```
1058
+
1059
+ ---
1060
+
1061
+ ## Choosing the Right Pattern
1062
+
1063
+ ### Pattern Selection Guide
1064
+
1065
+ | Your Goal | Use Pattern |
1066
+ |-----------|-------------|
1067
+ | Combine multiple API responses | Pattern 1 (Multi-source Aggregation) |
1068
+ | Extract mentions or keywords | Pattern 2 (Regex Filtering) |
1069
+ | Parse formatted text | Pattern 3 (Markdown Parsing) |
1070
+ | Detect changes in data | Pattern 4 (JSON Comparison) |
1071
+ | Prepare form data for CRM | Pattern 5 (CRM Transformation) |
1072
+ | Process GitHub releases | Pattern 6 (Release Processing) |
1073
+ | Add computed fields | Pattern 7 (Array Transformation) |
1074
+ | Format Slack messages | Pattern 8 (Block Kit Formatting) |
1075
+ | Get top results | Pattern 9 (Top N Filtering) |
1076
+ | Create text reports | Pattern 10 (String Aggregation) |
1077
+
1078
+ ### Combining Patterns
1079
+
1080
+ Many real workflows combine multiple patterns:
1081
+
1082
+ ```javascript
1083
+ // Example: Multi-source aggregation + Top N filtering
1084
+ const allItems = $input.all();
1085
+ const aggregated = [];
1086
+
1087
+ // Pattern 1: Aggregate from different sources
1088
+ for (const item of allItems) {
1089
+ // ... aggregation logic
1090
+ aggregated.push(normalizedItem);
1091
+ }
1092
+
1093
+ // Pattern 9: Get top 10 by score
1094
+ const top10 = aggregated
1095
+ .sort((a, b) => b.score - a.score)
1096
+ .slice(0, 10);
1097
+
1098
+ // Pattern 10: Generate report
1099
+ const report = `Top 10 Items:\n\n${top10.map((item, i) => `${i + 1}. ${item.title} (${item.score})`).join('\n')}`;
1100
+
1101
+ return [{json: {report, items: top10}}];
1102
+ ```
1103
+
1104
+ ---
1105
+
1106
+ ## Summary
1107
+
1108
+ **Most Useful Patterns**:
1109
+ 1. Multi-source Aggregation - Combining data from APIs, databases
1110
+ 2. Top N Filtering - Rankings, leaderboards, best results
1111
+ 3. Data Transformation - CRM data, field mapping, enrichment
1112
+
1113
+ **Key Techniques Across Patterns**:
1114
+ - Array methods (map, filter, reduce, sort, slice)
1115
+ - Regex for pattern matching
1116
+ - Object manipulation and destructuring
1117
+ - Error handling with optional chaining
1118
+ - Template literals for formatting
1119
+
1120
+ **See Also**:
1121
+ - [DATA_ACCESS.md](DATA_ACCESS.md) - Data access methods
1122
+ - [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes
1123
+ - [BUILTIN_FUNCTIONS.md](BUILTIN_FUNCTIONS.md) - Built-in helpers