Wrap legacy
Это содержимое пока не доступно на вашем языке.
The framework’s envelope format only kicks in when an adapter is configured. Before you set an adapter, events are stored raw:
{ "kind": "deposited", "amount": 100 }Once an adapter is in place, new events are wrapped in an envelope:
{ "_v": 1, "_t": "BankAccount.deposited", "_e": { "kind": "deposited", "amount": 100 } }That creates a problem when you add an adapter to an existing
journal: the old raw events have no envelope, but the adapter
expects one. Reading the first raw event throws MigrationError.
The fix is a one-shot bulk migration: walk the journal once, wrap every raw event as a version-1 envelope, write it back. From then on your normal defaultsAdapter / migratingAdapter replays them like any other v1 event. This is a real rewrite of the stored data, not a read-time shim.
In-memory journal
Section titled “In-memory journal”For the in-memory journal, migrateInMemoryJournal does the whole
walk-and-rewrite. You supply a manifestFor that derives the
stable _t discriminator from each event:
import { migrateInMemoryJournal, formatMigrationResult } from 'actor-ts';
const result = await migrateInMemoryJournal( journal, (e: { kind: string }) => `BankAccount.${e.kind}`,);
console.log(formatMigrationResult('events', result));// → "events: 3 wrapped, 0 already enveloped, 3 inspected"Sequence numbers, timestamps, and tags are preserved — only the event payload is wrapped. Run this before the actor with the new adapter recovers.
Other backends
Section titled “Other backends”migrateInMemoryJournal relies on an internal _remapForMigration
hook that only the in-memory journal exposes. Cassandra / SQLite /
Postgres / S3 journals each need a backend-specific rewrite path
(SQL UPDATE, CQL UPDATE, S3 PUT). Use the pure per-row
primitive wrapEventAsEnvelope as the building block:
import { wrapEventAsEnvelope } from 'actor-ts';
// Inside your own per-row rewrite loop:for (const row of rows) { const enveloped = wrapEventAsEnvelope( row.event, (e) => `BankAccount.${e.kind}`, ); await backend.rewrite(row.id, enveloped); // enveloped === { _v: 1, _t: 'BankAccount.deposited', _e: row.event }}wrapEventAsEnvelope(event, manifestFor, version?) is pure and
idempotent — an event that already looks like an envelope is
returned unchanged, so re-running the migration is safe.
Snapshots
Section titled “Snapshots”Legacy raw snapshots have the same problem. migrateSnapshotStore
wraps the latest snapshot per persistence id (sourced from the
journal), using wrapStateAsEnvelope under the hood:
import { migrateSnapshotStore } from 'actor-ts';
const persistenceIds = await journal.persistenceIds();const result = await migrateSnapshotStore(store, persistenceIds, (s) => 'BankAccount.State');After the migration
Section titled “After the migration”Once the journal is migrated, the raw events are v1 envelopes. The actor uses a normal adapter — there’s no special “legacy-wrapping” adapter at read time:
class Account extends PersistentActor<Command, EventV2, State> { override eventAdapter(): EventAdapter<EventV2> { return defaultsAdapter<EventV2>({ manifest: 'BankAccount.deposited', currentVersion: 2, defaults: { 1: { currency: 'USD' } }, }); }}The manifest here must match the _t your manifestFor produced
during the migration.
Signatures
Section titled “Signatures”function wrapEventAsEnvelope<E>( event: E, manifestFor: (e: E) => string, version?: number, // version default 1): JournalEnvelope<E>;
function wrapStateAsEnvelope<S>( state: S, manifestFor: (s: S) => string, version?: number,): JournalEnvelope<S>;
function migrateInMemoryJournal<E>( journal: Journal, manifestFor: (e: E) => string, options?: { version?: number },): Promise<MigrationResult>;
function migrateSnapshotStore<S>( store: SnapshotStore, persistenceIds: ReadonlyArray<string>, manifestFor: (s: S) => string, options?: { version?: number },): Promise<MigrationResult>;
type MigrationResult = { inspected: number; // entries examined wrapped: number; // raw → envelope skipped: number; // already enveloped, left untouched};Pitfalls
Section titled “Pitfalls”Where to next
Section titled “Where to next”- Migration overview — the bigger picture.
- Envelope format — what an envelope looks like.
- defaultsAdapter — for additive migrations once events are enveloped.
- migratingAdapter — for chained transformations once events are enveloped.
- Recipes — the recipe for “I’m introducing adapters to an existing system.”
