bps-kit 1.2.2 → 1.3.1

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