# App evolution

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

## Schema changes

### State schema changes

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

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](/building-with-livestore/events#schema-evolution) for more details.

## Parallel different app versions

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.


## `reference/events/unknown-event-handling.ts`

```ts filename="reference/events/unknown-event-handling.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

// ---cut---

const _schema = makeSchema({
  events,
  state,
  unknownEventHandling: {
    strategy: 'callback',
    onUnknownEvent: (event, error) => {
      console.warn('LiveStore saw an unknown event', { event, reason: error.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.