{"record":{"id":"c9f35270cafea9b3","repo":"clockworklabs/SpacetimeDB","slug":"fromcounterv7-timestamp-before-unix-epoch","errorCode":null,"errorMessage":"`fromCounterV7` `timestamp` before unix epoch","messagePattern":"`fromCounterV7` `timestamp` before unix epoch","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/lib/uuid.ts","lineNumber":167,"sourceCode":"   *   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);\n\n    // unix_ts_ms (48 bits)\n    bytes[0] = Number((tsMs >> 40n) & 0xffn);\n    bytes[1] = Number((tsMs >> 32n) & 0xffn);\n    bytes[2] = Number((tsMs >> 24n) & 0xffn);\n    bytes[3] = Number((tsMs >> 16n) & 0xffn);\n    bytes[4] = Number((tsMs >> 8n) & 0xffn);\n    bytes[5] = Number(tsMs & 0xffn);","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/lib/uuid.ts#L149-L185","documentation":"Uuidv7 encodes the timestamp as a 48-bit unix_ts_ms field counting from 1970-01-01. fromCounterV7 rejects any Timestamp whose __timestamp_micros_since_unix_epoch__ is negative, because pre-epoch times cannot be encoded in that unsigned field.","triggerScenarios":"Passing a Timestamp built from negative microseconds (e.g. Timestamp.fromMillis(-1n)) or from a Date before 1970 to Uuid.fromCounterV7.","commonSituations":"Test/mocked clocks set to zero or a negative value; arithmetic on timestamps that underflows below the epoch; feeding historical dates (pre-1970) into id generation.","solutions":["Use the current time: Timestamp.fromMillis(BigInt(Date.now())) or an equivalent now() helper","Guard the call: if (now.__timestamp_micros_since_unix_epoch__ < 0) use a non-negative fallback or reject the input","Fix the upstream clock/date source that produced the pre-epoch value"],"exampleFix":"// before\nconst now = Timestamp.fromMillis(-1n);\nconst uuid = Uuid.fromCounterV7(counter, now, rand); // throws\n\n// after\nconst now = Timestamp.fromMillis(BigInt(Date.now()));\nconst uuid = Uuid.fromCounterV7(counter, now, rand);","handlingStrategy":"validation","validationCode":"if (now.__timestamp_micros_since_unix_epoch__ < 0) {\n  throw new RangeError('timestamp must be on or after the unix epoch');\n}\nconst uuid = Uuid.fromCounterV7(counter, now, rand);","typeGuard":"function isEpochOrLater(t: Timestamp): boolean {\n  return t.__timestamp_micros_since_unix_epoch__ >= 0;\n}","tryCatchPattern":null,"preventionTips":["Derive timestamps from Date.now() or Timestamp.fromMillis(BigInt(Date.now()))","Keep mocked test clocks at or after 1970-01-01"],"tags":["uuid","timestamp","validation","uuidv7"],"backgroundTag":"invalid-timestamp","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}