Skip to content

Releases: clockworklabs/SpacetimeDB

SpacetimeDB 2.0 - Release Candidate 1

20 Feb 22:03

Choose a tag to compare

Pre-release

SpacetimeDB 2.0 is almost here! 🌟

image

Today we're announcing SpacetimeDB 2.0 as a preview release. SpacetimeDB 2.0 has some big changes, so buckle up!

TypeScript/JavaScript Support

In October we launched beta support for TypeScript and JavaScript, and we're happy to announce that with the release of 2.0 TypeScript support is leaving beta! We also have a bunch of updates to the API that being much needed consistency to the APIs.

Web framework integration

Along with TypeScript and JavaScript support, we've built integrations and templates with all your favorite frameworks including:

  • Angular
  • React
  • Nuxt
  • Vue
  • Next.js
  • Node
  • Deno
  • TanStack
  • Remix
  • Svelte
  • Bun

Unreal Engine/C++ Support

For all those who have been waiting for official Unreal Engine 5 support, it's here! Alongside C++ modules as well!

Incredible performance

SpacetimeDB 2.0 delivers eye watering throughput even for tiny transactions with high contention. Well over 100k transactions per second for TypeScript modules and up to 170k transactions per second for Rust modules!

image

A new Maincloud FREE tier and all new pricing!

With 2.0, we're also announcing a brand new, simpler pricing scheme along with a free tier for users who want to try out Maincloud!

This new simplified pricing makes it easier than ever to predict your costs. Check out our pricing calculator on the pricing page.

Spacerace Referral Program

Alongside our new pricing, we're introducing the Spacerace Referral Program where you can get up to 400x more free credits per month by referring friends to the platform.

image

Procedures and HTTP calls

With 2.0 your SpacetimeDB modules are getting a big boost in capability too. For the first time, your modules can now access the outside world with Procedure Functions. Procedure functions give you the ability to make HTTP calls from within your module, letting you integrate directly with external APIs. This is how easy it is to call ChatGPT's API from within your module:

export const ask_ai = spacetimedb.procedure(
  { prompt: t.string(), apiKey: t.string() },
  t.string(),
  (ctx, { prompt, apiKey }) => {
    // Make the HTTP request to OpenAI
    const response = ctx.http.fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: 'gpt-4',
        messages: [{ role: 'user', content: prompt }],
      }),
      // Give it some time to think
      timeout: TimeDuration.fromMillis(3000),
    });

    if (response.status !== 200) {
      throw new SenderError(`API returned status ${response.status}`);
    }

    const data = response.json();
    const aiResponse = data.choices?.[0]?.message?.content;

    if (!aiResponse) {
      throw new SenderError('Failed to parse AI response');
    }

    // Store the conversation in the database
    ctx.withTx(txCtx => {
      txCtx.db.aiMessage.insert({
        user: txCtx.sender,
        prompt,
        response: aiResponse,
        createdAt: txCtx.timestamp,
      });
    });

    return aiResponse;
  }

SpacetimeAuth (and Clerk and Auth0)

And to help you avoid needing to set up additional sidecars and services alongside SpacetimeDB we've also introduced SpacetimeAuth, our first party auth solution. SpacetimeAuth is completely free to use with SpacetimeDB Maincloud!

We've also built tutorials for how to integrate with other auth providers like Clerk and Auth0!

View Functions

In SpacetimeDB 2.0, adding sophisticated read permissions to expose portions of your data to users is as simple as creating a function:

export const players_for_level = spacetimedb.anonymousView(
   { name: 'players_for_level', public: true },
   t.array(playerAndLevelRow),
   (ctx) => {
       const out: Array<{ id: bigint; name: string; level: bigint }> = [];
       for (const playerLevel of ctx.db.playerLevels.level.filter(2n)) {
           const p = ctx.db.players.id.find(playerLevel.player_id);
           if (p) out.push({ id: p.id, name: p.name, level: playerLevel.level });
       }
       return out;
   }
);

You can think of view functions as a table that is backed by a reducer. From the client, they look just like any other table in your database, but in the module they're represented as a function that returns rows that you want to expose to clients. Better yet, the table is automatically parameterized by the identity of the viewing client, allowing different clients to see different data when viewing the same table.

Views can also return custom types allowing you to not only filter rows, but also columns. Views are an elegant way of solving read permissions without introducing a single new concept!

Typed Query Builder

In SpacetimeDB 2.0, you can kiss your stringly-typed queries goodbye! With our 2.0 SDK and code generation, we generate beautifully typed query builder APIs that allows your clients to build arbitrary queries over the data.

ctx.subscriptionBuilder().subscribe([tables.user.where(user => user.name.eq("Tyler")), tables.message]);

Never worry about runtime errors with your queries again! And the strongly typed queries also help LLMs succeed more often as well.

