@douglance/stdb-standard-types
v1.0.0
Published
Strictly-typed foundation for SpacetimeDB modules, eliminating implicit any types
Maintainers
Readme
@spacetimedb/standard-types
Strictly-typed foundation for SpacetimeDB modules, eliminating implicit any types and providing rich IntelliSense.
Problems Solved
- Implicit
anytypes forctxandargsin reducers and systems - Lack of autocomplete for
ctx.dbtables and their methods - Confusion around
ctx.timestampobject vs. a primitivebigint
Installation
npm install @spacetimedb/standard-typesUsage
Before: Untyped, Error-Prone
// ❌ No type safety, no autocomplete, runtime errors
export function joinGame(ctx, args) {
ctx.db.PlayerTag.insert({ entity_id: ctx.sender, name: args.name });
}Problems:
- No compile-time errors if table name is wrong (
PlayerTagvsplayerTag) - No autocomplete for
ctx.dbproperties - No validation of
argsshape - Implicit
anytypes everywhere
After: Fully Typed
import type { ReducerContext } from "@spacetimedb/standard-types";
import type { GameSchema } from "../generated/schema";
type JoinGameArgs = { name: string };
// ✅ Full type safety, compile-time errors, full autocomplete
export function joinGame(ctx: ReducerContext<GameSchema, JoinGameArgs>, args: JoinGameArgs) {
// TypeScript autocomplete shows `ctx.db.playerTag`, not `PlayerTag`
// ❌ Compile Error if wrong: Property 'PlayerTag' does not exist on type 'DbView<GameSchema>'
ctx.db.playerTag.insert({
entity_id: ctx.sender,
name: args.name,
color: "#FF0000",
});
}Benefits:
- Compile-time errors catch typos in table names
- Full IntelliSense autocomplete for all tables and methods
- Args are validated at compile time
- No implicit
anytypes
API Reference
SpacetimeSchema
Base type for a generated schema object.
export type SpacetimeSchema = {
db: Record<string, {
insert: (row: any) => void;
update: (row: any) => void;
delete: (row: any) => void;
find: (pk: any) => any | undefined;
iter: () => Iterable<any>;
}>;
};DbView<S>
The fully typed database view based on a schema.
export type DbView<S extends SpacetimeSchema> = S["db"];ReducerContext<S, Args>
The context passed to reducers, generic over the schema and reducer arguments.
export type ReducerContext<
S extends SpacetimeSchema,
Args extends Record<string, any> = {}
> = {
readonly sender: SpacetimeIdentity;
readonly timestamp: SpacetimeTimestamp;
readonly connectionId: SpacetimeConnectionId | null;
readonly db: DbView<S>;
readonly args: Args;
};SystemContext<S>
The context passed to systems.
export type SystemContext<S extends SpacetimeSchema> = {
readonly timestamp: SpacetimeTimestamp;
readonly db: DbView<S>;
};ClientLifecycleContext<S>
Context for client connection/disconnection hooks.
export type ClientLifecycleContext<S extends SpacetimeSchema> = {
readonly sender: SpacetimeIdentity;
readonly connectionId: SpacetimeConnectionId;
readonly db: DbView<S>;
};Example: Full Module
import { schema, table, t } from "spacetimedb/server";
import type { ReducerContext, SystemContext, ClientLifecycleContext } from "@spacetimedb/standard-types";
// Define schema
const PlayerTag = table({ name: 'PlayerTag', public: true }, {
entity_id: t.identity().primaryKey(),
name: t.string(),
});
const Position = table({ name: 'Position', public: true }, {
entity_id: t.identity().primaryKey(),
x: t.f32(),
y: t.f32(),
});
export const gameSchema = schema(PlayerTag, Position);
export type GameSchema = typeof gameSchema;
// Fully typed reducer
type JoinGameArgs = { name: string };
export function joinGame(ctx: ReducerContext<GameSchema, JoinGameArgs>, args: JoinGameArgs) {
ctx.db.playerTag.insert({ entity_id: ctx.sender, name: args.name });
ctx.db.position.insert({ entity_id: ctx.sender, x: 0, y: 0 });
}
// Fully typed system
export function updatePhysics(ctx: SystemContext<GameSchema>) {
for (const pos of ctx.db.position.iter()) {
// Physics logic with full type safety
}
}
// Fully typed lifecycle hook
export function handleConnect(ctx: ClientLifecycleContext<GameSchema>) {
console.log("Client connected:", ctx.sender);
}License
MIT
