spacetimedb

pub package License: Apache-2.0

Dart SDK for SpacetimeDB v2. Real-time sync, BSATN codec, code generation, and offline-first support.

spacetimedb provides the runtime client, BSATN codec, and generator tooling needed to build type-safe Flutter/Dart apps on top of SpacetimeDB v2. It supports live table updates via the modern SubscribeMulti protocol, server-defined views, reducer calls, authentication, and offline mutation replay.

Compatibility

SDK Version SpacetimeDB Server Protocol
1.2.0+ v2.0+ SubscribeMulti (modern)
0.1.0 v1.12.x Subscribe (legacy)

Features

  • SpacetimeDB v2 support — SubscribeMulti protocol, server-defined views, updated message format
  • WebSocket connection with reconnect handling and TLS support
  • BSATN binary encoding/decoding for SpacetimeDB data types
  • Generated, type-safe APIs for tables, reducers, enums, and views
  • Real-time table cache with synchronous insert/update/delete streams
  • Subscription API for SQL-based live queries (multi-query batching)
  • Authentication utilities with token persistence support
  • Offline-first mutation queue with optimistic updates
  • Web-safe storage with conditional imports (no dart:io dependency on web)

Installation

dependencies:
  spacetimedb: ^1.2.0

Then install dependencies:

dart pub get

Quick Start

Generate a typed client from your SpacetimeDB module:

dart run spacetimedb:generate -s http://localhost:3000 -d your_database -o lib/generated

Use the generated client in your app:

import 'package:spacetimedb/spacetimedb.dart';
import 'generated/client.dart';

final client = await SpacetimeDbClient.connect(
  host: 'localhost:3000',
  database: 'your_database',
  ssl: false,
  authStorage: InMemoryTokenStore(),
  initialSubscriptions: ['SELECT * FROM users'],
);

Usage

Connection

final client = await SpacetimeDbClient.connect(
  host: 'localhost:3000',
  database: 'your_database',
  ssl: false,
  authStorage: InMemoryTokenStore(),
);

client.connection.connectionStatus.listen((status) {
  print('Connection status: $status');
});

Tables

for (final user in client.users.iter()) {
  print('User: ${user.name}');
}

client.users.insertStream.listen((user) {
  print('Inserted user: ${user.name}');
});

Views (SpacetimeDB v2)

Views are server-defined, identity-scoped projections. The code generator produces typed view accessors alongside tables:

// Views work like tables — same cache and stream APIs
for (final game in client.myGames.iter()) {
  print('Game: ${game.id}, status: ${game.status}');
}

// Subscribe to view changes
client.myPlayerData.updateStream.listen((update) {
  print('Energy changed: ${update.old.energy} → ${update.new_.energy}');
});

Reducers

final result = await client.reducers.createUser(name: 'Alice');
if (result.isSuccess) {
  print('Reducer call succeeded');
}

Subscriptions (SubscribeMulti)

The SDK uses the modern SubscribeMulti protocol for efficient multi-query subscription batching:

await client.subscriptions.subscribe([
  'SELECT * FROM users WHERE active = true',
  'SELECT * FROM games WHERE status = "active"',
]);

Authentication

final client = await SpacetimeDbClient.connect(
  host: 'spacetimedb.example.com',
  database: 'app_db',
  ssl: true,
  authStorage: InMemoryTokenStore(),
);

print(client.identity?.toHexString);

Offline Support

final client = await SpacetimeDbClient.connect(
  host: 'localhost:3000',
  database: 'your_database',
  offlineStorage: JsonFileStorage(basePath: '/tmp/spacetimedb_cache'),
);

print('Pending mutations: ${client.syncState.pendingCount}');

Code Generation

Use the bundled executable to generate strongly-typed Dart APIs:

dart run spacetimedb:generate -s http://localhost:3000 -d your_database -o lib/generated

The generator produces typed code for:

  • Tables — Typed row classes with primary key access, iteration, and change streams
  • Views — Same API as tables, for server-defined filtered projections
  • Reducers — Typed caller methods with Future-based results
  • Sum types — Dart enums and sealed classes for SpacetimeDB enums

You can also generate from a local module path:

dart run spacetimedb:generate -p path/to/spacetimedb-module -o lib/generated

API Overview

API Purpose
SpacetimeDbClient.connect(...) Connect to a SpacetimeDB database and initialize generated APIs
client.<table>.iter() Read cached table rows with typed iteration
client.<table>.insertStream Listen for real-time inserts (synchronous delivery)
client.<table>.updateStream Listen for real-time updates with old/new values
client.<table>.deleteStream Listen for real-time deletes
client.<view>.iter() Read cached view rows (identity-scoped)
client.reducers.<name>(...) Call reducers with typed parameters/results
client.subscriptions.subscribe([...]) Start SubscribeMulti live SQL subscriptions
BsatnEncoder / BsatnDecoder Encode/decode BSATN payloads
AuthTokenStore Plug in custom token persistence
OfflineStorage Persist cached data and mutation queue for offline-first flows

Architecture

┌─────────────────────────────────────────────────┐
│                Flutter / Dart App               │
├─────────────────────────────────────────────────┤
│  Generated Client (tables, views, reducers)     │
├─────────────────────────────────────────────────┤
│  spacetimedb SDK                                │
│  ├── TableCache (sync broadcast streams)        │
│  ├── SubscriptionManager (SubscribeMulti)       │
│  ├── ReducerCaller (with offline queue)         │
│  ├── SpacetimeDbConnection (WebSocket + auth)   │
│  └── BSATN Codec (binary encode/decode)         │
├─────────────────────────────────────────────────┤
│  WebSocket (Protobuf) ←→ SpacetimeDB v2 Server  │
└─────────────────────────────────────────────────┘

Platform Support

Platform Runtime Code Generation Offline (File)
Android Yes Yes Yes
iOS Yes Yes Yes
macOS Yes Yes Yes
Windows Yes Yes Yes
Linux Yes Yes Yes
Web Yes N/A No*

* Web builds use InMemoryOfflineStorage. File-based JsonFileStorage requires dart:io and is not available on web. The SDK automatically provides a web-compatible stub that throws UnsupportedError if you try to use JsonFileStorage on web.

Security Considerations

  • Offline storage is unencrypted. Table snapshots and pending mutations are stored as plaintext JSON. Do not persist sensitive data (passwords, tokens, PII) without app-level encryption.
  • Use ssl: true in production. Without SSL, authentication tokens are sent in plaintext over the network.
  • Web platform auth tokens are passed as URL query parameters (WebSocket API limitation). These tokens are short-lived, but may appear in proxy logs. Always use SSL in production.
  • Token storage is pluggable via AuthTokenStore. For production apps, implement a secure storage backend (e.g., flutter_secure_storage).

Logging

By default, the SDK produces no log output. To enable logging:

// Option 1: Route to dart:developer (visible in DevTools)
SdkLogger.enableDeveloperLog();

// Option 2: Custom callback
SdkLogger.onLog = (level, message) {
  // 'D' = debug, 'I' = info, 'W' = warning, 'E' = error
  print('[$level] $message');
};

License

Apache License 2.0. See LICENSE.


Attribution

This project was originally forked from spacetimedb-dart-sdk by Mikael Wills. The original work provided the foundation for the WebSocket connection, BSATN codec, and initial code generation architecture.

Libraries

spacetimedb