npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hekireki

v0.39.0

Published

Hekireki is a code generator from Prisma schema to validation schemas, ORM models and ER diagrams, with a Studio for your schema and its data

Readme

img

Hekireki

Hekireki reads a Prisma schema and writes what the rest of the project needs from it: validation schemas, ORM models for nine languages, an ER diagram, and seed data. It also comes with a Studio for the schema and its database.

Installation

npm install -D hekireki

Quick start

Add a generator block to schema.prisma and run npx prisma generate. output is required.

generator Hekireki-Zod {
    provider = "hekireki-zod"
    output   = "./zod"
    type     = true  // Export TypeScript types
}

Every generator reads the tables and columns Prisma creates: a model or field without @map keeps the name it has in the schema ("Todo", "authorId"), and each ORM is told so where its own convention would say otherwise.

A /// comment on a field can carry a line for one of the validation generators, which uses it as it is. The prefix says which: @z. for Zod, @v. Valibot, @a. ArkType, @e. Effect Schema, @t. TypeBox, @j. AJV, @p. Pydantic, @ar. Active Record and @ecto. Ecto.

model User {
    /// @z.email()
    /// @v.pipe(v.string(), v.email())
    email String @unique
}

Generators

The provider of the generator block picks the output. The options are optional unless a section below says otherwise.

Validation schemas

  • hekireki-zod writes Zod schemas. type also exports the TypeScript type of each schema, relation adds a schema for each relation, and zod picks the flavour: "v4" (the default), "mini" or "@hono/zod-openapi".
  • hekireki-valibot, hekireki-arktype, hekireki-effect, hekireki-typebox and hekireki-ajv write Valibot, ArkType, Effect Schema, TypeBox and AJV schemas, with the same type and relation options.
  • hekireki-pydantic writes Pydantic models, with relation.

ORM models

Every ORM generator stores a DateTime as Prisma Client stores it, so that the ORM and Prisma read each other's rows alike. What that takes differs per ORM, and the sections further down say what each one does and what is left to the caller.

  • hekireki-drizzle writes Drizzle tables. See Drizzle.
  • hekireki-kysely writes the DB interface for Kysely. See Kysely.
  • hekireki-sqlalchemy writes SQLAlchemy models.
  • hekireki-django writes Django models. See Django.
  • hekireki-activerecord writes Rails models. locales names the directory for translated messages. See Active Record.
  • hekireki-gorm writes GORM structs. package is the Go package name, model by default.
  • hekireki-sea-orm writes SeaORM entities. renameAll is serde's rename_all for the entities and enums ("camelCase", for one). See SeaORM.
  • hekireki-ecto writes Ecto schemas. app is the application module the schemas live under, MyApp by default. See Ecto.
  • hekireki-eloquent writes Eloquent models. namespace is the PHP namespace, App\Models by default.
  • hekireki-efcore writes EF Core entities and a DbContext, on PostgreSQL, MySQL and SQLite. namespace is the C# namespace, Models by default, and context the name of the context, AppDbContext by default. See EF Core.
  • hekireki-exposed writes Exposed tables, on PostgreSQL. package is the Kotlin package, models by default, and dao writes the DAO classes beside the tables unless set to false. See Exposed.

Database and diagrams

  • hekireki-atlas writes the schema as Atlas HCL. schemaName is the schema the tables go in. See Atlas.
  • hekireki-er draws the ER diagram. outputs lists the files to write, one rendering per extension (.md, .dbml, .png, .svg), and theme is light or dark.
  • hekireki-seed writes the schema module that hekireki.config.ts is typed against. See Seed.

Drizzle

On MySQL, hekireki-drizzle reads and writes a @db.Timestamp column as UTC, and MySQL converts a TIMESTAMP through the connection's time_zone. Set it to '+00:00' on drizzle's connection, as Prisma's adapter does on its own, or the two see times that differ by the offset.

On CockroachDB it writes the PostgreSQL schema, for node-postgres, and an Int key with @default(sequence()) is an identity column, as Prisma makes it. Keep the session's TimeZone at UTC there, as on PostgreSQL, for a dbgenerated("CURRENT_TIMESTAMP") default to be UTC.

Kysely

