Skip to content

Repository files navigation

xid-ts

CI NPM version License

Globally unique sortable id generator. A Typescript port of https://github.com/rs/xid.

The binary representation is compatible with the Mongo DB 12-byte ObjectId. The value consists of:

  • a 4-byte timestamp value in seconds since the Unix epoch
  • a 3-byte machine identifier
  • a 2-byte value based on the process id
  • a 3-byte incrementing counter, initialized to a random value

The string representation is 20 bytes, using a base32 hex variant with characters [0-9a-v] to retain the sortable property of the id.

See the original xid project for more details.

Differences from the Go implementation

The wire format (bytes and string) is fully compatible with Go's xid, but a few behaviors differ:

  • Machine identifier: Go derives it from the platform machine id or hostname, so all processes on one machine share the same value. This port fills it with random bytes per state instead: JavaScript runtimes often host many isolates, worker threads or bundled copies of this module within one machine and even one process, and a per-state random value keeps ids from those instances from colliding. The trade-off is that ids cannot be grouped by machine.
  • Process id: taken from process.pid when available (without Go's container cpuset mixing), otherwise 2 random bytes.
  • JSON null: like Go, a zero (nil) Xid serializes to JSON null (toJSON() returns null), and Xid.fromValue(null) returns a zero Xid.

Requirements

A runtime with the Web Crypto API (crypto.getRandomValues): Node.js >= 20, Deno, Bun, Cloudflare Workers, and all modern browsers.

Usage

npm i xid-ts --save

Examples

Create and Parse Xid

import { Xid } from 'xid-ts'

const defaultXid = Xid.default()
assert.equal(defaultXid.isZero(), true)

const now = Math.floor(Date.now() / 1000)
const newXid = new Xid()
assert.equal(newXid.isZero(), false)
assert.isTrue(newXid.timestamp() >= now)

const xid = Xid.parse('9m4e2mr0ui3e8a215n4g')
assert.equal(xid.isZero(), false)
console.log(xid.toBytes()) // Uint8Array(12) [77, 136, 225, 91, 96, 244, 134, 228, 40, 65, 45, 201]
assert.equal(xid.toString(), '9m4e2mr0ui3e8a215n4g')
assert.equal(xid.timestamp(), 1300816219)
assert.equal(xid.counter(), 4271561)
assert.equal(xid.equals(newXid), false)
assert.equal(xid.equals(Xid.fromValue('9m4e2mr0ui3e8a215n4g')), true)
assert.equal(xid.equals(Xid.fromValue([77, 136, 225, 91, 96, 244, 134, 228, 40, 65, 45, 201])), true)
assert.equal(xid.equals(Xid.fromValue(Buffer.from([77, 136, 225, 91, 96, 244, 134, 228, 40, 65, 45, 201]))), true)
assert.equal(xid.equals(Xid.fromValue(new Uint8Array([77, 136, 225, 91, 96, 244, 134, 228, 40, 65, 45, 201]))), true)

// generate an id with a given time (the equivalent of Go's NewWithTime)
const oldXid = Xid.newWithTime(1300816219)
assert.equal(oldXid.timestamp(), 1300816219)

// compare ids byte by byte (sorting by this order == sorting by string)
const ids = [new Xid(), oldXid, xid]
ids.sort((a, b) => a.compare(b))

Encode & Decode With JSON and CBOR

https://github.com/yiwen-ai/xid-ts/blob/main/src/index.test.ts#L70

import { decode, encode } from 'cborg'
import { Xid } from 'xid-ts'

const xid = Xid.fromValue('9m4e2mr0ui3e8a215n4g')
const obj = {
  id: xid,
  name: 'yiwen'
}
const json = JSON.stringify(obj)
assert.equal(json, '{"id":"9m4e2mr0ui3e8a215n4g","name":"yiwen"}')
const obj1 = JSON.parse(json)
assert.isTrue(xid.equals(Xid.fromValue(obj1.id)))

const data = encode(obj)
assert.equal(Buffer.from(data).toString('hex'), 'a26269644c4d88e15b60f486e428412dc9646e616d6565796977656e')
// https://cbor.me/
// {"id": h'4D88E15B60F486E428412DC9', "name": "yiwen"}

const obj2 = decode(data)
assert.isTrue(xid.equals(Xid.fromValue(obj2.id)))

Cloudflare Workers

import { DurableObject } from "cloudflare:workers"
import { Xid, newState, type XidState } from 'xid-ts'

export class GlobalState extends DurableObject {
  #xidState: XidState = newState()

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env)
  }

  xid(): Xid {
    return new Xid(undefined, this.#xidState)
  }
}

export default {
  async fetch(request, env, ctx): Promise<Response> {
    const stub = env.GLOBAL_STATE.getByName("global")
    // _id is not Xid instance because it comes from DurableObject RPC serialization
    const _id = await stub.xid()
    const id = new Xid(_id)
    return new Response(JSON.stringify({id}), { status: 200 })
    // {"id":"d4amdocsodcmnao0bddg"}
  },
} satisfies ExportedHandler<Env>

License

Copyright © 2023-present Yiwen AI.

yiwen-ai/xid-ts is licensed under the MIT License. See LICENSE for the full license text.

About

xid is a globally unique id generator thought for the web. A Typescript port of https://github.com/rs/xid.

Resources

Stars

41 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages