Skip to content

App evolution

When building an app with LiveStore, you’ll need to keep some things in mind when evolving your app.

Generally any kind of changes to your state schema (e.g. SQLite tables, …) can be done at any time without any further considerations assuming the event materializer is updated to support the new schema.

Event schema changes require a bit more consideration. Changes to the event schema should generally be done in a backwards-compatible way. See Event schema evolution for more details.

In scenarios where you have multiple app versions rolled out in parallel (e.g. app version v3 with event schema v3 and app version v4 with event schema v4), you’ll need to keep the following in mind:

App instances running version 4 might commit events that are not yet supported by version 3. Your app needs to decide how to handle this scenario in one of the following ways:

  • Ignore unknown events
  • Cause an error in the app for unknown events
  • Handle events with a “catch all” event handler
  • Let app render a “app update required” screen. App can still be used in read-only mode.

LiveStore exposes a dedicated unknownEventHandling configuration on makeSchema so you can codify the desired behaviour instead of sprinkling ad-hoc checks across your app. The default is 'warn', which logs every unknown event and keeps processing.

const
const _schema: FromInputSchema.DeriveSchema<{
events: {
readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
};
state: InternalState;
unknownEventHandling: {
strategy: "callback";
onUnknownEvent: (event: UnknownEventContext, error: UnknownEventError) => void;
};
}>
_schema
=
makeSchema<{
events: {
readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
};
state: InternalState;
unknownEventHandling: {
strategy: "callback";
onUnknownEvent: (event: UnknownEventContext, error: UnknownEventError) => void;
};
}>(inputSchema: {
events: {
readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct<Fields extends Schema.Struct.Fields>.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
};
state: InternalState;
unknownEventHandling: {
strategy: "callback";
onUnknownEvent: (event: UnknownEventContext, error: UnknownEventError) => void;
};
}): FromInputSchema.DeriveSchema<...>
makeSchema
({
events: {
readonly todoCreated: State.SQLite.EventDef<"v1.TodoCreated", Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Type">, Schema.Struct.ReadonlySide<{
readonly id: Schema.String;
readonly text: Schema.String;
}, "Encoded">>;
}
events
,
state: InternalState
state
,
unknownEventHandling: {
strategy: "callback";
onUnknownEvent: (event: UnknownEventContext, error: UnknownEventError) => void;
}
unknownEventHandling
: {
strategy: "callback"
strategy
: 'callback',
onUnknownEvent: (event: UnknownEventContext, error: UnknownEventError) => void
onUnknownEvent
: (
event: UnknownEventContext
event
,
error: UnknownEventError
error
) => {
var console: Console
console
.
Console.warn(...data: any[]): void (+2 overloads)

The console.warn() static method outputs a warning message to the console at the 'warning' log level.

MDN Reference

warn
('LiveStore saw an unknown event', {
event: UnknownEventContext
event
,
reason: "event-definition-missing" | "materializer-missing"
reason
:
error: UnknownEventError
error
.
reason: "event-definition-missing" | "materializer-missing"
reason
})
},
},
})

Set the strategy to 'ignore' to silently skip forward-only events, 'fail' to stop immediately (useful during development), or 'callback' to forward them to custom telemetry while continuing to replay the log.