prisma-arktype
v2.5.0
Published
Generate ArkType schemas from your Prisma schema
Maintainers
Readme
prisma-arktype
Generate ArkType validation schemas from your Prisma schema.
This package is heavily inspired by and based on the structure of prismabox, which generates TypeBox schemas from Prisma schemas.
Features
- 🎯 Type-safe validation - Generate ArkType schemas that match your Prisma models
- 🔄 Automatic generation - Schemas are generated automatically when you run
prisma generate - 📦 Comprehensive coverage - Generates schemas for models, relations, where clauses, select, include, orderBy, and more
- 🎨 Customizable - Control schema generation with annotations
- 🚀 Zero config - Works out of the box with sensible defaults
Installation
npm install prisma-arktype arktype
# or
pnpm add prisma-arktype arktype
# or
yarn add prisma-arktype arktypeUsage
Basic Setup
Add the generator to your schema.prisma file:
generator prisma-arktype {
provider = "prisma-arktype"
output = "./generated/validators"
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Then run:
npx prisma generateConfiguration Options
Configure the generator in your schema.prisma:
generator prisma-arktype {
provider = "prisma-arktype"
output = "./generated/validators"
arktypeImportDependencyName = "arktype"
ignoredKeysOnInputModels = ["id", "createdAt", "updatedAt"]
}Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| output | string | "./prisma/generated/validators" | Output directory for generated schemas |
| arktypeImportDependencyName | string | "arktype" | The package name to import from |
| ignoredKeysOnInputModels | string[] | ["id", "createdAt", "updatedAt"] | Fields to exclude from input models |
Generated Schemas
For each model, the generator creates multiple schema types:
ModelPlain- Scalar fields only (strings, numbers, dates, enums) - no relationsModelRelations- Relationship fields only, referencing related model Plain typesModel- Complete composite schema combining Plain & RelationsModelWhere- Where clause schema for filteringModelWhereUnique- Unique where clause schema for finding specific recordsModelCreate- Input schema for creating recordsModelUpdate- Input schema for updating recordsModelSelect- Schema for selecting specific fieldsModelInclude- Schema for including relationsModelOrderBy- Schema for ordering results
Enums are generated as separate reusable types that are imported and referenced by models that use them.
Using Generated Schemas
import { type } from "arktype";
import { User, UserCreate, UserWhere } from "./generated/validators";
// Validate a user object
const userResult = User(someUserData);
if (userResult instanceof type.errors) {
console.error(userResult.summary);
} else {
// userResult is validated user data
console.log(userResult);
}
// Validate create input
const createData = {
email: "[email protected]",
name: "John Doe"
};
const createResult = UserCreate(createData);
// ...
// Validate where clauses
const whereClause = {
email: "[email protected]"
};
const whereResult = UserWhere(whereClause);
// ...Where Clause Filters
Where clauses support advanced filtering through dedicated filter types. Fields can accept either direct values or filter objects with comparison operators.
Filter Types
String Filters
String fields can use StringFilter for advanced text filtering:
import { UserWhere } from "./generated/validators";
// Direct value
const result1 = UserWhere({ email: "[email protected]" });
// Filter object
const result2 = UserWhere({
email: {
contains: "example", // Contains substring
startsWith: "user", // Starts with prefix
endsWith: ".com", // Ends with suffix
equals: "[email protected]", // Exact match
not: "[email protected]", // Not equal to
in: ["[email protected]", "[email protected]"], // In array
notIn: ["[email protected]"], // Not in array
gt: "a", // Greater than (lexicographic)
gte: "a", // Greater than or equal
lt: "z", // Less than
lte: "z" // Less than or equal
}
});Available operations: contains, startsWith, endsWith, equals, not, in, notIn, gt, gte, lt, lte
Number Filters
Integer fields (Int, BigInt) use IntFilter, while floating-point fields (Float, Decimal) use NumberFilter:
import { PostWhere } from "./generated/validators";
// Direct value
const result1 = PostWhere({ views: 100 });
// Filter object for integers
const result2 = PostWhere({
views: {
equals: 100,
gt: 50, // Greater than
gte: 50, // Greater than or equal
lt: 200, // Less than
lte: 200, // Less than or equal
in: [100, 200, 300], // In array
notIn: [0], // Not in array
not: 0 // Not equal to
}
});
// Filter object for floats/decimals
const result3 = PostWhere({
rating: {
gte: 4.5,
lte: 5.0
}
});Available operations: equals, gt, gte, lt, lte, in, notIn, not
Boolean Filters
Boolean fields use BooleanFilter:
import { PostWhere } from "./generated/validators";
// Direct value
const result1 = PostWhere({ published: true });
// Filter object
const result2 = PostWhere({
published: {
equals: true,
not: false
}
});Available operations: equals, not
Enum Filters
Enum fields use the generic enumFilter:
import { PaymentWhere } from "./generated/validators";
// Direct enum value
const result1 = PaymentWhere({ currency: "USD" });
// Filter object
const result2 = PaymentWhere({
currency: {
equals: "USD",
in: ["USD", "EUR", "GBP"],
notIn: ["JPY"],
not: "CAD"
}
});Available operations: equals, in, notIn, not
DateTime Filters
DateTime fields use DateTimeFilter:
import { PostWhere } from "./generated/validators";
// Direct Date value
const result1 = PostWhere({ createdAt: new Date("2024-01-01") });
// Filter object
const result2 = PostWhere({
createdAt: {
equals: new Date("2024-01-01"),
gt: new Date("2024-01-01"), // After
gte: new Date("2024-01-01"), // On or after
lt: new Date("2024-12-31"), // Before
lte: new Date("2024-12-31"), // On or before
in: [new Date("2024-01-01"), new Date("2024-06-01")],
notIn: [new Date("2024-07-04")],
not: new Date("2024-01-01")
}
});Available operations: equals, gt, gte, lt, lte, in, notIn, not
Array Filters
Array fields use specialized array filters with operations for list matching:
import { TagWhere } from "./generated/validators";
// String arrays
const result1 = TagWhere({
labels: {
isEmpty: false, // Array is empty
has: "important", // Array contains value
hasEvery: ["tag1", "tag2"], // Array contains all values
hasSome: ["tag1", "tag2"], // Array contains at least one value
equals: ["exact", "match"] // Array exactly matches
}
});
// Number arrays
const result2 = ScoresWhere({
values: {
isEmpty: false,
has: 100,
hasEvery: [90, 95, 100],
hasSome: [100, 200],
equals: [90, 95, 100]
}
});
// Enum arrays
const result3 = PermissionsWhere({
roles: {
isEmpty: false,
has: "ADMIN",
hasEvery: ["USER", "ADMIN"],
hasSome: ["ADMIN", "MODERATOR"],
equals: ["USER"]
}
});Available array filter types:
StringArrayFilter- forString[]fieldsNumberArrayFilter- forInt[],Float[],Decimal[]fieldsBigIntArrayFilter- forBigInt[]fieldsarrayFilter(EnumType)- for enum array fields
Available operations: isEmpty, has, hasEvery, hasSome, equals
Combining Filters
You can combine multiple filters in a single where clause:
import { PostWhere } from "./generated/validators";
const complexQuery = PostWhere({
title: { contains: "TypeScript" },
views: { gte: 100 },
published: true,
rating: { gte: 4.0 },
createdAt: {
gte: new Date("2024-01-01"),
lt: new Date("2024-12-31")
}
});Generated Code Examples
Enum Generation
For a Prisma enum like:
enum Currency {
USD
EUR
GBP
}The generator creates a separate reusable type:
// Currency.ts
import { type } from "arktype";
export const Currency = type("'USD' | 'EUR' | 'GBP'");Which is then imported and used in models:
// PaymentPlain.ts
import { type } from "arktype";
import { Currency } from "./Currency";
export const PaymentPlain = type({
"id": "string",
"amount": "number",
"currency": Currency, // Required enum
"status?": Currency.or("null") // Optional enum
});Relation Generation
For Prisma models with relations like:
model User {
id String @id
email String
posts Post[]
}
model Post {
id String @id
title String
author User @relation(fields: [authorId], references: [id])
authorId String
}The generator creates Plain types (without relations):
// UserPlain.ts
export const UserPlain = type({
"id": "string",
"email": "string"
});
// PostPlain.ts
export const PostPlain = type({
"id": "string",
"title": "string",
"authorId": "string"
});And Relations types that reference the Plain types:
// UserRelations.ts
import { PostPlain } from "./PostPlain";
export const UserRelations = type({
"posts": PostPlain.array() // Array of Post objects
});
// PostRelations.ts
import { UserPlain } from "./UserPlain";
export const PostRelations = type({
"author": UserPlain // Single User object
});The combined model merges both:
// User.ts
import { UserPlain } from "./UserPlain";
import { UserRelations } from "./UserRelations";
export const User = type(() => UserPlain.and(UserRelations));Annotations
Control schema generation using annotations in your Prisma schema. All annotations are added as documentation comments (///).
Available Annotations
| Annotation | Scope | Description |
|------------|-------|-------------|
| @prisma-arktype.hide | Model or Field | Completely hide from all generated schemas |
| @prisma-arktype.input.hide | Field | Hide from Create and Update input schemas |
| @prisma-arktype.create.input.hide | Field | Hide from Create input schema only |
| @prisma-arktype.update.input.hide | Field | Hide from Update input schema only |
| @prisma-arktype.schema="<schema>" | Field | Custom ArkType schema (inline or external) |
| @prisma-arktype.typeOverwrite="<type>" | Field | Override the generated ArkType type |
Hide Fields/Models
Completely exclude models or fields from all generated schemas:
/// @prisma-arktype.hide
model InternalModel {
id String @id
secret String
}
model User {
id String @id
email String
/// @prisma-arktype.hide
passwordHash String
}Hide from Input Models
Control which fields appear in Create and Update schemas:
model User {
id String @id
email String
/// @prisma-arktype.input.hide
/// Hidden from both Create and Update
computedField String
/// @prisma-arktype.create.input.hide
/// Only appears in Update schema
lastModified DateTime
/// @prisma-arktype.update.input.hide
/// Only appears in Create schema
initialStatus String
}Type Override
Override the default type mapping with custom ArkType type strings:
model User {
id String @id
/// @prisma-arktype.typeOverwrite="string.email"
email String
/// @prisma-arktype.typeOverwrite="string.url"
website String
/// @prisma-arktype.typeOverwrite="string.numeric"
phone String
}This allows you to use any ArkType type definition, including built-in refinements like string.email, string.url, number.integer, etc.
Custom Schemas
Bring your own ArkType schemas for any field using @prisma-arktype.schema:
model User {
id String @id
/// Inline schema for structured JSON
/// @prisma-arktype.schema="{ name: 'string', age: 'number' }"
profile Json
/// External schema from a file (named export)
/// @prisma-arktype.schema="../schemas/address:AddressSchema"
address Json
/// External schema (default export)
/// @prisma-arktype.schema="../schemas/config"
settings Json
}Import Path Rules:
- Paths are relative to the generated validators directory
- Named exports use colon syntax:
"path:ExportName" - Default exports omit the colon:
"path" - Works with ANY field type (not just Json)
Priority: schema > typeOverwrite > default type mapping
Example external schema file (schemas/address.ts):
import { type } from "arktype";
export const AddressSchema = type({
street: "string",
city: "string",
zipCode: "string",
country: "string",
});Type Mapping
Prisma types are mapped to ArkType as follows:
| Prisma Type | ArkType Type | Example Output |
|-------------|--------------|----------------|
| String | "string" | "string" |
| Int | "number.integer" | "number.integer" |
| BigInt | "number.integer" | "number.integer" |
| Float | "number" | "number" |
| Decimal | "number" | "number" |
| Boolean | "boolean" | "boolean" |
| DateTime | "Date" | "Date" |
| Json | "unknown" | "unknown" |
| Bytes | "instanceof Buffer" | "instanceof Buffer" |
| Enums | Reference to enum type | Currency (imported from ./Currency) |
| Relations | Reference to related Plain type | PostPlain or PostPlain.array() |
Special Handling
- Optional fields: Use
?on the key name ("name?": "string") - Nullable fields: Add
| nullto the type ("string | null") - Arrays: Use
.array()syntax for lists (type("string").array()orCurrency.array()) - Enums: Generated as separate reusable type definitions and imported where used
- Relations: Reference the Plain type of the related model, imported automatically
Differences from prismabox
While this package is inspired by prismabox, there are some key differences:
- ArkType vs TypeBox: Uses ArkType's syntax and type system instead of TypeBox
- Simpler type definitions: ArkType's string-based syntax makes schemas more readable
- No nullable wrapper: ArkType handles nullable types directly with union syntax
- Different validation API: Uses ArkType's validation approach
Development
Setup
# Clone the repository
git clone https://github.com/yourusername/prisma-arktype.git
cd prisma-arktype
# Install dependencies
pnpm install
# Build the project
pnpm build
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run linter
pnpm lint
# Fix linting issues
pnpm lint:fixTesting
This library has a completely schema-independent test suite using self-contained test models in prisma/schema/test-models.prisma.
Running Tests
# Run all tests
pnpm test:e2e
# Run tests in watch mode
pnpm test:watchTest Architecture
The test suite is designed to be 100% independent of production schemas:
- Self-Contained Schema -
prisma/schema/test-models.prismacontains all models needed for testing - No Production Dependencies - Tests work even if production schemas don't exist
- Comprehensive Coverage - Test models cover all Prisma types, relations, and generator features
- Portable - Can be used across different projects or extracted as a standalone test suite
Test Model Categories
The test schema includes specialized models for testing:
- Basic CRUD -
TestUser,TestPost,TestProfile - All Prisma Types -
TestAllTypes(String, Int, BigInt, Float, Decimal, Boolean, DateTime, Json, Bytes) - Relations - One-to-one, one-to-many, many-to-many, composite keys
- Annotations -
@prisma-arktype.hide,@prisma-arktype.input.hide,@prisma-arktype.typeOverwrite - Query Operations - Select, Include, OrderBy schemas
- Enums -
TestCurrency,TestStatus
Adding New Tests
- Add test models to
prisma/schema/test-models.prismaif needed - Update mapping in
__tests__/config/model-mapping.tsto reference your models - Write tests using helper functions from
__tests__/utils/test-helpers.ts - Run tests -
pnpm test
See existing test files for examples.
Why Schema-Independent?
- ✅ Tests never break due to production schema changes
- ✅ Contributors can run tests without setting up production databases
- ✅ Tests can be run in isolation (CI/CD, local development)
- ✅ Clear, documented examples of generator usage
- ✅ Easy to test new features by adding new test models
Publishing
This project uses Changesets for version management and publishing.
Creating a changeset
When you make changes that should be included in the next release:
pnpm changesetThis will prompt you to:
- Select the type of change (major, minor, patch)
- Provide a description of the changes
Commit the generated changeset file along with your changes.
Publishing workflow
- Create a changeset for your changes
- Open a PR with your changes and the changeset
- Merge the PR - The GitHub Action will automatically create a "Version Packages" PR
- Review and merge the Version Packages PR - This will:
- Update the version in package.json
- Update the CHANGELOG.md
- Publish the package to npm
- Create a GitHub release
Manual publishing (maintainers only)
# Build and publish
pnpm releasePrerequisites:
- Set up
NPM_TOKENsecret in GitHub repository settings - Ensure you have publish access to the npm package
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Create a changeset (
pnpm changeset) - Commit your changes following the commit message format (see below)
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Commit Message Format
This project uses Conventional Commits. Commit messages are automatically linted using commitlint and lefthook.
Format: <type>(<scope>): <subject>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testsbuild: Build system changesci: CI/CD changeschore: Other changes
Examples:
git commit -m "feat: add support for custom type validators"
git commit -m "fix: resolve issue with nullable DateTime fields"
git commit -m "docs: update installation instructions"
git commit -m "refactor: simplify where clause generation"Git Hooks
This project uses lefthook to manage git hooks:
- commit-msg: Validates commit message format
- pre-commit: Runs linter and checks for debug statements
- pre-push: Runs tests before pushing
To skip hooks (use sparingly):
git commit --no-verify -m "your message"License
MIT
Credits
This package is heavily based on prismabox by m1212e. Many thanks for the excellent foundation and architecture!
