prisma-effect-schema-generator
v0.1.15
Published
Prisma generator that emits Effect Schema values, relation schemas, and runtime introspection maps (primary keys, soft-delete columns, table descriptors) for every model — drop-in validation for your database rows.
Maintainers
Readme
prisma-effect-schema-generator
A standalone Prisma generator that emits
Effect Schema values for every
model in your schema.prisma. Drop-in runtime validation for your
database rows, perfect for sync engines, RPC layers, or anywhere you
need to assert that what you read from the DB actually matches what
your code expects.
A real-world example: the generator powers the schemas in
Cyberistic/livestore-tanstackdb
where a single schema.prisma drives both the Prisma/D1 DDL and the
LiveStore client-side validation.
generator effect_client {
provider = "prisma-effect-schema-generator"
}
model User {
id String @id
email String
role Role
}
enum Role {
ADMIN
USER
}// generated/effect-schemas/index.ts (auto-generated)
import { Schema } from "effect"
export const UserSchema = Schema.Struct({
id: Schema.String,
email: Schema.String,
role: Schema.Union(Schema.Literal("ADMIN"), Schema.Literal("USER")),
})
export type ModelName = | "User"
export const ALL_MODEL_NAMES = ["User"] as const// your code
import { Schema } from "effect"
import { UserSchema } from "./generated/effect-schemas"
const user = Schema.decodeUnknownSync(UserSchema)(rowFromDatabase)
// user is fully typed as { id: string; email: string; role: "ADMIN" | "USER" }Install
npm install --save-dev prisma-effect-schema-generator
npm install effect # the generator emits `Schema.X` from `effect`Published on npm as prisma-effect-schema-generator.
Configure
Add the generator to your schema.prisma:
generator effect_client {
provider = "prisma-effect-schema-generator"
output = "./generated/effect-schemas/index.ts" // optional
}Run npx prisma generate and the file at output is created (or
updated) on every regeneration.
All options
| Option | Default | Description |
|-----------------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
| output | ./generated/effect-schemas/index.ts | Where to write the generated module. Relative to the schema file's directory. |
| effectImport | "effect" | Module specifier to import Schema from. |
| effectImportName | "Schema" | Local binding name. Set to e.g. "S" to import as Schema as S. |
| effectVersion | "v3" | Target Effect major version. Set to "v4" to emit toStandardSchemaV1, Schema.Date, Schema.Literals, etc. See the Effect v4 schema migration guide. |
| bigIntAs | "BigIntFromSelf" | "BigInt" (string-encoded) or "BigIntFromSelf" (accepts native bigint). |
| decimalAs | "String" | "String" (precision-safe) or "Number" (lossy but ergonomic). |
| dateAs | "DateFromSelf" | "Date" (ISO-string codec), "DateFromSelf" (accepts native Date), or "DateFromMillis" (v4-only epoch-ms codec — Schema.DateFromMillis). In v4: "Date" → Schema.DateFromString, "DateFromSelf" → Schema.Date, "DateFromMillis" → Schema.DateFromMillis. |
| exportModelNames | "true" | Emit export const ALL_MODEL_NAMES = [...] as const. |
| exportModelNameType | "true" | Emit export type ModelName = "X" | "Y". |
| standardSchemaV1 | "false" | Wrap schemas in Schema.standardSchemaV1(...). Preserves the row type and Context = never, so Standard Schema consumers accept them directly. |
| relationColumns | "false" | Emit a separate Schema.Struct for each relation that has explicit local foreign-key columns. |
| idColumn | "false" | Emit PRIMARY_KEY_COLUMNS map from model name to primary-key column (or null). |
| softDeleteColumn | "false" | Emit SOFT_DELETE_COLUMNS map from model name to detected soft-delete column. |
| tables | "false" | Emit TableDescriptor / ColumnDescriptor interfaces and a TABLES map for runtime introspection. |
generator effect_client {
provider = "prisma-effect-schema-generator"
output = "./generated/effect-schemas/index.ts"
effectImport = "effect"
effectImportName = "Schema"
effectVersion = "v3"
bigIntAs = "BigIntFromSelf"
decimalAs = "String"
dateAs = "DateFromSelf"
exportModelNames = "true"
exportModelNameType = "true"
standardSchemaV1 = "false"
relationColumns = "false"
idColumn = "false"
softDeleteColumn = "false"
tables = "false"
}Introspection helpers (idColumn, softDeleteColumn, tables)
Enable these options when you need runtime metadata about your Prisma schema in addition to the per-model schemas.
generator effect_client {
provider = "prisma-effect-schema-generator"
idColumn = "true"
softDeleteColumn = "true"
tables = "true"
}
model User {
id String @id
email String
deletedAt DateTime?
}// generated/effect-schemas/index.ts
export const PRIMARY_KEY_COLUMNS = {
User: "id",
} as const satisfies Record<ModelName, string | null>
export const SOFT_DELETE_COLUMNS = {
User: "deletedAt",
} as const satisfies Partial<Record<ModelName, string>>
export interface ColumnDescriptor {
readonly name: string
readonly type: 'string' | 'number' | 'boolean' | 'date' | 'json' | 'bytes' | 'unknown'
readonly required: boolean
readonly list: boolean
readonly unique: boolean
readonly isEnum: boolean
readonly enumValues?: ReadonlyArray<string>
}
export interface TableDescriptor {
readonly name: string
readonly primaryKey: string | null
readonly softDelete: string | null
readonly columns: ReadonlyArray<ColumnDescriptor>
readonly includedInSync: boolean
}
export const TABLES: { [M in ModelName]: TableDescriptor } = {
User: {
name: "User",
primaryKey: "id",
softDelete: "deletedAt",
columns: [
{ name: "id", type: 'string', required: true, list: false, unique: true, isEnum: false },
{ name: "email", type: 'string', required: true, list: false, unique: false, isEnum: false },
{ name: "deletedAt", type: 'date', required: false, list: false, unique: false, isEnum: false },
],
includedInSync: true,
},
}Primary keys are detected from @id first, then a single String or Int
@unique column. Soft-delete columns are detected by name (deletedAt,
archivedAt, isDeleted, removedAt) and type (DateTime or Boolean).
TableDescriptor.includedInSync is currently true for every generated
table. It is intended for downstream tooling to override per-table when
building sync scopes.
Type mapping
| Prisma field | Effect Schema (default) | Encoded → Decoded |
|--------------------|--------------------------------|------------------------------------|
| String | Schema.String | string → string |
| Int | Schema.Number | number → number |
| Float | Schema.Number | number → number |
| BigInt | Schema.BigIntFromSelf | bigint → bigint |
| Decimal | Schema.String | string → string (precision-safe) |
| Boolean | Schema.Boolean | boolean → boolean |
| DateTime | Schema.DateFromSelf | Date → Date |
| Json | Schema.Unknown | unknown → unknown |
| Bytes | Schema.Uint8Array | number[] → Uint8Array |
| enum <E> | Schema.Union(Schema.Literal(...)...) | string → string (literal type) |
Wrappers:
| Prisma modifier | Wrapper |
|-----------------|-----------------------------------|
| field? | Schema.NullOr(...) |
| field[] | Schema.Array(...) |
| field[]? | Schema.NullOr(Schema.Array(...)) |
Relations (object-typed fields) are skipped from the model's own struct by
default. If you need schemas for relation foreign keys, enable
relationColumns:
generator effect_client {
provider = "prisma-effect-schema-generator"
relationColumns = "true"
}
model Post {
id String @id
authorId String
author User @relation(fields: [authorId], references: [id])
}// generated/effect-schemas/index.ts
export const PostAuthorRelationSchema = Schema.Struct({
authorId: Schema.String,
})Standard Schema v1
Enable standardSchemaV1 to have every emitted model/relation schema
wrapped with Schema.standardSchemaV1(...). The resulting values still
work as Effect schemas, but they also expose the
Standard Schema ~standard interface so
libraries like TanStack DB can consume them directly.
generator effect_client {
provider = "prisma-effect-schema-generator"
standardSchemaV1 = "true"
}export const UserSchema = Schema.standardSchemaV1(Schema.Struct({
id: Schema.String,
email: Schema.String,
}))
// The wrapped schema preserves its row type and Context is already `never`,
// so it works with both Effect APIs and Standard Schema consumers:
import { State } from "@livestore/livestore"
const users = State.SQLite.table({
name: "users",
schema: UserSchema, // no cast needed
})Effect v4
Set effectVersion = "v4" to emit schemas that use the Effect v4 API. The
key differences from the default v3 output are:
Schema.standardSchemaV1(...)→Schema.toStandardSchemaV1(...)Schema.DateFromSelf→Schema.Date(defaultdateAs = "DateFromSelf")Schema.Date(ISO-string codec) →Schema.DateFromString(whendateAs = "Date")- (v4-only)
dateAs = "DateFromMillis"→Schema.DateFromMillisfor epoch-ms wire formats (e.g. LiveStoreINTEGERcolumns). Falls back toSchema.Dateunder v3. Schema.Union(Schema.Literal("A"), Schema.Literal("B"))→Schema.Literals(["A", "B"])
generator effect_client {
provider = "prisma-effect-schema-generator"
effectVersion = "v4"
}// generated/effect-schemas/index.ts
export const UserSchema = Schema.toStandardSchemaV1(Schema.Struct({
id: Schema.String,
createdAt: Schema.Date,
role: Schema.Literals(["ADMIN", "USER"]),
}))See the Effect v4 schema migration guide for the full API mapping.
Programmatic use
The generator can be driven without Prisma, e.g. for build scripts or custom tooling:
import { runGenerator, resolveOptions } from "prisma-effect-schema-generator"
runGenerator({
output: "./out.ts",
schemaDir: process.cwd(),
datamodel: {
models: [
{
name: "User",
dbName: null,
fields: [
{ kind: "scalar", name: "id", type: "String", isRequired: true, isList: false },
{ kind: "scalar", name: "email", type: "String", isRequired: true, isList: false },
],
},
],
enums: [],
},
rawConfig: resolveOptions({ bigIntAs: "BigInt" }),
})Lower-level building blocks (renderModule, prismaFieldToEffectSchema,
enumToSchema, shouldQuoteName, ...) are also exported.
Requirements
- Node.js >= 18
- Prisma 7+ (the
@prisma/generator-helper@7API) effect3.x or 4.x (peer dependency)
Development
npm install
npm run build # tsc -> dist/
npm test # vitest
npm run typecheck # tsc --noEmitRelated projects
How is this different from effect-prisma-generator?
This project is focused on generating Effect Schema values from your Prisma schema—standalone, serializable validators you can use anywhere (read-path validation, sync engines, RPC payloads, form validation, etc.).
m9tdev/effect-prisma-generator
takes the opposite approach: it generates an Effect-native service wrapper
around Prisma Client, so every Prisma operation returns an Effect and
plugs into Effect's Layer / Context system. It is great if you want to
run Prisma through Effect's runtime.
| | prisma-effect-schema-generator | effect-prisma-generator |
|---|---|---|
| Output | Schema.Struct values + introspection maps | PrismaService Effect service |
| Needs Prisma Client at runtime | No | Yes |
| Primary use case | Validate rows / introspect schema | Execute Prisma operations in Effect |
| Relations | Optional relationColumns schemas | Full Prisma Client relation API |
| Errors | Standard Schema decode errors | Typed PrismaError unions |
If you need Effect-powered Prisma Client operations, use
effect-prisma-generator. If you need portable Effect schemas and metadata
from your schema, use this package.
License
MIT
