{"record":{"id":"c90c84457a8dead7","repo":"clockworklabs/SpacetimeDB","slug":"cannot-deserialize-refs-without-a-typespace","errorCode":null,"errorMessage":"cannot deserialize refs without a typespace","messagePattern":"cannot deserialize refs without a typespace","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/lib/algebraic_type.ts","lineNumber":170,"sourceCode":"        return primitiveSerializers[ty.tag];\n    }\n  },\n  /** @deprecated Use `makeSerializer` instead. */\n  serializeValue(\n    writer: BinaryWriter,\n    ty: AlgebraicTypeType,\n    value: any,\n    typespace?: TypespaceType\n  ) {\n    AlgebraicType.makeSerializer(ty, typespace)(writer, value);\n  },\n  makeDeserializer(\n    ty: AlgebraicTypeType,\n    typespace?: TypespaceType\n  ): Deserializer<any> {\n    if (ty.tag === 'Ref') {\n      if (!typespace)\n        throw new Error('cannot deserialize refs without a typespace');\n      while (ty.tag === 'Ref') ty = typespace.types[ty.value];\n    }\n    switch (ty.tag) {\n      case 'Product':\n        return ProductType.makeDeserializer(ty.value, typespace);\n      case 'Sum':\n        return SumType.makeDeserializer(ty.value, typespace);\n      case 'Array':\n        if (ty.value.tag === 'U8') {\n          return deserializeUint8Array;\n        } else {\n          const deserialize = AlgebraicType.makeDeserializer(\n            ty.value,\n            typespace\n          );\n          return reader => {\n            const length = reader.readU32();\n            const result: any[] = Array(length);","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/lib/algebraic_type.ts#L152-L188","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the typespace as the second argument: AlgebraicType.makeDeserializer(ty, typespace)","Or resolve refs up front: while (ty.tag === 'Ref') ty = typespace.types[ty.value]","Use generated bindings which pass the typespace automatically"],"exampleFix":"// before\nconst deser = AlgebraicType.makeDeserializer(refTy); // -> throws\n\n// after\nconst deser = AlgebraicType.makeDeserializer(refTy, schema.typespace);","handlingStrategy":"validation","validationCode":"function assertDeserializableType(ty: AlgebraicTypeType, typespace?: TypespaceType): void {\n  if (ty.tag === 'Ref' && !typespace) {\n    throw new Error(`Ref type (index ${ty.value}) cannot be deserialized without a typespace`);\n  }\n}","typeGuard":"const isRefType = (ty: AlgebraicTypeType): ty is { tag: 'Ref'; value: number } =>\n  ty.tag === 'Ref';","tryCatchPattern":null,"preventionTips":["Keep the typespace next to any AlgebraicType you store for decoding","Write one helper that resolves refs (while (ty.tag === 'Ref')) and use it everywhere","Add a unit test that round-trips every schema type to catch missing-typespace paths"],"tags":["deserialization","typescript","spacetimedb","typespace","schema"],"backgroundTag":"type-resolution-failed","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}