| name | evernote-sdk-patterns |
| description | Advanced Evernote SDK patterns and best practices.
Use when implementing complex note operations, batch processing,
search queries, or optimizing SDK usage.
Trigger with phrases like "evernote sdk patterns", "evernote best practices",
"evernote advanced", "evernote batch operations".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Evernote SDK Patterns
Overview
Production-ready patterns for working with the Evernote SDK, including search, filtering, batch operations, and resource handling.
Prerequisites
- Completed
evernote-install-auth and evernote-hello-world
- Understanding of Evernote data model (Notes, Notebooks, Tags, Resources)
- Familiarity with Promises/async patterns
Core Patterns
Pattern 1: Search with NoteFilter
const Evernote = require('evernote');
async function searchNotes(noteStore, searchQuery, maxResults = 100) {
const filter = new Evernote.NoteStore.NoteFilter({
words: searchQuery,
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeContentLength: true,
includeCreated: true,
includeUpdated: true,
includeTagGuids: true,
includeNotebookGuid: true
});
const result = await noteStore.findNotesMetadata(filter, 0, maxResults, spec);
console.log(`Found ${result.totalNotes} notes (returned )`);
result;
}
Pattern 2: Search Grammar Queries
function buildSearchQuery(options = {}) {
const parts = [];
if (options.notebook) {
parts.push(`notebook:"${options.notebook}"`);
}
if (options.tags && options.tags.length) {
options.tags.forEach(tag => parts.push(`tag:"${tag}"`));
}
if (options.excludeTags && options.excludeTags.length) {
options.excludeTags.forEach(tag => parts.push(`-tag:"${tag}"`));
}
if (options.createdAfter) {
parts.push(`created:${formatDateForSearch(options.createdAfter)}`);
}
if (options.updatedAfter) {
parts.push(`updated:${formatDateForSearch(options.updatedAfter)}`);
}
if (options.inTitle) {
parts.push(`intitle:"${options.inTitle}"`);
}
if (options.hasAttachments) {
parts.();
}
(options. !== ) {
parts.();
}
(options.) {
parts.(options.);
}
(options.) {
+ parts.();
}
parts.();
}
() {
date.().(, ).(, );
}
query = ({
: ,
: [],
: (.() - * * * * )
});
Pattern 3: Paginated Note Retrieval
async function* getAllNotesMetadata(noteStore, filter, spec, pageSize = 100) {
let offset = 0;
let hasMore = true;
while (hasMore) {
const result = await noteStore.findNotesMetadata(filter, offset, pageSize, spec);
for (const note of result.notes) {
yield note;
}
offset += result.notes.length;
hasMore = offset < result.totalNotes;
console.log(`Progress: ${offset}/${result.totalNotes}`);
}
}
async function processAllNotes(noteStore) {
const filter = new Evernote.NoteStore.NoteFilter({ words: 'tag:process' });
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeUpdated: true
});
for await (const note (noteStore, filter, spec)) {
.();
}
}
Pattern 4: Efficient Note Content Retrieval
async function getNoteWithOptions(noteStore, noteGuid, options = {}) {
const {
withContent = true,
withResources = false,
withRecognition = false,
withAlternateData = false
} = options;
return noteStore.getNote(
noteGuid,
withContent,
withResources,
withRecognition,
withAlternateData
);
}
const note = await getNoteWithOptions(noteStore, guid);
const noteWithFiles = await getNoteWithOptions(noteStore, guid, {
withResources: true
});
Pattern 5: Creating Notes with Attachments
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
async function createNoteWithAttachment(noteStore, title, content, filePath) {
const Evernote = require('evernote');
const fileBuffer = fs.readFileSync(filePath);
const hash = crypto.createHash('md5').update(fileBuffer).digest('hex');
const mimeType = getMimeType(filePath);
const resource = new Evernote.Types.Resource();
resource.data = new Evernote.Types.Data();
resource.data.body = fileBuffer;
resource.data.size = fileBuffer.length;
resource.data.bodyHash = Buffer.from(hash, 'hex');
resource.mime = mimeType;
resource. = ..();
resource.. = path.(filePath);
enml = ;
note = ..();
note. = title;
note. = enml;
note. = [resource];
noteStore.(note);
}
() {
ext = path.(filePath).();
mimeTypes = {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
:
};
mimeTypes[ext] || ;
}
Pattern 6: Working with Tags
async function getOrCreateTag(noteStore, tagName) {
const tags = await noteStore.listTags();
const existing = tags.find(t =>
t.name.toLowerCase() === tagName.toLowerCase()
);
if (existing) {
return existing;
}
const tag = new Evernote.Types.Tag();
tag.name = tagName;
return noteStore.createTag(tag);
}
async function addTagsToNote(noteStore, noteGuid, tagNames) {
const note = await noteStore.getNote(noteGuid, false, false, false, false);
const tagGuids = await Promise.all(
tagNames.map(async name => {
const tag = await getOrCreateTag(noteStore, name);
return tag.;
})
);
existingTags = note. || [];
allTags = [... ([...existingTags, ...tagGuids])];
note. = allTags;
noteStore.(note);
}
Pattern 7: Notebook Operations
async function getOrCreateNotebook(noteStore, notebookName, stack = null) {
const notebooks = await noteStore.listNotebooks();
const existing = notebooks.find(n =>
n.name.toLowerCase() === notebookName.toLowerCase()
);
if (existing) {
return existing;
}
const notebook = new Evernote.Types.Notebook();
notebook.name = notebookName;
if (stack) {
notebook.stack = stack;
}
return noteStore.createNotebook(notebook);
}
async function moveNoteToNotebook(noteStore, noteGuid, notebookName) {
const notebook = await getOrCreateNotebook(noteStore, notebookName);
const note = await noteStore.getNote(noteGuid, false, false, false, false);
note.notebookGuid = notebook.guid;
return noteStore.updateNote(note);
}
Pattern 8: Error Handling Wrapper
class EvernoteError extends Error {
constructor(originalError) {
super(originalError.message || 'Evernote API error');
this.name = 'EvernoteError';
this.code = originalError.errorCode;
this.parameter = originalError.parameter;
this.rateLimitDuration = originalError.rateLimitDuration;
this.original = originalError;
}
get isRateLimit() {
return this.code === Evernote.Errors.EDAMErrorCode.RATE_LIMIT_REACHED;
}
get isNotFound() {
return this.code === Evernote.Errors.EDAMErrorCode.UNKNOWN;
}
get isInvalidData() {
return this.code === Evernote.Errors.EDAMErrorCode.;
}
}
() {
{
();
} (error) {
(error. !== ) {
(error);
}
error;
}
}
{
note = (
noteStore.(guid, , , , )
);
} (error) {
(error && error.) {
.();
}
}
Pattern 9: Batch Operations with Rate Limit Handling
async function batchProcess(items, operation, options = {}) {
const {
concurrency = 1,
delayMs = 100,
onProgress = () => {}
} = options;
const results = [];
let processed = 0;
for (const item of items) {
try {
const result = await operation(item);
results.push({ success: true, item, result });
} catch (error) {
if (error.rateLimitDuration) {
console.log(`Rate limited, waiting ${error.rateLimitDuration}s...`);
await sleep(error.rateLimitDuration * 1000);
const result = await operation(item);
results.push({ success: true, item, result });
} else {
results.push({ success: false, item, error });
}
}
processed++;
onProgress(processed, items.length);
(processed < items.) {
(delayMs);
}
}
results;
}
= ms => ( (resolve, ms));
notes = (noteStore, );
results = (
notes.,
noteStore.(note.),
{
: ,
: .()
}
);
Output
- Reusable SDK patterns for common operations
- Efficient search with NoteFilter
- Pagination for large result sets
- Attachment handling with proper MIME types
- Tag and notebook management utilities
- Production error handling
Error Handling
| Error | Cause | Solution |
|---|
RATE_LIMIT_REACHED | Too many API calls | Use rateLimitDuration, add delays |
BAD_DATA_FORMAT | Invalid ENML | Validate before sending |
DATA_CONFLICT | Concurrent modification | Refetch and retry |
QUOTA_REACHED | Account storage full | Check user's remaining quota |
Resources
Next Steps
See evernote-core-workflow-a for note creation and management workflows.