Skip to content

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), and applesauce-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

  1. Factory architecture/packages/core/src/event-factory was deleted. Every builder now extends the Promise-based EventFactory that 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.
  2. React integration – The React package no longer exposes FactoryProvider, FactoryContext, or useEventFactory. Components are expected to instantiate factories directly.
  3. Relay flow – The new low-level relay.req endpoint emits structured { type: "EVENT" | "EOSE" | ... } messages for advanced REQ control, but .request() / .subscription() keep returning NostrEvent or "EOSE". CLOSED reasons now map to typed errors, and completion is opt-in through the completeWhen helper 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

  1. Replace any dependency on applesauce-factory or applesauce-core/event-factory helpers 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.
  1. Remove helper wrappers (buildEvent, modifyEvent, createEvent) and blueprint imports; none of those files exist in v6.
  2. Ensure package.json keeps all applesauce-* packages on the same major version before installing.

3.2 Rewrite factory usage

Use these before/after recipes when migrating existing code:

ts
// Before (v5)
import { EventFactory } from "applesauce-factory";

const signed = await EventFactory.fromKind(1).content(body).as(signer).sign();
ts
// 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 called modifyPublicTags, keep doing so via the fluent API (factory.modifyPublicTags(cb) still exists on EventFactory).
  • Fallback for unsupported kinds – When a typed factory does not exist, compose operations manually:
ts
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/helperReplacement factory class
NoteBlueprint, NoteReplyBlueprintNoteFactory.create(), NoteFactory.reply()
CommentBlueprint, ThreadCommentBlueprintCommentFactory.create(parent, content, options)
ProfileBlueprintProfileFactory.create() / .modify(event) (core)
ContactsBlueprintContactsFactory.create() / .modify(event)
GiftWrapBlueprintGiftWrapFactory.create(recipient, content)
ListBlueprint, RelaySetBlueprint, MuteListBlueprint, etc.ListFactory, RelaySetFactory, MuteListFactory, FavoriteRelaysFactory, etc. in applesauce-common/factories
Wallet WalletBlueprint, TokenBlueprint, ZapBlueprintWalletFactory, WalletTokenFactory, NutzapFactory from applesauce-wallet/factories
Wallet-connect request/response blueprintsWalletRequestFactory, 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

  1. Delete any <FactoryProvider> wrapper and remove FactoryContext imports. Only EventStoreProvider, AccountsProvider, and ActionsProvider remain in applesauce-react/providers.
  2. Replace useEventFactory() with direct factory instantiation. Example conversion for a button handler:
tsx
// 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();
  1. Account-managed signers integrate cleanly:
ts
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();
  1. Update any documentation, tutorials, or generators that still reference the removed hooks. See .changeset/clean-react-factory-context.md for the authoritative removal note.

5. Relay and operator updates

5.1 Low-level req shapes

  • Relay.req is 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 via switch instead of comparing to "EOSE".
  • relay.request() and relay.subscription() remain the simplest high-level APIs. They still emit NostrEvent values and the string "EOSE", so most applications do not need code changes.
  • RelayPool.req and RelayGroup.req forward 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 as OPEN/NOTICE messages or custom completion triggers. The higher-level methods continue to be the supported path for typical UI and action code.
  • The eoseTimeout option disappeared; req-based flows now rely on the completeWhen operator or the built-in Relay.completeOr(...) helpers to finish a request.

5.2 Relay group completion conditions

  • RelayGroup.request() now defaults to RelayGroup.completeOnAny(RelayGroup.completeAfterFirstRelay(5_000), RelayGroup.completeOnAllEose()) (see packages/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.complete to override the default with your own GroupRequestCompleteOperator. Applesauce ships helpers you can compose:
    • RelayGroup.completeAfterFirstRelay(timeout) – complete timeout ms 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.
ts
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

  • markFromRelay and toEventStore were 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

ts
// 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

  1. Search all .ts, .tsx, .js, .mjs files 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".
  1. 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" → import completeWhen/onlyEvents and switch on message.type.
  1. Rewrite import blocks so each module only imports from its new location (core factories, common factories, wallet factories, relay operators, etc.).
  2. 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

  1. Reinstall your project dependencies (npm/pnpm/yarn) so that every applesauce-* package resolves to the v6 release.
  2. 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.
  3. Rebuild whatever artifacts you publish (Typedoc, Storybook, internal docs) to remove references to FactoryProvider, blueprints, or other removed APIs.
  4. 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.