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.
- package/.bps-kit.json +4 -4
- package/README.md +3 -0
- package/implementation_plan.md.resolved +37 -0
- package/package.json +2 -2
- package/templates/agents-template/ARCHITECTURE.md +21 -9
- package/templates/agents-template/agents/automation-specialist.md +157 -0
- package/templates/agents-template/rules/GEMINI.md +2 -10
- package/templates/agents-template/workflows/automate.md +153 -0
- package/templates/skills_normal/n8n-code-javascript/BUILTIN_FUNCTIONS.md +764 -0
- package/templates/skills_normal/n8n-code-javascript/COMMON_PATTERNS.md +1110 -0
- package/templates/skills_normal/n8n-code-javascript/DATA_ACCESS.md +782 -0
- package/templates/skills_normal/n8n-code-javascript/ERROR_PATTERNS.md +763 -0
- package/templates/skills_normal/n8n-code-javascript/README.md +350 -0
- package/templates/skills_normal/n8n-code-javascript/SKILL.md +699 -0
- package/templates/skills_normal/n8n-code-python/COMMON_PATTERNS.md +794 -0
- package/templates/skills_normal/n8n-code-python/DATA_ACCESS.md +702 -0
- package/templates/skills_normal/n8n-code-python/ERROR_PATTERNS.md +601 -0
- package/templates/skills_normal/n8n-code-python/README.md +386 -0
- package/templates/skills_normal/n8n-code-python/SKILL.md +748 -0
- package/templates/skills_normal/n8n-code-python/STANDARD_LIBRARY.md +974 -0
- package/templates/skills_normal/n8n-expression-syntax/COMMON_MISTAKES.md +393 -0
- package/templates/skills_normal/n8n-expression-syntax/EXAMPLES.md +483 -0
- package/templates/skills_normal/n8n-expression-syntax/README.md +93 -0
- package/templates/skills_normal/n8n-expression-syntax/SKILL.md +516 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/README.md +99 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/SEARCH_GUIDE.md +374 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/SKILL.md +642 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/VALIDATION_GUIDE.md +442 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md +618 -0
- package/templates/skills_normal/n8n-node-configuration/DEPENDENCIES.md +789 -0
- package/templates/skills_normal/n8n-node-configuration/OPERATION_PATTERNS.md +913 -0
- package/templates/skills_normal/n8n-node-configuration/README.md +364 -0
- package/templates/skills_normal/n8n-node-configuration/SKILL.md +785 -0
- package/templates/skills_normal/n8n-validation-expert/ERROR_CATALOG.md +943 -0
- package/templates/skills_normal/n8n-validation-expert/FALSE_POSITIVES.md +720 -0
- package/templates/skills_normal/n8n-validation-expert/README.md +290 -0
- package/templates/skills_normal/n8n-validation-expert/SKILL.md +689 -0
- package/templates/skills_normal/n8n-workflow-patterns/README.md +251 -0
- package/templates/skills_normal/n8n-workflow-patterns/SKILL.md +411 -0
- package/templates/skills_normal/n8n-workflow-patterns/ai_agent_workflow.md +784 -0
- package/templates/skills_normal/n8n-workflow-patterns/database_operations.md +785 -0
- package/templates/skills_normal/n8n-workflow-patterns/http_api_integration.md +734 -0
- package/templates/skills_normal/n8n-workflow-patterns/scheduled_tasks.md +773 -0
- package/templates/skills_normal/n8n-workflow-patterns/webhook_processing.md +545 -0
- package/templates/vault/n8n-code-javascript/SKILL.md +10 -10
- package/templates/vault/n8n-code-python/SKILL.md +11 -11
- package/templates/vault/n8n-expression-syntax/SKILL.md +4 -4
- package/templates/vault/n8n-mcp-tools-expert/SKILL.md +9 -9
- package/templates/vault/n8n-node-configuration/SKILL.md +2 -2
- package/templates/vault/n8n-validation-expert/SKILL.md +3 -3
- 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
|