clockworklabs/SpacetimeDB · error · Error
cannot serialize refs without a typespace
Error message
cannot serialize refs without a typespace
What it means
Thrown by SpacetimeDB's local test harness (crates/testing, call_http_route_get) when a test dispatches a GET to a path and the compiled module has no GET route for it. The harness resolves the route via module_def.match_http_route(&Method::Get, path) against the routes the module registered in its #[spacetimedb::http_router] function (a spacetimedb::http::Router built with .get(path, handler) / .post / .any). A None match becomes this error.
Solutions
- Add or fix the route in the module's #[spacetimedb::http_router] function: Router::new().get("/health", health_handler).
- Check the method: call_http_route_get only matches GET routes; a .post(...)-only registration will not match (use the POST-equivalent call or register with .get/.any).
- Compare the exact path string (leading slash, segments, trailing slash) between the test call and the router registration.
- Rebuild/re-publish the module so the harness's module_def actually contains the route.
Example fix
// before (module)
#[spacetimedb::http_router]
fn routes() -> Router { Router::new().post("/hello", hello) }
// test
let body = instance.call_http_route_get("/hello").await; // Err: no GET route registered for /hello
// after
#[spacetimedb::http_router]
fn routes() -> Router { Router::new().get("/hello", hello) }
let body = instance.call_http_route_get("/hello").await?; Defensive patterns
Strategy: try-catch
Try / catch
// In module tests, distinguish 'route missing' from 'handler failed':
let body = match instance.call_http_route_get("/health").await {
Ok(b) => b,
Err(e) if e.to_string().contains("no GET route registered") => panic!(
"module router lacks GET /health - register it via Router::get in #[spacetimedb::http_router]"
),
Err(e) => panic!("handler failed: {e:#}"),
}; Prevention
- Keep route paths as consts in the module crate and reference them from both the router and the tests.
- Add the route registration first, then the test that exercises it.
- During bring-up, register with .any(path, handler) so method mismatches do not fail, then narrow to .get(...).
When it happens
Trigger: Calling instance.call_http_route_get("/health") in a module test when the router never registers /health, registers it only via .post(...) (GET-only lookup will not match), or registers a different path string (trailing slash, different segment, typo).
Common situations: Path typo between test and router; copying a POST-route test and forgetting the method; writing the test before adding the route; testing a module built from stale sources that predate the route.
Related errors
- begin mutable transaction while one is already active
- cannot deserialize refs without a typespace
- commit without active mutable transaction
- database is not open
- delete without active mutable transaction
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/afeea8b50b21247b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bindings-typescript/src/lib/algebraic_type.ts:131
I16: { tag: 'I16' } as const,
U16: { tag: 'U16' } as const,
I32: { tag: 'I32' } as const,
U32: { tag: 'U32' } as const,
I64: { tag: 'I64' } as const,
U64: { tag: 'U64' } as const,
I128: { tag: 'I128' } as const,
U128: { tag: 'U128' } as const,
I256: { tag: 'I256' } as const,
U256: { tag: 'U256' } as const,
F32: { tag: 'F32' } as const,
F64: { tag: 'F64' } as const,
makeSerializer(
ty: AlgebraicTypeType,
typespace?: TypespaceType
): Serializer<any> {
if (ty.tag === 'Ref') {
if (!typespace)
throw new Error('cannot serialize refs without a typespace');
while (ty.tag === 'Ref') ty = typespace.types[ty.value];
}
switch (ty.tag) {
case 'Product':
return ProductType.makeSerializer(ty.value, typespace);
case 'Sum':
return SumType.makeSerializer(ty.value, typespace);
case 'Array':
if (ty.value.tag === 'U8') {
return serializeUint8Array;
} else {
const serialize = AlgebraicType.makeSerializer(ty.value, typespace);
return (writer, value) => {
writer.writeU32(value.length);
for (const elem of value) {
serialize(writer, elem);
}
};View on GitHub (pinned to 6dee26c6ef)