Migrating from Applesauce v5 to v6
This guide focuses on mechanical migrations. Follow the numbered sections in order and treat them as a checklist that both humans and LLM agents can execute.
1. Goals and scope
- What changed – Legacy factory helpers/contexts were removed, relay APIs now emit structured messages, and the React package only ships store/account/action providers.
- What this guide covers – Import moves, API removals, behavioral changes, and the new typed factory workflow across
applesauce-core,applesauce-common,applesauce-actions,applesauce-wallet(-connect), andapplesauce-relay. - What this guide skips – Feature additions that do not break existing code (new examples, extra factories you do not already use).
2. Conceptual shifts
- Factory architecture –
/packages/core/src/event-factorywas deleted. Every builder now extends the Promise-basedEventFactorythat lives in/packages/core/src/factories/event.ts. Domain logic moved from blueprints to kind-specific factory classes under/packages/common/src/factories,/packages/wallet/src/factories, and/packages/wallet-connect/src/factories. - React integration – The React package no longer exposes
FactoryProvider,FactoryContext, oruseEventFactory. Components are expected to instantiate factories directly. - Relay flow – The new low-level
relay.reqendpoint emits structured{ type: "EVENT" | "EOSE" | ... }messages for advanced REQ control, but.request()/.subscription()keep returningNostrEventor"EOSE". CLOSED reasons now map to typed errors, and completion is opt-in through thecompleteWhenhelper for the new API.
Understanding these shifts makes the mechanical steps below easier to follow.
3. Event creation migration
3.1 Update imports and dependencies
- Replace any dependency on
applesauce-factoryorapplesauce-core/event-factoryhelpers with the new entry points:
import { EventFactory, blankEventTemplate } from "applesauce-core/factories";import { NoteFactory, CommentFactory, ... } from "applesauce-common/factories";- Wallet and wallet-connect projects:
applesauce-wallet/factories,applesauce-wallet-connect/factories.
- Remove helper wrappers (
buildEvent,modifyEvent,createEvent) and blueprint imports; none of those files exist in v6. - Ensure
package.jsonkeeps allapplesauce-* packages on the same major version before installing.
3.2 Rewrite factory usage
Use these before/after recipes when migrating existing code:
// Before (v5)
import { EventFactory } from "applesauce-factory";
const signed = await EventFactory.fromKind(1).content(body).as(signer).sign();// After (v6)
import { NoteFactory } from "applesauce-common/factories";
const signed = await NoteFactory.create(body).as(signer).sign();Key rules:
- Start from static
create/reply/modify– Every typed factory exposes static entry points and sets required tags automatically. - Use
.chain(...)helpers sparingly – If you previously calledmodifyPublicTags, keep doing so via the fluent API (factory.modifyPublicTags(cb)still exists onEventFactory). - Fallback for unsupported kinds – When a typed factory does not exist, compose operations manually:
import { EventFactory, blankEventTemplate } from "applesauce-core/factories";
import { modifyPublicTags } from "applesauce-core/operations/tags";
const draft = new EventFactory((resolve) => resolve(blankEventTemplate(7000)))
.content(payload)
.modifyPublicTags((tags) => [...tags, ["d", stableId]]);
const signed = await draft.sign(signer);3.3 Common blueprint → factory mappings
| Removed blueprint/helper | Replacement factory class |
|---|---|
NoteBlueprint, NoteReplyBlueprint | NoteFactory.create(), NoteFactory.reply() |
CommentBlueprint, ThreadCommentBlueprint | CommentFactory.create(parent, content, options) |
ProfileBlueprint | ProfileFactory.create() / .modify(event) (core) |
ContactsBlueprint | ContactsFactory.create() / .modify(event) |
GiftWrapBlueprint | GiftWrapFactory.create(recipient, content) |
ListBlueprint, RelaySetBlueprint, MuteListBlueprint, etc. | ListFactory, RelaySetFactory, MuteListFactory, FavoriteRelaysFactory, etc. in applesauce-common/factories |
Wallet WalletBlueprint, TokenBlueprint, ZapBlueprint | WalletFactory, WalletTokenFactory, NutzapFactory from applesauce-wallet/factories |
| Wallet-connect request/response blueprints | WalletRequestFactory, WalletResponseFactory, WalletNotificationFactory from applesauce-wallet-connect/factories |
Use packages/common/src/factories/index.ts, packages/wallet/src/factories/index.ts, and packages/wallet-connect/src/factories/index.ts as the authoritative export lists when searching for a new builder.
4. React changes
- Delete any
<FactoryProvider>wrapper and removeFactoryContextimports. OnlyEventStoreProvider,AccountsProvider, andActionsProviderremain inapplesauce-react/providers. - Replace
useEventFactory()with direct factory instantiation. Example conversion for a button handler:
// Before
const factory = useEventFactory();
await factory.fromKind(1).content(message).sign();
// After
import { NoteFactory } from "applesauce-common/factories";
await NoteFactory.create(message).as(accountManager.signer).sign();- Account-managed signers integrate cleanly:
import { AccountManager } from "applesauce-accounts";
import { CommentFactory } from "applesauce-common/factories";
const manager = new AccountManager();
const draft = CommentFactory.create(parent, body).as(manager.signer);
const signed = await draft.sign();- Update any documentation, tutorials, or generators that still reference the removed hooks. See
.changeset/clean-react-factory-context.mdfor the authoritative removal note.
5. Relay and operator updates
5.1 Low-level req shapes
Relay.reqis a lower-level REQ helper meant for apps that need complete control over the request lifecycle (custom completion, auth handling, deduplication). It emits structured messages:{ type: "OPEN" | "EVENT" | "EOSE" | "NOTICE" | "CLOSED" }. Handle these viaswitchinstead of comparing to"EOSE".relay.request()andrelay.subscription()remain the simplest high-level APIs. They still emitNostrEventvalues and the string"EOSE", so most applications do not need code changes.RelayPool.reqandRelayGroup.reqforward the structured message objects for advanced callers; use them only when you also opt into the low-level flow.- Recommendation – Stick with
.request()/.subscription()unless you need streaming metadata such asOPEN/NOTICEmessages or custom completion triggers. The higher-level methods continue to be the supported path for typical UI and action code. - The
eoseTimeoutoption disappeared;req-based flows now rely on thecompleteWhenoperator or the built-inRelay.completeOr(...)helpers to finish a request.
5.2 Relay group completion conditions
RelayGroup.request()now defaults toRelayGroup.completeOnAny(RelayGroup.completeAfterFirstRelay(5_000), RelayGroup.completeOnAllEose())(seepackages/relay/src/group.ts). That means the stream keeps running after the first relay hits EOSE: it either waits five seconds for more relays to answer or until every relay reports EOSE/ERROR, whichever happens first.- Pass
opts.completeto override the default with your ownGroupRequestCompleteOperator. Applesauce ships helpers you can compose:RelayGroup.completeAfterFirstRelay(timeout)– completetimeoutms after the first relay emits EOSE.RelayGroup.completeOnAllEose()– wait until every relay is no longer OPEN.RelayGroup.completeOnAny(...)/.completeOnAll(...)– OR/AND combinators for multiple strategies.
- Example: stop after collecting 50 events or when all relays finish, whichever occurs first.
import { RelayGroup } from "applesauce-relay";
import { filter, mapTo, take } from "rxjs";
const custom = RelayGroup.completeOnAny(
(source) =>
source.pipe(
filter((m) => m.type === "EVENT"),
take(50),
mapTo(true),
),
RelayGroup.completeOnAllEose(),
);
group.request(filters, { complete: custom }).subscribe(handleEvent);Use these operators when migrating any bespoke completion logic that previously depended on eoseTimeout or implicit "EOSE" strings.
5.2 Operators
markFromRelayandtoEventStorewere removed. Drop them from import lists.- New operator:
import { completeWhen } from "applesauce-relay/operators";— complete a request when a predicate stream emits truthy values. - Updated operators (
completeOnEose,onlyEvents,storeEvents) accept the new message unions.
5.3 Example migration
// Before: relying on implicit string EOSE
pool.subscription(relays, filters).pipe(
takeWhile((msg) => msg !== "EOSE"),
markFromRelay(relayUrl),
);
// After: structured messages + built-in tagging
pool
.req(filters, { relays })
.pipe(
completeWhen(Relay.completeOr(Relay.completeOnAllEose())),
filter((msg) => msg.type === "EVENT"),
map((msg) => msg.event),
)
.subscribe(store.add);6. AI-friendly refactor algorithm
- Search all
.ts,.tsx,.js,.mjsfiles for these patterns:
- Module specifiers containing
applesauce-factory,applesauce-core/event-factory,.blueprints,.helpers/buildEvent,markFromRelay,toEventStore,FactoryProvider,FactoryContext,useEventFactory, or string comparisons to"EOSE".
- Classify each match:
- If it references blueprints/helpers → swap to the corresponding factory import from
applesauce-*/factories. - If it references React factory context → instantiate the needed factory inline and remove the provider/hook usage.
- If it references relay operators or compares to
"EOSE"→ importcompleteWhen/onlyEventsand switch onmessage.type.
- Rewrite import blocks so each module only imports from its new location (core factories, common factories, wallet factories, relay operators, etc.).
- Verify by running your project's automated tests (unit, integration, or end-to-end) plus any manual flows that exercise factories, relays, and React bindings.
7. Verification steps
- Reinstall your project dependencies (npm/pnpm/yarn) so that every
applesauce-*package resolves to the v6 release. - Run the unit/integration/end-to-end suites in your own application, with extra attention on features that now import factories or talk to relays.
- Rebuild whatever artifacts you publish (Typedoc, Storybook, internal docs) to remove references to
FactoryProvider, blueprints, or other removed APIs. - If you maintain automation (codemods, LLM prompts, CI scripts) that depended on the removed symbols, rerun them against a sample project to confirm the new search-and-replace logic works.
Following these steps will migrate a v5 project to v6 without relying on the deprecated factory context, blueprints, or relay behaviors.
