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
- Generate exactly 4 bytes at the call site: crypto.getRandomValues(new Uint8Array(4))
- When slicing from a larger buffer, use randomBytes.slice(0, 4)
- 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
- Allocate the 4-byte buffer at the call site instead of reusing a shared larger buffer
- Do not copy the 16-byte pattern from Uuid.fromRandomBytesV4 into V7 calls
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
- `fromCounterV7` uuid `counter` must be non-negative
- `fromCounterV7` `timestamp` before unix epoch
- Invalid hex UUID
- Cannot convert ${typeof value} to ${what}: expected bigint,
- Invalid UUID: must be between 0 and `MAX_UUID_BIGINT`
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/e226656e802ec54b.
Report an issue: GitHub.