Even better, you can access this same query builder API inside your view functions to return queries from your views so they can be optimized by our incremental evaluation query engine!

export const high_scorers = spacetimedb.anonymousView(
  { name: 'high_scorers', public: true },
  t.array(players.rowType),
  (ctx) => {
    return ctx.from.players
      .where(p => p.score.gte(1000n))
      .where(p => p.name.ne('BOT'));
  }
);

Event Tables

Event tables are also all new in SpacetimeDB 2.0. Event tables allow you to publish short lived event information to clients without needing to clean up your tables later on. Event tables act like regular tables, but the rows are automatically deleted by SpacetimeDB at the end of a transaction. This both saves you storage costs and also bandwidth, as the database needs to synchronize less state!

Publishing an event to clients is as simple as inserting into a row into the event table, and subscribing to it like any other table.

// server
const damageEvent = table({
  public: true,
  event: true,
}, {
  entity_id: t.identity(),
  damage: t.u32(),
  source: t.string(),
});

export const attack = spacetimedb.reducer(
  { target_id: t.identity(), damage: t.u32() },
  (ctx, { target_id, damage }) => {
    // Game logic...

    // Publish the event
    ctx.db.damageEvent.insert({
      entity_id: target_id,
      damage,
      source: "melee_attack",
    });
  }
);

// client
conn.db.damageEvent.onInsert((ctx, event) => {
  console.log(`Entity ${event.entityId} took ${event.damage} damage from ${event.source}`);
});

spacetime.json configuration

With 2.0, we're also introducing a new config file to make it easier to interact with your databases. Now with spacetime.json you can save all your database configuration in a single file that you check in to git, so instead of this:

spacetime generate --lang typescript --out-dir src/module_bindings
spacetime publish --server maincloud --module-path spacetimedb my-database

You can now just type this:

spacetime generate
spacetime publish

spacetime dev

Development is getting simpler too, with our new spacetime dev command. This command is your one stop shop for development. It:

  1. Watches for changes in your module code
  2. Compiles your module
  3. Generates your client module bindings
  4. Publishes your module
  5. Runs your client

Now all you have to do is change your code and your whole app updates instantly!

All new dashboard and metrics

We've also added beautiful new dashboards and analytics so you can monitor your apps and data in real-time! And they update in real-time too! It's incredible to see tables updating live, trust us!

Better LLM tools and benchmarks

SpacetimeDB is now also optimized for LLMs as well, both to help you understand SpacetimeDB and also to help you write your SpacetimeDB apps. New projects automatically ship with the appropriate AGENT.md files to help you get started immediately.

Project collaborators and organizations

For groups and teams, we've not introduced project collaborators and organizations. With project collaborators you can invite your friends and colleagues to build and publish modules with you.

Larger organizations can sign up for our Maincloud Team tier to reserve their team name and get advanced features for permissions management.

Postgres Wire Protocol Compatibility

If you've got existing tools that work with Postgres, they might work with SpacetimeDB now too! SpacetimeDB 2.0 supports the Postgres Wire Protocol, meaning you can use psql and other Postgres tools to query SpacetimeDB directly.

SpacetimeDB does not yet support the full suite of Postgres features, but this is the beginning of a long effort to make SpacetimeDB fully Postgres compatible.

Much much more

Over the past several months we've merged literally hundreds of bug fixes, UX improvements, and performance improvements. If you haven't tried Spacet...

Read more

Release v1.12.0 - Client-side Typed Query Builder

04 Feb 17:18

Choose a tag to compare

The end of 1.0 is nigh! 🤩

v1.12 will be the last minor version before we drop SpacetimeDB 2.0! SpacetimeDB 2.0 will come with code breaking changes (you may need to change your modules and clients), but it will not require a migration of the existing data directory, and include backwards compatibility with existing clients and modules, so all existing databases and modules will continue working on both Standalone deploys and Maincloud.

If you're not subscribed to our YouTube channel, you should be. Trust me on that one.

We've got some great updates today in v1.12!

