๐Ÿ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

evernote-performance-tuning

Optimize Evernote integration performance. Use when improving response times, reducing API calls, or scaling Evernote integrations. Trigger with phrases like "evernote performance", "optimize evernote", "evernote speed", "evernote caching". allowed-tools: Read, Write, Edit, Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

evernote-pack

Claude Code skill pack for Evernote (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the evernote-pack plugin:

/plugin install evernote-pack@claude-code-plugins-plus

Click to copy

Instructions

# Evernote Performance Tuning ## Overview Optimize Evernote API integration performance through caching, efficient API usage, connection pooling, and smart data retrieval strategies. ## Prerequisites - Working Evernote integration - Understanding of API rate limits - Caching infrastructure (Redis recommended) ## Instructions ### Step 1: Response Caching ```javascript // services/cache-service.js const Redis = require('ioredis'); class EvernoteCacheService { constructor(redisUrl) { this.redis = new Redis(redisUrl); this.defaultTTL = 300; // 5 minutes } /** * Cache key generators */ keys = { notebooks: (userId) => `evernote:${userId}:notebooks`, tags: (userId) => `evernote:${userId}:tags`, note: (guid) => `evernote:note:${guid}`, noteMetadata: (guid) => `evernote:note:${guid}:meta`, search: (userId, query) => `evernote:${userId}:search:${this.hashQuery(query)}`, syncState: (userId) => `evernote:${userId}:syncState` }; hashQuery(query) { const crypto = require('crypto'); return crypto.createHash('md5').update(query).digest('hex'); } /** * Cache notebooks (longer TTL - rarely change) */ async cacheNotebooks(userId, notebooks) { const key = this.keys.notebooks(userId); await this.redis.setex(key, 3600, JSON.stringify(notebooks)); // 1 hour } async getNotebooks(userId) { const key = this.keys.notebooks(userId); const cached = await this.redis.get(key); return cached ? JSON.parse(cached) : null; } /** * Cache note metadata (shorter TTL) */ async cacheNoteMetadata(guid, metadata) { const key = this.keys.noteMetadata(guid); await this.redis.setex(key, this.defaultTTL, JSON.stringify(metadata)); } async getNoteMetadata(guid) { const key = this.keys.noteMetadata(guid); const cached = await this.redis.get(key); return cached ? JSON.parse(cached) : null; } /** * Cache full note content (configurable TTL) */ async cacheNote(guid, note, ttl = 300) { const key = this.keys.note(guid); await this.redis.setex(key, ttl, JSON.stringify(note)); } async getNote(guid) { const key = this.keys.note(guid); const cached = await this.redis.get(key); return cached ? JSON.parse(cached) : null; } /** * Cache search results (short TTL) */ async cacheSearch(userId, query, results) { const key = this.keys.search(userId, query); await this.redis.setex(key, 60, JSON.stringify(results)); // 1 minute } async getSearch(userId, query) { const key = this.keys.search(userId, query); const cached = await this.redis.get(key); return cached ? JSON.parse(cached) : null; } /** * Invalidate cache on changes */ async invalidateNote(guid) { await this.redis.del(this.keys.note(guid)); await this.redis.del(this.keys.noteMetadata(guid)); } async invalidateUserCache(userId) { const pattern = `evernote:${userId}:*`; const keys = await this.redis.keys(pattern); if (keys.length > 0) { await this.redis.del(...keys); } } } module.exports = EvernoteCacheService; ``` ### Step 2: Cached Client Wrapper ```javascript // services/cached-evernote-client.js const Evernote = require('evernote'); const EvernoteCacheService = require('./cache-service'); class CachedEvernoteClient { constructor(accessToken, userId, cacheService) { this.client = new Evernote.Client({ token: accessToken, sandbox: process.env.EVERNOTE_SANDBOX === 'true' }); this.noteStore = this.client.getNoteStore(); this.userId = userId; this.cache = cacheService; } /** * List notebooks with caching */ async listNotebooks(forceRefresh = false) { if (!forceRefresh) { const cached = await this.cache.getNotebooks(this.userId); if (cached) { console.log('Cache HIT: notebooks'); return cached; } } console.log('Cache MISS: notebooks'); const notebooks = await this.noteStore.listNotebooks(); await this.cache.cacheNotebooks(this.userId, notebooks); return notebooks; } /** * Get note with caching */ async getNote(guid, options = {}) { const { withContent = true, withResources = false, forceRefresh = false } = options; // Only cache if requesting content without resources const canCache = withContent && !withResources; if (canCache && !forceRefresh) { const cached = await this.cache.getNote(guid); if (cached) { console.log('Cache HIT: note', guid); return cached; } } console.log('Cache MISS: note', guid); const note = await this.noteStore.getNote( guid, withContent, withResources, false, false ); if (canCache) { await this.cache.cacheNote(guid, note); } return note; } /** * Search with caching */ async search(query, options = {}) { const { maxResults = 50, forceRefresh = false } = options; if (!forceRefresh) { const cached = await this.cache.getSearch(this.userId, query); if (cached) { console.log('Cache HIT: search', query); return cached; } } console.log('Cache MISS: search', query); const filter = new Evernote.NoteStore.NoteFilter({ words: query }); const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeTitle: true, includeCreated: true, includeUpdated: true, includeTagGuids: true, includeNotebookGuid: true }); const results = await this.noteStore.findNotesMetadata( filter, 0, maxResults, spec ); await this.cache.cacheSearch(this.userId, query, results); return results; } } module.exports = CachedEvernoteClient; ``` ### Step 3: Request Batching ```javascript // utils/request-batcher.js class RequestBatcher { constructor(options = {}) { this.batchSize = options.batchSize || 10; this.batchDelay = options.batchDelay || 100; this.queue = []; this.processing = false; } /** * Add request to batch */ async add(operation) { return new Promise((resolve, reject) => { this.queue.push({ operation, resolve, reject }); if (!this.processing) { this.processBatch(); } }); } /** * Process queued requests */ async processBatch() { if (this.queue.length === 0) { this.processing = false; return; } this.processing = true; // Take batch from queue const batch = this.queue.splice(0, this.batchSize); // Execute in parallel await Promise.all( batch.map(async ({ operation, resolve, reject }) => { try { const result = await operation(); resolve(result); } catch (error) { reject(error); } }) ); // Small delay before next batch if (this.queue.length > 0) { await new Promise(r => setTimeout(r, this.batchDelay)); this.processBatch(); } else { this.processing = false; } } } module.exports = RequestBatcher; ``` ### Step 4: Efficient Data Retrieval ```javascript // services/optimized-note-service.js class OptimizedNoteService { constructor(noteStore, cache) { this.noteStore = noteStore; this.cache = cache; } /** * Get minimal data needed (don't over-fetch) */ async getNotePreview(guid) { // Only get metadata, not full content return this.noteStore.getNote(guid, false, false, false, false); } /** * Get notes with content in single request */ async getNotesWithContent(guids) { // Evernote doesn't support batch note retrieval // Optimize by parallelizing with rate limit awareness const results = []; const batchSize = 5; for (let i = 0; i < guids.length; i += batchSize) { const batch = guids.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(guid => this.noteStore.getNote(guid, true, false, false, false) ) ); results.push(...batchResults); // Small delay between batches if (i + batchSize < guids.length) { await new Promise(r => setTimeout(r, 100)); } } return results; } /** * Get resources separately (more efficient for large files) */ async getNoteWithLazyResources(guid) { // Get note without resource data const note = await this.noteStore.getNote(guid, true, false, false, false); // Create lazy loader for resources note.loadResource = async (resourceGuid) => { return this.noteStore.getResource( resourceGuid, true, // withData false, // withRecognition false, // withAttributes false // withAlternateData ); }; return note; } /** * Efficient search with pagination */ async* searchPaginated(query, pageSize = 50) { const filter = new Evernote.NoteStore.NoteFilter({ words: query }); const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeTitle: true, includeUpdated: true }); let offset = 0; let total = null; while (total === null || offset < total) { const result = await this.noteStore.findNotesMetadata( filter, offset, pageSize, spec ); total = result.totalNotes; for (const note of result.notes) { yield note; } offset += result.notes.length; } } } module.exports = OptimizedNoteService; ``` ### Step 5: Connection Optimization ```javascript // utils/connection-manager.js class ConnectionManager { constructor() { this.clients = new Map(); this.maxIdleTime = 5 * 60 * 1000; // 5 minutes } /** * Get or create client for user */ getClient(userId, accessToken) { const existing = this.clients.get(userId); if (existing) { existing.lastUsed = Date.now(); return existing.client; } const client = new Evernote.Client({ token: accessToken, sandbox: process.env.EVERNOTE_SANDBOX === 'true' }); this.clients.set(userId, { client, lastUsed: Date.now() }); return client; } /** * Clean up idle connections */ cleanup() { const now = Date.now(); for (const [userId, data] of this.clients) { if (now - data.lastUsed > this.maxIdleTime) { this.clients.delete(userId); console.log(`Cleaned up idle connection for user ${userId}`); } } } /** * Start cleanup interval */ startCleanup(interval = 60000) { setInterval(() => this.cleanup(), interval); } } module.exports = ConnectionManager; ``` ### Step 6: Performance Monitoring ```javascript // utils/performance-monitor.js class PerformanceMonitor { constructor() { this.metrics = { apiCalls: 0, cacheHits: 0, cacheMisses: 0, totalLatency: 0, errors: 0 }; this.callDurations = []; } /** * Track API call */ trackCall(operation, duration, fromCache = false) { this.metrics.apiCalls++; this.metrics.totalLatency += duration; this.callDurations.push({ operation, duration, fromCache, timestamp: Date.now() }); if (fromCache) { this.metrics.cacheHits++; } else { this.metrics.cacheMisses++; } // Keep last 1000 calls if (this.callDurations.length > 1000) { this.callDurations.shift(); } } /** * Track error */ trackError(operation, error) { this.metrics.errors++; console.error(`[PERF] Error in ${operation}:`, error.message); } /** * Get performance stats */ getStats() { const avgLatency = this.metrics.apiCalls > 0 ? this.metrics.totalLatency / this.metrics.apiCalls : 0; const cacheHitRate = this.metrics.apiCalls > 0 ? (this.metrics.cacheHits / this.metrics.apiCalls) * 100 : 0; // Calculate p95 latency const sortedDurations = [...this.callDurations] .map(c => c.duration) .sort((a, b) => a - b); const p95Index = Math.floor(sortedDurations.length * 0.95); const p95Latency = sortedDurations[p95Index] || 0; return { totalCalls: this.metrics.apiCalls, cacheHits: this.metrics.cacheHits, cacheMisses: this.metrics.cacheMisses, cacheHitRate: `${cacheHitRate.toFixed(1)}%`, avgLatencyMs: avgLatency.toFixed(2), p95LatencyMs: p95Latency.toFixed(2), errors: this.metrics.errors }; } /** * Create instrumented wrapper */ instrument(noteStore) { const monitor = this; return new Proxy(noteStore, { get(target, prop) { const original = target[prop]; if (typeof original !== 'function') { return original; } return async (...args) => { const start = Date.now(); try { const result = await original.apply(target, args); const duration = Date.now() - start; monitor.trackCall(prop, duration); return result; } catch (error) { const duration = Date.now() - start; monitor.trackCall(prop, duration); monitor.trackError(prop, error); throw error; } }; } }); } } module.exports = PerformanceMonitor; ``` ### Step 7: Usage Example ```javascript // example-optimized.js const Redis = require('ioredis'); const EvernoteCacheService = require('./services/cache-service'); const CachedEvernoteClient = require('./services/cached-evernote-client'); const PerformanceMonitor = require('./utils/performance-monitor'); async function main() { const redis = new Redis(process.env.REDIS_URL); const cache = new EvernoteCacheService(redis); const monitor = new PerformanceMonitor(); const userId = 'user-123'; const client = new CachedEvernoteClient( process.env.EVERNOTE_ACCESS_TOKEN, userId, cache ); // Instrument for monitoring client.noteStore = monitor.instrument(client.noteStore); // First call - cache miss console.log('\nFirst request (cache miss):'); console.time('First notebooks call'); await client.listNotebooks(); console.timeEnd('First notebooks call'); // Second call - cache hit console.log('\nSecond request (cache hit):'); console.time('Second notebooks call'); await client.listNotebooks(); console.timeEnd('Second notebooks call'); // Search with caching console.log('\nSearch (cache miss):'); console.time('First search'); await client.search('meeting notes'); console.timeEnd('First search'); console.log('\nSearch (cache hit):'); console.time('Second search'); await client.search('meeting notes'); console.timeEnd('Second search'); // Print stats console.log('\nPerformance Stats:', monitor.getStats()); redis.quit(); } main().catch(console.error); ``` ## Output - Redis-based response caching - Cache-aware client wrapper - Request batching for bulk operations - Efficient data retrieval patterns - Connection pooling - Performance monitoring ## Performance Tips | Optimization | Impact | When to Use | |--------------|--------|-------------| | Cache notebooks | High | Always (rarely change) | | Cache search results | Medium | Repeated searches | | Lazy load resources | High | Large attachments | | Request batching | Medium | Bulk operations | | Skip content flag | High | Listing notes | ## Resources - [Rate Limits](https://dev.evernote.com/doc/articles/rate_limits.php) - [API Reference](https://dev.evernote.com/doc/reference/) - [Redis Documentation](https://redis.io/documentation) ## Next Steps For cost optimization, see `evernote-cost-tuning`.

Skill file: plugins/saas-packs/evernote-pack/skills/evernote-performance-tuning/SKILL.md