Drizzle adapter
@smonn/ids/drizzle provides Drizzle custom column types bound to a codec. It
requires drizzle-orm as an optional peer dependency.
pnpm add drizzle-ormPostgreSQL
Section titled “PostgreSQL”import { pgTable } from "drizzle-orm/pg-core";import { idColumn } from "@smonn/ids/drizzle";import { createTimestampId } from "@smonn/ids";
const usr = createTimestampId("usr");
export const users = pgTable("users", { id: idColumn(usr).primaryKey(),});// users.id is typed as Id<"usr"> end-to-endimport { mysqlTable } from "drizzle-orm/mysql-core";import { idColumnMysql } from "@smonn/ids/drizzle";import { createTimestampId } from "@smonn/ids";
const usr = createTimestampId("usr");
export const users = mysqlTable("users", { id: idColumnMysql(usr).primaryKey(),});// users.id is typed as Id<"usr"> end-to-endSQLite
Section titled “SQLite”import { sqliteTable } from "drizzle-orm/sqlite-core";import { idColumnSqlite } from "@smonn/ids/drizzle";import { createTimestampId } from "@smonn/ids";
const usr = createTimestampId("usr");
export const users = sqliteTable("users", { id: idColumnSqlite(usr).primaryKey(),});// users.id is typed as Id<"usr"> end-to-endAll three column builders (idColumn, idColumnMysql, idColumnSqlite) work with
any codec variant — any codec that exposes safeParse satisfies the required interface
(Timestamp, Opaque Timestamp, Reverse Timestamp, Signed Timestamp, Digest, and Wrapped
key codecs all qualify).
- Write path: values are validated via
codec.safeParsebefore being passed to the driver. A cast-smuggled or otherwise invalid string throwsIdsError("invalid_id")at write time. Passingnullorundefinedalso throws — usenullableIdColumnfor nullable columns. - Read path: values are normalised via
codec.safeParse()rather than the strictis(). Data at rest should already be canonical (ADR-0003), butsafeParseis a safe boundary for stale non-canonical values. An unrecognised value throws at read time so corrupt data surfaces immediately. - Column type:
dataType()returns"text"by default; pass{ columnType: "..." }as the second argument toidColumnto override (e.g.idColumn(usr, { columnType: "varchar(30)" })).
Auto-generating IDs on insert — generatedIdColumn family
Section titled “Auto-generating IDs on insert — generatedIdColumn family”The generatedIdColumn, generatedIdColumnMysql, and generatedIdColumnSqlite column builders wire .$defaultFn(() => codec.generate()) so inserts that omit the ID field receive a freshly generated Id<Brand> automatically — no per-call-site id needed:
import { pgTable } from "drizzle-orm/pg-core";import { generatedIdColumn } from "@smonn/ids/drizzle";import { createTimestampId } from "@smonn/ids";
const usr = createTimestampId("usr");
export const users = pgTable("users", { id: generatedIdColumn(usr).primaryKey(),});// id is auto-filled on insert — await db.insert(users).values({ name: "Alice" }) worksFor MySQL and SQLite use generatedIdColumnMysql and generatedIdColumnSqlite respectively — they behave identically but target those dialects’ column types.
These builders require IdGeneratingCodec — a codec that exposes a synchronous generate(). Only the Timestamp codec and Reverse Timestamp codec qualify; Opaque, Signed, Wrapped, and Digest codecs are a compile-time error.
Nullable columns
Section titled “Nullable columns”nullableIdColumn(codec) is a PostgreSQL-only variant that normalises null and undefined driver values to null rather than throwing. Use it for optional foreign keys and LEFT JOIN results.
import { pgTable } from "drizzle-orm/pg-core";import { nullableIdColumn } from "@smonn/ids/drizzle";import { createTimestampId } from "@smonn/ids";
const usr = createTimestampId("usr");
// default: text columnexport const posts = pgTable("posts", { authorId: nullableIdColumn(usr),});// posts.authorId is Id<"usr"> | null end-to-end
// explicit char column — matches existing DDLexport const comments = pgTable("comments", { authorId: nullableIdColumn(usr, { columnType: "char(26)" }),});- Read path:
nullandundefineddriver values are returned asnull. Non-null values go throughcodec.safeParse()and throwIdsError("invalid_id")if the stored value does not parse as a validId<Brand>. - Write path:
toDrivernormalisesnullandundefinedtonull; non-null values are validated viacodec.safeParseand an invalid string throwsIdsError("invalid_id")at write time. - Column type:
dataType()returns"text"by default; pass{ columnType: "..." }as the second argument tonullableIdColumnto override (e.g.nullableIdColumn(usr, { columnType: "char(26)" })).
Error handling
Section titled “Error handling”The read path throws IdsError with code "invalid_id" when the stored value does not parse
as a valid Id<Brand>. The underlying ParseError is attached as err.cause. Catch and
narrow using isIdsError:
import { idColumn, isIdsError } from "@smonn/ids/drizzle";
try { // query that triggers a read through idColumn / idColumnMysql / idColumnSqlite} catch (err) { if (isIdsError(err) && err.code === "invalid_id") { // err.cause is the ParseError returned by safeParse }}IdsError, isIdsError, and IdsErrorCode are re-exported from @smonn/ids/drizzle — no
separate import from "@smonn/ids" is needed. For the full list of IdsErrorCode values, see
the error-code reference.