Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    epicenterhq

    drizzle-orm

    epicenterhq/drizzle-orm
    Coding
    4,029

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Drizzle ORM patterns for type branding and custom types. Use when working with Drizzle column definitions, branded types, or custom type conversions.

    SKILL.md

    Drizzle ORM Guidelines

    Reference Repositories

    • Drizzle ORM : TypeScript ORM with SQL-like query builder
    • Turso : Edge-hosted LibSQL database (Epicenter's database)

    Upstream Grounding

    When Drizzle schema definitions, migration snapshots, query builder APIs, column typing, custom types, or driver integration affect correctness, use source-backed grounding before relying on memory. If DeepWiki MCP is available, ask a narrow question against drizzle-team/drizzle-orm; for libSQL, Turso sync, embedded replicas, D1 compatibility, or remote SQLite behavior, ask against tursodatabase/turso. If DeepWiki is unavailable or the repo is not indexed, use upstream source or official docs directly. Treat DeepWiki as orientation, then verify decisive details against local installed types, generated migrations, source, driver versions, or official docs before changing code.

    Skip DeepWiki for repo-local schema naming and storage-boundary conventions already documented below.

    Schema And Migration Rules

    • Export a single schema object from the app's database module and pass that same object to drizzle(...) and Drizzle Kit config.
    • Keep table definitions, relations, and schema exports explicit. Avoid dynamic schema construction that Drizzle Kit cannot statically inspect.
    • Treat Drizzle Kit snapshots as the diff source of truth. Review generated SQL and snapshot changes together.
    • Pick a casing strategy once per database. Do not mix app-level camelCase with ad hoc SQL aliases unless the boundary owns that mapping.
    • Use drizzle-zod, drizzle-valibot, or a local schema parser at IO boundaries when external input becomes a row. Do not treat inferred insert types as runtime validation.

    Query Builder Rules

    • Prefer the typed query builder for application queries. Use raw SQL only for expressions the query builder cannot express cleanly.
    • Keep joins and selected shapes near the caller that owns the response contract.
    • Add indexes in schema beside the query pattern that needs them.

    Driver Boundaries

    • bun:sqlite and better-sqlite3 are local synchronous SQLite drivers. Do not use them in Cloudflare Workers.
    • D1 is a Cloudflare binding with Worker-specific behavior. Keep it behind Worker code and generated bindings.
    • libSQL and Turso are SQLite-compatible but have network, sync, and compatibility details that are not generic SQLite. Use the turso skill for those decisions.

    Use $type() for Branded Strings, Not customType

    When you need a column with a branded TypeScript type but no actual data transformation, use $type<T>() instead of customType.

    The Rule

    If toDriver and fromDriver would be identity functions (x) => x, use $type<T>() instead.

    Why

    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.

    Bad Pattern

    // 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
    });
    

    Good Pattern

    // 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>;
    }
    

    When to Use customType

    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),
    });
    

    Keep Data in Intermediate Representation

    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.

    Example: DateTimeString

    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);
    
    Recommended Servers
    vastlint - IAB XML VAST validator and linter
    vastlint - IAB XML VAST validator and linter
    Niche
    Niche
    MantleKit Launch Planner
    MantleKit Launch Planner
    Repository
    epicenterhq/epicenter
    Files