clockworklabs/SpacetimeDB · error · Error

cannot serialize refs without a typespace

Error message

cannot serialize refs without a typespace

What it means

AlgebraicType.makeSerializer(ty, typespace) builds a serializer for a SATS algebraic type. A Ref type is only an index into a typespace (the array of types emitted with a module's schema), so when ty.tag === 'Ref' and no typespace was passed there is no way to resolve the referenced type. The library throws instead of guessing and emitting corrupt bytes.

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 524b4487d9)

Solutions

  1. Pass the module's typespace as the second argument: AlgebraicType.makeSerializer(ty, typespace)
  2. Or dereference the ref yourself first: while (ty.tag === 'Ref') ty = typespace.types[ty.value]
  3. Prefer the schema-generated table/reducer bindings, which always carry the typespace

Example fix

// before
const ser = AlgebraicType.makeSerializer(ty); // ty = { tag: 'Ref', value: 3 } -> throws

// after
const ser = AlgebraicType.makeSerializer(ty, schema.typespace);
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Prevention

When it happens

Trigger: Calling AlgebraicType.makeSerializer({ tag: 'Ref', value: n }) or AlgebraicType.serializeValue(writer, refTy, value) without the second typespace argument; hand-building serializers from module schema metadata and dropping the typespace when recursing into Product/Sum elements.

Common situations: Writing custom row encoders over raw AlgebraicType metadata; porting code from an older bindings version where the typespace argument was not threaded through recursive calls; passing only the inner type of a Ref-containing product while forgetting the containing schema's typespace.

Related errors


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