Skip to content

Schema registry

For larger codebases with many event types, knowing what exists becomes a problem. Each PersistentActor declares its events; some have adapters; versions live inside the adapter configs. No central catalog.

SchemaRegistry is the optional catalog:

import { SchemaRegistry } from 'actor-ts';
export const registry = new SchemaRegistry()
.add('Deposited', 2)
.add('Withdrawn', 1)
.add('AccountFrozen', 3)
.add('AccountClosed', 1);

A simple typed map of event-name → current-version. Use it to:

  • Document the current state of every schema.
  • Validate adapters declare the right version.
  • Drive tooling — admin dashboards, dev-tools showing journal contents.

Most projects don’t need it. Reach for it when you have 10+ event types and find yourself manually tracking which is at which version.

import { SchemaRegistry } from 'actor-ts';
// Shared module — `schemas.ts`:
export const registry = new SchemaRegistry()
.add('Deposited', 2)
.add('Withdrawn', 1)
.add('Frozen', 3);
export type SchemaName = 'Deposited' | 'Withdrawn' | 'Frozen';

Then in actors:

import { registry } from './schemas.js';
class Account extends PersistentActor<...> {
override eventAdapter() {
return new MigratingAdapter<DepositedEventV2>({
currentVersion: registry.versionOf('Deposited'), // → 2
chain: [...],
});
}
}

The adapter’s currentVersion and the registry’s value must agree. Pull from the registry to enforce.

class SchemaRegistry {
add(name: string, version: number): SchemaRegistry;
versionOf(name: string): number;
has(name: string): boolean;
list(): Array<{ name: string; version: number }>;
}
  • add(name, version) — register a schema. Returns the registry for chaining.
  • versionOf(name) — read the current version. Throws if unregistered.
  • has(name) — check existence.
  • list() — enumerate everything registered.

Immutable: add() returns a new registry; the original isn’t mutated. Useful for test scaffolding where you want fresh registries per test.

import { SchemaRegistry, validateAdapter } from 'actor-ts';
// During tests / startup:
const adapter = new MigratingAdapter<EventV2>({ currentVersion: 2, chain: [...] });
validateAdapter(adapter, registry, 'Deposited');
// → throws if currentVersion(2) !== registry.versionOf('Deposited')

For production code, call validateAdapter at system startup as a safety net: if you bump an adapter’s version but forget to update the registry, the validate call fails loud.

A few common tooling shapes that benefit from the registry:

// Admin panel: list every event type and its current version
const all = registry.list(); // → [{ name: 'Deposited', version: 2 }, ...]
// Dev script: warn when an event in the journal has a version
// HIGHER than what the registry knows (a deploy issue)
for await (const event of query.eventsByPersistenceId(...)) {
const envelopeVersion = event.event._v;
const expectedMax = registry.versionOf(event.event._t);
if (envelopeVersion > expectedMax) {
console.warn(`event ${event.event._t} has version ${envelopeVersion}, registry knows up to ${expectedMax}`);
}
}

The SchemaRegistry API reference covers the full surface.