ima-claude 2.9.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/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Functions Examples
|
|
3
|
+
*
|
|
4
|
+
* Demonstrating core FP concepts with practical examples
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// 1. BASIC PURITY
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
// ❌ Impure: Depends on external state
|
|
12
|
+
let taxRate = 0.1
|
|
13
|
+
const calculateTotalImpure = (price) => price * (1 + taxRate)
|
|
14
|
+
|
|
15
|
+
// ✅ Pure: All dependencies explicit
|
|
16
|
+
const calculateTotal = (price, taxRate) => price * (1 + taxRate)
|
|
17
|
+
|
|
18
|
+
// ✅ Pure with pre-configuration
|
|
19
|
+
const createTaxCalculator = (taxRate) => (price) => price * (1 + taxRate)
|
|
20
|
+
const calculateWithTax = createTaxCalculator(0.1)
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// 2. COMPOSITION WITHOUT UTILITIES
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
// Data validation example
|
|
27
|
+
const validateRequired = (value) => value != null && value !== ''
|
|
28
|
+
const validateEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
|
29
|
+
const validateLength = (min, max) => (value) =>
|
|
30
|
+
value.length >= min && value.length <= max
|
|
31
|
+
|
|
32
|
+
// Compose with early returns (no pipe utility needed)
|
|
33
|
+
const validateUserEmail = (email) => {
|
|
34
|
+
if (!validateRequired(email)) {
|
|
35
|
+
return { valid: false, error: 'Email is required' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!validateEmail(email)) {
|
|
39
|
+
return { valid: false, error: 'Invalid email format' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!validateLength(5, 100)(email)) {
|
|
43
|
+
return { valid: false, error: 'Email must be 5-100 characters' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { valid: true, value: email }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// 3. DEPENDENCY INJECTION
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
// Pure business logic
|
|
54
|
+
const hashPassword = (password, hasher) => hasher.hash(password)
|
|
55
|
+
const saveToDatabase = (data, database) => database.save(data)
|
|
56
|
+
|
|
57
|
+
// Compose with explicit dependencies
|
|
58
|
+
const saveUser = async (userData, hasher, database) => {
|
|
59
|
+
const hashedPassword = await hashPassword(userData.password, hasher)
|
|
60
|
+
const userToSave = { ...userData, password: hashedPassword }
|
|
61
|
+
return saveToDatabase(userToSave, database)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Function factory for convenience
|
|
65
|
+
const createUserService = (hasher, database) => ({
|
|
66
|
+
save: (userData) => saveUser(userData, hasher, database),
|
|
67
|
+
find: (id) => database.findById(id)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// 4. IMMUTABILITY
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
// Pure array operations
|
|
75
|
+
const users = [
|
|
76
|
+
{ id: 1, name: 'Alice', active: true },
|
|
77
|
+
{ id: 2, name: 'Bob', active: false },
|
|
78
|
+
{ id: 3, name: 'Charlie', active: true }
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
// Add user (immutable)
|
|
82
|
+
const addUser = (users, newUser) => [...users, newUser]
|
|
83
|
+
|
|
84
|
+
// Remove user (immutable)
|
|
85
|
+
const removeUser = (users, userId) =>
|
|
86
|
+
users.filter(user => user.id !== userId)
|
|
87
|
+
|
|
88
|
+
// Update user (immutable)
|
|
89
|
+
const updateUser = (users, userId, updates) =>
|
|
90
|
+
users.map(user =>
|
|
91
|
+
user.id === userId ? { ...user, ...updates } : user
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// Filter active users
|
|
95
|
+
const getActiveUsers = (users) => users.filter(user => user.active)
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// 5. DATA TRANSFORMATION PIPELINE
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
// Transform user data without utilities
|
|
102
|
+
const processUserData = (rawData) => {
|
|
103
|
+
// Normalize
|
|
104
|
+
const normalized = {
|
|
105
|
+
id: rawData.id,
|
|
106
|
+
name: rawData.name?.trim() || '',
|
|
107
|
+
email: rawData.email?.toLowerCase() || ''
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate
|
|
111
|
+
const emailValidation = validateUserEmail(normalized.email)
|
|
112
|
+
if (!emailValidation.valid) {
|
|
113
|
+
return { valid: false, error: emailValidation.error }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Enhance
|
|
117
|
+
const enhanced = {
|
|
118
|
+
...normalized,
|
|
119
|
+
displayName: normalized.name || 'Anonymous',
|
|
120
|
+
createdAt: new Date().toISOString()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { valid: true, data: enhanced }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// 6. ERROR HANDLING
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
// Result type pattern
|
|
131
|
+
const divide = (a, b) =>
|
|
132
|
+
b === 0
|
|
133
|
+
? { success: false, error: 'Division by zero' }
|
|
134
|
+
: { success: true, data: a / b }
|
|
135
|
+
|
|
136
|
+
// Chain operations with early returns
|
|
137
|
+
const calculate = (a, b, c) => {
|
|
138
|
+
const step1 = divide(a, b)
|
|
139
|
+
if (!step1.success) return step1
|
|
140
|
+
|
|
141
|
+
const step2 = divide(step1.data, c)
|
|
142
|
+
return step2
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Try-catch wrapper for async
|
|
146
|
+
const tryCatch = (fn) => async (...args) => {
|
|
147
|
+
try {
|
|
148
|
+
const data = await fn(...args)
|
|
149
|
+
return { success: true, data }
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return { success: false, error: error.message }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// 7. CONFIGURATION PRE-COMPILATION (Performance)
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
// Problem: O(items × validators) - repeated config access
|
|
160
|
+
const validateItemsSlow = (items, validators) => {
|
|
161
|
+
return items.map(item => {
|
|
162
|
+
const errors = []
|
|
163
|
+
for (const validator of validators) {
|
|
164
|
+
if (!validator.validate(item[validator.field])) {
|
|
165
|
+
errors.push(validator.message)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { item, valid: errors.length === 0, errors }
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Solution: O(validators + items) - pre-compiled
|
|
173
|
+
const createItemValidator = (validators) => {
|
|
174
|
+
// Pre-compile validator functions
|
|
175
|
+
const compiledValidators = validators.map(v => ({
|
|
176
|
+
field: v.field,
|
|
177
|
+
validate: v.validate,
|
|
178
|
+
message: v.message
|
|
179
|
+
}))
|
|
180
|
+
|
|
181
|
+
return (item) => {
|
|
182
|
+
const errors = []
|
|
183
|
+
for (const validator of compiledValidators) {
|
|
184
|
+
if (!validator.validate(item[validator.field])) {
|
|
185
|
+
errors.push(validator.message)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { item, valid: errors.length === 0, errors }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const validateItemsFast = (items, validators) => {
|
|
193
|
+
const validator = createItemValidator(validators)
|
|
194
|
+
return items.map(validator)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// 8. MEMOIZATION (Pure Functions Only)
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
const memoize = (fn) => {
|
|
202
|
+
const cache = new Map()
|
|
203
|
+
return (...args) => {
|
|
204
|
+
const key = JSON.stringify(args)
|
|
205
|
+
if (cache.has(key)) return cache.get(key)
|
|
206
|
+
const result = fn(...args)
|
|
207
|
+
cache.set(key, result)
|
|
208
|
+
return result
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Expensive calculation (pure)
|
|
213
|
+
const fibonacci = (n) => {
|
|
214
|
+
if (n <= 1) return n
|
|
215
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const fibonacciMemoized = memoize(fibonacci)
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// EXPORTS FOR TESTING
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
// Basic purity
|
|
226
|
+
calculateTotal,
|
|
227
|
+
createTaxCalculator,
|
|
228
|
+
|
|
229
|
+
// Composition
|
|
230
|
+
validateRequired,
|
|
231
|
+
validateEmail,
|
|
232
|
+
validateLength,
|
|
233
|
+
validateUserEmail,
|
|
234
|
+
|
|
235
|
+
// Dependency injection
|
|
236
|
+
saveUser,
|
|
237
|
+
createUserService,
|
|
238
|
+
|
|
239
|
+
// Immutability
|
|
240
|
+
addUser,
|
|
241
|
+
removeUser,
|
|
242
|
+
updateUser,
|
|
243
|
+
getActiveUsers,
|
|
244
|
+
|
|
245
|
+
// Data transformation
|
|
246
|
+
processUserData,
|
|
247
|
+
|
|
248
|
+
// Error handling
|
|
249
|
+
divide,
|
|
250
|
+
calculate,
|
|
251
|
+
tryCatch,
|
|
252
|
+
|
|
253
|
+
// Performance
|
|
254
|
+
createItemValidator,
|
|
255
|
+
validateItemsFast,
|
|
256
|
+
|
|
257
|
+
// Memoization
|
|
258
|
+
memoize,
|
|
259
|
+
fibonacciMemoized
|
|
260
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Functions Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Demonstrating comprehensive testing enabled by purity
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
calculateTotal,
|
|
9
|
+
createTaxCalculator,
|
|
10
|
+
validateRequired,
|
|
11
|
+
validateEmail,
|
|
12
|
+
validateUserEmail,
|
|
13
|
+
addUser,
|
|
14
|
+
removeUser,
|
|
15
|
+
updateUser,
|
|
16
|
+
divide,
|
|
17
|
+
calculate,
|
|
18
|
+
memoize
|
|
19
|
+
} = require('../pure-functions')
|
|
20
|
+
|
|
21
|
+
describe('Pure Functions - Comprehensive Testing', () => {
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// BASIC PURITY TESTS
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
describe('calculateTotal', () => {
|
|
27
|
+
it('calculates total with tax', () => {
|
|
28
|
+
expect(calculateTotal(100, 0.1)).toBe(110)
|
|
29
|
+
expect(calculateTotal(50, 0.2)).toBe(60)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('handles zero price', () => {
|
|
33
|
+
expect(calculateTotal(0, 0.1)).toBe(0)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('handles zero tax rate', () => {
|
|
37
|
+
expect(calculateTotal(100, 0)).toBe(100)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('createTaxCalculator', () => {
|
|
42
|
+
it('creates specialized calculator', () => {
|
|
43
|
+
const calc = createTaxCalculator(0.1)
|
|
44
|
+
expect(calc(100)).toBe(110)
|
|
45
|
+
expect(calc(50)).toBe(55)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// VALIDATION TESTS (All Edge Cases)
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
describe('validateRequired', () => {
|
|
54
|
+
it('validates non-empty values', () => {
|
|
55
|
+
expect(validateRequired('test')).toBe(true)
|
|
56
|
+
expect(validateRequired(0)).toBe(true)
|
|
57
|
+
expect(validateRequired(false)).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('rejects empty values', () => {
|
|
61
|
+
expect(validateRequired('')).toBe(false)
|
|
62
|
+
expect(validateRequired(null)).toBe(false)
|
|
63
|
+
expect(validateRequired(undefined)).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('validateEmail', () => {
|
|
68
|
+
it('validates correct emails', () => {
|
|
69
|
+
expect(validateEmail('test@example.com')).toBe(true)
|
|
70
|
+
expect(validateEmail('user.name@domain.co.uk')).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('rejects invalid emails', () => {
|
|
74
|
+
expect(validateEmail('invalid')).toBe(false)
|
|
75
|
+
expect(validateEmail('missing@domain')).toBe(false)
|
|
76
|
+
expect(validateEmail('@example.com')).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('validateUserEmail - Composition', () => {
|
|
81
|
+
it('validates complete email', () => {
|
|
82
|
+
const result = validateUserEmail('test@example.com')
|
|
83
|
+
expect(result.valid).toBe(true)
|
|
84
|
+
expect(result.value).toBe('test@example.com')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('rejects missing email', () => {
|
|
88
|
+
const result = validateUserEmail('')
|
|
89
|
+
expect(result.valid).toBe(false)
|
|
90
|
+
expect(result.error).toBe('Email is required')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('rejects invalid format', () => {
|
|
94
|
+
const result = validateUserEmail('invalid')
|
|
95
|
+
expect(result.valid).toBe(false)
|
|
96
|
+
expect(result.error).toBe('Invalid email format')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('rejects too short', () => {
|
|
100
|
+
const result = validateUserEmail('a@b')
|
|
101
|
+
expect(result.valid).toBe(false)
|
|
102
|
+
expect(result.error).toBe('Email must be 5-100 characters')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('rejects too long', () => {
|
|
106
|
+
const longEmail = 'a'.repeat(100) + '@example.com'
|
|
107
|
+
const result = validateUserEmail(longEmail)
|
|
108
|
+
expect(result.valid).toBe(false)
|
|
109
|
+
expect(result.error).toBe('Email must be 5-100 characters')
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// IMMUTABILITY TESTS
|
|
115
|
+
// ============================================================================
|
|
116
|
+
|
|
117
|
+
describe('Immutable Array Operations', () => {
|
|
118
|
+
const users = [
|
|
119
|
+
{ id: 1, name: 'Alice', active: true },
|
|
120
|
+
{ id: 2, name: 'Bob', active: false }
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
describe('addUser', () => {
|
|
124
|
+
it('adds user without mutation', () => {
|
|
125
|
+
const newUser = { id: 3, name: 'Charlie', active: true }
|
|
126
|
+
const result = addUser(users, newUser)
|
|
127
|
+
|
|
128
|
+
expect(result).toHaveLength(3)
|
|
129
|
+
expect(result[2]).toEqual(newUser)
|
|
130
|
+
expect(users).toHaveLength(2) // Original unchanged
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('removeUser', () => {
|
|
135
|
+
it('removes user without mutation', () => {
|
|
136
|
+
const result = removeUser(users, 1)
|
|
137
|
+
|
|
138
|
+
expect(result).toHaveLength(1)
|
|
139
|
+
expect(result[0].id).toBe(2)
|
|
140
|
+
expect(users).toHaveLength(2) // Original unchanged
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('updateUser', () => {
|
|
145
|
+
it('updates user without mutation', () => {
|
|
146
|
+
const result = updateUser(users, 1, { name: 'Alice Updated' })
|
|
147
|
+
|
|
148
|
+
expect(result[0].name).toBe('Alice Updated')
|
|
149
|
+
expect(users[0].name).toBe('Alice') // Original unchanged
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('returns same array if user not found', () => {
|
|
153
|
+
const result = updateUser(users, 999, { name: 'Not Found' })
|
|
154
|
+
|
|
155
|
+
expect(result).toHaveLength(2)
|
|
156
|
+
expect(result).toEqual(users)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// ERROR HANDLING TESTS
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
describe('divide', () => {
|
|
166
|
+
it('divides numbers successfully', () => {
|
|
167
|
+
const result = divide(10, 2)
|
|
168
|
+
expect(result.success).toBe(true)
|
|
169
|
+
expect(result.data).toBe(5)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('handles division by zero', () => {
|
|
173
|
+
const result = divide(10, 0)
|
|
174
|
+
expect(result.success).toBe(false)
|
|
175
|
+
expect(result.error).toBe('Division by zero')
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe('calculate - Chained Operations', () => {
|
|
180
|
+
it('calculates successfully', () => {
|
|
181
|
+
const result = calculate(20, 2, 5)
|
|
182
|
+
expect(result.success).toBe(true)
|
|
183
|
+
expect(result.data).toBe(2) // (20 / 2) / 5 = 2
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('short-circuits on first error', () => {
|
|
187
|
+
const result = calculate(20, 0, 5)
|
|
188
|
+
expect(result.success).toBe(false)
|
|
189
|
+
expect(result.error).toBe('Division by zero')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('short-circuits on second error', () => {
|
|
193
|
+
const result = calculate(20, 2, 0)
|
|
194
|
+
expect(result.success).toBe(false)
|
|
195
|
+
expect(result.error).toBe('Division by zero')
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// SYSTEMATIC EDGE CASE TESTING
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
describe('Edge Cases - All JavaScript Types', () => {
|
|
204
|
+
const allTypes = [
|
|
205
|
+
null,
|
|
206
|
+
undefined,
|
|
207
|
+
true,
|
|
208
|
+
false,
|
|
209
|
+
0,
|
|
210
|
+
1,
|
|
211
|
+
-1,
|
|
212
|
+
'',
|
|
213
|
+
'string',
|
|
214
|
+
[],
|
|
215
|
+
[1, 2],
|
|
216
|
+
{},
|
|
217
|
+
{ key: 'value' },
|
|
218
|
+
NaN,
|
|
219
|
+
Infinity,
|
|
220
|
+
-Infinity
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
describe('validateRequired - handles all types', () => {
|
|
224
|
+
allTypes.forEach(input => {
|
|
225
|
+
it(`handles ${typeof input}: ${JSON.stringify(input)}`, () => {
|
|
226
|
+
expect(() => validateRequired(input)).not.toThrow()
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// MEMOIZATION TESTS
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
describe('memoize', () => {
|
|
237
|
+
it('caches results', () => {
|
|
238
|
+
const expensive = jest.fn((n) => n * 2)
|
|
239
|
+
const memoized = memoize(expensive)
|
|
240
|
+
|
|
241
|
+
expect(memoized(5)).toBe(10)
|
|
242
|
+
expect(expensive).toHaveBeenCalledTimes(1)
|
|
243
|
+
|
|
244
|
+
expect(memoized(5)).toBe(10)
|
|
245
|
+
expect(expensive).toHaveBeenCalledTimes(1) // Still 1, cached
|
|
246
|
+
|
|
247
|
+
expect(memoized(10)).toBe(20)
|
|
248
|
+
expect(expensive).toHaveBeenCalledTimes(2) // New input
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('works with multiple arguments', () => {
|
|
252
|
+
const add = jest.fn((a, b) => a + b)
|
|
253
|
+
const memoized = memoize(add)
|
|
254
|
+
|
|
255
|
+
expect(memoized(2, 3)).toBe(5)
|
|
256
|
+
expect(add).toHaveBeenCalledTimes(1)
|
|
257
|
+
|
|
258
|
+
expect(memoized(2, 3)).toBe(5)
|
|
259
|
+
expect(add).toHaveBeenCalledTimes(1) // Cached
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Anti-Patterns Reference
|
|
2
|
+
|
|
3
|
+
Common FP anti-patterns to avoid.
|
|
4
|
+
|
|
5
|
+
## 1. Impure Functions Disguised as Pure
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// ANTI-PATTERN: Hidden mutation
|
|
9
|
+
function processUser(user) {
|
|
10
|
+
user.lastProcessed = new Date() // Mutation!
|
|
11
|
+
return validateUser(user)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// CORRECT: Returns new object
|
|
15
|
+
function processUser(user) {
|
|
16
|
+
return {
|
|
17
|
+
...user,
|
|
18
|
+
lastProcessed: new Date()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. Configuration in Hot Paths
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// ANTI-PATTERN: O(records x fields) - schema accessed every iteration
|
|
27
|
+
function processRecords(records, schema) {
|
|
28
|
+
return records.map(record => {
|
|
29
|
+
return schema.fields.reduce((obj, field) => {
|
|
30
|
+
obj[field.name] = transformField(record[field.name], field.type)
|
|
31
|
+
return obj
|
|
32
|
+
}, {})
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// CORRECT: Pre-compile the transformation
|
|
37
|
+
function createRecordProcessor(schema) {
|
|
38
|
+
const transformers = schema.fields.map(field =>
|
|
39
|
+
value => transformField(value, field.type)
|
|
40
|
+
)
|
|
41
|
+
return record => transformers.reduce((obj, transform, i) => {
|
|
42
|
+
obj[schema.fields[i].name] = transform(record[schema.fields[i].name])
|
|
43
|
+
return obj
|
|
44
|
+
}, {})
|
|
45
|
+
}
|
|
46
|
+
const processor = createRecordProcessor(schema) // Setup once
|
|
47
|
+
const results = records.map(processor) // Fast execution
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 3. Over-Engineering Simple Cases
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
// ANTI-PATTERN: Complex machinery for simple validation
|
|
54
|
+
const validateEmail = createAdvancedValidator({
|
|
55
|
+
type: 'email',
|
|
56
|
+
rules: [createRule('format', emailRegex), createRule('length', {min: 5, max: 254})],
|
|
57
|
+
transformers: [createTransformer('lowercase'), createTransformer('trim')]
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// CORRECT: Simple, direct approach
|
|
61
|
+
const validateEmail = (email) => {
|
|
62
|
+
const trimmed = email.trim().toLowerCase()
|
|
63
|
+
return trimmed.length >= 5 && trimmed.length <= 254 && emailRegex.test(trimmed)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 4. Inline Validation Mixed with Business Logic
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// ANTI-PATTERN
|
|
71
|
+
if (!body || !body.email) {
|
|
72
|
+
return errorResponse(c, 400, 'Email is required')
|
|
73
|
+
}
|
|
74
|
+
// ... more business logic
|
|
75
|
+
|
|
76
|
+
// CORRECT: Separate validation function
|
|
77
|
+
const validateAuthRequest = (body) => {
|
|
78
|
+
if (!body || !body.email) throw new ValidationError('Email is required')
|
|
79
|
+
return { email: body.email }
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 5. Per-Request Service Instantiation
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// ANTI-PATTERN
|
|
87
|
+
authRoutes.post('/request', async (c) => {
|
|
88
|
+
const authService = new AuthService(env) // Created every request!
|
|
89
|
+
// ...
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// CORRECT: Middleware DI or service container
|
|
93
|
+
app.use(ctx => { ctx.authService = authService }) // Created once
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 6. Creating Custom FP Utilities
|
|
97
|
+
|
|
98
|
+
> **Note**: This is about not CREATING your own FP utilities. Using established libraries (lodash, Ramda, etc.) is perfectly fine.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// ANTI-PATTERN: Creating your own pipe/compose/curry utilities
|
|
102
|
+
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)
|
|
103
|
+
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
|
|
104
|
+
|
|
105
|
+
// CORRECT: Native function calls with early returns
|
|
106
|
+
const validateUser = (userData) => {
|
|
107
|
+
const requiredCheck = validateRequired(['email', 'name'])(userData)
|
|
108
|
+
if (!requiredCheck.valid) return requiredCheck
|
|
109
|
+
return validateEmail(userData)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Quick Checklist
|
|
114
|
+
|
|
115
|
+
- [ ] No hidden mutations in function bodies
|
|
116
|
+
- [ ] Configuration accessed once, not per-item
|
|
117
|
+
- [ ] Complexity matches the problem size
|
|
118
|
+
- [ ] Validation separated from business logic
|
|
119
|
+
- [ ] Services instantiated once, injected via context
|
|
120
|
+
- [ ] No custom FP utility functions (pipe, compose, curry)
|