clockworklabs/SpacetimeDB · error · RangeError

Timestamp is outside of the representable range for ISO…

Error message

Timestamp is outside of the representable range for ISO string formatting

What it means

The SpacetimeType/table derive machinery maps Rust items onto SATS types: structs become product types and enums become sum types. syn::Data::Union has no SATS representation, so deriving on a union is rejected with a span on the union token.

Solutions

  1. Convert the union to an enum whose variants carry each alternative's payload — enums are fully supported sum types.
  2. If the union is only a local layout trick, remove the derive and keep it out of the schema; store a representable type in tables instead.

Example fix

// before
#[derive(SpacetimeType)]
union Value { i: i64, f: f64 }

// after
#[derive(SpacetimeType)]
enum Value { Int(i64), Float(f64) }
Defensive patterns

Strategy: validation

Validate before calling

# CI check: no derives on unions
rg -nU '#\[(derive\([^)]*SpacetimeType|spacetimedb::table[^\]]*\)\]\s*(pub\s+)?union' src/ && exit 1 || true

Prevention

When it happens

Trigger: Applying #[derive(SpacetimeType)] / #[spacetimedb::spacetimedb_type] (or the table attribute, which routes through the same code) to a union item.

Common situations: Porting FFI-style code that uses unions for memory layouts; expecting a union to serialize like an untagged variant.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/570e20009c6a361d. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-typescript/src/lib/timestamp.ts:133

  }

  /**
   * Get an ISO 8601 / RFC 3339 formatted string representation of this timestamp with microsecond precision.
   *
   * This method preserves the full microsecond precision of the timestamp,
   * and throws `RangeError` if the `Timestamp` is outside the range representable in ISO format.
   *
   * @returns ISO 8601 formatted string with microsecond precision (e.g., '2025-02-17T10:30:45.123456Z')
   */
  toISOString(): string {
    const micros = this.__timestamp_micros_since_unix_epoch__;
    const millis = micros / Timestamp.MICROS_PER_MILLIS;

    if (
      millis > BigInt(Number.MAX_SAFE_INTEGER) ||
      millis < BigInt(Number.MIN_SAFE_INTEGER)
    ) {
      throw new RangeError(
        'Timestamp is outside of the representable range for ISO string formatting'
      );
    }

    const date = new Date(Number(millis));
    const isoBase = date.toISOString(); // Format: '2025-02-17T10:30:45.123Z'

    // Extract the full 6 decimal places of microseconds
    const microsRemainder = Math.abs(Number(micros % 1000000n));
    const fractionalPart = String(microsRemainder).padStart(6, '0');

    // Replace the 3-digit millisecond part with the full 6-digit microsecond part
    return isoBase.replace(/\.\d{3}Z$/, `.${fractionalPart}Z`);
  }

  since(other: Timestamp): TimeDuration {
    return new TimeDuration(
      this.__timestamp_micros_since_unix_epoch__ -

View on GitHub (pinned to 6dee26c6ef)