clockworklabs/SpacetimeDB · error · Error

`fromCounterV7` requires `randomBytes.length == 4`

Error message

`fromCounterV7` requires `randomBytes.length == 4`

What it means

Uuid.fromCounterV7 builds a UUIDv7 from a 31-bit monotonic counter, a Timestamp, and exactly 4 random bytes. The 4 bytes are copied into bytes 12-15 of the UUID layout (the random tail after version/variant/counter bits), so no other length is accepted. The check runs before the counter is mutated, so a failed call has no side effects.

Source

Thrown at crates/bindings-typescript/src/lib/uuid.ts:159

   * ```ts
   * const now = Timestamp.fromMillis(1_686_000_000_000n);
   * const counter = { value: 1 };
   * const randomBytes = new Uint8Array(4);
   *
   * const uuid = Uuid.fromCounterV7(counter, now, randomBytes);
   *
   * console.assert(
   *   uuid.toString() === "0000647e-5180-7000-8000-000200000000"
   * );
   * ```
   */
  static fromCounterV7(
    counter: { value: number },
    now: Timestamp,
    randomBytes: Uint8Array
  ): Uuid {
    if (randomBytes.length !== 4) {
      throw new Error('`fromCounterV7` requires `randomBytes.length == 4`');
    }

    if (counter.value < 0) {
      throw new Error('`fromCounterV7` uuid `counter` must be non-negative');
    }

    if (now.__timestamp_micros_since_unix_epoch__ < 0) {
      throw new Error('`fromCounterV7` `timestamp` before unix epoch');
    }

    // 31-bit monotonic counter with wraparound
    const counterVal = counter.value;
    counter.value = (counterVal + 1) & 0x7fff_ffff;

    // 48-bit unix timestamp (ms)
    const tsMs = now.toMillis() & 0xffff_ffff_ffffn;

    const bytes = new Uint8Array(16);

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Generate exactly 4 bytes at the call site: crypto.getRandomValues(new Uint8Array(4))
  2. When slicing from a larger buffer, use randomBytes.slice(0, 4)
  3. Check randomBytes.length === 4 before calling if the buffer comes from external code

Example fix

// before
const rand = crypto.getRandomValues(new Uint8Array(16));
const uuid = Uuid.fromCounterV7(counter, now, rand); // throws

// after
const rand = crypto.getRandomValues(new Uint8Array(4));
const uuid = Uuid.fromCounterV7(counter, now, rand);
Defensive patterns

Strategy: validation

Validate before calling

const rand = crypto.getRandomValues(new Uint8Array(4));
if (rand.length !== 4) throw new TypeError('randomBytes must be exactly 4 bytes');
const uuid = Uuid.fromCounterV7(counter, now, rand);

Type guard

function isFourRandomBytes(b: unknown): b is Uint8Array {
  return b instanceof Uint8Array && b.length === 4;
}

Prevention

When it happens

Trigger: Calling Uuid.fromCounterV7(counter, now, randomBytes) with a Uint8Array whose length is not 4: e.g. the 16-byte buffer from crypto.getRandomValues(new Uint8Array(16)) reused from the V4 code path, or an 8-byte buffer.

Common situations: Copy-pasting the Uuid.fromRandomBytesV4(bytes16) pattern into a V7 call; slicing a shared crypto buffer at the wrong offset; adapting example code that passed a zero-filled new Uint8Array(4) placeholder.

Related errors


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