{"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/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-typescript/src/lib/uuid.ts#L141-L177","documentation":"Uuid.fromCounterV7 builds a UUIDv7 from a 31-bit monotonic counter, a Timestamp, and caller-supplied randomness. The random bytes fill a fixed 4-byte field of the counter-v7 layout, so the method requires exactly 4 bytes and validates this first. Any other array length is rejected before the counter or timestamp are touched.","triggerScenarios":"Calling Uuid.fromCounterV7(counter, now, randomBytes) where randomBytes.length is not 4: a 16-byte buffer from crypto.getRandomValues(new Uint8Array(16)), an empty array, or a wrongly-sized slice of a shared entropy pool.","commonSituations":"Reusing a 16-byte random buffer written for UUIDv4 code paths; porting code from another UUID library whose API takes 16 random bytes; slicing a shared randomness array with an off-by-N length.","solutions":["Pass exactly 4 fresh random bytes: crypto.getRandomValues(new Uint8Array(4))","Check randomBytes.length === 4 before calling fromCounterV7 and fail fast with your own message","If you do not need counter-based monotonic IDs, generate a plain random UUID instead of calling fromCounterV7"],"exampleFix":"// before\nconst rand = crypto.getRandomValues(new Uint8Array(16));\nconst id = Uuid.fromCounterV7(counter, now, rand); // throws: length != 4\n\n// after\nconst rand = crypto.getRandomValues(new Uint8Array(4));\nconst id = Uuid.fromCounterV7(counter, now, rand);","handlingStrategy":"validation","validationCode":"if (randomBytes.length !== 4) {\n  throw new Error(`fromCounterV7 needs exactly 4 random bytes, got ${randomBytes.length}`);\n}\nconst id = Uuid.fromCounterV7(counter, now, randomBytes);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate the 4 random bytes at the call site: crypto.getRandomValues(new Uint8Array(4))","Wrap fromCounterV7 in one helper that enforces length, counter range and timestamp once","Never share a random buffer between UUID generation strategies with different layouts"],"tags":["uuid","typescript","argument-validation"],"backgroundTag":"invalid-argument-validation","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}