clockworklabs/SpacetimeDB · error · Error

cannot deserialize refs without a typespace

Error message

cannot deserialize refs without a typespace

What it means

AlgebraicType.makeDeserializer(ty, typespace) mirrors makeSerializer for decoding. A Ref algebraic type is just an index into a typespace; without that array the concrete type cannot be resolved, so the method throws immediately rather than producing a decoder that would misread the stream.

Source

Thrown at crates/bindings-typescript/src/lib/algebraic_type.ts:170

        return primitiveSerializers[ty.tag];
    }
  },
  /** @deprecated Use `makeSerializer` instead. */
  serializeValue(
    writer: BinaryWriter,
    ty: AlgebraicTypeType,
    value: any,
    typespace?: TypespaceType
  ) {
    AlgebraicType.makeSerializer(ty, typespace)(writer, value);
  },
  makeDeserializer(
    ty: AlgebraicTypeType,
    typespace?: TypespaceType
  ): Deserializer<any> {
    if (ty.tag === 'Ref') {
      if (!typespace)
        throw new Error('cannot deserialize refs without a typespace');
      while (ty.tag === 'Ref') ty = typespace.types[ty.value];
    }
    switch (ty.tag) {
      case 'Product':
        return ProductType.makeDeserializer(ty.value, typespace);
      case 'Sum':
        return SumType.makeDeserializer(ty.value, typespace);
      case 'Array':
        if (ty.value.tag === 'U8') {
          return deserializeUint8Array;
        } else {
          const deserialize = AlgebraicType.makeDeserializer(
            ty.value,
            typespace
          );
          return reader => {
            const length = reader.readU32();
            const result: any[] = Array(length);

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Pass the typespace as the second argument: AlgebraicType.makeDeserializer(ty, typespace)
  2. Or resolve refs up front: while (ty.tag === 'Ref') ty = typespace.types[ty.value]
  3. Use generated bindings which pass the typespace automatically

Example fix

// before
const deser = AlgebraicType.makeDeserializer(refTy); // -> throws

// after
const deser = AlgebraicType.makeDeserializer(refTy, schema.typespace);
Defensive patterns

Strategy: validation

Validate before calling

function assertDeserializableType(ty: AlgebraicTypeType, typespace?: TypespaceType): void {
  if (ty.tag === 'Ref' && !typespace) {
    throw new Error(`Ref type (index ${ty.value}) cannot be deserialized without a typespace`);
  }
}

Type guard

const isRefType = (ty: AlgebraicTypeType): ty is { tag: 'Ref'; value: number } =>
  ty.tag === 'Ref';

Prevention

When it happens

Trigger: Calling AlgebraicType.makeDeserializer({ tag: 'Ref', value: n }) without the typespace argument; building deserializers by hand from raw schema metadata (e.g. custom clients decoding column values) and not threading the typespace through recursive calls.

Common situations: Custom decode paths over module schema metadata; code copied from samples that predate the typespace parameter; refactoring that passes ty.value of a variant element but loses the outer typespace.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/c90c84457a8dead7. Report an issue: GitHub.