.agents/skills/drizzle-orm/SKILL.md
Drizzle ORM patterns for type branding, custom types, and SQLite column definitions. Use when the user mentions Drizzle, drizzle-orm, or when working with database schemas, branded column types, or custom type conversions in Drizzle.
npx skillsauth add epicenterhq/epicenter drizzle-ormInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this pattern when you need to:
$type<T>() and customType for column definitions.toDriver/fromDriver conversions that add runtime overhead.When you need a column with a branded TypeScript type but no actual data transformation, use $type<T>() instead of customType.
If toDriver and fromDriver would be identity functions (x) => x, use $type<T>() instead.
Even with identity functions, customType still invokes mapFromDriverValue on every row:
// drizzle-orm/src/utils.ts - runs for EVERY column of EVERY row
const rawValue = row[columnIndex]!;
const value = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
Query 1000 rows with 3 date columns = 3000 function calls doing nothing.
// Runtime overhead for identity functions
customType<{ data: DateTimeString; driverParam: DateTimeString }>({
dataType: () => 'text',
toDriver: (value) => value, // called on every write
fromDriver: (value) => value, // called on every read
});
// Zero runtime overhead - pure type assertion
text().$type<DateTimeString>();
$type<T>() is a compile-time-only type override:
// drizzle-orm/src/column-builder.ts
$type<TType>(): $Type<this, TType> {
return this as $Type<this, TType>;
}
Only when data genuinely transforms between app and database:
// JSON: object ↔ string - actual transformation
customType<{ data: UserPrefs; driverParam: string }>({
toDriver: (value) => JSON.stringify(value),
fromDriver: (value) => JSON.parse(value),
});
Prefer keeping data serialized (strings) through the system, parsing only at the edges (UI components).
The principle: If data enters serialized and leaves serialized, keep it serialized in the middle. Parse at the edges where you actually need the rich representation.
Instead of parsing DateTimeString into Temporal.ZonedDateTime at the database layer:
// Bad: parse on every read, re-serialize at API boundaries
customType<{ data: Temporal.ZonedDateTime; driverParam: string }>({
fromDriver: (value) => fromDateTimeString(value),
});
Keep it as a string until the UI actually needs it:
// Good: string stays string, parse only in date-picker component
text().$type<DateTimeString>();
// In UI component:
const temporal = fromDateTimeString(row.createdAt);
// After edit:
const updated = toDateTimeString(temporal);
documentation
Yjs CRDT patterns, shared types (Y.Map, Y.Array, Y.Text), conflict resolution, and document storage. Use when the user mentions Yjs, Y.Doc, CRDTs, collaborative editing, or when handling shared types, implementing real-time sync, or optimizing document storage.
tools
Voice and tone rules for all written content—prose, UI text, tooltips, error messages. Use when the user says "fix the tone", "rewrite this", "sounds like AI", "sounds corporate", or when writing any user-facing text, landing pages, product copy, or open-source documentation.
tools
Workspace API patterns for defineTable, defineKv, versioning, migrations, data access (CRUD + observation), withActions, and extension ordering. Use when the user mentions workspace, defineTable, defineKv, createWorkspace, withActions, withExtension, defineQuery, defineMutation, connectWorkspace, or when defining schemas, reading/writing table data, observing changes, writing migrations, chaining extensions, or attaching actions to a workspace client.
documentation
Standard workflow for implementing features with specs and planning documents. Use when the user says "start a new feature", "how should I plan this", "what's the process", or when starting implementation, planning work, or working on any non-trivial task.