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 and emitNostrEventvalues; Group and Pool high-level methods now error withRelayGroupErrorwhen every active relay fails.req()is a policy-free, one-interaction lifecycle stream. Authentication, reconnect, and repeat belong torequest()andsubscription().- Relay, Group, and Pool subscriptions have no built-in duration or inactivity timeout; compose RxJS
timeoutortakeUntil(timer(...))when the caller needs a bound. 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.
Custom completion can still finish intentionally early. If its condition and total active-cohort failure arise from the same final relay error, RelayGroupError wins. Low-level RelayGroup.req() and RelayPool.req() retain structured lifecycle/error messages; the aggregate belongs only to high-level request() and subscription() Observables.
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.