* docs: add applesauce v5 upgrade plan Comprehensive migration plan covering: - Package updates (add applesauce-common, update to v5) - EventFactory import migration (applesauce-factory → applesauce-core) - Unified event loader setup - ActionHub → ActionRunner migration - useObservableMemo → use$ hook migration - New features: casting system, encrypted content caching - Documentation and skills updates needed * feat: upgrade applesauce libraries to v5 Major upgrade from applesauce v4 to v5 with breaking changes: Package updates: - applesauce-core: ^4.0.0 → ^5.0.0 - applesauce-actions: ^4.0.0 → ^5.0.0 - applesauce-loaders: ^4.0.0 → ^5.0.0 - applesauce-react: ^4.0.0 → ^5.0.0 - applesauce-relay: ^4.0.0 → ^5.0.0 - applesauce-signers: ^4.0.0 → ^5.0.0 - applesauce-accounts: ^4.0.0 → ^5.0.0 - Added new applesauce-common: ^5.0.0 package API migrations: - EventFactory: applesauce-factory → applesauce-core/event-factory - ActionHub → ActionRunner with async function pattern (not generators) - useObservableMemo → use$ hook across all components - Helper imports: article, highlight, threading, zap, comment, lists moved from applesauce-core to applesauce-common - parseCoordinate → parseReplaceableAddress - Subscription options: retries → reconnect - getEventPointerFromETag now returns null instead of throwing New features: - Unified event loader via createEventLoaderForStore - Updated loaders.ts to use v5 unified loader pattern Documentation: - Updated CLAUDE.md with v5 patterns and migration notes - Updated applesauce-core skill for v5 changes - Created new applesauce-common skill Test fixes: - Updated publish-spellbook.test.ts for v5 ActionRunner pattern - Updated publish-spell.test.ts with eventStore mock - Updated relay-selection.test.ts with valid test events - Updated loaders.test.ts with valid 64-char hex event IDs - Added createEventLoaderForStore mock --------- Co-authored-by: Claude <noreply@anthropic.com>
12 KiB
Applesauce v5 Upgrade Plan for Grimoire
Overview
This plan outlines the migration from applesauce v4 to v5, covering breaking changes, new features to adopt, and documentation updates.
Phase 1: Package Updates & Import Migration
1.1 Update package.json Dependencies
Current versions:
"applesauce-accounts": "^4.1.0",
"applesauce-actions": "^4.0.0",
"applesauce-content": "^4.0.0",
"applesauce-core": "latest",
"applesauce-loaders": "^4.2.0",
"applesauce-react": "^4.0.0",
"applesauce-relay": "latest",
"applesauce-signers": "^4.1.0"
Target versions:
"applesauce-accounts": "^5.0.0",
"applesauce-actions": "^5.0.0",
"applesauce-common": "^5.0.0", // NEW - required for v5
"applesauce-content": "^5.0.0",
"applesauce-core": "^5.0.0",
"applesauce-loaders": "^5.0.0",
"applesauce-react": "^5.0.0",
"applesauce-relay": "^5.0.0",
"applesauce-signers": "^5.0.0"
Remove:
"applesauce-factory": "..." // Removed in v5 - EventFactory now in applesauce-core
1.2 EventFactory Import Migration
File: src/services/hub.ts
// Before (v4)
import { EventFactory } from "applesauce-factory";
// After (v5)
import { EventFactory } from "applesauce-core/event-factory";
1.3 Helper Import Migration
Many helpers moved from applesauce-core/helpers to applesauce-common/helpers. Need to audit all imports:
Helpers that moved to applesauce-common/helpers:
- Profile helpers:
getDisplayName,getProfilePicture - Social graph:
groupPubkeysByRelay,getSeenRelays - Threading:
getNip10References,interpretThreadTags - Zaps:
getZapAmount,getZapSender,isValidZap - Lists:
FAVORITE_RELAYS_KIND,getListTags,getRelaysFromList - Article helpers:
getArticleTitle,getArticleSummary, etc. - Highlight helpers: all
getHighlight*functions - Comment helpers:
getCommentReplyPointer
Files to update (from exploration):
src/lib/nostr-utils.tssrc/lib/event-title.tssrc/lib/nip34-helpers.tssrc/lib/nip-c0-helpers.tssrc/services/loaders.tssrc/services/relay-selection.tssrc/hooks/useProfile.tssrc/hooks/useNostrEvent.tssrc/components/nostr/RichText.tsx- All kind renderers in
src/components/nostr/kinds/
Strategy: Use grep to find all applesauce-core/helpers imports and update to applesauce-common/helpers where appropriate. Some low-level helpers remain in applesauce-core/helpers.
Phase 2: Loader Migration (Unified Event Loader)
2.1 Current Loader Architecture
File: src/services/loaders.ts
Currently uses:
createEventLoader- for single eventscreateAddressLoader- for replaceable/addressable eventscreateTimelineLoader- for timelines- Custom
eventLoaderwrapper with smart relay hint merging
2.2 Unified Loader Setup
Replace separate loaders with unified loader:
// Before (v4)
import {
createEventLoader,
createAddressLoader,
createTimelineLoader,
} from "applesauce-loaders/loaders";
const baseEventLoader = createEventLoader(pool, { eventStore, extraRelays });
export const addressLoader = createAddressLoader(pool, { eventStore, extraRelays });
export const profileLoader = createAddressLoader(pool, { eventStore, bufferTime: 200, extraRelays });
// After (v5)
import { createEventLoaderForStore } from "applesauce-loaders/loaders";
// One-time setup - attaches loader to eventStore
createEventLoaderForStore(eventStore, pool, {
bufferTime: 200,
followRelayHints: true,
extraRelays: AGGREGATOR_RELAYS,
lookupRelays: ["wss://purplepag.es/", "wss://index.hzrd149.com/"],
});
// Usage changes - use eventStore methods directly
eventStore.event({ id: "event_id" }).subscribe(...);
eventStore.replaceable({ kind: 0, pubkey: "pubkey" }).subscribe(...);
eventStore.addressable({ kind: 30023, pubkey, identifier }).subscribe(...);
2.3 Custom eventLoader Wrapper
The current smart relay hint merging in eventLoader() needs to be preserved. Options:
Option A: Keep custom wrapper, use unified loader internally
export function eventLoader(pointer, context) {
// Keep existing relay hint merging logic
const enhancedPointer = mergeRelayHints(pointer, context);
// Use eventStore.event() from unified loader
return eventStore.event(enhancedPointer);
}
Option B: Move relay hint logic to a cacheRequest/extraRelays callback
createEventLoaderForStore(eventStore, pool, {
bufferTime: 200,
followRelayHints: true,
extraRelays: (pointer) => getRelayHintsForPointer(pointer),
});
Recommendation: Option A - Keep custom wrapper for backward compatibility and control over relay selection.
2.4 Hook Updates for Unified Loader
Files to update:
src/hooks/useNostrEvent.ts- Use eventStore.event() / eventStore.replaceable()src/hooks/useProfile.ts- Use eventStore.replaceable() for kind 0src/hooks/useTimeline.ts- May continue using createTimelineLoadersrc/hooks/useLiveTimeline.ts- No change (uses pool.subscription directly)src/hooks/useAccountSync.ts- Use eventStore.addressable() for kind 10002
Phase 3: Action System Migration (ActionHub → ActionRunner)
3.1 Breaking Changes
Before (v4): Actions are async generators that yield events
export function PublishSpellbook(options) {
return async function* ({ factory }: ActionContext): AsyncGenerator<NostrEvent> {
const draft = await factory.build({ kind, content, tags });
const event = await factory.sign(draft);
yield event; // ActionHub handles publishing
};
}
After (v5): Actions are async functions that call context.publish()
export function PublishSpellbook(options) {
return async function ({ factory, publish }: ActionContext): Promise<void> {
const draft = await factory.build({ kind, content, tags });
const event = await factory.sign(draft);
await publish(event); // Explicit publish call
};
}
3.2 Files to Migrate
-
src/services/hub.ts:// Before import { ActionHub } from "applesauce-actions"; export const hub = new ActionHub(eventStore, factory, publishEvent); // After import { ActionRunner } from "applesauce-actions"; export const hub = new ActionRunner(eventStore, factory, publishEvent); -
src/actions/publish-spellbook.ts:- Convert from async generator to async function
- Replace
yield eventwithawait publish(event)
-
src/actions/publish-spell.ts:- Same conversion pattern
-
src/actions/delete-event.ts:- Already uses direct factory.sign + pool.publish pattern
- Consider migrating to ActionRunner for consistency
3.3 Action Context Changes
v5 ActionContext provides additional features:
publish(event, relays?)- Explicit publish with optional relay overridecast(event, Cast)- Cast events to typed classes- Sub-action support for composing actions
Phase 4: React Hooks Migration (use$ Hook)
4.1 New use$ Hook
Replace useObservableMemo with use$:
// Before (v4)
import { useObservableMemo } from "applesauce-react/hooks";
const event = useObservableMemo(() => eventStore.event(eventId), [eventId]);
const activeAccount = useObservableMemo(() => accounts.active$, []);
// After (v5)
import { use$ } from "applesauce-react/hooks";
const event = use$(() => eventStore.event(eventId), [eventId]);
const activeAccount = use$(accounts.active$); // Direct observable, no factory needed
4.2 Files to Update
Search for all useObservableMemo imports and update:
src/hooks/useNostrEvent.tssrc/hooks/useTimeline.tssrc/hooks/useLiveTimeline.tssrc/hooks/useAccountSync.tssrc/hooks/useStable.ts- Any component using
useObservableMemodirectly
4.3 Type Differences
use$ has better TypeScript inference:
BehaviorSubject<T>→ returnsT(never undefined)Observable<T>→ returnsT | undefined
Phase 5: Adopt New Features
5.1 Casting System
Add casting for improved type safety and reactive patterns:
import { castEvent, Note, User, Profile } from "applesauce-common/casts";
// Cast events to typed classes
const note = castEvent(event, Note, eventStore);
// Synchronous properties
console.log(note.id, note.createdAt, note.isReply);
// Reactive observables
const profile = use$(note.author.profile$);
const replies = use$(note.replies$);
Potential use cases in Grimoire:
- Note renderers - cast kind 1 events for cleaner access
- Profile components - use
Usercast for profile data - Reply threads - use reactive
replies$observable - Zap displays - cast for amount/sender access
5.2 Encrypted Content Caching
For DM/encrypted content features:
import { persistEncryptedContent } from "applesauce-common/helpers";
// Setup in services/event-store.ts or main.tsx
persistEncryptedContent(eventStore, storage);
// Decrypted content automatically cached
await unlockHiddenBookmarks(bookmarks, signer);
5.3 Blueprints and Operations
Move event creation to blueprints for cleaner code:
// Before - manual event building
const draft = await factory.build({
kind: 30777,
content: JSON.stringify(content),
tags: [["d", slug], ["title", title]]
});
// After - using blueprints (if available for kind 30777)
import { SpellbookBlueprint } from "applesauce-common/blueprints";
const draft = await factory.build(SpellbookBlueprint({ title, content, slug }));
Note: Custom kinds like 30777 may not have built-in blueprints, but the pattern can be used for standard kinds.
Phase 6: Documentation Updates
6.1 CLAUDE.md Updates
Update the following sections:
- Package structure - Add
applesauce-commonto stack description - Import patterns - Update helper import paths
- Loader documentation - Document unified loader pattern
- Action documentation - Document ActionRunner pattern
- Hook documentation - Document
use$hook - New features - Add casting system documentation
- Helper caching note - Update import paths in examples
6.2 Skill Updates
.claude/skills/applesauce-core/SKILL.md:
- Update import paths for helpers that moved to applesauce-common
- Add section on unified event loader
- Update examples to use
use$hook - Add casting system documentation
- Note v5 breaking changes
New skill: .claude/skills/applesauce-common/SKILL.md:
- Document casting system (Note, User, Profile, etc.)
- Document helpers that moved from applesauce-core
- Document blueprints and operations
- Document encrypted content caching
.claude/skills/applesauce-signers/SKILL.md:
- Verify no breaking changes (appears stable)
- Update if any signer interface changes
6.3 Code Comments
Update inline comments in affected files to reflect v5 patterns.
Phase 7: Testing & Verification
7.1 Test Updates
Update test files for new patterns:
src/actions/publish-spell.test.tssrc/actions/publish-spellbook.test.ts- Any tests using mocked applesauce imports
7.2 Verification Steps
- Run
npm installafter package.json updates - Fix all TypeScript compilation errors
- Run
npm run lintand fix issues - Run
npm run test:runand fix failing tests - Run
npm run buildto verify production build - Manual testing of critical flows:
- Profile loading
- Timeline feeds
- Event detail views
- Publishing events
- Account login/logout
Migration Order
Recommended order to minimize breakage:
- Phase 1.1-1.3: Package updates and imports (required first)
- Phase 3: Action system migration (isolated change)
- Phase 2: Loader migration (larger change, test carefully)
- Phase 4: Hook migration (use$ is compatible alongside useObservableMemo)
- Phase 5: New features adoption (optional enhancements)
- Phase 6: Documentation updates
- Phase 7: Final testing and verification
Rollback Plan
If v5 migration encounters blocking issues:
- Revert package.json to v4 versions
- Run
npm installto restore v4 packages - Git revert any code changes
- Document blocking issues for resolution
Estimated Scope
Files to modify:
package.json- 1 file- Service files (
src/services/) - 3-4 files - Hook files (
src/hooks/) - 5-6 files - Action files (
src/actions/) - 3 files - Helper usage across codebase - 20+ files
- Documentation files - 4-5 files
New files:
.claude/skills/applesauce-common/SKILL.md
Risk areas:
- Loader migration (most complex, affects data loading)
- Helper import paths (many files, easy to miss some)
- Action generator → async function (behavior change)