@typed/id provides schemas for identifier formats and Effect-based generators. Generate an ID in
the command that creates an entity, store it with that entity, and reuse it for rendering and later
updates. Do not generate identity from a template projection.
pnpm add @typed/id effect
Create an entity with the shared Ids facade
Ids is the application facade. Its requirements bubble through a command until the runtime
provides them, so creation stays explicit and testable.
import { Effect } from "effect"
import { Ids } from "@typed/id/Ids"
type Issue = { readonly id: string; readonly title: string }
const createIssue = Effect.fn(function* (title: string) {
const id = yield* Ids.uuid7
return { id, title } satisfies Issue
})
const program = createIssue("Document @typed/id").pipe(Effect.provide(Ids.Default))
const issue = await Effect.runPromise(program)
Provide Ids.Default once around the application or feature that shares IDs. Its UUIDv7 facade
shares one lazy Uuid7State for that Layer, preserving local sequence state. This is local
monotonicity, not a distributed ordering or authorization guarantee.
Choose a focused generator when the facade is unnecessary
Use a focused module when one boundary needs one format and no application-wide generator service.
import { Effect } from "effect"
import { uuid7, Uuid7State } from "@typed/id/Uuid7"
const id = await Effect.runPromise(uuid7.pipe(Effect.provide(Uuid7State.Default)))
uuid7 requires Uuid7State; the facade is preferable when several commands must share its state.
The API reference lists UUIDv4, UUIDv5, UUIDv7, ULID, KSUID,
NanoId, CUID, their schemas, and their focused dependencies.
Choose a format by its dependency
| Need | Focused module | Dependency or contract |
|---|---|---|
| Random UUID | Uuid4 | RandomValues |
| Deterministic name | Uuid5 | explicit namespace and name |
| Time-bearing UUID | Uuid7 | shared Uuid7State |
| Time-bearing string | Ulid or Ksuid | time and entropy |
| Compact random string | NanoId | entropy |
Use Ids when the feature shares these dependencies; use a focused generator at a narrow boundary.
Decode external IDs through a schema
Brands prevent accidental format mixing in TypeScript. A schema also validates an ID arriving from JSON, a URL, or storage.
import { Schema } from "effect"
import { Uuid7 } from "@typed/id/Uuid7"
const Issue = Schema.Struct({ id: Uuid7, title: Schema.String })
const decodeIssue = Schema.decodeUnknownEffect(Issue)
const issue = decodeIssue({
id: "018f3c8a-4c00-7000-8000-000000000001",
title: "Document @typed/id",
})
A UUID format is not a domain type or permission check. Add a domain distinction when two entities must not mix, then perform normal authorization after decoding.
Make generation deterministic in tests
IdsTest is deliberately separate from production imports. Each test Layer supplies fixed time,
seeded entropy, and fresh UUIDv7 sequence state.
import { Effect } from "effect"
import { expect } from "@effect/vitest"
import { Ids } from "@typed/id/Ids"
import { IdsTest } from "@typed/id/IdsTest"
const pair = Effect.fn(function* () {
return [yield* Ids.uuid7, yield* Ids.uuid7] as const
})
const first = await Effect.runPromise(pair().pipe(Effect.provide(IdsTest({ currentTime: 0 }))))
const repeated = await Effect.runPromise(pair().pipe(Effect.provide(IdsTest({ currentTime: 0 }))))
expect(first[0]).not.toBe(first[1])
expect(first[0]).toBe(repeated[0])
The first assertion proves sequence state advances within one Layer; the second proves an identical
fresh Layer reproduces the sequence. currentTime fixes DateTimes; advancing the TestClock
provided by IdsTest does not advance that fixed time service. Provide a custom DateTimes Layer
when a test needs generator time to change.
Carry identity through acknowledgement
Keep a client-generated entity key when a server later returns a persistent ID. Replacing the rendering key makes acknowledgement look like deleting and remounting a row, which can discard focus or local draft state. Store the server ID beside the client key instead.
This matters for keyed rows and optimistic edits. If identity changes unexpectedly, trace the creation command and count generator executions. Deterministic layers make equal generator-call sequences comparable; they do not make different programs consume the same IDs.