The Models SDK enables you to keep your frontend applications up to date with your backend database. A model is a single instance of live, observable data. It uses a sync function to initialize with the latest data and a merge function to update its state upon receiving change events from your backend database.
To receive notifications when there are changes in the state, subscribe to changes.
Create a model using the
models.get() method on the client. If you specify a name that already exists, it will be returned.
Instantiate a model using a unique name. A unique name identifies the model on the client and corresponds to the channel name used to subscribe to state updates from the backend:
The sync function instructs your model to initialize with the latest data from the backend and is necessary when creating a model. It can be any function that optionally accepts parameters and returns a promise with the latest state of your data model, along with a
The following is an example sync function that fetches the latest model state from your database:
The following example is an output from the sync function:
The sync function needs to be registered when a model is instantiated:
Call the sync function directly on the model to bootstrap its state during the initial page load:
The SDK automatically attempts to re-execute the sync function in the case of an error, as specified by the
syncOptions.retryStrategy defined in your
sequenceID enables the SDK to identify the point in the stream of change events that corresponds to the current version of the database’s state.
Internally, the SDK replays change events starting from the point indicated by the
sequenceID to update the model state with any realtime changes occurring since the state was read from the database.
The sync function calls your backend endpoint to retrieve the highest
sequenceID present in the outbox when reading the state:
Use the read committed transaction isolation level for the correct function, when reading the database state. PostgreSQL uses this as default.
The SDK must initialize the model’s state using the sync function and then precisely apply a sequence of change events from your backend, beginning at the correct point in the stream. This starting point is the
sequenceID your backend endpoint returns.
After the SDK executes the sync function, it internally connects to the channel and begins subscribing to live, yet unprocessed, messages. It then paginates backward through the channel’s message history, starting from the attachment point, using
untilAttach. The number of items on each page can be configured via the
syncOptions.historyPageSize defined in your
When a message is sent to the
sequenceID, the model processes subsequent messages to maintain continuity with live messages. If the target
sequenceID is unreachable, there is insufficient message history to resume from the correct point to update the model. The SDK will attempt a re-sync. It re-syncs by calling the sync function again to acquire a newer version of the model state.
The amount of history available to query on the channel is determined by your message storage configuration on the channel. This configuration must match the
syncOptions.messageRetentionPeriod defined in your
ClientOptions. The SDK uses this configuration option as a hint as to whether to resynchronize via the sync function and skip paginating through history when messages are expected to have expired.
To ensure the model’s state is up to date, cache the state obtained from the backend endpoint used by the sync function. The model state returned from this endpoint must not exceed the message retention period set on the channel. If no previous messages exist on the channel, the model is assumed to be a new state without any modifications.
A merge function is required when you instantiate a model. The merge function instructs your model how to compute the next version of the model state upon receiving a change event from the backend. The merge function takes the previous model state and the change event as inputs and outputs the next version of the model state.
The following example is a merge function, invoked for all events received on the channel name specified in the model:
The merge function needs to be registered when a model is instantiated:
The event passed to the merge function can be either confirmed or optimistic:
- change events received from your backend. They describe the result of a change to the data which has been committed to your database.
- events that describe mutations that have happened locally, but have not yet been confirmed by your backend.
The SDK might call the merge function multiple times with the same state and event; to rebase optimistic events onto the newly confirmed state. It’s essential that the merge function, when executed with identical inputs, consistently produces the same output. The merge function should be pure and deterministic, not depending on any external state.
Change events from your backend are delivered to the SDK as messages over the channels. By default, messages are processed by the SDK in the order in which they are received.
The SDK supports a short buffering time buffering for change events, facilitating short-term reordering and de-duplication. By default, his feature is not enabled. To activate it, set the
eventBufferOptions.bufferMS in the
ClientOption to a non-zero value.
By default, the events in the buffer are ordered numerically if the
message.ID can be coerced to a number. Otherwise, events will be ordered lexicographically by their
message.ID. You can specify a custom ordering based on any part of the message via the
The Models SDK supports optimistic updates, a feature that enables you to immediately render changes to your data model before the backend confirms the changes, making updates appear instantaneous.
Optimistic events are used to make local, provisional changes to your data, anticipating that your backend will eventually confirm or reject these changes. The optimistic event will be processed by your merge function and included in the local model state optimistically.
model.optimistic() method to apply an optimistic update to your model.
model.optimistic()on your model with the optimistic event.
- Apply the corresponding change to your backend.
- Await the confirmation of the optimistic update from the backend, or optionally cancel the optimistic update.
model.optimistic() function returns a promise resolving to two values:
- A confirmation promise that resolves when the backend confirms or rejects the optimistic update.
- A function to explicitly cancel the optimistic update.
The following demonstrates an optimistic update to implementing changes in the model:
The SDK requires a mechanism to identify when a change event received from the backend matches an optimistic event that has already been applied locally.
To achieve this, your clients need to assign a unique
mutationID to each optimistic event. This ID can be any string, though it’s commonly a UUID. The
mutationID must be sent to your backend and included in the confirmation event that your backend writes to the outbox:
The SDK automatically rewinds the optimistic event and rejects the associated confirmation promise if the corresponding confirmed change event is not received from the backend within a specified timeframe. You can configure this timeout period using the
You can also broadcast a rejection event from your backend in order to explicitly reject a given optimistic update. This is achieved by setting the rejected flag to true in the outbox record:
You can reject optimistic updates by broadcasting a rejection event from your backend. Set the rejected flag to true in the outbox record:
To receive realtime notifications when there are changes in the state, you can subscribe to changes to the changes.
The following example subscribes to model state changes:
Subscriptions operate on an optimistic model by default. The callback is triggered whenever the optimistic state changes or confirmed changes occur.
To respond solely to confirmed changes, set the
optimistic option to
false in the
A model instance can be in one of the following lifecycle states:
- the model has been initialized but has not yet attached to the underlying channel.
- the model is synchronizing its state from the backend.
- the model is attached to the channel and processing realtime.
- the user has paused the model.
- the model has errored processing data from the sync, or from the stream.
- the model has been disposed, either by the user disposing it or an unrecoverable error.
Listen for model state change events on a model instance:
You can pause a model to prevent the model from consuming realtime updates, while reserving the ability to resume it at some point in the future. Pausing can be useful in instances such as when the UI rendering the model data is temporarily out-of-view.
The following pauses the model. New events will not be processed and subscription callbacks will not be invoked:
The following example un-pauses the model. Event processing will resume and changes will be made available to subscribers:
When a model is no longer needed, disposed of it to free up resources: