
applesauce-common
by purrgrammer
a nostr client for magicians
SKILL.md
name: applesauce-common description: This skill should be used when working with applesauce-common library for social/NIP-specific helpers, casting system, blueprints, and operations. New in applesauce v5 - contains helpers that moved from applesauce-core.
applesauce-common Skill (v5)
This skill provides comprehensive knowledge for working with applesauce-common, a new package in applesauce v5 that contains social/NIP-specific utilities, the casting system, blueprints, and operations.
Note: applesauce-common was introduced in v5. Many helpers that were previously in applesauce-core/helpers have moved here.
When to Use This Skill
Use this skill when:
- Working with article, highlight, threading, zap, or reaction helpers
- Using the casting system for typed event access
- Creating events with blueprints
- Modifying events with operations
- Working with NIP-specific social features
Package Structure
applesauce-common/
├── helpers/ # Social/NIP-specific helpers
│ ├── article.js # NIP-23 article helpers
│ ├── highlight.js # NIP-84 highlight helpers
│ ├── threading.js # NIP-10 thread helpers
│ ├── comment.js # NIP-22 comment helpers
│ ├── zap.js # NIP-57 zap helpers
│ ├── reaction.js # NIP-25 reaction helpers
│ ├── lists.js # NIP-51 list helpers
│ └── ...
├── casts/ # Typed event classes
│ ├── Note.js
│ ├── User.js
│ ├── Profile.js
│ ├── Article.js
│ └── ...
├── blueprints/ # Event creation blueprints
└── operations/ # Event modification operations
Helpers (Migrated from applesauce-core)
Article Helpers (NIP-23)
import {
getArticleTitle,
getArticleSummary,
getArticleImage,
getArticlePublished
} from 'applesauce-common/helpers/article';
// All helpers cache internally - no useMemo needed
const title = getArticleTitle(event);
const summary = getArticleSummary(event);
const image = getArticleImage(event);
const publishedAt = getArticlePublished(event);
Highlight Helpers (NIP-84)
import {
getHighlightText,
getHighlightSourceUrl,
getHighlightSourceEventPointer,
getHighlightSourceAddressPointer,
getHighlightContext,
getHighlightComment
} from 'applesauce-common/helpers/highlight';
const text = getHighlightText(event);
const sourceUrl = getHighlightSourceUrl(event);
const eventPointer = getHighlightSourceEventPointer(event);
const addressPointer = getHighlightSourceAddressPointer(event);
const context = getHighlightContext(event);
const comment = getHighlightComment(event);
Threading Helpers (NIP-10)
import { getNip10References } from 'applesauce-common/helpers/threading';
// Parse NIP-10 thread structure
const refs = getNip10References(event);
if (refs.root) {
console.log('Root event:', refs.root.e);
console.log('Root address:', refs.root.a);
}
if (refs.reply) {
console.log('Reply to:', refs.reply.e);
}
Comment Helpers (NIP-22)
import { getCommentReplyPointer } from 'applesauce-common/helpers/comment';
const pointer = getCommentReplyPointer(event);
if (pointer) {
// Handle reply target
}
Zap Helpers (NIP-57)
import {
getZapAmount,
getZapSender,
getZapRecipient,
getZapComment
} from 'applesauce-common/helpers/zap';
const amount = getZapAmount(event); // In millisats
const sender = getZapSender(event); // Pubkey
const recipient = getZapRecipient(event);
const comment = getZapComment(event);
List Helpers (NIP-51)
import { getRelaysFromList } from 'applesauce-common/helpers/lists';
const relays = getRelaysFromList(event);
Casting System
The casting system transforms raw Nostr events into typed classes with both synchronous properties and reactive observables.
Basic Usage
import { castEvent, Note, User, Profile } from 'applesauce-common/casts';
// Cast an event to a typed class
const note = castEvent(event, Note, eventStore);
// Access synchronous properties
console.log(note.id);
console.log(note.createdAt);
console.log(note.isReply);
// Subscribe to reactive observables
note.author.profile$.subscribe(profile => {
console.log('Author name:', profile?.name);
});
Available Casts
- Note - Kind 1 short text notes
- User - User with profile and social graph
- Profile - Kind 0 profile metadata
- Article - Kind 30023 long-form articles
- Reaction - Kind 7 reactions
- Zap - Kind 9735 zap receipts
- Comment - NIP-22 comments
- Share - Reposts/quotes
- Bookmarks - NIP-51 bookmarks
- Mutes - NIP-51 mute lists
With React
import { use$ } from 'applesauce-react/hooks';
import { castEvent, Note } from 'applesauce-common/casts';
function NoteComponent({ event }) {
const note = castEvent(event, Note, eventStore);
// Subscribe to author's profile
const profile = use$(note.author.profile$);
// Subscribe to replies
const replies = use$(note.replies$);
return (
<div>
<span>{profile?.name}</span>
<p>{note.content}</p>
<span>{replies?.length} replies</span>
</div>
);
}
Blueprints
Blueprints are factory functions that create Nostr events with automatic tag extraction and proper NIP compliance. They eliminate manual tag building and reduce boilerplate code significantly.
Key Benefits
- Automatic Tag Extraction: Blueprints automatically extract hashtags (#word), mentions (nostr:npub), and event quotes (nostr:note/nevent) from text content
- NIP Compliance: Each blueprint follows the correct NIP specifications for its event type
- Less Code: Replace 50-100 lines of manual tag building with 5-10 lines
- Type Safety: Full TypeScript support with proper types for all options
- Maintainable: Centralized event building logic that's easier to update
Using Blueprints
import { EventFactory } from 'applesauce-core/event-factory';
import { NoteBlueprint } from 'applesauce-common/blueprints';
const factory = new EventFactory();
factory.setSigner(signer);
// Create event from blueprint
const draft = await factory.create(NoteBlueprint, content, options);
const event = await factory.sign(draft);
NoteBlueprint (Kind 1)
Creates short text notes with automatic hashtag, mention, and quote extraction.
What it handles automatically:
- Extracts
#hashtagsfrom content →ttags - Extracts
nostr:npub...mentions →ptags - Extracts
nostr:note...andnostr:nevent...quotes →qtags (NIP-18) - Adds custom emoji tags (NIP-30)
import { NoteBlueprint } from 'applesauce-common/blueprints';
// Simple note
const draft = await factory.create(
NoteBlueprint,
'Hello #nostr! Check out nostr:npub1abc...',
{}
);
// With custom emojis
const draft = await factory.create(
NoteBlueprint,
'Hello :rocket:!',
{
emojis: [{ shortcode: 'rocket', url: 'https://example.com/rocket.png' }]
}
);
// The blueprint automatically adds:
// - ["t", "nostr"] for #nostr
// - ["p", "decoded-pubkey"] for the npub mention
// - ["emoji", "rocket", "https://example.com/rocket.png"] for custom emoji
Options:
emojis?: Array<{ shortcode: string; url: string }>- Custom emojis (NIP-30)contentWarning?: boolean | string- Content warning tag
Before/After Example:
// ❌ BEFORE: Manual tag building (~70 lines)
const hashtags = content.match(/#(\w+)/g)?.map(tag => tag.slice(1)) || [];
const mentionRegex = /nostr:(npub1[a-z0-9]+)/g;
const mentions = [];
let match;
while ((match = mentionRegex.exec(content)) !== null) {
try {
const { data } = nip19.decode(match[1]);
mentions.push(data);
} catch (e) { /* ignore */ }
}
// ... more extraction logic ...
draft.tags = [
...hashtags.map(t => ['t', t]),
...mentions.map(p => ['p', p]),
// ... more tags ...
];
// ✅ AFTER: Blueprint handles everything
const draft = await factory.create(NoteBlueprint, content, { emojis });
NoteReplyBlueprint (Kind 1 Reply)
Creates threaded note replies following NIP-10 conventions.
What it handles automatically:
- Extracts root event from parent's tags (NIP-10)
- Adds proper
etags with markers (root, reply) - Copies
ptags from parent for notifications - Extracts hashtags, mentions, and quotes from content
- Uses
qtags for quotes instead ofetags (correct semantic)
import { NoteReplyBlueprint } from 'applesauce-common/blueprints';
// Reply to a note
const parentEvent = await eventStore.event(parentId).toPromise();
const draft = await factory.create(
NoteReplyBlueprint,
parentEvent,
'Great point! #bitcoin',
{
emojis: [{ shortcode: 'fire', url: 'https://example.com/fire.png' }]
}
);
// The blueprint automatically:
// 1. Finds root from parent's tags (if parent is also a reply)
// 2. Adds ["e", rootId, relay, "root"]
// 3. Adds ["e", parentId, relay, "reply"]
// 4. Copies all ["p", ...] tags from parent
// 5. Extracts #bitcoin → ["t", "bitcoin"]
// 6. Adds emoji tag
Options:
emojis?: Array<{ shortcode: string; url: string }>- Custom emojiscontentWarning?: boolean | string- Content warning
Before/After Example:
// ❌ BEFORE: Manual NIP-10 threading (~95 lines)
const parentRefs = getNip10References(parentEvent);
const rootId = parentRefs.root?.e || parentEvent.id;
const rootRelay = parentRefs.root?.relay || '';
draft.tags = [
['e', rootId, rootRelay, 'root'],
['e', parentEvent.id, '', 'reply'],
];
// Copy p-tags from parent
const parentPTags = parentEvent.tags.filter(t => t[0] === 'p');
draft.tags.push(...parentPTags);
if (!parentPTags.some(t => t[1] === parentEvent.pubkey)) {
draft.tags.push(['p', parentEvent.pubkey]);
}
// ... hashtag extraction ...
// ... mention extraction ...
// ✅ AFTER: Blueprint handles NIP-10 threading
const draft = await factory.create(
NoteReplyBlueprint,
parentEvent,
content,
{ emojis }
);
ReactionBlueprint (Kind 7)
Creates reactions to events (likes, custom emoji reactions).
What it handles automatically:
- Adds
etag pointing to reacted event - Adds
ktag for event kind - Adds
ptag for event author - Handles custom emoji reactions (
:shortcode:format) - Supports both string emoji and Emoji objects
import { ReactionBlueprint } from 'applesauce-common/blueprints';
// Simple like (+ emoji)
const draft = await factory.create(ReactionBlueprint, messageEvent, '+');
// Custom emoji reaction
const draft = await factory.create(
ReactionBlueprint,
messageEvent,
{
shortcode: 'rocket',
url: 'https://example.com/rocket.png'
}
);
// String emoji
const draft = await factory.create(ReactionBlueprint, messageEvent, '🚀');
// The blueprint automatically adds:
// - ["e", messageEvent.id]
// - ["k", messageEvent.kind.toString()]
// - ["p", messageEvent.pubkey]
// For custom emoji: ["emoji", "rocket", "url"]
Options:
- Second parameter:
emoji?: string | { shortcode: string; url: string }
Before/After Example:
// ❌ BEFORE: Manual reaction building (~15 lines per adapter)
draft.kind = 7;
draft.content = typeof emoji === 'string' ? emoji : `:${emoji.shortcode}:`;
draft.tags = [
['e', messageEvent.id],
['k', messageEvent.kind.toString()],
['p', messageEvent.pubkey],
];
if (typeof emoji === 'object') {
draft.tags.push(['emoji', emoji.shortcode, emoji.url]);
}
// ✅ AFTER: Blueprint handles reactions
const draft = await factory.create(ReactionBlueprint, messageEvent, emoji);
GroupMessageBlueprint (Kind 9 - NIP-29)
Creates NIP-29 group chat messages.
What it handles automatically:
- Adds
htag with group ID - Extracts hashtags, mentions, and quotes from content
- Adds custom emoji tags
- Handles message threading with
previousfield
import { GroupMessageBlueprint } from 'applesauce-common/blueprints';
// Send message to NIP-29 group
const draft = await factory.create(
GroupMessageBlueprint,
{ id: groupId, relay: relayUrl },
'Hello group! #welcome',
{
previous: [], // Array of previous message events for threading
emojis: [{ shortcode: 'wave', url: 'https://example.com/wave.png' }]
}
);
// The blueprint automatically adds:
// - ["h", groupId]
// - ["t", "welcome"] for #welcome hashtag
// - ["emoji", "wave", "url"] for custom emoji
Options:
previous?: NostrEvent[]- Previous messages for threading (required, use[]if no threading)emojis?: Array<{ shortcode: string; url: string }>- Custom emojis
Note: The previous field is required by the type, but can be an empty array if you don't need threading.
DeleteBlueprint (Kind 5 - NIP-09)
Creates event deletion requests.
What it handles automatically:
- Adds
etags for each event to delete - Sets proper kind and content format
- Adds optional reason in content
import { DeleteBlueprint } from 'applesauce-common/blueprints';
// Delete single event
const draft = await factory.create(
DeleteBlueprint,
[eventToDelete],
'Accidental post'
);
// Delete multiple events
const draft = await factory.create(
DeleteBlueprint,
[event1, event2, event3],
'Cleaning up old posts'
);
// Without reason
const draft = await factory.create(DeleteBlueprint, [event], '');
// The blueprint automatically:
// - Sets kind to 5
// - Adds ["e", eventId] for each event
// - Sets content to reason (or empty)
Parameters:
events: (string | NostrEvent)[]- Events to delete (IDs or full events)reason?: string- Optional deletion reason
Adding Custom Tags
Blueprints handle common tags automatically, but you can add custom tags afterward:
// Create with blueprint
const draft = await factory.create(NoteBlueprint, content, { emojis });
// Add custom tags not handled by blueprint
draft.tags.push(['client', 'grimoire', '31990:...']);
draft.tags.push(['a', `${kind}:${pubkey}:${identifier}`]);
// Add NIP-92 imeta tags for blob attachments
for (const blob of blobAttachments) {
draft.tags.push(['imeta', `url ${blob.url}`, `x ${blob.sha256}`, ...]);
}
// Sign the modified draft
const event = await factory.sign(draft);
Protocol-Specific Tag Additions
Some protocols require additional tags beyond what blueprints provide:
// NIP-29: Add q-tag for replies (not in blueprint yet)
const draft = await factory.create(GroupMessageBlueprint, group, content, options);
if (replyToId) {
draft.tags.push(['q', replyToId]);
}
// NIP-53: Add a-tag for live activity context
const draft = await factory.create(ReactionBlueprint, messageEvent, emoji);
draft.tags.push(['a', liveActivityATag, relay]);
Available Blueprints
All blueprints from applesauce-common/blueprints:
- NoteBlueprint - Kind 1 short text notes
- NoteReplyBlueprint - Kind 1 threaded replies (NIP-10)
- ReactionBlueprint - Kind 7 reactions (NIP-25)
- GroupMessageBlueprint - Kind 9 group messages (NIP-29)
- DeleteBlueprint - Kind 5 deletion requests (NIP-09)
- MetadataBlueprint - Kind 0 profile metadata
- ContactsBlueprint - Kind 3 contact lists
- ArticleBlueprint - Kind 30023 long-form articles (NIP-23)
- HighlightBlueprint - Kind 9802 highlights (NIP-84)
- ZapRequestBlueprint - Kind 9734 zap requests (NIP-57)
- And more - check
node_modules/applesauce-common/dist/blueprints/
Best Practices
- Always use blueprints when creating standard event types - they handle NIPs correctly
- Add custom tags after blueprint creation for app-specific metadata
- Don't extract tags manually - let blueprints handle hashtags, mentions, quotes
- Use proper emoji format - blueprints expect
{ shortcode, url }objects - Check blueprint source - when in doubt, read the blueprint code for exact behavior
Operations
Operations modify existing events.
import { addTag, removeTag } from 'applesauce-common/operations';
// Add a tag to an event
const modified = addTag(event, ['t', 'bitcoin']);
// Remove a tag
const updated = removeTag(event, 'client');
Migration from v4
Helper Import Changes
// ❌ Old (v4)
import { getArticleTitle } from 'applesauce-core/helpers';
import { getNip10References } from 'applesauce-core/helpers/threading';
import { getZapAmount } from 'applesauce-core/helpers/zap';
// ✅ New (v5)
import { getArticleTitle } from 'applesauce-common/helpers/article';
import { getNip10References } from 'applesauce-common/helpers/threading';
import { getZapAmount } from 'applesauce-common/helpers/zap';
Helpers that stayed in applesauce-core
These protocol-level helpers remain in applesauce-core/helpers:
getTagValue,hasNameValueTaggetProfileContentparseCoordinate,getEventPointerFromETag,getAddressPointerFromATagisFilterEqual,matchFilter,mergeFiltersgetSeenRelays,mergeRelaySetsgetInboxes,getOutboxesnormalizeURL
Best Practices
Helper Caching
All helpers in applesauce-common cache internally using symbols:
// ❌ Don't memoize helper calls
const title = useMemo(() => getArticleTitle(event), [event]);
// ✅ Call helpers directly
const title = getArticleTitle(event);
Casting vs Helpers
Use helpers when you need specific fields:
const title = getArticleTitle(event);
const amount = getZapAmount(event);
Use casts when you need reactive data or multiple related properties:
const note = castEvent(event, Note, eventStore);
const profile$ = note.author.profile$;
const replies$ = note.replies$;
Related Skills
- applesauce-core - Protocol-level helpers and event store
- applesauce-signers - Event signing abstractions
- nostr - Nostr protocol fundamentals
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon
