Skip to content

Unique ID

Entities need identifiers that remain stable while their other data changes. A plain string carries no indication of which kind of entity it identifies, so code can accidentally mix identifiers for different domain concepts.

UniqueId<TKind> wraps a UUID value and uses TKind as a compile-time marker. Create identifiers through its static factory; the class has a private constructor and freezes each instance.

Call UniqueId.create() with a kind name. The factory generates a UUID v4 when no usable identifier is provided:

import { UniqueId } from '@xeno-js/shared'
const orderId = UniqueId.create('Order')
console.log(orderId.toString())

The generated value is available as a string through toString(), or as the underlying Guid through getValue().

Use a different kind for each domain identifier. TypeScript then prevents passing one kind where another is expected:

import { UniqueId } from '@xeno-js/shared'
type OrderId = UniqueId<'Order'>
type CustomerId = UniqueId<'Customer'>
function loadOrder(id: OrderId): void {
console.log(`Loading order ${id.toString()}`)
}
const orderId: OrderId = UniqueId.create('Order')
const customerId: CustomerId = UniqueId.create('Customer')
loadOrder(orderId)
// loadOrder(customerId) // Type error: a Customer ID is not an Order ID.

The kind argument exists for typing; it does not change the UUID or add a runtime prefix. Keep the kind string consistent wherever the same identifier type is used.

Pass an existing UUID string as the optional second argument when rebuilding an identifier, for example when mapping a value read from storage:

import { UniqueId } from '@xeno-js/shared'
const storedOrderId = 'c0a80123-4567-489a-8abc-1234567890ab'
const orderId = UniqueId.create('Order', storedOrderId)
console.log(orderId.toString())

UniqueId.create() keeps the supplied string only when it is non-empty and a valid GUID. If it is empty or invalid, the factory generates a new UUID instead. Validate external input separately if silently replacing an invalid value is not appropriate for the calling workflow.

Use equals() to compare identifiers of the same kind:

import { UniqueId } from '@xeno-js/shared'
const originalId = UniqueId.create('Order', 'c0a80123-4567-489a-8abc-1234567890ab')
const rehydratedId = UniqueId.create('Order', 'c0a80123-4567-489a-8abc-1234567890ab')
const anotherOrderId = UniqueId.create('Order')
console.log(originalId.equals(rehydratedId)) // true
console.log(originalId.equals(anotherOrderId)) // false

Equality compares the underlying GUID values. equals() also returns false when the other value is not a UniqueId instance.

  • create(kind) generates a UUID v4.
  • create(kind, id) uses id only when it is non-empty and a valid GUID; otherwise, it generates a UUID v4.
  • toString() returns the identifier’s string representation, and getValue() returns its underlying Guid.
  • equals() compares the underlying values.
  • Instances are frozen after construction.
  • The generic kind distinguishes identifier types at compile time; it does not alter their runtime representation.

Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section