{"record":{"id":"e226656e802ec54b","repo":"clockworklabs/SpacetimeDB","slug":"fromcounterv7-requires-randombytes-length-4","errorCode":null,"errorMessage":"`fromCounterV7` requires `randomBytes.length == 4`","messagePattern":"`fromCounterV7` requires `randomBytes\\.length == 4`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/lib/uuid.ts","lineNumber":159,"sourceCode":"   * ```ts\n   * const now = Timestamp.fromMillis(1_686_000_000_000n);\n   * const counter = { value: 1 };\n   * const randomBytes = new Uint8Array(4);\n   *\n   * const uuid = Uuid.fromCounterV7(counter, now, randomBytes);\n   *\n   * console.assert(\n   *   uuid.toString() === \"0000647e-5180-7000-8000-000200000000\"\n   * );\n   * ```\n   */\n  static fromCounterV7(\n    counter: { value: number },\n    now: Timestamp,\n    randomBytes: Uint8Array\n  ): Uuid {\n    if (randomBytes.length !== 4) {\n      throw new Error('`fromCounterV7` requires `randomBytes.length == 4`');\n    }\n\n    if (counter.value < 0) {\n      throw new Error('`fromCounterV7` uuid `counter` must be non-negative');\n    }\n\n    if (now.__timestamp_micros_since_unix_epoch__ < 0) {\n      throw new Error('`fromCounterV7` `timestamp` before unix epoch');\n    }\n\n    // 31-bit monotonic counter with wraparound\n    const counterVal = counter.value;\n    counter.value = (counterVal + 1) & 0x7fff_ffff;\n\n    // 48-bit unix timestamp (ms)\n    const tsMs = now.toMillis() & 0xffff_ffff_ffffn;\n\n    const bytes = new Uint8Array(16);","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/lib/uuid.ts#L141-L177","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst rand = crypto.getRandomValues(new Uint8Array(16));\nconst uuid = Uuid.fromCounterV7(counter, now, rand); // throws\n\n// after\nconst rand = crypto.getRandomValues(new Uint8Array(4));\nconst uuid = Uuid.fromCounterV7(counter, now, rand);","handlingStrategy":"validation","validationCode":"const rand = crypto.getRandomValues(new Uint8Array(4));\nif (rand.length !== 4) throw new TypeError('randomBytes must be exactly 4 bytes');\nconst uuid = Uuid.fromCounterV7(counter, now, rand);","typeGuard":"function isFourRandomBytes(b: unknown): b is Uint8Array {\n  return b instanceof Uint8Array && b.length === 4;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["uuid","typescript","validation","uuidv7"],"backgroundTag":"argument-validation","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}