hekireki-kysely writes one DB interface, each column typed as the driver of the datasource reads and binds it: better-sqlite3 on SQLite, pg on PostgreSQL, mysql2 on MySQL. Kysely converts nothing and runs no hook, so what the types cannot say is the caller's:

  • On SQLite a DateTime is text, compared as text: write it as Prisma does, date.toISOString().replace('Z', '+00:00'), or =, @unique and @@id do not meet Prisma's rows. A DateTime with now() or a literal default is asked for on insert there, as Prisma Client writes it itself: SQLite's own default is other text (CURRENT_TIMESTAMP sorts before Prisma's rows, and Prisma reads the literal as an Invalid Date).
  • pg and mysql2 read a column without a zone in the process's zone, Prisma in UTC: set pg's type parsers 1114, 1082, 1115 and 1182 to UTC with pg.defaults.parseInputDatesAsUTC = true, and give mysql2 timezone: 'Z'. MySQL refuses ISO text with a Z; write a Date or MySQL's 2030-01-02 03:04:05.678901.
  • @db.Time and @db.Timetz are string, the time of day the drivers read.
  • @updatedAt is asked for on insert, optional or not, and must be set on every update.

examples/kysely in the repository runs a schema against Kysely on SQLite.

Django

hekireki-django keeps a DateTime as Prisma Client keeps it:

  • A column without a zone is declared with the UtcDateTimeField written into models.py. It stores the UTC instant (on SQLite Prisma's text, 2030-01-02T03:04:05.678+00:00) and reads it back as UTC, aware under USE_TZ = True and in TIME_ZONE under False. A @db.Timestamptz is Django's own field.
  • now() is filled in by the model, in UTC (the UTC date or time on @db.Date and @db.Time), and a literal default on a date or time column is its UTC date or time.
  • An @updatedAt on a date or time column stamps the UTC date or time (UtcDateField, UtcTimeField) where Django's auto_now would take the local one. A model with @updatedAt is managed by AutoNowQuerySet, so QuerySet.update() bumps it as updateMany does.

Keep USE_TZ = True, Django's default: with False, Django sets a PostgreSQL session to TIME_ZONE, and a dbgenerated() default such as CURRENT_TIMESTAMP is evaluated in that zone.

Active Record

hekireki-activerecord writes app/models from the schema: application_record.rb and one model per Prisma model, as a Rails developer would write them (and prisma_date_time.rb where a column needs it). What Rails works out on its own (table_name, class_name, foreign_key, inverse_of where the names follow its conventions) is left unsaid; what the schema promises is written: attribute defaults, enum, timestamps, associations with dependent: from onDelete, and validations from the columns (presence, uniqueness, length).

A /// @ar. line on a field is a validator (presence, length(maximum: 255)) added to that field's validates; on a model it is a Ruby line written into the class as it is (has_secure_password, has_many :tags, through: :post_tags, scope ...). A message or name given per locale, message: { ja: "...", en: "..." } or @ar.name(ja: "..."), goes to config/locales/models/<model>/<locale>.yml under the key Rails reads. From app/models that directory is the application's own; from anywhere else, locales has to name it, relative to output.

generator Hekireki-ActiveRecord {
    provider = "hekireki-activerecord"
    output   = "../app/models"
}

/// @ar.name(ja: "やること", en: "Todo")
model Todo {
  id        Int      @id @default(autoincrement())
  /// @ar.name(ja: "タイトル", en: "Title")
  /// @ar.presence(message: { ja: "を入力してください", en: "is required" })
  /// @ar.length(maximum: 255, too_long: { ja: "%{count}文字以内で入力してください", en: "must be %{count} characters or less" })
  title     String   @db.VarChar(255)
  completed Boolean  @default(false)
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  @@map("todos")
}
# app/models/todo.rb
class Todo < ApplicationRecord
  attribute :completed, default: false

  validates :title, presence: true, length: { maximum: 255 }
end
# config/locales/models/todo/ja.yml
ja:
  activerecord:
    models:
      todo: 'やること'
    attributes:
      todo:
        title: 'タイトル'
    errors:
      models:
        todo:
          attributes:
            title:
              blank: 'を入力してください'
              too_long: '%{count}文字以内で入力してください'

The tables stay Prisma's to create, so set config.active_record.migration_error = false and create the test database with prisma migrate deploy.

A DateTime is stored as Prisma Client stores it:

  • Every @updatedAt column is bumped, not only the one aliased to updated_at.
  • A column holding fewer than three digits of a second (@db.Timestamp(0), MySQL's bare @db.DateTime) is sent Prisma's milliseconds for the database to round (precision: 3), where Active Record would cut them off first.
  • A @db.Timetz column is read as a time, not a String. Active Record still warns unknown OID 1266 once as it reads the table's columns, before the declared type takes over.
  • On SQLite every DateTime column is declared with PrismaDateTime, which writes Prisma's text (2030-01-02T03:04:05.678+00:00) so that comparisons, unique indexes and ordering see both clients' rows alike. On SQL Server a @db.DateTime or @db.SmallDateTime column is too: it sends milliseconds for the server to round to 1/300 s or to the minute as Prisma's are, where the adapter would round or cut them first. The type is generated into prisma_date_time.rb beside the models, so Zeitwerk loads it from wherever it loads them, whatever output is.
  • On SQLite and SQL Server, whose CURRENT_TIMESTAMP is text of another shape or the server's local time, now() is filled in by the model.

Keep config.active_record.default_timezone at :utc, Rails' default: with :local, a timestamp column is written in the process's zone and Prisma reads it as UTC.

examples/active-record in the repository runs a fuller schema against Active Record on SQLite, and is the place to read what each thing becomes.

Ecto

hekireki-ecto writes one Ecto.Schema module per model. A model with a /// @ecto line on it or on one of its fields also gets a changeset/2:

  • cast of every field it can write;
  • validate_required of the required fields Prisma gives no default;
  • a unique_constraint for each @unique and @@unique;
  • a foreign_key_constraint for each foreign key;
  • a no_assoc_constraint for each has_one or has_many whose rows the database will not let go of (Restrict or NoAction on delete, which Prisma makes of a required relation that names neither), so a refused delete is an error on the association.

Each constraint is named as the database reports it: Prisma's <table>_<columns>_key and _fkey, cut to the database's limit in bytes as Prisma cuts them, or the map: given. On SQLite a unique key takes the name Ecto derives itself, and a foreign key gets no constraint, since SQLite names none when it refuses a row; with relationMode = "prisma" there is no foreign key in the database to report.

A /// @ecto. line on a field is an Ecto.Changeset function that takes the field first, written without the field: validate_length(max: 50), or validate_required(message: "...") for a message of the field's own. unique_constraint(message: "...") on a @unique field and foreign_key_constraint(message: "...") on a foreign key give the constraint its message. On a relation field, assoc_constraint (the side with the key) puts a missing row's error on the association, and no_assoc_constraint(message: "...") on a has_one or has_many gives a refused delete its message (or asks for one where the database cascades).

On a model, a line is a step of the pipeline written as it is (validate_confirmation(:password)); unique_constraint([:a, :b], message: "...") on the fields of a @@unique gives that constraint its message; cast(empty_values: []) hands its options to the changeset's cast; and a bare /// @ecto asks for the changeset with nothing added. With empty values of the model's own, "" is a value, as it is to the column, so the required fields are checked by a validate_not_null written into the module, which counts only nil as missing (validate_required would count "" too); a field's own validate_required line keeps Ecto's meaning. Ecto has no association for a foreign key of several columns, so the constraints of one are foreign_key_constraints with the error on the relation's name.

model User {
  id    Int     @id @default(autoincrement())
  /// @ecto.validate_required(message: "名前を入力してください")
  /// @ecto.validate_length(max: 50, message: "%{count}文字以内で入力してください")
  name  String
  age   Int?
  email String? @unique
}
def changeset(user, attrs) do
  user
  |> cast(attrs, [:name, :age, :email])
  |> validate_required([:name], message: "名前を入力してください")
  |> validate_length(:name, max: 50, message: "%{count}文字以内で入力してください")
  |> unique_constraint(:email, name: "User_email_key")
end

A DateTime is kept as Prisma Client keeps it, through modules written alongside the schemas: PrismaDateTime holds the instant in UTC in milliseconds (on SQLite the text Prisma writes, 2030-01-01T09:00:00.000+00:00, so an equality or a range finds Prisma's rows), PrismaDate a @db.Date and PrismaTime a @db.Time or @db.Timetz. now() and @updatedAt are timestamps() calls of those types. An optional @updatedAt is stored as null on insert only when given by Ecto.Changeset.force_change/3, as a struct's nil is no change. On PostgreSQL and MySQL the module's documentation names the session time zone a @db.Timestamptz, @db.Timetz or TIMESTAMP column needs.

examples/ecto in the repository runs a schema against Ecto on SQLite.

Exposed

hekireki-exposed keeps a DateTime as Prisma Client keeps it, with no conversion through the JVM's zone: an Instant bound and read as the UTC instant, to the millisecond (a finer value is truncated, as a JavaScript Date holds it, in every native type), the UTC date of a @db.Date, the UTC time of a @db.Time, and a @db.Timetz at offset zero.

now(), literal defaults and every @updatedAt are filled in by the client on a DAO or DSL insert, and the DAO bumps @updatedAt on update. A DSL Table.update has no such hook, so set it there yourself: it[updatedAt] = Instant.now().

pgjdbc sets the session to the JVM's zone, so open the connection in UTC, as Prisma's is, for a dbgenerated("CURRENT_TIMESTAMP") default or now() in SQL:

Database.connect(url, setupConnection = { it.createStatement().use { s -> s.execute("SET TIME ZONE 'UTC'") } })

EF Core

hekireki-efcore stores a DateTime as Prisma Client does, whatever its DateTimeKind: the UTC instant (a Local value converted), read back as Utc, in Prisma's text form on SQLite, and now() filled in by EF Core rather than by the database's clock. It takes Npgsql's legacy timestamp behaviour to be off.

On MySQL, compare a @db.Time column with a parameter rather than a constant: MySql.EntityFrameworkCore writes a constant TimeSpan as TIME '03:04:05', without its fraction.

SeaORM

hekireki-sea-orm writes one entity per model into a directory, with a mod.rs and a prelude.rs. A DateTime is kept as Prisma Client keeps it:

  • now(), @updatedAt and a literal default are filled by ActiveModelBehavior::before_save, in UTC with milliseconds, so the crate needs chrono and async-trait beside sea-orm.
  • On SQLite every DateTime is a PrismaDateTime (written into prisma_date_time.rs), which binds Prisma's text, 2030-01-02T03:04:05.000+00:00: filter with Column::At.eq(PrismaDateTime(instant)), not a bare DateTimeUtc, which sqlx writes without the .000 of a whole second.
  • On PostgreSQL and MySQL a DateTime is a NaiveDateTime in UTC, @db.Timestamptz a DateTimeWithTimeZone, a MySQL @db.Timestamp a DateTimeUtc. @db.Timetz is refused: sqlx has no type to read it into.

examples/sea-orm in the repository runs a schema against SeaORM on SQLite, PostgreSQL and MySQL.

Atlas

hekireki-atlas writes the tables as Prisma Migrate makes them, so that atlas schema diff against a database Prisma made has nothing to change. Its schemaName is public unless given (main on SQLite): on MySQL, where the schema is the database, give the database's name.

Studio

npx hekireki studio
npx hekireki studio --url file:./dev.db

Your schema and database in the browser, on SQLite, PostgreSQL and MySQL. Without flags, Studio reads the schema of prisma.config.ts, else ./prisma/schema.prisma or ./schema.prisma, and listens on port 5555.

Browse the rows of each model: search, add, edit and export them.

The rows of the User model

Run SQL with completion from the schema. The ER diagram marks the tables a statement reads, and the result comes with the TypeScript types of its rows.

A SELECT typed in the SQL console

Try Prisma Client calls and see the SQL each one sends.

prisma.user.findMany() in the Prisma Client playground

Edit the schema beside its ER diagram, which follows the file as it changes.

The Prisma schema editor and the ER diagram

Read the docs of every model: its fields, and an example of each operation.

The generated docs of the User model

Seed

npx hekireki seed                    # Through the project's Prisma Client
npx hekireki seed --sql seed.sql     # Or as a SQL script

The hekireki-seed generator writes a schema module; hekireki.config.ts imports it and is typed against it, so each model's fields are known as the config is written.

// hekireki.config.ts
import { defineConfig } from 'hekireki'

import { schema } from './prisma/seed/schema' // written by the hekireki-seed generator

export default defineConfig(schema, {
  seed: 42,
  models: {
    User: { count: 100, fields: { age: { min: 18, max: 65 } } },
    Tag: { data: [{ label: 'prisma' }, { label: 'effect' }] },
  },
})

Every foreign key, unique constraint and enum is respected.

License

MIT. See LICENSE.