Skip to content

TypeScript codegen: forward reference in types.ts causes ReferenceError at runtime #4584

@redrockvp

Description

@redrockvp

Bug Summary

The TypeScript code generator (spacetime generate --lang typescript) for Rust modules sometimes emits direct property references to type objects in types.ts that are defined later in the file. This causes a ReferenceError: Cannot access '<Type>' before initialization at runtime.

The codegen already handles this for some types by using lazy get accessors, but it misses certain cases, producing a mix of lazy and eager references in the same object literal.

Error

types.ts:733 Uncaught ReferenceError: Cannot access 'TileOverride' before initialization
    at types.ts:733:39

Version

  • SpacetimeDB CLI: 2.0.3
  • Module language: Rust
  • Client: TypeScript (React via spacetimedb/react)

Root Cause

In the generated types.ts, type objects are defined in a specific order. When type A contains a field referencing type B, and B is defined after A, the reference must be deferred.

The codegen already uses lazy get accessors for some fields:

// Correct — lazy evaluation, works with forward references
get terrainProfile() {
    return __t.option(TerrainProfile);
},
get placementConstraints() {
    return __t.option(PlacementConstraints);
},

But for other fields in the same object, it emits eager references:

// Broken — eager evaluation, TileOverride not yet defined at this point
tileOverrides: __t.option(__t.array(TileOverride)),

The inconsistency suggests the codegen has logic to detect forward references and emit get accessors, but that logic doesn't cover all cases — possibly missing when the type is wrapped in __t.array() or __t.option(__t.array(...)).

Reproduction

  1. Create a Rust SpacetimeDB module with two types where type A has a Vec<B> field and B is defined in a separate file that sorts alphabetically after A
  2. Run spacetime generate --lang typescript --out-dir ./bindings --module-path ./server
  3. The generated types.ts will contain an eager reference to B inside A's definition
  4. Import types.ts in a client → runtime crash

Workaround

Manually edit the generated types.ts to wrap the offending line in a lazy getter:

// Before (broken):
tileOverrides: __t.option(__t.array(TileOverride)),

// After (fixed):
get tileOverrides() {
    return __t.option(__t.array(TileOverride));
},

This must be reapplied after every spacetime generate run.

Suggested Fix

The codegen should use lazy get accessors for all fields that reference other user-defined types, not just some. Alternatively, topologically sort type definitions so that referenced types are always emitted before their dependents.

Environment

  • Windows 11 Pro
  • SpacetimeDB 2.0.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions