xid-ts
v1.3.0
Published
xid is a globally unique id generator thought for the web. A Typescript port of https://github.com/rs/xid.
Readme
xid-ts
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.pidwhen available (without Go's containercpusetmixing), otherwise 2 random bytes. - JSON null: like Go, a zero (nil) Xid serializes to JSON
null(toJSON()returnsnull), andXid.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 --saveExamples
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.
