Subscription Reference
Subscriptions replicate database rows to your client in real-time. When you subscribe to a query, SpacetimeDB sends you the matching rows immediately and then pushes updates whenever those rows change.
Quick Start
Here's a complete example showing how to subscribe to data and react to changes:
- TypeScript
- C#
- Rust
import { DbConnection, User, Message } from './module_bindings';
// Connect to the database
const conn = DbConnection.builder()
.withUri('wss://maincloud.spacetimedb.com')
.withModuleName('my_module')
.onConnect((ctx) => {
// Subscribe to users and messages
ctx.subscriptionBuilder()
.onApplied(() => {
console.log('Subscription ready!');
// Initial data is now in the client cache
for (const user of ctx.db.user.iter()) {
console.log(`User: ${user.name}`);
}
})
.subscribe(['SELECT * FROM user', 'SELECT * FROM message']);
})
.build();
// React to new rows being inserted
conn.db.user.onInsert((ctx, user) => {
console.log(`New user joined: ${user.name}`);
});
// React to rows being deleted
conn.db.user.onDelete((ctx, user) => {
console.log(`User left: ${user.name}`);
});
// React to rows being updated
conn.db.user.onUpdate((ctx, oldUser, newUser) => {
console.log(`${oldUser.name} changed name to ${newUser.name}`);
});// Connect to the database
var conn = DbConnection.Builder()
.WithUri("wss://maincloud.spacetimedb.com")
.WithModuleName("my_module")
.OnConnect((ctx) =>
{
// Subscribe to users and messages
ctx.SubscriptionBuilder()
.OnApplied(() =>
{
Console.WriteLine("Subscription ready!");
// Initial data is now in the client cache
foreach (var user in ctx.Db.User.Iter())
{
Console.WriteLine($"User: {user.Name}");
}
})
.Subscribe(new[] { "SELECT * FROM user", "SELECT * FROM message" });
})
.Build();
// React to new rows being inserted
conn.Db.User.OnInsert += (ctx, user) =>
{
Console.WriteLine($"New user joined: {user.Name}");
};
// React to rows being deleted
conn.Db.User.OnDelete += (ctx, user) =>
{
Console.WriteLine($"User left: {user.Name}");
};
// React to rows being updated
conn.Db.User.OnUpdate += (ctx, oldUser, newUser) =>
{
Console.WriteLine($"{oldUser.Name} changed name to {newUser.Name}");
};// Connect to the database
let conn = DbConnection::builder()
.with_uri("wss://maincloud.spacetimedb.com")
.with_module_name("my_module")
.on_connect(|ctx| {
// Subscribe to users and messages
ctx.subscription_builder()
.on_applied(|ctx| {
println!("Subscription ready!");
// Initial data is now in the client cache
for user in ctx.db.user().iter() {
println!("User: {}", user.name);
}
})
.subscribe(["SELECT * FROM user", "SELECT * FROM message"]);
})
.build();
// React to new rows being inserted
conn.db().user().on_insert(|ctx, user| {
println!("New user joined: {}", user.name);
});
// React to rows being deleted
conn.db().user().on_delete(|ctx, user| {
println!("User left: {}", user.name);
});
// React to rows being updated
conn.db().user().on_update(|ctx, old_user, new_user| {
println!("{} changed name to {}", old_user.name, new_user.name);
});How Subscriptions Work
- Subscribe: Register SQL queries describing the data you need
- Receive initial data: SpacetimeDB sends all matching rows immediately
- Receive updates: When subscribed rows change, you get real-time updates
- React to changes: Use row callbacks (
onInsert,onDelete,onUpdate) to handle changes
The client maintains a local cache of subscribed data. Reading from the cache is instant since it's local memory.
For more information on subscription SQL syntax see the SQL docs.
API Reference
This section describes the two main interfaces: SubscriptionBuilder and SubscriptionHandle.
SubscriptionBuilder
- TypeScript
- C#
- Rust
interface SubscriptionBuilder {
// Register a callback to run when the subscription is applied.
onApplied(callback: (ctx: SubscriptionEventContext) => void): SubscriptionBuilder;
// Register a callback to run when the subscription fails.
// This callback may run when attempting to apply the subscription,
// or later during the subscription's lifetime if the module's interface changes.
onError(callback: (ctx: ErrorContext, error: Error) => void): SubscriptionBuilder;
// Subscribe to the following SQL queries.
// Returns immediately; callbacks are invoked when data arrives from the server.
subscribe(querySqls: string[]): SubscriptionHandle;
// Subscribe to all rows from all tables.
// Intended for applications where memory and bandwidth are not concerns.
subscribeToAllTables(): void;
}public sealed class SubscriptionBuilder
{
/// <summary>
/// Register a callback to run when the subscription is applied.
/// </summary>
public SubscriptionBuilder OnApplied(
Action<SubscriptionEventContext> callback
);
/// <summary>
/// Register a callback to run when the subscription fails.
///
/// Note that this callback may run either when attempting to apply the subscription,
/// in which case <c>Self::on_applied</c> will never run,
/// or later during the subscription's lifetime if the module's interface changes,
/// in which case <c>Self::on_applied</c> may have already run.
/// </summary>
public SubscriptionBuilder OnError(
Action<ErrorContext, Exception> callback
);
/// <summary>
/// Subscribe to the following SQL queries.
///
/// This method returns immediately, with the data not yet added to the DbConnection.
/// The provided callbacks will be invoked once the data is returned from the remote server.
/// Data from all the provided queries will be returned at the same time.
///
/// See the SpacetimeDB SQL docs for more information on SQL syntax:
/// <a href="pathname:///docs/sql">pathname:///docs/sql</a>
/// </summary>
public SubscriptionHandle Subscribe(
string[] querySqls
);
/// <summary>
/// Subscribe to all rows from all tables.
///
/// This method is intended as a convenience
/// for applications where client-side memory use and network bandwidth are not concerns.
/// Applications where these resources are a constraint
/// should register more precise queries via <c>Self.Subscribe</c>
/// in order to replicate only the subset of data which the client needs to function.
/// </summary>
public void SubscribeToAllTables();
}pub struct SubscriptionBuilder<M: SpacetimeModule> { /* private fields */ }
impl<M: SpacetimeModule> SubscriptionBuilder<M> {
/// Register a callback that runs when the subscription has been applied.
/// This callback receives a context containing the current state of the subscription.
pub fn on_applied(mut self, callback: impl FnOnce(&M::SubscriptionEventContext) + Send + 'static);
/// Register a callback to run when the subscription fails.
///
/// Note that this callback may run either when attempting to apply the subscription,
/// in which case [`Self::on_applied`] will never run,
/// or later during the subscription's lifetime if the module's interface changes,
/// in which case [`Self::on_applied`] may have already run.
pub fn on_error(mut self, callback: impl FnOnce(&M::ErrorContext, crate::Error) + Send + 'static);
/// Subscribe to a subset of the database via a set of SQL queries.
/// Returns a handle which you can use to monitor or drop the subscription later.
pub fn subscribe<Queries: IntoQueries>(self, query_sql: Queries) -> M::SubscriptionHandle;
/// Subscribe to all rows from all tables.
///
/// This method is intended as a convenience
/// for applications where client-side memory use and network bandwidth are not concerns.
/// Applications where these resources are a constraint
/// should register more precise queries via [`Self::subscribe`]
/// in order to replicate only the subset of data which the client needs to function.
pub fn subscribe_to_all_tables(self);
}
/// Types which specify a list of query strings.
pub trait IntoQueries {
fn into_queries(self) -> Box<[Box<str>]>;
}A SubscriptionBuilder provides an interface for registering subscription queries with a database.
It allows you to register callbacks that run when the subscription is successfully applied or when an error occurs.
Once applied, a client will start receiving row updates to its client cache.
A client can react to these updates by registering row callbacks for the appropriate table.
Example Usage
- TypeScript
- C#
- Rust
// Establish a database connection
import { DbConnection } from './module_bindings';
const conn = DbConnection.builder()
.withUri('https://maincloud.spacetimedb.com')
.withModuleName('my_module')
.build();
// Register a subscription with the database
const userSubscription = conn
.subscriptionBuilder()
.onApplied((ctx) => { /* handle applied state */ })
.onError((ctx, error) => { /* handle error */ })
.subscribe(['SELECT * FROM user', 'SELECT * FROM message']);// Establish a database connection
var conn = ConnectToDB();
// Register a subscription with the database
var userSubscription = conn
.SubscriptionBuilder()
.OnApplied((ctx) => { /* handle applied state */ })
.OnError((errorCtx, error) => { /* handle error */ })
.Subscribe(new string[] { "SELECT * FROM user", "SELECT * FROM message" });// Establish a database connection
let conn: DbConnection = connect_to_db();
// Register a subscription with the database
let subscription_handle = conn
.subscription_builder()
.on_applied(|ctx| { /* handle applied state */ })
.on_error(|error_ctx, error| { /* handle error */ })
.subscribe(["SELECT * FROM user", "SELECT * FROM message"]);