Typed Query Builder

  • Added a typed query builder for the TypeScript client. (#4021)
  • Added a Rust client query builder. (#4003)
  • Added a typed query builder for the C# client, plus additional regression coverage. (#3982, #4123)

You can now subscribe to your server's data with type safe queries from clients:

TypeScript

import { queries } from "./module_bindings";
conn.subscriptionBuilder().subscribe([queries.user.build()]);

C#

conn.SubscriptionBuilder().AddQuery(ctx => ctx.From.User().Build()).Subscribe();

Rust

conn.subscription_builder().add_query(|ctx| ctx.from.user().build()).subscribe();

NOTE: We would like to remove the .build() for 2.0, but you can try it out with the existing API.

Other highlights

  • New front-end framework SDKs

    • Added a Vue framework SDK. (#4037)
    • Added a Svelte framework integration. (#4063)
  • Organizations support

    • Introduced Organizations support. (#4087)
  • Operational tooling improvements

    • Added spacetime login --no-browser for headless/CI environments. (#4142)
    • Core: added a method to query the replica IDs managed by the host controller. (#4160)

Developer experience & docs

  • Added new authentication tutorials:
  • Documentation updates and clarifications, including explaining “maincloud” context. (#4029, #4071, #4168)

Performance & internal improvements

  • Multiple datastore and update-path optimizations (construction, view evaluation fast paths, layout usage, and small data-structure refinements). (#4133, #4134, #4138, #4136)
  • Reduced JS worker request/reply sizes and improved data-structure choices for better cache efficiency. (#4150, #4151)

Reliability & correctness

  • Fixed Hash indices not working. (#4060)
  • Durability: notify waiters after lockfile drop. (#4051)
  • Fixed occasional panic when a client disconnects. (#4162)
  • Improved blob handling flexibility (copy_filter and related). (#4096)
  • Marked MutTxId as !Send to avoid incorrect cross-thread usage. (#4039)
  • Added support and filtering improvements around Uuid (including React Hook where support and making the new Uuid type filterable). (#4030, #3991)

New contributors

What's Changed

Read more

Release v1.11.3 - Bug fixes + Performance improvements

15 Jan 16:38
0244973

Choose a tag to compare

We've got another one for you! A bunch of small fixes including a critical feature to spacetime dev.

spacetime dev

spacetime dev now gets an additional --template argument, just like spacetime init. This allows you to quickly get started with SpacetimeDB on the CLI.

Bug Fixes & Improvements

  • Many small improvements for C# and Unreal.

What's Changed

  • Additional Hex parsing checks to ConnectionId and Identity by @rekhoff in #3988
  • Fix C# null string serialization error + add regression coverage for Issue #3960 by @rekhoff in #3967
  • Update 00300-rust.md by @cloutiertyler in #3943
  • Clarify Windows installation instructions in README by @dejaime in #3923
  • Combine table and index attributes in Rust example by @kistz in #3948
  • CI - LLM benchmark update uses clockworklabs-bot by @bfops in #3998
  • fix(codegen): Initialize UPROPERTY fields in generated Unreal C++ code by @brougkr in #3990
  • CI - LLM benchmark update uses clockwork-labs-bot by @bfops in #4004
  • Fix link to Blackholio demo in Unreal tutorial and update project structure in README by @homersimpsons in #3994
  • Misc docs and small CLI improvements by @cloutiertyler in #3953
  • CI - Decrease number of iterations for long-running UUID test by @bfops in #4014
  • deadlock fix: Make procedure tx abis synchronous by @Shubham8287 in #3968
  • Fixed SERVER_MSG_COMPRESSION_TAG_* descriptions by @egormanga in #4018
  • Provide a common Index trait & simplify and shrink TableIndex by @Centril in #3973
  • --feature no-core-pinning disables core pinning by @Centril in #3983
  • Time out database shutdown by @kim in #4019
  • Unreal SDK failing to handle fragmented messages by @JasonAtClockwork in #4002
  • fix(codegen/unreal): eliminate UObject memory leak in reducer event handlers by @brougkr in #3987
  • Fixes basic issues using the basic-react template. by @cloutiertyler in #4017
  • CI - "Fix" v8 error in TypeScript CI by @bfops in #4022
  • Add error handling for ranged seeks on non-range compat indices by @Centril in #3974
  • Make Uuid Nameable by @kistz in #4011
  • Docs - insert -> try_insert by @bfops in #4025
  • CI - Fix /update-llm-benchmark getting cancelled by @bfops in #4027
  • Add Hash indices by @Centril in #3976
  • Update authentication docs by @JulienLavocat in #4020
  • Despecialize direct indices with too large values into B-Tree indices by @Centril in #3978
  • Expand doc comment on FilterableValue with requirements to implement by @gefjon in #4024
  • Build all of the typescript templates in CI by @jsdt in #3980
  • Move Bound struct out of SpacetimeDB.Internal to SpacetimeDB and Local out of SpacetiemDB.Runtime by @rekhoff in #3996
  • Add Unreal Result type to support C++ and Blueprint by @JasonAtClockwork in #3834
  • Enable RefOption returns from Views to support Views returning a single class by @rekhoff in #3964
  • Version bump to 1.11.3 by @jdetter in #4041
  • Fix logic for ipv6 connections in is_port_available by @jdetter in #4005
  • docs: Add confirmed reads configuration to SDK reference docs by @kim in #4036
  • Add a feature flag no-job-core-pinning by @Centril in #4046
  • docs: fix incorrect dirName for authentication sidebar by @JulienLavocat in #4047

New Contributors

Full Changelog: v1.11.2...v1.11.3

Release v1.11.2 - Bug fixes + Performance improvements

09 Jan 22:16

Choose a tag to compare

Hello again everyone! We have another release with more improvements.

Host

  • A bunch of performance improvements related to optimizing small transactions and the time between transactions
  • A critical deadlock fix
  • Many small fixes

C#

  • Procedures in C# are here! You can try them out now, but they're not documented, you can see an example usage here. They'll be in beta until 1.12. Please let us know if you run into issues if you try them out.

Typescript

  • A performance improvement to use a new ABI function for faster index lookups

Unreal

  • Several bug fixes submitted by @KirstenWF! Thank you for the contributions.

New /templates directory for sharing templates

  • We've consolidated our various projects and templates into the /templates directory in the repository. We'll be adding many more templates as we go. If you want to use a template you can install one with:
spacetime init --template <the-template-name> # or
spacetime dev --template <the-template-name>

What's Changed

New Contributors

Full Changelog: v1.11.1...v1.11.2

Release v1.11.1 - Bug Fixes + Performance Improvements

18 Dec 21:23

Choose a tag to compare

Hello again everyone! This is likely going to be our last release before the holidays. We will potentially have one more release before the end of the year but for now we hope you are all happy with these improvements 🙂

Typescript

This release includes several bug fixes and performance improvements related to Typescript:

  • SpacetimeDB now processes sourcemaps included in Typescript modules, so stacktraces in both new and existing modules now show the filenames and line numbers from your source code, not from the bundled JS file.
  • The UTF-8 processing of the TextEncoder and TextDecoder classes is now implemented in native code, meaning string de/serialization will now have less overhead.
  • useTable in a React client now works when specifying columns with camelcase names.
  • Generated client code is now compatible with the noImplicitOverride Typescript configuration option.

What's Changed

Full Changelog: v1.11.0...v1.11.1

⛄ 🎄 🎁 Happy Holidays everyone! 🎁 🎄 ⛄

Release v1.11.0 - Typed Query Builder

09 Dec 13:35

Choose a tag to compare

Today we've released query builders for Rust and TypeScript modules. The purpose of the query builder API is so that you can write views that will take advantage of the unique performance guarantees of SpacetimeDB's query engine, particularly for realtime subscription updates.

The query builder also now allows you to iterate or scan a table in a view, something that previously wasn't possible using only the index accessors exposed by ViewContext and AnonymousViewContext.

The query builder exposes the following query operators:

  • .where()
    Used for filtering. Equivalent to a WHERE condition in SQL.
  • .leftSemijoin()
    Equivalent to an inner join in sql where a row is return from the lhs only if it matches with a row on the rhs.
  • .rightSemijoin()
    Equivalent to an inner join in sql where a row is return from the rhs only if it matches with a row on the lhs.

Examples (Rust)

use spacetimedb::{Identity, Query, Table, ViewContext, AnonymousViewContext};

#[spacetimedb::table(name = player_state)]
pub struct PlayerState {
    #[unique]
    player_id: u64,
    online: bool,
}

#[spacetimedb::table(name = player_internal)]
pub struct Player {
    #[auto_inc]
    #[primary_key]
    id: u64,
    #[unique]
    identity: Identity,
    name: String,
    #[index(btree)]
    age: u8,
}

#[spacetimedb::table(name = moderator_internal)]
pub struct Moderator {
    #[unique]
    player_id: u64,
}

/// Returns all players.
/// Equivalent to `SELECT * FROM player_internal`.
#[spacetimedb::view(name = player, public)]
fn player(ctx: &AnonymousViewContext) -> Query<Player> {
    ctx.from.player_internal().build()
}

/// Returns the caller's player.
/// Equivalent to `SELECT * FROM player_internal WHERE "identity" = :sender`.
#[spacetimedb::view(name = my_player, public)]
fn my_player(ctx: &ViewContext) -> Query<Player> {
    ctx.from.player_internal().r#where(|p| p.identity.eq(ctx.sender)).build()
}

/// Returns only online players.
/// Equivalent to:
/// ```sql
/// SELECT q.*
/// FROM player_state p JOIN player_internal q ON p.player_id = q.id
/// WHERE p.online
/// ```
#[spacetimedb::view(name = online_player, public)]
fn online_player(ctx: &AnonymousViewContext) -> Query<Player> {
    ctx.from
        .player_state()
        .r#where(|p| p.online.eq(true))
        .right_semijoin(ctx.from.player_internal(), |(p, q)| p.player_id.eq(q.id))
        .build()
}

/// Returns only the caller's player if online.
/// Equivalent to:
/// ```sql
/// SELECT q.*
/// FROM player_state p JOIN player_internal q ON p.player_id = q.id
/// WHERE p.online AND q.identity = :sender
/// ```
#[spacetimedb::view(name = my_online_player, public)]
fn my_online_player(ctx: &ViewContext) -> Query<Player> {
    ctx.from
        .player_state()
        .r#where(|p| p.online.eq(true))
        .right_semijoin(ctx.from.player_internal(), |(p, q)| p.player_id.eq(q.id))
        .r#where(|p| p.identity.eq(ctx.sender))
        .build()
}

/// Returns the moderators.
/// Equivalent to:
/// ```sql
/// SELECT p.* FROM player_internal p JOIN moderator_internal m ON p.id = m.player_id
/// ```
#[spacetimedb::view(name = moderator, public)]
fn moderator(ctx: &AnonymousViewContext) -> Query<Player> {
    ctx.from
        .player_internal()
        .left_semijoin(ctx.from.moderator_internal(), |(p, m)| p.id.eq(m.player_id))
        .build()
}

Examples (TypeScript)

import { schema, table, t, type RowObj } from 'spacetimedb/server';

const playerState = table('playerState', {
  playerId: t.u64().unique(),
  online: t.bool(),
});

const playerInternal = table('playerInternal', {
  id: t.u64().primaryKey().autoInc(),
  identity: t.identity().unique(),
  name: t.string(),
  age: t.u8().index('btree'),
});

const spacetimedb = schema(playerState, playerInternal);

spacetimedb.view(
  { name: 'my_online_player', public: true },
  t.array(playerInternal.row()),
  ctx => {
    return ctx.from.playerState
      .where(p => p.online.eq(true))
      .rightSemijoin(ctx.from.playerInternal, (p, q) => p.playerId.eq(q.id))
      .where(p => p.identity.eq(ctx.sender))
      .build();
  }
);

Bug Fixes

  • Fixes an issue with the --delete-data=on-conflict flag of spacetimedb publish
  • Fixes an issue where databases were returning 400/500 errors after publishing with -c
  • Fixes an issue where on_insert and on_delete were not firing correctly for per-client (ViewContext) views

What's Changed

New Contributors

Full Changelog: v1.10.0...v1.11.0

Release v1.10.0 - Procedures and HTTP Requests

27 Nov 04:42

Choose a tag to compare

Today we have an absolute game changer for SpacetimeDB. SpacetimeDB 1.10 introduces the ability for databases to perform HTTP requests to external services right from within your module! This is one of our most-requested features, and we're very excited to share it with you all.

SpacetimeDB Reducers are extremely powerful. They are atomic, transactional, pure, retryable, and replayable. The challenge was: in order to maintain these properties and guarantees, they need to be isolated from the outside world and can't be allowed to cause any observable side effects.

However, HTTP requests are inherently side-effecting, and are too useful not to have. It turns out the solution is pretty simple: keep reducers side effect free, and introduce a new kind of database function with weaker guarantees and more powers. Enter a new type of SpacetimeDB function: Procedures.

Examples

Just like a reducer, a procedure is a function defined in your module which runs inside the database. Unlike a reducer, procedures don't correspond 1-to-1 with transactions. Instead, you explicitly manage transactions inside the body of your procedure:

#[spacetimedb::procedure]
fn find_highest_level_player(ctx: &mut ProcedureContext) {
    let highest_level_player = ctx.with_tx(|ctx| {
        ctx.db.player().iter().max_by_key(|player| player.level)
    });
    match highest_level_player {
        Some(player) => log::info!("Congratulations to {}", player.id),
        None => log::warn!("No players..."),
    }
}

Being able to run code in the database without a transaction opens a lot of possibilities for new APIs we could expose. The first of these, releasing today, is HTTP requests:

#[spacetimedb::procedure]
fn get_request(ctx: &mut ProcedureContext) {
    match ctx.http.get("https://example.invalid") {
        Ok(response) => {
            let (response, body) = response.into_parts();
            log::info!(
                "Got response with status {} and body {}",
                response.status,
                body.into_string_lossy(),
            )
        },
        Err(error) => log::error!("Request failed: {error:?}"),
    }
}

Take a look at the documentation on the new procedure APIs for more details,
and join us on Discord to let us know what other side effects you want APIs for!

As of this release, only Rust and TypeScript modules can define procedures. We're hard at work adding support to C# modules, and will be releasing them soon. We'll also be cleaning up the new APIs in response to your feedback, so for now procedures are unstable and subject to breaking changes.

TypeScript fixes

In this release, we've also fixed quite a few issues which were reported in the new TypeScript SDK.

  • This issue by exporting the SubscriptionHandle type with the REMOTE_MODULE type applied.
  • This issue by converting to camelCase for column names in code generation.
  • Fixes an issue where onMyReducer callbacks were passing arguments as variadic params, while the types indicated they would be passed as an object. onMyReducer((ctx, argA, argB, argC) => {}) vs onMyReducer((ctx, { argA, argB, argC}) => {})`
  • Fixes an issue where the table type name was used instead of the table name in code generation for constructing tables.
  • Fixes issue with ScheduleAt being used in non-table types.
  • Fixes issue where template projects do not use the correct lifecycle reducer setup
  • Fixes an issue where .insert() returns incorrect objects
  • Fixes an issue where .update() causes error with .autoInc() field

We intend for TypeScript modules and clients to rapidly approach stability. The most invasive breaking changes have already been made.

What's Changed

New Contributors

Full Changelog: v1.9.0...v1.10.0

Release v1.9.0 - Project Collaborators

21 Nov 19:29

Choose a tag to compare

Today we have a long overdue feature we're releasing, project collaborators! 👯

Project Collaborators

Now you can invite other members of your team to join your projects that you deploy to Maincloud.

image

In order to add collaborators, navigate to your project on the website and go to Settings > Collaborators, and then press Add People to add a new collaborator to your project.

Depending on the role you assign the user, they will be able to perform actions that were previously only possible for the database owner to run, including updating the module, viewing logs, and editing tables.

TypeScript (Beta) - API Update

We also have the first major update to our TypeScript API. This change dramatically improves usability in a few key areas and fixes some critical bugs in the TypeScript and React SDKs.

Important

This update also comes with a few breaking changes to the TypeScript API, which are detailed below. In general, we will try to minimize the number of changes to the existing API, but while TypeScript is in Beta we will be making a few important changes until we stabilize the API completely.

TypeScript Modules

TypeScript modules only get a modest change from the previous API:

  • Table accessor names and index accessor names are converted to camelCase on the ctx, so if your table name is foo_bar, the accessor changes from ctx.db.foo_bar to ctx.db.fooBar. This allows you to use whatever table name you want for your tables without running afoul of TypeScript linters.
  • Infer<> now also does InferTypeOfRow<> if applicable which means you can forget about InferTypeOfRow and just use Infer in all cases to get the type of a type builder.

TypeScript SDK

The TypeScript SDK has now been unified with the API of server modules. Code generation now uses the same types and functions that you use on the server. The eventual goals is to allow you to use your actual server types in your TypeScript client without needing to do any code generation. This leads to the following changes:

  • All types exported by generated files are now TypeBuilders, meaning that if you were previously using a type from a generated file, you will now have to do const x: Infer<typeof MyType> instead of const x: MyType. This may seem like an inconvenience, but it vastly improves a lot of the other APIs and is very flexible to extend and is inspired by the very powerful Zod library.
  • We no longer generate and export MyTypeVariants for sum types (these are now accessed by Infer<typeof MyType.variants.myVariant>)
  • Your module_bindings now export a tables object with references to all the TableDefs
  • Your module_bindings now export a reducers object with references to all the ReducerDefs
  • On the client my_table.iter() now returns IterableIterator instead of an Array
  • MyType.getTypeScriptAlgebraicType() has been replaced with MyType.algebraicType
  • Reducers are now called with the same format on the server and clients. e.g. ctx.reducers.createPlayer(argA, argB) -> ctx.reducers.createPlayer({ argA, argB })
  • Reducer callbacks now also take arguments the same way: ctx.reducers.onCreatePlayer(ctx, argA, argB) -> ctx.reducers.onCreatePlayer(ctx, { argA, argB }) & ctx.reducers.removeOnCreatePlayer(ctx, argA, argB) -> ctx.reducers.removeOnCreatePlayer(ctx, { argA, argB })
  • count() now returns a bigint instead of a number to match the server API e.g. myTable.count(): number -> myTable.count(): bigint. This may be changed in the future as it is unlikely that you will have a table with more than 2^53 rows.

Notable things that did not change:

  • MyType.serialize(writer: BinaryWriter, value: Infer<typeof MyType>) and MyType.deserialize(reader: BinaryReader): Infer<typeof MyType> are still supported exactly as before.
  • The MyType.MyVariant(...) constructor function on sum types is still present, but implemented with the private MyType.create('MyVariant', ...). We could choose to move away from this API later if we didn't like the variants polluting the namespace

Warning

You will need to regenerate your module bindings for your TypeScript clients with the latest version of the spacetime CLI tool.

React SDK

The React SDK gets a major improvement in both usability and correctness.

  • useSpacetimeDB() no longer takes type parameters
  • useSpacetimeDB() now returns a ConnectionState. All fields on the ConnectionState are not React state and will cause a rerender any time they change
  • useTable() now takes a TableDef parameter and type params are inferred
  • useTable() now just returns a tuple with the first element being an Array instead of a object with { rows }
  • Added a useReducer() React hook

So now you can write this in your React client:

  import { reducers, tables } from "./module_bindings";

  const { identity, isActive: connected } = useSpacetimeDB();
  const setName = useReducer(reducers.setName);
  const sendMessage = useReducer(reducers.sendMessage);
  const [onlineUsers] = useTable(tables.user, where(eq('online', true)));

The API for using a view is the same as using a table:

  const [myViewRows] = useTable(tables.myView);

What's Changed

Read more

Release v1.8.0 - Module Defined Views

12 Nov 15:46

Choose a tag to compare

Module Defined Views

The shipping continues! This time we have Module Defined Views or just "views". Views are a simple, but incredibly expressive way to define custom and intricate read permissioning for your tables.

Views, which are inspired by a similar concept from SQL, are virtual tables that are defined by new "view functions" in your module and derived from other tables or parameters.

Views are defined by read-only procedural, functions in the language of your module. This function returns data derived from your database tables. You can then query and subscribe to this view as you would any normal database table and it will be updated automatically in realtime.

Here's a look at the syntax for defining a view in the various module languages:

Rust

Declare with #[view]. First argument is a view context (&ViewContext or &AnonymousViewContext). Return Option<T> (0–1 row) or Vec<T> (many rows).

#[view(name = my_player, public)]
fn my_player(ctx: &ViewContext) -> Option<Player> {
    ctx.db.player().identity().find(ctx.sender)
}

#[view(name = players_for_level, public)]
fn players_for_level(ctx: &AnonymousViewContext) -> Vec<Player> {
    ctx.db
        .player_level()
        .level()
        .filter(2u64) // players for level 2
        .map(|player| {
            ctx.db
                .player()
                .id()
                .find(player.player_id)
        })
        .collect()
}

Notes

  • A name is required.
  • Only the context parameter is allowed; no extra args (yet).
  • The context provides a read-only view of the database
  • Mutations are not allowed
  • Full table scans are not allowed

C#

Use [SpacetimeDB.View] with ViewContext or AnonymousViewContext. Return a single row as T? or many rows as List<T> / T[].

[SpacetimeDB.View(Name = "my_player", Public = true)]
public static Player? MyPlayer(ViewContext ctx) =>
    ctx.Db.Player.Identity.Find(ctx.Sender) as Player;

[SpacetimeDB.View(Name = "players_for_level", Public = true)]
public static List<Player> PlayerLocations(AnonymousViewContext ctx) {
    var rows = new List<Player>();
    foreach (var player in ctx.Db.PlayerLevel.Level.Filter(2))
    {
        if (ctx.Db.Player.Id.Find(player.PlayerId) is Player p)
        {
            rows.Add(p);
        }
    }
    return rows;
}

TypeScript

Register with schema.view(...) or schema.anonymousView(...). Use t.option(row) for 0–1 row or t.array(row) for many rows.

spacetimedb.view(
  { name: 'my_player', public: true },
  t.option(players.row()),
  (ctx) => {
  return ctx.db.players.identity.find(ctx.sender) ?? null;
  }
);

spacetimedb.anonymousView(
  { name: 'players_for_level', public: true },
  t.option(players.row()),
  (ctx) => {
    const out = [];
    for (const pl of ctx.db.playerLevels.level.find(2)) {
      const p = ctx.db.players.id.find(pl.player_id);
      if (p) out.push(p);
    }
    return out;
  }
);

Row-level security rules

Currently procedurally defined view functions are limited to index probing tables so that we can efficiently compute the real-time delta for procedural functions. However, we also plan to shortly add the ability to return typed queries from view functions which will allow you to define performant, incrementally evaluated queries which execute full tables scans.

This functionality will make views strictly more expressive and powerful than the existing unstable RLS (row-level security) rules API that we introduced earlier this year. As such we will be deprecating the RLS API in favor of the view API. Here is an idea (not final API) of what that might look like in TypeScript

spacetimedb.view(
  { name: 'high_level_players', public: true },
  t.query(players.row()),
  (ctx) => {
  return ctx.from(ctx.db.player).where(player => gt(player.level, 50))
  }
);

What's Changed

Full Changelog: v1.7.0...v1.8.0

Release v1.7.0 - `spacetime dev`

03 Nov 21:29

Choose a tag to compare

New CLI Command - spacetime dev

We are continuing the month of shipping with a new CLI command for you all, spacetime dev.

spacetime dev automates your development loop to make developing with SpacetimeDB much faster and easier. It does the following:

  1. Generates types for your client (spacetime generate)
  2. Builds your application (spacetime build)
  3. Publishes your module (spacetime publish)
  4. Subscribes to your module logs (spacetime logs)
  5. Watches your module files for changes and repeats the process

spacetime dev is 5 commands in one and it's a real game changer once you try it!

With the introduction of spacetime dev we're also introducing a canonical default project structure:

my-spacetimedb-project/
├── package.json
├── tsconfig.json
├── src/                     # regular client app code
│   ├── main.ts
│   ├── App.tsx
│   └── components/
│
└── spacetimedb/             # SpacetimeDB module lives here
    ├── package.json         # its own TypeScript package
    ├── tsconfig.json
    └── src/
        ├── index.ts         # entrypoint for the module
        └── schema.ts         # schema / logic

With this structure, you can just include a spacetimedb folder inside your client project and have spacetime dev automatically publish your modules for you while you're developing. This makes it super easy to develop apps with a single server and client.

Although convenient, this default project structure is not mandatory and can be customized and configured for more complex multi-client applications or in situations where you want to separate your client and server into different repositories.

Usage

If you run spacetime dev in a directory without a spacetimedb directory (and do not specify a module-project-path), spacetime dev will take you through the process of setting up a new project.

$ spacetime dev
No SpacetimeDB project found in current directory.
Would you like to initialize a new project? yes
WARNING: This command is UNSTABLE and subject to breaking changes.

✔ Project name · my-spacetime-app
✔ Project path · ./my-spacetime-app
? Select a client type for your project (you can add other clients later) ›
❯ React - React web app with TypeScript server
  Use Template - Choose from a list of built-in template projects or clone an existing SpacetimeDB project from GitHub
  None

If you run spacetime dev in a directory with an existing spacetimedb directory (or specify a module-project-path), spacetime dev will ask you to publish a new module or connect to an existing one.

$ spacetime dev
Found existing SpacetimeDB project.
Now we need to select a database to publish to.

Selected database: grumpy-lunchroom-2109
Tip: Use `--database grumpy-lunchroom-2109` to skip this question next time

Starting development mode...
Database: grumpy-lunchroom-2109
Watching for changes in: /Users/tylercloutier/Developer/SpacetimeDB/my-spacetime-app/spacetimedb
Press Ctrl+C to stop

Updating .env.local with database name grumpy-lunchroom-2109...
Building...
Build complete!
Generating module bindings...
Generate finished successfully.
Publishing...
Build finished successfully.
Uploading to local => http://127.0.0.1:3000
Checking for breaking changes...
Publishing module...
JavaScript / TypeScript support is currently in BETA.
There may be bugs. Please file issues if you encounter any.
<https://github.com/clockworklabs/SpacetimeDB/issues/new>
Created new database with name: grumpy-lunchroom-2109, identity: c20008053ab940eb968e981c506037220be5dba2a948f8f7e7a131ba156bf28f
Published successfully!
---
Watching for file changes...
2025-10-30T23:19:12.862606Z  INFO: Creating table `person`
2025-10-30T23:19:12.863539Z  INFO: Database initialized

In either case, spacetime dev will watch for changes in your module files, update your client generated code, build your module, and publish it automatically!

Updated spacetime init

The spacetime init subcommand has been completely reworked. There are a few major changes here:

  1. You now specify a project name when creating a project
  2. There is a new interactive mode for selecting project templates (see below)

SpacetimeDB Project Templates

A Template is a pre-made project configuration that you can quickly deploy via spacetime init. Now when you run spacetime init you'll see all of the built-in options ready to deploy immediately:

You are logged in to SpacetimeDB.
✔ Project name · my-spacetime-app
✔ Project path · ./my-spacetime-app
✔ Select a client type for your project (you can add other clients later) · Use Template - Choose from a list of built-in template projects or clone an existing SpacetimeDB project from GitHub

Available built-in templates:
  basic-typescript - A basic TypeScript client and server template with only stubs for code
  basic-c-sharp - A basic C# client and server template with only stubs for code
  basic-rust - A basic Rust client and server template with only stubs for code
  basic-react - React web app with TypeScript server
  quickstart-chat-rust - Rust server/client implementing quickstart chat
  quickstart-chat-c-sharp - C# server/client implementing quickstart chat
  quickstart-chat-typescript - TypeScript server/client implementing quickstart chat

? Template ID or GitHub repository (owner/repo) or git URL ›

As you can see you can also specify a Github URL here to deploy a custom template. This allows the community to build and share their own templates to be used by others.

Auth claims in modules

Client credentials are now exposed to your module code, so you can have more control over your authorization logic. For some examples of how to use it to secure your app, check out the docs.

Other changes

Docusaurus Migration

We recently migrated all of our documentation to docusaurus! You can view the newly updated documentation here: https://spacetimedb.com/docs .

Postgres Wire Protocol on Maincloud

The big update here is that we have re-enabled postgres wire protocol on Maincloud! You can see the full release notes for our Postgres Wire Protocol release here: https://github.com/clockworklabs/SpacetimeDB/releases/tag/v1.5.0 .

Default Server

The default server for newly installed CLI tools has been changed from local to maincloud. This only affects new users as existing users will already have a default server set in their spacetime CLI config.toml.

What's Changed

Read more