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

  1. Add or fix the route in the module's #[spacetimedb::http_router] function: Router::new().get("/health", health_handler).
  2. 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).
  3. Compare the exact path string (leading slash, segments, trailing slash) between the test call and the router registration.
  4. 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

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


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)