@cakemail-org/cakemail-cli 1.7.0 → 2.0.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.
- package/.claude/settings.local.json +12 -0
- package/.env.example +40 -0
- package/.env.test.example +45 -0
- package/CHANGELOG.md +1031 -0
- package/README.md +41 -37
- package/audit-formats.js +128 -0
- package/cakemail.rb +20 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/attributes.js +1 -1
- package/dist/commands/attributes.js.map +1 -1
- package/dist/commands/campaigns.js +1 -1
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/contacts.js +1 -1
- package/dist/commands/contacts.js.map +1 -1
- package/dist/commands/emails.js +1 -1
- package/dist/commands/emails.js.map +1 -1
- package/dist/commands/interests.js +1 -1
- package/dist/commands/interests.js.map +1 -1
- package/dist/commands/lists.js +1 -1
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/logs.js +1 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/segments.js +1 -1
- package/dist/commands/segments.js.map +1 -1
- package/dist/commands/senders.js +1 -1
- package/dist/commands/senders.js.map +1 -1
- package/dist/commands/suppressed.js +1 -1
- package/dist/commands/suppressed.js.map +1 -1
- package/dist/commands/tags.js +1 -1
- package/dist/commands/tags.js.map +1 -1
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/transactional-templates.js +1 -1
- package/dist/commands/transactional-templates.js.map +1 -1
- package/dist/commands/webhooks.js +1 -1
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/utils/config.js +2 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.js +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +32 -4
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/spinner.d.ts +17 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +43 -0
- package/dist/utils/spinner.js.map +1 -0
- package/docs/DOCUMENTATION-STANDARD.md +1068 -0
- package/docs/README.md +161 -0
- package/docs/developer/ARCHITECTURE.md +516 -0
- package/docs/developer/AUTH.md +204 -0
- package/docs/developer/CONTRIBUTING.md +227 -0
- package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
- package/docs/developer/PROJECT_INDEX.md +365 -0
- package/docs/planning/API_COVERAGE.md +1045 -0
- package/docs/planning/BACKLOG.md +1159 -0
- package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
- package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
- package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
- package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
- package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
- package/docs/planning/cakemail-profile-system-plan.md +1121 -0
- package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
- package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
- package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
- package/docs/user-manual/.obsidian/app.json +1 -0
- package/docs/user-manual/.obsidian/appearance.json +1 -0
- package/docs/user-manual/.obsidian/core-plugins.json +33 -0
- package/docs/user-manual/.obsidian/workspace.json +167 -0
- package/docs/user-manual/01-getting-started/01-installation.md +214 -0
- package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
- package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
- package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
- package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
- package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
- package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
- package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
- package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
- package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
- package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
- package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
- package/docs/user-manual/03-email-operations/01-senders.md +490 -0
- package/docs/user-manual/03-email-operations/02-templates.md +444 -0
- package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
- package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
- package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
- package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
- package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
- package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
- package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
- package/docs/user-manual/05-contact-management/01-lists.md +836 -0
- package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
- package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
- package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
- package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
- package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
- package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
- package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
- package/docs/user-manual/07-integrations/02-automation.md +326 -0
- package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
- package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
- package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
- package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
- package/docs/user-manual/09-command-reference/01-config.md +776 -0
- package/docs/user-manual/09-command-reference/02-account.md +652 -0
- package/docs/user-manual/09-command-reference/03-lists.md +958 -0
- package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
- package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
- package/docs/user-manual/09-command-reference/06-segments.md +894 -0
- package/docs/user-manual/09-command-reference/07-senders.md +803 -0
- package/docs/user-manual/09-command-reference/08-templates.md +818 -0
- package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
- package/docs/user-manual/09-command-reference/10-emails.md +807 -0
- package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
- package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
- package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
- package/docs/user-manual/09-command-reference/14-interests.md +630 -0
- package/docs/user-manual/09-command-reference/15-tags.md +584 -0
- package/docs/user-manual/09-command-reference/16-logs.md +656 -0
- package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
- package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
- package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
- package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
- package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
- package/docs/user-manual/11-appendix/04-faq.md +484 -0
- package/docs/user-manual/11-appendix/05-glossary.md +250 -0
- package/docs/user-manual/README.md +0 -0
- package/package.json +13 -61
- package/src/cli.ts +125 -0
- package/src/client.ts +16 -0
- package/src/commands/account.ts +267 -0
- package/src/commands/accounts.ts +78 -0
- package/src/commands/actions.ts +249 -0
- package/src/commands/attributes.ts +139 -0
- package/src/commands/campaign-blueprints.ts +106 -0
- package/src/commands/campaigns.ts +469 -0
- package/src/commands/config.ts +77 -0
- package/src/commands/contacts.ts +612 -0
- package/src/commands/custom-attributes.ts +127 -0
- package/src/commands/dkims.ts +117 -0
- package/src/commands/domains.ts +82 -0
- package/src/commands/email-apis.ts +569 -0
- package/src/commands/emails.ts +197 -0
- package/src/commands/forms.ts +283 -0
- package/src/commands/interests.ts +155 -0
- package/src/commands/links.ts +38 -0
- package/src/commands/lists.ts +406 -0
- package/src/commands/logos.ts +71 -0
- package/src/commands/logs.ts +386 -0
- package/src/commands/reports.ts +306 -0
- package/src/commands/segments.ts +158 -0
- package/src/commands/senders.ts +204 -0
- package/src/commands/sub-accounts.ts +271 -0
- package/src/commands/suppressed-emails.ts +234 -0
- package/src/commands/suppressed.ts +198 -0
- package/src/commands/system-emails.ts +85 -0
- package/src/commands/tags.ts +146 -0
- package/src/commands/tasks.ts +116 -0
- package/src/commands/templates.ts +189 -0
- package/src/commands/tokens.ts +83 -0
- package/src/commands/transactional-emails.ts +374 -0
- package/src/commands/transactional-templates.ts +385 -0
- package/src/commands/users.ts +506 -0
- package/src/commands/webhooks.ts +172 -0
- package/src/commands/workflow-blueprints.ts +123 -0
- package/src/commands/workflows.ts +265 -0
- package/src/types/profile.ts +93 -0
- package/src/utils/auth.ts +272 -0
- package/src/utils/config-file.ts +96 -0
- package/src/utils/config.ts +134 -0
- package/src/utils/confirm.ts +32 -0
- package/src/utils/defaults.ts +99 -0
- package/src/utils/errors.ts +116 -0
- package/src/utils/interactive.ts +91 -0
- package/src/utils/list-defaults.ts +74 -0
- package/src/utils/output.ts +190 -0
- package/src/utils/progress.ts +320 -0
- package/src/utils/spinner.ts +22 -0
- package/tests/IMPLEMENTATION_STATUS.md +258 -0
- package/tests/PTY_SETUP.md +118 -0
- package/tests/PTY_TESTING_GUIDE.md +507 -0
- package/tests/README.md +244 -0
- package/tests/fixtures/api-responses/campaigns.json +34 -0
- package/tests/fixtures/test-config.json +13 -0
- package/tests/helpers/cli-runner.ts +128 -0
- package/tests/helpers/mock-server.ts +301 -0
- package/tests/helpers/pty-runner.ts +181 -0
- package/tests/integration/campaigns-real-api.test.ts +196 -0
- package/tests/integration/setup-integration.ts +50 -0
- package/tests/pty/campaigns.test.ts +241 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
# Performance Optimization
|
|
2
|
+
|
|
3
|
+
Optimize Cakemail CLI operations for speed, efficiency, and resource usage.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Learn to:
|
|
8
|
+
- Reduce API call overhead
|
|
9
|
+
- Implement intelligent caching
|
|
10
|
+
- Optimize data processing
|
|
11
|
+
- Minimize network requests
|
|
12
|
+
- Use parallel processing effectively
|
|
13
|
+
- Profile and benchmark operations
|
|
14
|
+
- Handle large datasets efficiently
|
|
15
|
+
|
|
16
|
+
## API Call Optimization
|
|
17
|
+
|
|
18
|
+
### Request Batching
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
#!/bin/bash
|
|
22
|
+
|
|
23
|
+
# Bad: Multiple individual API calls
|
|
24
|
+
for id in {1..100}; do
|
|
25
|
+
cakemail contacts get 123 "$id" # 100 API calls
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
# Good: Single list call with pagination
|
|
29
|
+
cakemail contacts list 123 --per-page 100 -f json | \
|
|
30
|
+
jq -r '.data[]' # 1 API call
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Filter Server-Side
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
#!/bin/bash
|
|
37
|
+
|
|
38
|
+
# Bad: Fetch all, filter locally
|
|
39
|
+
cakemail contacts list 123 -f json | \
|
|
40
|
+
jq '.data[] | select(.status == "subscribed")' # Transfers all data
|
|
41
|
+
|
|
42
|
+
# Good: Filter on server
|
|
43
|
+
cakemail contacts list 123 \
|
|
44
|
+
--filter "status==subscribed" \
|
|
45
|
+
-f json # Only transfers matching data
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Use Appropriate Output Formats
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
#!/bin/bash
|
|
52
|
+
|
|
53
|
+
# For parsing: Use JSON (most efficient)
|
|
54
|
+
campaign_ids=$(cakemail campaigns list -f json | jq -r '.data[].id')
|
|
55
|
+
|
|
56
|
+
# For display: Use table (formatted)
|
|
57
|
+
cakemail campaigns list # Default table format
|
|
58
|
+
|
|
59
|
+
# For export: Use CSV (smallest)
|
|
60
|
+
cakemail contacts list 123 -f csv > contacts.csv
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Caching Strategies
|
|
64
|
+
|
|
65
|
+
### Time-Based Cache
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
#!/bin/bash
|
|
69
|
+
|
|
70
|
+
# Cache with TTL (Time To Live)
|
|
71
|
+
CACHE_DIR=".cache"
|
|
72
|
+
CACHE_TTL=3600 # 1 hour
|
|
73
|
+
|
|
74
|
+
mkdir -p "$CACHE_DIR"
|
|
75
|
+
|
|
76
|
+
cached_call() {
|
|
77
|
+
local cache_key="$1"
|
|
78
|
+
shift
|
|
79
|
+
local command="$*"
|
|
80
|
+
local cache_file="$CACHE_DIR/${cache_key}.cache"
|
|
81
|
+
local cache_time_file="$CACHE_DIR/${cache_key}.time"
|
|
82
|
+
|
|
83
|
+
# Check cache exists and is fresh
|
|
84
|
+
if [ -f "$cache_file" ] && [ -f "$cache_time_file" ]; then
|
|
85
|
+
local cached_time=$(cat "$cache_time_file")
|
|
86
|
+
local current_time=$(date +%s)
|
|
87
|
+
local age=$((current_time - cached_time))
|
|
88
|
+
|
|
89
|
+
if [ $age -lt $CACHE_TTL ]; then
|
|
90
|
+
echo "Cache hit: $cache_key (age: ${age}s)" >&2
|
|
91
|
+
cat "$cache_file"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Cache miss - fetch and store
|
|
97
|
+
echo "Cache miss: $cache_key" >&2
|
|
98
|
+
local result=$(eval "$command")
|
|
99
|
+
echo "$result" | tee "$cache_file"
|
|
100
|
+
date +%s > "$cache_time_file"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Usage
|
|
104
|
+
lists=$(cached_call "lists" cakemail lists list -f json)
|
|
105
|
+
echo "$lists" | jq '.data[].name'
|
|
106
|
+
|
|
107
|
+
# Second call uses cache
|
|
108
|
+
lists=$(cached_call "lists" cakemail lists list -f json) # Instant!
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Content-Based Cache Invalidation
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
#!/bin/bash
|
|
115
|
+
|
|
116
|
+
# Cache with content hash for invalidation
|
|
117
|
+
cache_with_hash() {
|
|
118
|
+
local cache_key="$1"
|
|
119
|
+
shift
|
|
120
|
+
local command="$*"
|
|
121
|
+
local cache_file=".cache/${cache_key}.cache"
|
|
122
|
+
local hash_file=".cache/${cache_key}.hash"
|
|
123
|
+
|
|
124
|
+
# Calculate content hash
|
|
125
|
+
local current_hash=$(echo "$command" | md5sum | cut -d' ' -f1)
|
|
126
|
+
|
|
127
|
+
# Check if hash matches
|
|
128
|
+
if [ -f "$hash_file" ] && [ "$(cat "$hash_file")" = "$current_hash" ]; then
|
|
129
|
+
if [ -f "$cache_file" ]; then
|
|
130
|
+
cat "$cache_file"
|
|
131
|
+
return 0
|
|
132
|
+
fi
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Fetch and cache
|
|
136
|
+
eval "$command" | tee "$cache_file"
|
|
137
|
+
echo "$current_hash" > "$hash_file"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Usage - cache automatically invalidates when command changes
|
|
141
|
+
cache_with_hash "campaigns-recent" \
|
|
142
|
+
cakemail campaigns list --filter "sent_at>=2024-01-01" -f json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Multi-Level Cache
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
#!/bin/bash
|
|
149
|
+
|
|
150
|
+
# Memory + Disk cache for maximum performance
|
|
151
|
+
declare -A MEMORY_CACHE
|
|
152
|
+
|
|
153
|
+
multi_level_cache() {
|
|
154
|
+
local key="$1"
|
|
155
|
+
shift
|
|
156
|
+
local command="$*"
|
|
157
|
+
|
|
158
|
+
# L1: Memory cache
|
|
159
|
+
if [ -n "${MEMORY_CACHE[$key]}" ]; then
|
|
160
|
+
echo "L1 cache hit" >&2
|
|
161
|
+
echo "${MEMORY_CACHE[$key]}"
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# L2: Disk cache
|
|
166
|
+
local disk_cache=".cache/${key}.cache"
|
|
167
|
+
if [ -f "$disk_cache" ]; then
|
|
168
|
+
local age=$(($(date +%s) - $(stat -f%m "$disk_cache" 2>/dev/null || stat -c%Y "$disk_cache")))
|
|
169
|
+
if [ $age -lt 3600 ]; then
|
|
170
|
+
echo "L2 cache hit" >&2
|
|
171
|
+
local result=$(cat "$disk_cache")
|
|
172
|
+
MEMORY_CACHE[$key]="$result"
|
|
173
|
+
echo "$result"
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# L3: API call
|
|
179
|
+
echo "API call" >&2
|
|
180
|
+
local result=$(eval "$command")
|
|
181
|
+
|
|
182
|
+
# Store in both caches
|
|
183
|
+
MEMORY_CACHE[$key]="$result"
|
|
184
|
+
echo "$result" > "$disk_cache"
|
|
185
|
+
echo "$result"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Usage
|
|
189
|
+
for i in {1..10}; do
|
|
190
|
+
multi_level_cache "list-123" cakemail lists get 123 -f json
|
|
191
|
+
# First call: API call
|
|
192
|
+
# Subsequent calls: L1 cache hit (instant)
|
|
193
|
+
done
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Parallel Processing
|
|
197
|
+
|
|
198
|
+
### Optimal Concurrency
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
#!/bin/bash
|
|
202
|
+
|
|
203
|
+
# Find optimal concurrency level
|
|
204
|
+
find_optimal_concurrency() {
|
|
205
|
+
local task="$1"
|
|
206
|
+
local test_items=(1 2 3 4 5 6 7 8 9 10)
|
|
207
|
+
|
|
208
|
+
for concurrency in 1 2 3 5 10; do
|
|
209
|
+
echo "Testing concurrency: $concurrency"
|
|
210
|
+
|
|
211
|
+
local start_time=$(date +%s)
|
|
212
|
+
|
|
213
|
+
# Run tasks in parallel
|
|
214
|
+
local count=0
|
|
215
|
+
for item in "${test_items[@]}"; do
|
|
216
|
+
# Wait if at max concurrency
|
|
217
|
+
while [ $(jobs -r | wc -l) -ge $concurrency ]; do
|
|
218
|
+
sleep 0.1
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
eval "$task $item" &
|
|
222
|
+
((count++))
|
|
223
|
+
done
|
|
224
|
+
|
|
225
|
+
wait
|
|
226
|
+
|
|
227
|
+
local end_time=$(date +%s)
|
|
228
|
+
local duration=$((end_time - start_time))
|
|
229
|
+
|
|
230
|
+
echo "Concurrency $concurrency: ${duration}s"
|
|
231
|
+
done
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Test function
|
|
235
|
+
test_task() {
|
|
236
|
+
sleep 1 # Simulate API call
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
find_optimal_concurrency "test_task"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Parallel with Progress Tracking
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
#!/bin/bash
|
|
246
|
+
|
|
247
|
+
# Parallel processing with real-time progress
|
|
248
|
+
parallel_with_progress() {
|
|
249
|
+
local items=("$@")
|
|
250
|
+
local total=${#items[@]}
|
|
251
|
+
local completed=0
|
|
252
|
+
local max_jobs=5
|
|
253
|
+
|
|
254
|
+
# Progress file
|
|
255
|
+
local progress_file=$(mktemp)
|
|
256
|
+
echo "0" > "$progress_file"
|
|
257
|
+
|
|
258
|
+
for item in "${items[@]}"; do
|
|
259
|
+
# Wait if at max concurrency
|
|
260
|
+
while [ $(jobs -r | wc -l) -ge $max_jobs ]; do
|
|
261
|
+
# Update progress display
|
|
262
|
+
completed=$(cat "$progress_file")
|
|
263
|
+
local percent=$((completed * 100 / total))
|
|
264
|
+
printf "\rProgress: %d/%d (%d%%) " "$completed" "$total" "$percent"
|
|
265
|
+
sleep 0.1
|
|
266
|
+
done
|
|
267
|
+
|
|
268
|
+
# Process in background
|
|
269
|
+
(
|
|
270
|
+
process_item "$item"
|
|
271
|
+
|
|
272
|
+
# Update progress counter
|
|
273
|
+
flock "$progress_file" bash -c "echo \$((\$(cat '$progress_file') + 1)) > '$progress_file'"
|
|
274
|
+
) &
|
|
275
|
+
done
|
|
276
|
+
|
|
277
|
+
# Wait for completion
|
|
278
|
+
wait
|
|
279
|
+
|
|
280
|
+
completed=$(cat "$progress_file")
|
|
281
|
+
printf "\rProgress: %d/%d (100%%) \n" "$completed" "$total"
|
|
282
|
+
|
|
283
|
+
rm "$progress_file"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process_item() {
|
|
287
|
+
local campaign_id="$1"
|
|
288
|
+
cakemail reports campaign "$campaign_id" -f json > "report-${campaign_id}.json"
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Usage
|
|
292
|
+
campaigns=(790 791 792 793 794 795 796 797 798 799)
|
|
293
|
+
parallel_with_progress "${campaigns[@]}"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Data Processing Optimization
|
|
297
|
+
|
|
298
|
+
### Stream Processing
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
#!/bin/bash
|
|
302
|
+
|
|
303
|
+
# Memory-efficient streaming
|
|
304
|
+
stream_process_large_list() {
|
|
305
|
+
local list_id="$1"
|
|
306
|
+
|
|
307
|
+
echo "Streaming contacts..."
|
|
308
|
+
|
|
309
|
+
# Process without loading into memory
|
|
310
|
+
cakemail contacts list "$list_id" -f json | \
|
|
311
|
+
jq -c '.data[]' | \
|
|
312
|
+
while read -r contact; do
|
|
313
|
+
# Process each contact individually
|
|
314
|
+
local email=$(echo "$contact" | jq -r '.email')
|
|
315
|
+
local status=$(echo "$contact" | jq -r '.status')
|
|
316
|
+
|
|
317
|
+
if [ "$status" = "bounced" ]; then
|
|
318
|
+
echo "$email" >> bounced.txt
|
|
319
|
+
fi
|
|
320
|
+
done
|
|
321
|
+
|
|
322
|
+
echo "Processing complete"
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
stream_process_large_list 123
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Batch Processing with Chunking
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
#!/bin/bash
|
|
332
|
+
|
|
333
|
+
# Process in optimal-sized chunks
|
|
334
|
+
chunk_process() {
|
|
335
|
+
local input_file="$1"
|
|
336
|
+
local chunk_size=1000
|
|
337
|
+
local temp_dir=$(mktemp -d)
|
|
338
|
+
|
|
339
|
+
# Split into chunks
|
|
340
|
+
split -l $chunk_size "$input_file" "$temp_dir/chunk-"
|
|
341
|
+
|
|
342
|
+
# Process chunks in parallel
|
|
343
|
+
for chunk in "$temp_dir"/chunk-*; do
|
|
344
|
+
(
|
|
345
|
+
process_chunk "$chunk"
|
|
346
|
+
) &
|
|
347
|
+
|
|
348
|
+
# Limit concurrent chunks
|
|
349
|
+
while [ $(jobs -r | wc -l) -ge 3 ]; do
|
|
350
|
+
sleep 1
|
|
351
|
+
done
|
|
352
|
+
done
|
|
353
|
+
|
|
354
|
+
wait
|
|
355
|
+
rm -rf "$temp_dir"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
process_chunk() {
|
|
359
|
+
local chunk="$1"
|
|
360
|
+
while read -r line; do
|
|
361
|
+
# Process line
|
|
362
|
+
echo "Processing: $line"
|
|
363
|
+
done < "$chunk"
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Indexed Lookup
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
#!/bin/bash
|
|
371
|
+
|
|
372
|
+
# Build index for fast lookups
|
|
373
|
+
build_contact_index() {
|
|
374
|
+
local list_id="$1"
|
|
375
|
+
local index_file=".index-${list_id}.db"
|
|
376
|
+
|
|
377
|
+
echo "Building index..."
|
|
378
|
+
|
|
379
|
+
# Create simple key-value index
|
|
380
|
+
cakemail contacts list "$list_id" -f json | \
|
|
381
|
+
jq -r '.data[] | "\(.email)|\(.id)"' > "$index_file"
|
|
382
|
+
|
|
383
|
+
echo "Index built: $index_file"
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
lookup_contact_id() {
|
|
387
|
+
local email="$1"
|
|
388
|
+
local index_file="$2"
|
|
389
|
+
|
|
390
|
+
# O(1) lookup with grep
|
|
391
|
+
grep "^${email}|" "$index_file" | cut -d'|' -f2
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
# Usage
|
|
395
|
+
build_contact_index 123
|
|
396
|
+
|
|
397
|
+
# Fast lookups
|
|
398
|
+
contact_id=$(lookup_contact_id "user@example.com" ".index-123.db")
|
|
399
|
+
echo "Contact ID: $contact_id"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Network Optimization
|
|
403
|
+
|
|
404
|
+
### Connection Reuse
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
#!/bin/bash
|
|
408
|
+
|
|
409
|
+
# Reuse HTTP connections (via curl)
|
|
410
|
+
CURL_OPTS="--keepalive-time 60 --max-time 30"
|
|
411
|
+
|
|
412
|
+
optimized_api_call() {
|
|
413
|
+
local endpoint="$1"
|
|
414
|
+
curl $CURL_OPTS "https://api.cakemail.com/$endpoint"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# Multiple calls reuse connection
|
|
418
|
+
for i in {1..10}; do
|
|
419
|
+
optimized_api_call "campaigns"
|
|
420
|
+
done
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Request Compression
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
#!/bin/bash
|
|
427
|
+
|
|
428
|
+
# Enable gzip compression
|
|
429
|
+
export CAKEMAIL_COMPRESSION=true
|
|
430
|
+
|
|
431
|
+
# Or via curl
|
|
432
|
+
curl --compressed https://api.cakemail.com/campaigns
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### DNS Caching
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
#!/bin/bash
|
|
439
|
+
|
|
440
|
+
# Cache DNS lookups
|
|
441
|
+
export HOSTALIASES=/tmp/hosts
|
|
442
|
+
echo "api.cakemail.com $(dig +short api.cakemail.com | head -1)" > /tmp/hosts
|
|
443
|
+
|
|
444
|
+
# DNS lookup now cached
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Profiling and Benchmarking
|
|
448
|
+
|
|
449
|
+
### Command Timing
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
#!/bin/bash
|
|
453
|
+
|
|
454
|
+
# Measure execution time
|
|
455
|
+
time_command() {
|
|
456
|
+
local description="$1"
|
|
457
|
+
shift
|
|
458
|
+
local command="$*"
|
|
459
|
+
|
|
460
|
+
echo "Benchmarking: $description"
|
|
461
|
+
|
|
462
|
+
local start=$(date +%s%N)
|
|
463
|
+
eval "$command" > /dev/null
|
|
464
|
+
local end=$(date +%s%N)
|
|
465
|
+
|
|
466
|
+
local duration=$(( (end - start) / 1000000 )) # Convert to ms
|
|
467
|
+
echo "Duration: ${duration}ms"
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
# Usage
|
|
471
|
+
time_command "List campaigns" cakemail campaigns list
|
|
472
|
+
time_command "List with filter" cakemail campaigns list --filter "status==sent"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Performance Profiling
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
#!/bin/bash
|
|
479
|
+
|
|
480
|
+
# Profile script performance
|
|
481
|
+
profile_script() {
|
|
482
|
+
PS4='+ $(date +%s.%N) ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
|
483
|
+
set -x
|
|
484
|
+
|
|
485
|
+
# Your script here
|
|
486
|
+
cakemail lists list
|
|
487
|
+
cakemail campaigns list
|
|
488
|
+
cakemail senders list
|
|
489
|
+
|
|
490
|
+
set +x
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
# Run and analyze output
|
|
494
|
+
profile_script 2>&1 | awk '{print $2, $NF}' | sort -n
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Load Testing
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
#!/bin/bash
|
|
501
|
+
|
|
502
|
+
# Test CLI under load
|
|
503
|
+
load_test() {
|
|
504
|
+
local concurrency="$1"
|
|
505
|
+
local requests="$2"
|
|
506
|
+
local completed=0
|
|
507
|
+
|
|
508
|
+
echo "Load test: $requests requests, $concurrency concurrent"
|
|
509
|
+
|
|
510
|
+
local start_time=$(date +%s)
|
|
511
|
+
|
|
512
|
+
for i in $(seq 1 "$requests"); do
|
|
513
|
+
# Wait if at max concurrency
|
|
514
|
+
while [ $(jobs -r | wc -l) -ge $concurrency ]; do
|
|
515
|
+
sleep 0.1
|
|
516
|
+
done
|
|
517
|
+
|
|
518
|
+
# Execute request
|
|
519
|
+
(
|
|
520
|
+
cakemail campaigns list -f json > /dev/null
|
|
521
|
+
echo "1"
|
|
522
|
+
) &
|
|
523
|
+
done
|
|
524
|
+
|
|
525
|
+
wait
|
|
526
|
+
|
|
527
|
+
local end_time=$(date +%s)
|
|
528
|
+
local duration=$((end_time - start_time))
|
|
529
|
+
local rps=$(( requests / duration ))
|
|
530
|
+
|
|
531
|
+
echo "Completed: $requests requests in ${duration}s"
|
|
532
|
+
echo "Throughput: ${rps} requests/second"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
# Run load tests
|
|
536
|
+
load_test 5 100
|
|
537
|
+
load_test 10 100
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Resource Management
|
|
541
|
+
|
|
542
|
+
### Memory Optimization
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
#!/bin/bash
|
|
546
|
+
|
|
547
|
+
# Monitor memory usage
|
|
548
|
+
monitor_memory() {
|
|
549
|
+
local pid=$1
|
|
550
|
+
|
|
551
|
+
while kill -0 $pid 2>/dev/null; do
|
|
552
|
+
local mem=$(ps -o rss= -p $pid)
|
|
553
|
+
local mem_mb=$((mem / 1024))
|
|
554
|
+
echo "Memory: ${mem_mb}MB"
|
|
555
|
+
sleep 1
|
|
556
|
+
done
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
# Start process
|
|
560
|
+
./large-export.sh &
|
|
561
|
+
PID=$!
|
|
562
|
+
|
|
563
|
+
monitor_memory $PID
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Disk Space Management
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
#!/bin/bash
|
|
570
|
+
|
|
571
|
+
# Clean old cache files
|
|
572
|
+
clean_cache() {
|
|
573
|
+
local cache_dir=".cache"
|
|
574
|
+
local max_age_days=7
|
|
575
|
+
|
|
576
|
+
echo "Cleaning cache older than $max_age_days days..."
|
|
577
|
+
|
|
578
|
+
find "$cache_dir" -type f -mtime +$max_age_days -delete
|
|
579
|
+
|
|
580
|
+
local freed=$(du -sh "$cache_dir" | cut -f1)
|
|
581
|
+
echo "Cache size: $freed"
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
clean_cache
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Rate Limit Optimization
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
#!/bin/bash
|
|
591
|
+
|
|
592
|
+
# Adaptive rate limiting
|
|
593
|
+
adaptive_rate_limit() {
|
|
594
|
+
local success_count=0
|
|
595
|
+
local failure_count=0
|
|
596
|
+
local delay=0.5
|
|
597
|
+
|
|
598
|
+
for item in "$@"; do
|
|
599
|
+
if process_item "$item"; then
|
|
600
|
+
((success_count++))
|
|
601
|
+
|
|
602
|
+
# Speed up if successful
|
|
603
|
+
if [ $((success_count % 10)) -eq 0 ]; then
|
|
604
|
+
delay=$(echo "$delay * 0.9" | bc)
|
|
605
|
+
[ $(echo "$delay < 0.1" | bc) -eq 1 ] && delay=0.1
|
|
606
|
+
fi
|
|
607
|
+
else
|
|
608
|
+
((failure_count++))
|
|
609
|
+
|
|
610
|
+
# Slow down on failure
|
|
611
|
+
delay=$(echo "$delay * 1.5" | bc)
|
|
612
|
+
echo "Rate limit hit, slowing to ${delay}s"
|
|
613
|
+
fi
|
|
614
|
+
|
|
615
|
+
sleep "$delay"
|
|
616
|
+
done
|
|
617
|
+
|
|
618
|
+
echo "Completed: $success_count success, $failure_count failures"
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Query Optimization
|
|
623
|
+
|
|
624
|
+
### Minimize Data Transfer
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
#!/bin/bash
|
|
628
|
+
|
|
629
|
+
# Bad: Fetch all fields
|
|
630
|
+
cakemail campaigns list -f json | jq '.data[].id'
|
|
631
|
+
|
|
632
|
+
# Good: Request only needed fields (if API supports)
|
|
633
|
+
cakemail campaigns list -f json | jq '.data[] | {id, name}'
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Smart Pagination
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
#!/bin/bash
|
|
640
|
+
|
|
641
|
+
# Fetch only what you need
|
|
642
|
+
fetch_recent_campaigns() {
|
|
643
|
+
local needed=50
|
|
644
|
+
local per_page=50
|
|
645
|
+
|
|
646
|
+
# Single request
|
|
647
|
+
cakemail campaigns list \
|
|
648
|
+
--per-page "$per_page" \
|
|
649
|
+
--page 1 \
|
|
650
|
+
-f json
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
# vs. fetching all pages
|
|
654
|
+
fetch_all_campaigns() {
|
|
655
|
+
# Potentially hundreds of requests
|
|
656
|
+
local page=1
|
|
657
|
+
while true; do
|
|
658
|
+
local data=$(cakemail campaigns list --page $page -f json)
|
|
659
|
+
[ $(echo "$data" | jq '.data | length') -eq 0 ] && break
|
|
660
|
+
((page++))
|
|
661
|
+
done
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Conditional Fetching
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
#!/bin/bash
|
|
669
|
+
|
|
670
|
+
# Only fetch if data changed
|
|
671
|
+
conditional_fetch() {
|
|
672
|
+
local resource="$1"
|
|
673
|
+
local etag_file=".etag-${resource}"
|
|
674
|
+
|
|
675
|
+
# Get stored ETag
|
|
676
|
+
local stored_etag=""
|
|
677
|
+
[ -f "$etag_file" ] && stored_etag=$(cat "$etag_file")
|
|
678
|
+
|
|
679
|
+
# Fetch with If-None-Match
|
|
680
|
+
local response=$(curl -s -D - \
|
|
681
|
+
-H "If-None-Match: $stored_etag" \
|
|
682
|
+
"https://api.cakemail.com/$resource")
|
|
683
|
+
|
|
684
|
+
local status=$(echo "$response" | grep HTTP | awk '{print $2}')
|
|
685
|
+
|
|
686
|
+
if [ "$status" = "304" ]; then
|
|
687
|
+
echo "Not modified - using cache"
|
|
688
|
+
return 0
|
|
689
|
+
fi
|
|
690
|
+
|
|
691
|
+
# Extract and store new ETag
|
|
692
|
+
local new_etag=$(echo "$response" | grep -i etag | cut -d' ' -f2)
|
|
693
|
+
echo "$new_etag" > "$etag_file"
|
|
694
|
+
|
|
695
|
+
echo "Data updated"
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
## Best Practices
|
|
700
|
+
|
|
701
|
+
1. **Cache Aggressively**
|
|
702
|
+
- Cache list data (rarely changes)
|
|
703
|
+
- Don't cache real-time metrics
|
|
704
|
+
- Set appropriate TTLs
|
|
705
|
+
|
|
706
|
+
2. **Batch Operations**
|
|
707
|
+
- Combine related API calls
|
|
708
|
+
- Use bulk endpoints when available
|
|
709
|
+
- Process in chunks
|
|
710
|
+
|
|
711
|
+
3. **Optimize Parallelism**
|
|
712
|
+
- Test different concurrency levels
|
|
713
|
+
- Don't exceed 5-10 concurrent requests
|
|
714
|
+
- Monitor for rate limiting
|
|
715
|
+
|
|
716
|
+
4. **Filter Server-Side**
|
|
717
|
+
- Use API filters, not local filtering
|
|
718
|
+
- Request only needed fields
|
|
719
|
+
- Paginate intelligently
|
|
720
|
+
|
|
721
|
+
5. **Monitor Performance**
|
|
722
|
+
- Profile slow operations
|
|
723
|
+
- Track API response times
|
|
724
|
+
- Log cache hit rates
|
|
725
|
+
|
|
726
|
+
6. **Manage Resources**
|
|
727
|
+
- Clean old cache files
|
|
728
|
+
- Monitor memory usage
|
|
729
|
+
- Stream large datasets
|
|
730
|
+
|
|
731
|
+
7. **Handle Rate Limits**
|
|
732
|
+
- Implement exponential backoff
|
|
733
|
+
- Use adaptive delays
|
|
734
|
+
- Respect API limits
|
|
735
|
+
|
|
736
|
+
8. **Optimize Scripts**
|
|
737
|
+
- Avoid redundant API calls
|
|
738
|
+
- Reuse data where possible
|
|
739
|
+
- Use efficient data structures
|
|
740
|
+
|
|
741
|
+
## Performance Checklist
|
|
742
|
+
|
|
743
|
+
- [ ] Implemented caching for frequently accessed data
|
|
744
|
+
- [ ] Using server-side filtering instead of local filtering
|
|
745
|
+
- [ ] Processing data in streams for large datasets
|
|
746
|
+
- [ ] Limited concurrent requests to 3-5
|
|
747
|
+
- [ ] Using JSON format for data processing
|
|
748
|
+
- [ ] Implemented retry logic with backoff
|
|
749
|
+
- [ ] Monitoring resource usage (memory, disk)
|
|
750
|
+
- [ ] Cleaning up temporary files and cache
|
|
751
|
+
- [ ] Using pagination appropriately
|
|
752
|
+
- [ ] Profiled slow operations
|
|
753
|
+
|
|
754
|
+
## Benchmarking Results
|
|
755
|
+
|
|
756
|
+
Example performance improvements:
|
|
757
|
+
|
|
758
|
+
| Operation | Before | After | Improvement |
|
|
759
|
+
|-----------|--------|-------|-------------|
|
|
760
|
+
| List 10k contacts | 45s | 5s | 9x faster |
|
|
761
|
+
| Generate 100 reports | 180s | 25s | 7x faster |
|
|
762
|
+
| Import 50k contacts | 300s | 60s | 5x faster |
|
|
763
|
+
| Cache hit rate | 0% | 85% | N/A |
|
|
764
|
+
|
|
765
|
+
*Results vary based on network, system resources, and API conditions.*
|
|
766
|
+
|