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

frourio-framework-prisma-generators

v7.8.1

Published

A Prisma generator that produces immutable, type-safe TypeScript model classes from your Prisma schema.

Readme

frourio-framework-prisma-generators

A Prisma generator that produces immutable, type-safe TypeScript model classes from your Prisma schema.

Features

  • Generates immutable TypeScript model classes from Prisma schema
  • toDto() method for DTO conversion (DateTime to ISO string, Decimal to number, etc.)
  • fromPrismaValue() for bridging from Prisma Client results
  • Builder pattern for flexible construction, model extension, and test fixtures
  • dto.config.ts spec file (recommended) — single source of truth for DTO shape: hide / nested / profiles / jsonType / per-view select, transforms, computed, raw. Preferred over /// schema annotations.
  • Custom typing for Json fields (@json annotation, or jsonType in spec)
  • Field hiding from DTO output (@dto(hidden: true) annotation, or hide: true in spec)
  • Custom DTO profiles with pick/omit (@dto.profile annotation, or profiles in spec — spec recommended)
  • Auto-generated relation types (WithIncludes)
  • Automatic foreign key field exclusion when a relation field exists
  • Repository generation (beta) — auto-generated repository classes with findBy, paginate, and CRUD
  • View-driven DTO generation — declare select, transforms, computed, and raw views in the spec; the generator emits per-view types, classes, and repository methods

Requirements

Install

npm install -D frourio-framework-prisma-generators

Setup

Add the generator configuration to your schema.prisma:

generator frourio_framework_prisma_model_generator {
    provider = "frourio-framework-prisma-model-generator"
    output   = "__generated__/models"
    additionalTypePath = "./additionalType.config" // Required when using @json annotation
    spec               = "./dto.config.ts"         // Optional — enables view-driven DTO generation
}

| Option | Description | |--------|-------------| | provider | Generator name (fixed value) | | output | Output directory (relative to prisma schema) | | additionalTypePath | Import path for custom types used with @json annotation | | spec | Path to view-driven DTO spec file (see View-Driven DTO Generation) |

Repository Generator (Beta)

Add a separate generator block to enable auto-generated repository classes:

generator repository {
    provider  = "frourio-framework-prisma-repository-generator"
    output    = "__generated__/repository"
    modelPath = "__generated__/model"    // Path to model generator output
    spec      = "./dto.config.ts"        // Optional — enables view repository methods
}

| Option | Description | |--------|-------------| | provider | Generator name (fixed value) | | output | Output directory (relative to prisma schema) | | modelPath | Path to model generator output (for import resolution) | | spec | Path to view-driven DTO spec file. Use the same spec file as the model generator to enable findById{View} / findMany{View} / paginate{View} methods |


Examples

Working examples are available in the examples/ directory:

Basic Usage

| File | Description | |------|-------------| | basic/01-from-prisma-value.ts | fromPrismaValue and toDto — query, convert, and output | | basic/02-dto-hidden.ts | @dto(hidden: true) — hide sensitive fields like password | | basic/03-dto-profiles.ts | @dto.profile — purpose-specific DTOs with pick/omit | | basic/04-json-typed-fields.ts | @json — custom TypeScript types for Json fields | | basic/05-dto-nested.ts | @dto(nested: true) — auto-convert relations to nested DTOs |

Builder Pattern

| File | Description | |------|-------------| | builder/01-basic-builder.ts | Skip unused relations with the builder | | builder/02-test-fixture.ts | Test fixtures with Faker | | builder/03-extend-model.ts | Custom fields, builder extension, DTO customization |

Repository Pattern (Beta)

| File | Description | |------|-------------| | repository/UserRepository.ts | Extending the generated repository with custom queries | | repository/JsonField.repository.ts | Simple repository usage without the generator |

Note: Repository generation is a beta feature. Add a separate repository generator block to enable it.

View-Driven DTO

| File | Description | |------|-------------| | views/01-direct-view.ts | Direct use of generated view select + class + DTO | | views/02-repository-views.ts | findById{View} / findMany{View} / paginate{View} repository methods | | views/03-computed-fields.ts | Computed fields added to the DTO via computed: { from } |


Repository Generator (Beta)

When the repository generator is enabled, it produces:

  • BaseRepository — abstract class with CRUD operations and pagination
  • {Model}Repository — concrete class per model with auto-generated query methods

Auto-Generated Methods

For each model, the following methods are generated based on schema metadata:

| Source | Generated Method | Example | |--------|-----------------|---------| | @id field | findBy{Field}(value) | findById(id: number) | | @unique field | findBy{Field}(value) | findByEmail(email: string) | | @@unique([x, y]) | findBy{X}And{Y}(x, y) | findByBookIdAndPostId(bookId, postId) | | All models | paginate(args?) | Typed filtering, sorting, and pagination |

Inherited Methods (from BaseRepository)

| Method | Description | |--------|-------------| | findMany(args?) | Find all matching records | | findFirst(args?) | Find the first matching record | | count(args?) | Count matching records | | exists(where) | Check if a matching record exists | | create(args) | Create a record and return the model | | createMany(args) | Batch create records (returns count) | | update(args) | Update a record and return the model | | updateMany(args) | Batch update records (returns count) | | upsert(args) | Create or update a record | | delete(args) | Delete a record and return the model | | deleteMany(args?) | Batch delete records (returns count) | | aggregate(args) | Aggregate operations (count, sum, avg, min, max) | | cursorPaginate(args) | Cursor-based pagination for large datasets | | withTransaction(tx) | Create a repository instance bound to a transaction |

Usage — Direct

import { PrismaClient } from "@prisma/client";
import { UserRepository } from "./__generated__/repository/User.repository";

const prisma = new PrismaClient();
const userRepo = new UserRepository(prisma.user);

// Auto-generated findBy methods
const user = await userRepo.findById(1);
const userByEmail = await userRepo.findByEmail("[email protected]");

// Paginate with typed filters
const page = await userRepo.paginate({
  page: 1,
  perPage: 20,
  where: { name: { contains: "alice", mode: "insensitive" } },
  orderBy: { field: "id", direction: "desc" },
});

Usage — Extending with Custom Methods

import { PrismaClient } from "@prisma/client";
import { UserRepository as GeneratedUserRepository } from "./__generated__/repository/User.repository";

export class UserRepository extends GeneratedUserRepository {
  // Add custom query methods
  async findActiveUsers() {
    return this.findMany({ where: { active: true } });
  }
}

View-Driven DTO Generation

Recommended way to configure DTOs. A single typed dto.config.ts is preferred over /// schema annotations: it is type-checked against the Prisma schema, supports IDE autocomplete, allows arrow functions / static maps for transforms and computed (which annotations cannot express), and keeps DTO concerns out of the schema file. New projects should configure DTOs through the spec; existing @dto(hidden: true) / @dto(nested: true) / @dto.profile / @json annotations remain supported for backward compatibility.

Declare view shapes, transforms, and computed fields in a single spec file. The model generator emits per-view types and a View class; the repository generator adds findById{View} / findMany{View} / paginate{View} methods. Base model configuration (hide, nested, profiles, jsonType) can also be expressed in the spec as the recommended alternative to /// schema annotations.

Enable

Add spec to both generator blocks (see Setup):

generator frourio_framework_prisma_model_generator {
    provider = "frourio-framework-prisma-model-generator"
    output   = "__generated__/model"
    spec     = "./dto.config.ts"
}

generator repository {
    provider  = "frourio-framework-prisma-repository-generator"
    output    = "__generated__/repository"
    modelPath = "__generated__/model"
    spec      = "./dto.config.ts"
}

Spec file (dto.config.ts)

The spec default-exports registerModelDtos([...]). Each entry is a defineModelDto(modelName, { base?, views? }).

import {
  registerModelDtos,
  defineModelDto,
} from "frourio-framework-prisma-generators/spec";

export default registerModelDtos([
  defineModelDto("User", {
    // base: configures the default DTO (alternative to /// annotations)
    base: {
      fields: {
        password: { hide: true },   // === @dto(hidden: true)
        posts:    { nested: true }, // === @dto(nested: true)
      },
      profiles: [
        { name: "Public", pick: ["id", "email", "name"] },
        { name: "Admin",  omit: ["password"] },
      ],
    },
    // views: per-view select + mapping
    views: {
      listItem: {
        select: { id: true, email: true, name: true },
      },
      profile: {
        select: {
          id: true, email: true, name: true,
          posts: { select: { id: true, title: true, published: true } },
        },
      },
    },
  }),
]);

Two orthogonal blocks per model:

  • base — configures the full-model DTO ({Model}Model + {Model}ModelDto). Equivalent to /// schema annotations.
    • fields[name].hide — exclude from DTO (same as @dto(hidden: true))
    • fields[name].nested — expand relation as nested DTO (same as @dto(nested: true))
    • fields[name].map — static enum→label map
    • fields[name].jsonType — custom TS type for Json fields (same as @json(type: [...]))
    • profiles — same as @dto.profile (array of { name, pick?, omit? })
  • views — per-view query shape + mapping. Each key becomes a generated view with its own Select, Row, Dto, and View class.

View types

Generated per view (for defineModelDto("Post", { views: { detail: {...} } })):

  • postDetailSelect — typed Prisma.PostSelect const for prisma.post.findMany({ select })
  • PostDetailRow — raw row shape returned by Prisma
  • PostDetailDto — flattened DTO type (after transforms / computed applied)
  • PostDetailView — class with static fromPrismaValue(row) and toDto()

Output path: __generated__/views/{Model}.views.ts.

Select view

views: {
  listItem: {
    select: { id: true, email: true, name: true },
  },
}

Transforms — function

adminListItem: {
  select: { id: true, title: true, published: true },
  transforms: {
    published: (v) => (v ? "公開" : "非公開"),  // v inferred as boolean
  },
}

transforms rewrites specific fields. The value can be a function (receives the raw DB value) or a static map. Keys support dot-paths ("students.attendance") to target fields inside nested relations.

Transforms — static map

withStatus: {
  select: { id: true, status: true },
  transforms: {
    status: {
      DRAFT: "下書き",
      PUBLISHED: "公開",
      ARCHIVED: "アーカイブ",
    },
  },
}

Sugar for enum→label mapping.

Computed fields

detail: {
  select: { id: true, title: true, published: true },
  computed: {
    summary: {
      from: (v) => `${v.title} (${v.published ? "公開" : "非公開"})`,
    },
  },
}

Adds a property not present in select. Return type is inferred from from. The function runs inside the view class's toDto().

Raw views

stats: {
  raw: (prisma, args: { authorId: number }) =>
    prisma.post.aggregate({
      where: { authorId: args.authorId },
      _count: { id: true },
      _sum:   { viewCount: true },
    }),
  map: (row) => ({
    totalPosts: row._count.id,
    totalViews: row._sum.viewCount ?? 0,
  }),
}

Bypasses select-based generation. prisma is typed as PrismaClient; args is explicit; row is inferred from raw's return. The DTO type comes from map's return.

Usage — direct

import {
  userProfileSelect,
  UserProfileView,
  type UserProfileDto,
} from "./__generated__/views/User.views";

const row = await prisma.user.findUnique({
  where: { id: 1 },
  select: userProfileSelect,
});
const dto: UserProfileDto | null = row
  ? UserProfileView.fromPrismaValue(row).toDto()
  : null;

Usage — repository methods

When both generators share the same spec, each non-raw view produces three repository methods:

| Method | Returns | |--------|---------| | findById{View}(id) | {Model}{View}Dto \| null | | findMany{View}(args?) | {Model}{View}Dto[] | | paginate{View}(args?) | { rows, page, perPage, total, totalPages } of {Model}{View}Dto |

const userRepo = new UserRepository(prisma.user);

const profile = await userRepo.findByIdProfile(1);
//    ^? UserProfileDto | null

const items = await userRepo.findManyListItem();
//    ^? UserListItemDto[]

const paged = await userRepo.paginateProfile({
  page: 1,
  perPage: 20,
  orderBy: { field: "id", direction: "desc" },
});

Generated Model Structure

For each Prisma model, the following are generated:

model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  title     String
  content   String?
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int?
}

DTO Type ({Model}ModelDto)

The return type of toDto(). DateTime is converted to string. Foreign key fields (e.g. authorId) are automatically excluded when a corresponding relation field exists.

export type PostModelDto = {
  id: number;
  createdAt: string;    // DateTime → string (ISO 8601)
  title: string;
  content?: string | null;
  author?: UserWithIncludes | null;
};

Constructor Args Type ({Model}ModelConstructorArgs)

The constructor argument type. DateTime remains as the Date type.

export type PostModelConstructorArgs = {
  id: number;
  createdAt: Date;      // DateTime → Date
  title: string;
  content?: string | null;
  author?: UserWithIncludes | null;
};

FromPrismaValue Args Type ({Model}ModelFromPrismaValueArgs)

The argument type for fromPrismaValue(). Pass the Prisma record as self and relations as separate arguments.

export type PostModelFromPrismaValueArgs = {
  self: PrismaPost;
  author?: UserWithIncludes;
};

Model Class ({Model}Model)

An immutable model class. All fields are stored as private readonly and accessed via getters.

export class PostModel {
  private readonly _id: number;
  private readonly _createdAt: Date;
  private readonly _title: string;
  private readonly _content?: string | null;
  private readonly _author?: UserWithIncludes | null;

  constructor(args: PostModelConstructorArgs) { ... }

  static fromPrismaValue(args: PostModelFromPrismaValueArgs) {
    return new PostModel({
      id: args.self.id,
      createdAt: args.self.createdAt,
      title: args.self.title,
      content: args.self.content,
      author: args.author,
    });
  }

  toDto() {
    return {
      id: this._id,
      createdAt: this._createdAt.toISOString(),
      title: this._title,
      content: this._content,
      author: this._author,
    };
  }

  get id() { return this._id; }
  get createdAt() { return this._createdAt; }
  get title() { return this._title; }
  get content() { return this._content; }
  get author() { return this._author; }
}

Relation Type ({Model}WithIncludes)

A WithIncludes type is generated for models referenced by relation fields. It includes the related model's relation fields as optional properties.

export type UserWithIncludes = PartialBy<
  Prisma.UserGetPayload<typeof includeUser>,
  keyof (typeof includeUser)['include']
>;

Type Mapping

Conversion rules from Prisma types to TypeScript types:

| Prisma Type | Constructor / Getter | DTO (toDto()) | Conversion | |-------------|---------------------|-----------------|------------| | String | string | string | None | | Int | number | number | None | | Float | number | number | None | | Boolean | boolean | boolean | None | | DateTime | Date | string | .toISOString() | | Json | Prisma.JsonValue | Prisma.JsonValue | None (overridable via @json) | | Decimal | number | number | .toNumber() in fromPrismaValue | | BigInt | bigint | string | .toString() | | Bytes | Buffer | string | Buffer.from().toString('base64') | | Enum | Prisma{EnumName} | Prisma{EnumName} | None | | Relation | {Type}WithIncludes | {Type}WithIncludes | None |

Nullable fields use ? with | null typing. In toDto(), they are safely converted using optional chaining (?.) and nullish coalescing (?? null).


Annotations

An annotation system using Prisma's /// documentation comments.

@json - Custom Typing for Json Fields

Assign a custom TypeScript type to Json fields.

Syntax

fieldName Json /// @json(type: [TypeName])

Configuration

Set the additionalTypePath to the import path of your custom types:

generator frourio_framework_prisma_model_generator {
    provider           = "frourio-framework-prisma-model-generator"
    output             = "__generated__/models"
    additionalTypePath = "./additionalType.config"
}

Example

1. Add annotations to your schema:

model JsonField {
  id         Int  @id @default(autoincrement())
  rawJson    Json
  jsonObject Json /// @json(type: [JsonObject])
  jsonArray  Json /// @json(type: [JsonArray])
}

2. Define your custom types:

// prisma/additionalType.config.ts
export type JsonObject = {
  foo: string;
  bar: number;
};

export type JsonArray = JsonObject[];

3. Generated output:

import { JsonObject, JsonArray } from '../../additionalType.config';

export type JsonFieldModelDto = {
  id: number;
  rawJson: Prisma.JsonValue;   // No annotation → Prisma.JsonValue
  jsonObject: JsonObject;       // @json → custom type
  jsonArray: JsonArray;         // @json → custom type
};

In fromPrismaValue(), the value is cast via as unknown as JsonObject.


@dto(hidden: true) - Hide Fields from DTO

Exclude specific fields from the toDto() output and the {Model}ModelDto type. Useful for sensitive fields like password that should not appear in API responses.

Syntax

fieldName Type /// @dto(hidden: true)

Example

model User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  name     String?
  password String /// @dto(hidden: true)
}

Generated Output

// DTO type — password is excluded
export type UserModelDto = {
  id: number;
  email: string;
  name?: string | null;
  // password is not included here
};

export class UserModel {
  private readonly _password: string;  // Still stored internally

  toDto() {
    return {
      id: this._id,
      email: this._email,
      name: this._name,
      // password is not output
    };
  }

  get password() { return this._password; }  // Still accessible via getter
}

Behavior

| Target | Effect of hidden | |--------|-------------------| | {Model}ModelDto type | Excluded | | toDto() method | Excluded | | {Model}ModelConstructorArgs type | Not affected (included) | | fromPrismaValue() | Not affected (included) | | Private fields | Not affected (retained) | | Getters | Not affected (accessible) |


@dto(nested: true) - Nested DTO Conversion

Automatically convert relation fields into their model's DTO form in toDto() output. Without this annotation, relation fields are passed through as raw Prisma WithIncludes types.

Syntax

fieldName Model[] /// @dto(nested: true)

Example

model User {
  id    Int    @id @default(autoincrement())
  email String
  posts Post[] /// @dto(nested: true)
  books Book[]
}

Generated Output

// DTO type — posts uses PostModelDto instead of PostWithIncludes
export type UserModelDto = {
  id: number;
  email: string;
  posts: PostModelDto[];        // @dto(nested: true) → nested DTO
  books: BookWithIncludes[];    // no annotation → raw Prisma type
};

export class UserModel {
  toDto() {
    return {
      id: this._id,
      email: this._email,
      // posts are automatically converted via builder + toDto
      posts: this._posts.map((el) =>
        PostModel.builder().fromPrisma(el).build().toDto()
      ),
      books: this._books,
    };
  }
}

Behavior

| Target | Effect of nested | |--------|-------------------| | {Model}ModelDto type | Relation type becomes {Related}ModelDto | | toDto() method | Automatically converts via builder().fromPrisma().build().toDto() | | {Model}ModelConstructorArgs type | Not affected (uses WithIncludes) | | fromPrismaValue() | Not affected (uses WithIncludes) |


@dto.profile - Custom DTO Profiles

Recommended: define profiles in dto.config.ts via base.profiles instead — it is type-checked, supports IDE autocomplete, and keeps DTO definitions out of the schema. See View-Driven DTO Generation. The annotation form below is still supported for backward compatibility.

Generate purpose-specific DTO types and methods. Control field inclusion with pick (include only specified fields) or omit (exclude specified fields).

Syntax

Write /// comments above the model declaration:

/// @dto.profile(name: ProfileName, pick: [field1, field2, ...])
/// @dto.profile(name: ProfileName, omit: [field1, field2, ...])
model ModelName {
  ...
}
  • name: Profile name (PascalCase recommended). Used for method and type names.
  • pick: List of field names to include (mutually exclusive with omit).
  • omit: List of field names to exclude (mutually exclusive with pick).

Example

/// @dto.profile(name: Public, pick: [id, email, name])
/// @dto.profile(name: Admin, omit: [password])
model User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  name     String?
  password String /// @dto(hidden: true)

  posts Post[]
}

Generated Output

// Default DTO — password excluded by @dto(hidden: true)
export type UserModelDto = {
  id: number;
  email: string;
  name?: string | null;
  posts: PostWithIncludes[];
};

// Public profile — only the 3 fields specified by pick
export type UserPublicDto = {
  id: number;
  email: string;
  name?: string | null;
};

// Admin profile — all fields except password (including relations)
export type UserAdminDto = {
  id: number;
  email: string;
  name?: string | null;
  posts: PostWithIncludes[];
};

export class UserModel {
  // Default DTO
  toDto(): UserModelDto { ... }

  // Profile DTOs
  toPublicDto(): UserPublicDto { ... }
  toAdminDto(): UserAdminDto { ... }
}

Profiles and Hidden Fields

Profiles override @dto(hidden: true). If a profile explicitly picks a hidden field, it will be included in that profile's output.

/// @dto.profile(name: Debug, pick: [id, password])
model User {
  id       Int    @id
  password String /// @dto(hidden: true)
}

In this case:

  • toDto()password is excluded (hidden applies)
  • toDebugDto()password is included (profile overrides hidden)

Validation

  • Using both pick and omit on the same profile causes the profile to be ignored
  • Referencing a non-existent field name logs a warning and skips that field
  • Duplicate profile names on the same model use only the first definition
  • An empty pick / omit list causes the profile to be skipped

Combining Annotations

Multiple annotations can be specified on the same field:

model Config {
  id       Int  @id
  settings Json /// @json(type: [SettingsObject]) @dto(hidden: true)
}

In this case:

  • settings is stored internally as the SettingsObject type
  • Excluded from toDto() and ConfigModelDto
  • Accessible via getter (config.settings) as the SettingsObject type

Builder Pattern

Each model generates a {Model}ModelBuilder class with a fluent API. The builder provides:

  • Optional relations — skip relations you don't need (list relations default to [])
  • Individual scalar setters — perfect for test fixtures with Faker
  • Extensibility — subclass the builder to add custom fields

Note: fromPrismaValue() remains strict — all relations are required. The builder is a separate, opt-in flexible path.

Basic Usage — Skip Unused Relations

// Before (fromPrismaValue — all relations required):
const user = UserModel.fromPrismaValue({
  self: prismaUser,
  posts: loadedPosts,
  books: [],                         // must pass even if unused
  initiatorUserNotification: [],      // must pass even if unused
  receivingUserNotification: [],      // must pass even if unused
});

// After (builder — only set what you need):
const user = UserModel.builder()
  .fromPrisma(prismaUser)            // sets all scalar fields
  .posts(loadedPosts)                // only the relation you loaded
  .build();                          // books, notifications → []

Test Fixtures with Faker

Individual scalar setters make the builder ideal for test data generation:

import { faker } from "@faker-js/faker";

function createUserFixture(overrides?: Partial<{
  id: number;
  email: string;
  name: string | null;
  password: string;
}>) {
  return UserModel.builder()
    .id(overrides?.id ?? faker.number.int())
    .email(overrides?.email ?? faker.internet.email())
    .name(overrides?.name ?? faker.person.fullName())
    .password(overrides?.password ?? faker.internet.password())
    .build();
  // All relations default to [] — only specify scalars
}

// In tests:
const user = createUserFixture();
const admin = createUserFixture({ email: "[email protected]" });

Extending Models — Custom Fields

Subclass the generated model and builder to add custom fields:

import {
  UserModel,
  UserModelBuilder,
  UserModelConstructorArgs,
} from "./__generated__/model/User.model";

// 1. Extend the model
type AppUserArgs = UserModelConstructorArgs & { fullName: string };

class AppUser extends UserModel {
  private readonly _fullName: string;

  constructor(args: AppUserArgs) {
    super(args);
    this._fullName = args.fullName;
  }

  get fullName() { return this._fullName; }

  override toDto() {
    return { ...super.toDto(), fullName: this._fullName };
  }
}

// 2. Extend the builder
class AppUserBuilder extends UserModelBuilder {
  private _fullName?: string;

  fullName(value: string): this {
    this._fullName = value;
    return this;
  }

  override build(): AppUser {
    if (!this._fullName) throw new Error('"fullName" is required');
    return new AppUser({
      ...this.buildArgs(),   // protected — accessible from subclasses
      fullName: this._fullName,
    });
  }
}

// Usage:
const appUser = new AppUserBuilder()
  .fromPrisma(prismaUser)
  .fullName("John Doe")
  .posts(loadedPosts)
  .build();

appUser.toDto();
// → { id, email, name, posts, ..., fullName }

Extending Models — Custom DTO Output

Override toDto() to transform nested relations into rich DTOs:

class UserWithNestedDtos extends UserModel {
  override toDto() {
    return {
      ...super.toDto(),
      posts: this.posts.map((post) =>
        PostModel.builder().fromPrisma(post).build().toDto()
      ),
    };
  }
}

Generated Builder API Reference

For a model with scalar fields (id, email, name, password) and list relations (posts, books):

export class UserModelBuilder {
  protected _args: Partial<UserModelConstructorArgs>;

  // Set all scalars from a Prisma record (with type conversions)
  fromPrisma(value: PrismaUser): this;

  // Individual scalar setters
  id(value: number): this;
  email(value: string): this;
  name(value: string | null): this;
  password(value: string): this;

  // Relation setters
  posts(value: PostWithIncludes[]): this;
  books(value: BookWithIncludes[]): this;

  // Resolve args with validation and defaults (protected for subclasses)
  protected buildArgs(): UserModelConstructorArgs;

  // Construct the model instance
  build(): UserModel;
}

Default Values in buildArgs()

| Field Kind | Default | |------------|---------| | Required scalar | Throws Error if not set | | Optional scalar | null | | List relation | [] | | Optional single relation | undefined | | Required single relation | Throws Error if not set |



frourio-framework-prisma-generators (Japanese / 日本語)

Prisma schema からイミュータブルな TypeScript モデルクラスを自動生成するジェネレーターです。

機能一覧

  • Prisma schema から型安全なモデルクラスを生成
  • toDto() メソッドによる DTO 変換(DateTime の ISO 文字列変換、Decimal の number 変換など)
  • fromPrismaValue() による Prisma Client からの変換
  • Builder パターン — 柔軟な構築、モデル拡張、テスト fixture 生成
  • dto.config.ts spec ファイル(推奨) — DTO 形状の単一情報源: hide / nested / profiles / jsonType / view ごとの selecttransformscomputedraw/// スキーマアノテーションより優先推奨。
  • Json 型フィールドのカスタム型指定(@json アノテーション、または spec の jsonType
  • フィールド非表示機能(@dto(hidden: true) アノテーション、または spec の hide: true
  • カスタム DTO プロファイル(@dto.profile アノテーション、または spec の profilesspec 推奨
  • リレーションフィールドの自動型生成(WithIncludes 型)
  • 外部キーフィールドの自動除外(リレーションフィールドが存在する場合)
  • リポジトリ生成(ベータ) — findBypaginate、CRUD 付きリポジトリクラスを自動生成
  • View 駆動 DTO 生成 — spec ファイルに selecttransformscomputedraw view を宣言すると、view ごとの型・クラス・リポジトリメソッドが自動生成される

要件

  • prisma, @prisma/[email protected] 以降 (両方同じバージョンが必要です!)

インストール

npm install -D frourio-framework-prisma-generators

セットアップ

schema.prisma にジェネレーター設定を追加します:

generator frourio_framework_prisma_model_generator {
    provider = "frourio-framework-prisma-model-generator"
    output   = "__generated__/models"
    additionalTypePath = "./additionalType.config" // Json 型フィールドに型を指定する場合に必要
    spec               = "./dto.config.ts"         // 任意 — View 駆動 DTO 生成を有効化
}

| オプション | 説明 | |-----------|------| | provider | ジェネレーター名(固定値) | | output | 生成先ディレクトリ(Prisma schema からの相対パス) | | additionalTypePath | @json アノテーションで使用する型のインポートパス | | spec | View 駆動 DTO の spec ファイルパス(View 駆動 DTO 生成参照) |

リポジトリジェネレーター(ベータ)

別のジェネレーターブロックを追加してリポジトリクラスの自動生成を有効にします:

generator repository {
    provider  = "frourio-framework-prisma-repository-generator"
    output    = "__generated__/repository"
    modelPath = "__generated__/model"    // モデルジェネレーターの出力パス
    spec      = "./dto.config.ts"        // 任意 — view のリポジトリメソッドを有効化
}

| オプション | 説明 | |-----------|------| | provider | ジェネレーター名(固定値) | | output | 生成先ディレクトリ(Prisma schema からの相対パス) | | modelPath | モデルジェネレーターの出力パス(import 解決用) | | spec | View 駆動 DTO の spec ファイルパス。モデルジェネレーターと同じ spec を指定すると findById{View} / findMany{View} / paginate{View} メソッドが生成される |


サンプルコード

examples/ ディレクトリに動作するサンプルがあります:

基本的な使い方

| ファイル | 説明 | |---------|------| | basic/01-from-prisma-value.ts | fromPrismaValuetoDto — クエリ、変換、出力 | | basic/02-dto-hidden.ts | @dto(hidden: true)password 等のセンシティブフィールドを隠す | | basic/03-dto-profiles.ts | @dto.profile — pick/omit による用途別 DTO | | basic/04-json-typed-fields.ts | @json — Json フィールドにカスタム TypeScript 型を指定 | | basic/05-dto-nested.ts | @dto(nested: true) — リレーションをネスト DTO に自動変換 |

Builder パターン

| ファイル | 説明 | |---------|------| | builder/01-basic-builder.ts | 不要なリレーションをスキップ | | builder/02-test-fixture.ts | Faker を使ったテスト fixture | | builder/03-extend-model.ts | カスタムフィールド追加、Builder 拡張、DTO カスタマイズ |

リポジトリパターン

| ファイル | 説明 | |---------|------| | repository/UserRepository.ts | 生成されたリポジトリを継承してカスタムクエリを追加 | | repository/JsonField.repository.ts | ジェネレーターなしのシンプルなリポジトリ利用 |

注意: リポジトリ生成はベータ機能です。別の repository ジェネレーターブロックを追加して有効化してください。

View 駆動 DTO

| ファイル | 説明 | |---------|------| | views/01-direct-view.ts | 生成された view select + クラス + DTO を直接使う | | views/02-repository-views.ts | findById{View} / findMany{View} / paginate{View} リポジトリメソッド | | views/03-computed-fields.ts | computed: { from } で DTO に計算フィールドを追加 |


リポジトリジェネレーター(ベータ)

リポジトリジェネレーターを有効にすると、以下が生成されます:

  • BaseRepository — CRUD 操作とページネーションを提供する抽象クラス
  • {Model}Repository — モデルごとの具象クラス(自動生成されたクエリメソッド付き)

自動生成されるメソッド

スキーマのメタデータに基づいて、各モデルに以下のメソッドが生成されます:

| ソース | 生成されるメソッド | 例 | |-------|-----------------|-----| | @id フィールド | findBy{Field}(value) | findById(id: number) | | @unique フィールド | findBy{Field}(value) | findByEmail(email: string) | | @@unique([x, y]) | findBy{X}And{Y}(x, y) | findByBookIdAndPostId(bookId, postId) | | 全モデル共通 | paginate(args?) | 型付きフィルタリング、ソート、ページネーション |

継承メソッド(BaseRepository から)

| メソッド | 説明 | |---------|------| | findMany(args?) | 条件に一致する全件を取得 | | findFirst(args?) | 条件に一致する最初の1件を取得 | | count(args?) | 条件に一致するレコード数を返す | | exists(where) | 条件に一致するレコードの存在チェック | | create(args) | レコードを作成してモデルを返す | | createMany(args) | 一括作成(件数を返す) | | update(args) | レコードを更新してモデルを返す | | updateMany(args) | 一括更新(件数を返す) | | upsert(args) | レコードの作成または更新 | | delete(args) | レコードを削除してモデルを返す | | deleteMany(args?) | 一括削除(件数を返す) | | aggregate(args) | 集約操作(count, sum, avg, min, max) | | cursorPaginate(args) | カーソルベースのページネーション(大規模データ向け) | | withTransaction(tx) | トランザクションにバインドしたリポジトリインスタンスを生成 |

使い方 — 直接利用

import { PrismaClient } from "@prisma/client";
import { UserRepository } from "./__generated__/repository/User.repository";

const prisma = new PrismaClient();
const userRepo = new UserRepository(prisma.user);

// 自動生成された findBy メソッド
const user = await userRepo.findById(1);
const userByEmail = await userRepo.findByEmail("[email protected]");

// 型付きフィルタでページネーション
const page = await userRepo.paginate({
  page: 1,
  perPage: 20,
  where: { name: { contains: "alice", mode: "insensitive" } },
  orderBy: { field: "id", direction: "desc" },
});

使い方 — カスタムメソッドの追加

import { PrismaClient } from "@prisma/client";
import { UserRepository as GeneratedUserRepository } from "./__generated__/repository/User.repository";

export class UserRepository extends GeneratedUserRepository {
  // カスタムクエリメソッドを追加
  async findActiveUsers() {
    return this.findMany({ where: { active: true } });
  }
}

View 駆動 DTO 生成

DTO 設定の推奨方式。 単一の型付き dto.config.ts/// スキーマアノテーションより推奨。Prisma スキーマに対して型チェックされ、IDE 補完が効き、transformscomputed でアロー関数や静的マップを使える(アノテーションでは表現不能)。DTO 関連設定をスキーマファイルから分離できる。新規プロジェクトは spec で DTO を設定すること。既存の @dto(hidden: true) / @dto(nested: true) / @dto.profile / @json アノテーションも後方互換のため引き続きサポート。

view の形状・transform・計算フィールドを単一 spec ファイルに宣言します。モデルジェネレーターが view ごとの型と View クラスを生成し、リポジトリジェネレーターが findById{View} / findMany{View} / paginate{View} メソッドを追加します。モデル全体のベース設定(hidenestedprofilesjsonType)も spec で表現でき、/// スキーマアノテーションの推奨代替になります。

有効化

両方のジェネレーターブロックに spec を追加します(セットアップ参照):

generator frourio_framework_prisma_model_generator {
    provider = "frourio-framework-prisma-model-generator"
    output   = "__generated__/model"
    spec     = "./dto.config.ts"
}

generator repository {
    provider  = "frourio-framework-prisma-repository-generator"
    output    = "__generated__/repository"
    modelPath = "__generated__/model"
    spec      = "./dto.config.ts"
}

spec ファイル (dto.config.ts)

registerModelDtos([...]) をデフォルトエクスポートします。各要素は defineModelDto(modelName, { base?, views? }) です。

import {
  registerModelDtos,
  defineModelDto,
} from "frourio-framework-prisma-generators/spec";

export default registerModelDtos([
  defineModelDto("User", {
    // base: デフォルト DTO の設定(/// アノテーションの代替)
    base: {
      fields: {
        password: { hide: true },   // === @dto(hidden: true)
        posts:    { nested: true }, // === @dto(nested: true)
      },
      profiles: [
        { name: "Public", pick: ["id", "email", "name"] },
        { name: "Admin",  omit: ["password"] },
      ],
    },
    // views: view ごとの select + マッピング
    views: {
      listItem: {
        select: { id: true, email: true, name: true },
      },
      profile: {
        select: {
          id: true, email: true, name: true,
          posts: { select: { id: true, title: true, published: true } },
        },
      },
    },
  }),
]);

モデルごとに独立した 2 ブロック:

  • base — フルモデル DTO({Model}Model + {Model}ModelDto)の設定。/// スキーマアノテーション と等価。
    • fields[name].hide — DTO から除外(@dto(hidden: true) と同じ)
    • fields[name].nested — リレーションをネスト DTO として展開(@dto(nested: true) と同じ)
    • fields[name].map — enum→label の静的マップ
    • fields[name].jsonTypeJson フィールドのカスタム TS 型(@json(type: [...]) と同じ)
    • profiles@dto.profile と同じ({ name, pick?, omit? } の配列)
  • views — view ごとのクエリ形状 + マッピング。各キーが SelectRowDtoView クラスを持つ view として生成される。

View の種別

view ごとの生成物(例: defineModelDto("Post", { views: { detail: {...} } })):

  • postDetailSelectprisma.post.findMany({ select }) で使う型付き Prisma.PostSelect 定数
  • PostDetailRow — Prisma が返す生のロウ型
  • PostDetailDto — transforms / computed 適用後のフラット DTO 型
  • PostDetailViewstatic fromPrismaValue(row)toDto() を持つクラス

出力先: __generated__/views/{Model}.views.ts

Select view

views: {
  listItem: {
    select: { id: true, email: true, name: true },
  },
}

Transforms — 関数

adminListItem: {
  select: { id: true, title: true, published: true },
  transforms: {
    published: (v) => (v ? "公開" : "非公開"),  // v は boolean と推論
  },
}

transforms は特定フィールドを書き換えます。値は関数(DB の生値を受け取る)または静的マップ。キーはドットパス("students.attendance")でネストしたリレーション内のフィールドも対象にできます。

Transforms — 静的マップ

withStatus: {
  select: { id: true, status: true },
  transforms: {
    status: {
      DRAFT: "下書き",
      PUBLISHED: "公開",
      ARCHIVED: "アーカイブ",
    },
  },
}

enum→label 変換のシュガー。

計算フィールド (computed)

detail: {
  select: { id: true, title: true, published: true },
  computed: {
    summary: {
      from: (v) => `${v.title} (${v.published ? "公開" : "非公開"})`,
    },
  },
}

select に存在しないプロパティを追加します。戻り値型は from から推論。関数は view クラスの toDto() 内で実行されます。

Raw view

stats: {
  raw: (prisma, args: { authorId: number }) =>
    prisma.post.aggregate({
      where: { authorId: args.authorId },
      _count: { id: true },
      _sum:   { viewCount: true },
    }),
  map: (row) => ({
    totalPosts: row._count.id,
    totalViews: row._sum.viewCount ?? 0,
  }),
}

select ベースの生成をバイパスします。prismaPrismaClient 型付き、args は明示注釈、rowraw の戻り値から推論。DTO 型は map の戻り値から決まります。

使い方 — 直接利用

import {
  userProfileSelect,
  UserProfileView,
  type UserProfileDto,
} from "./__generated__/views/User.views";

const row = await prisma.user.findUnique({
  where: { id: 1 },
  select: userProfileSelect,
});
const dto: UserProfileDto | null = row
  ? UserProfileView.fromPrismaValue(row).toDto()
  : null;

使い方 — リポジトリメソッド

両方のジェネレーターが同じ spec を参照すると、raw 以外の各 view に対して 3 つのリポジトリメソッドが生成されます:

| メソッド | 戻り値 | |---------|-------| | findById{View}(id) | {Model}{View}Dto \| null | | findMany{View}(args?) | {Model}{View}Dto[] | | paginate{View}(args?) | {Model}{View}Dto のページ結果 { rows, page, perPage, total, totalPages } |

const userRepo = new UserRepository(prisma.user);

const profile = await userRepo.findByIdProfile(1);
//    ^? UserProfileDto | null

const items = await userRepo.findManyListItem();
//    ^? UserListItemDto[]

const paged = await userRepo.paginateProfile({
  page: 1,
  perPage: 20,
  orderBy: { field: "id", direction: "desc" },
});

生成されるモデル構造

各 Prisma モデルに対して、以下が生成されます:

model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  title     String
  content   String?
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int?
}

DTO 型 ({Model}ModelDto)

toDto() の戻り値型です。DateTimestring に変換されます。外部キーフィールド(authorId など)はリレーションフィールドがある場合、自動的に除外されます。

export type PostModelDto = {
  id: number;
  createdAt: string;    // DateTime → string (ISO 8601)
  title: string;
  content?: string | null;
  author?: UserWithIncludes | null;
};

コンストラクタ引数型 ({Model}ModelConstructorArgs)

モデルのコンストラクタ引数型です。DateTimeDate 型のまま保持されます。

export type PostModelConstructorArgs = {
  id: number;
  createdAt: Date;      // DateTime → Date
  title: string;
  content?: string | null;
  author?: UserWithIncludes | null;
};

FromPrismaValue 引数型 ({Model}ModelFromPrismaValueArgs)

fromPrismaValue() の引数型です。self に Prisma モデルのレコードを、リレーションは個別引数で渡します。

export type PostModelFromPrismaValueArgs = {
  self: PrismaPost;
  author?: UserWithIncludes;
};

モデルクラス ({Model}Model)

イミュータブルなモデルクラスです。全フィールドは private readonly で保持され、getter でアクセスします。

export class PostModel {
  private readonly _id: number;
  private readonly _createdAt: Date;
  private readonly _title: string;
  private readonly _content?: string | null;
  private readonly _author?: UserWithIncludes | null;

  constructor(args: PostModelConstructorArgs) { ... }

  static fromPrismaValue(args: PostModelFromPrismaValueArgs) {
    return new PostModel({
      id: args.self.id,
      createdAt: args.self.createdAt,
      title: args.self.title,
      content: args.self.content,
      author: args.author,
    });
  }

  toDto() {
    return {
      id: this._id,
      createdAt: this._createdAt.toISOString(),
      title: this._title,
      content: this._content,
      author: this._author,
    };
  }

  get id() { return this._id; }
  get createdAt() { return this._createdAt; }
  get title() { return this._title; }
  get content() { return this._content; }
  get author() { return this._author; }
}

リレーション型 ({Model}WithIncludes)

リレーションフィールドを持つモデルには WithIncludes 型が生成されます。リレーション先のフィールドをオプショナルとして含む型です。

export type UserWithIncludes = PartialBy<
  Prisma.UserGetPayload<typeof includeUser>,
  keyof (typeof includeUser)['include']
>;

型変換ルール

| Prisma 型 | Constructor / Getter | DTO (toDto()) | 変換処理 | |-----------|---------------------|-----------------|---------| | String | string | string | なし | | Int | number | number | なし | | Float | number | number | なし | | Boolean | boolean | boolean | なし | | DateTime | Date | string | .toISOString() | | Json | Prisma.JsonValue | Prisma.JsonValue | なし(@json で上書き可) | | Decimal | number | number | fromPrismaValue.toNumber() | | BigInt | bigint | string | .toString() | | Bytes | Buffer | string | Buffer.from().toString('base64') | | Enum | Prisma{EnumName} | Prisma{EnumName} | なし | | Relation | {Type}WithIncludes | {Type}WithIncludes | なし |

nullable フィールドは ? 付きで | null 型になり、toDto() ではオプショナルチェーン(?.)と null 合体(?? null)で安全に変換されます。


アノテーション

Prisma の /// ドキュメントコメントを使ったアノテーションシステムです。

@json - Json 型フィールドのカスタム型指定

Json 型フィールドに独自の TypeScript 型を指定できます。

構文

fieldName Json /// @json(type: [TypeName])

設定

additionalTypePath にカスタム型のインポート先を指定してください:

generator frourio_framework_prisma_model_generator {
    provider           = "frourio-framework-prisma-model-generator"
    output             = "__generated__/models"
    additionalTypePath = "./additionalType.config"
}

使用例

1. スキーマにアノテーションを追加:

model JsonField {
  id         Int  @id @default(autoincrement())
  rawJson    Json
  jsonObject Json /// @json(type: [JsonObject])
  jsonArray  Json /// @json(type: [JsonArray])
}

2. カスタム型を定義:

// prisma/additionalType.config.ts
export type JsonObject = {
  foo: string;
  bar: number;
};

export type JsonArray = JsonObject[];

3. 生成結果:

import { JsonObject, JsonArray } from '../../additionalType.config';

export type JsonFieldModelDto = {
  id: number;
  rawJson: Prisma.JsonValue;   // アノテーションなし → Prisma.JsonValue
  jsonObject: JsonObject;       // @json → カスタム型
  jsonArray: JsonArray;         // @json → カスタム型
};

fromPrismaValue() では as unknown as JsonObject でキャストされます。


@dto(hidden: true) - フィールド非表示

特定のフィールドを toDto() の出力と {Model}ModelDto 型から除外します。password のようなセンシティブなフィールドを API レスポンスに含めたくない場合に使用します。

構文

fieldName Type /// @dto(hidden: true)

使用例

model User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  name     String?
  password String /// @dto(hidden: true)
}

生成結果

// DTO 型 — password が除外される
export type UserModelDto = {
  id: number;
  email: string;
  name?: string | null;
  // password はここに含まれない
};

export class UserModel {
  private readonly _password: string;  // 内部では保持される

  toDto() {
    return {
      id: this._id,
      email: this._email,
      name: this._name,
      // password は出力されない
    };
  }

  get password() { return this._password; }  // getter ではアクセス可能
}

動作仕様

| 対象 | hidden の影響 | |------|-------------| | {Model}ModelDto 型 | 除外される | | toDto() メソッド | 除外される | | {Model}ModelConstructorArgs 型 | 影響なし(含まれる) | | fromPrismaValue() | 影響なし(含まれる) | | private フィールド | 影響なし(保持される) | | getter | 影響なし(アクセス可能) |


@dto(nested: true) - ネスト DTO 変換

リレーションフィールドを toDto() 出力時に自動的にそのモデルの DTO 形式に変換します。このアノテーションがない場合、リレーションは Prisma の WithIncludes 型のまま渡されます。

構文

fieldName Model[] /// @dto(nested: true)

使用例

model User {
  id    Int    @id @default(autoincrement())
  email String
  posts Post[] /// @dto(nested: true)
  books Book[]
}

生成結果

// DTO 型 — posts は PostWithIncludes ではなく PostModelDto を使用
export type UserModelDto = {
  id: number;
  email: string;
  posts: PostModelDto[];        // @dto(nested: true) → ネスト DTO
  books: BookWithIncludes[];    // アノテーションなし → 生の Prisma 型
};

export class UserModel {
  toDto() {
    return {
      id: this._id,
      email: this._email,
      // posts は builder + toDto で自動変換される
      posts: this._posts.map((el) =>
        PostModel.builder().fromPrisma(el).build().toDto()
      ),
      books: this._books,
    };
  }
}

動作仕様

| 対象 | nested の影響 | |------|-------------| | {Model}ModelDto 型 | リレーション型が {Related}ModelDto になる | | toDto() メソッド | builder().fromPrisma().build().toDto() で自動変換 | | {Model}ModelConstructorArgs 型 | 影響なしWithIncludes を使用) | | fromPrismaValue() | 影響なしWithIncludes を使用) |


@dto.profile - カスタム DTO プロファイル

推奨: プロファイルは dto.config.tsbase.profiles で定義する方を推奨。型チェック・IDE 補完が効き、DTO 定義をスキーマから分離できる。詳細は View 駆動 DTO 生成 参照。以下のアノテーション形式も後方互換のため引き続きサポート。

用途別に異なる DTO 型とメソッドを生成します。pick(指定フィールドのみ含める)または omit(指定フィールドを除外)で制御します。

構文

モデルの上に /// コメントとして記述します:

/// @dto.profile(name: ProfileName, pick: [field1, field2, ...])
/// @dto.profile(name: ProfileName, omit: [field1, field2, ...])
model ModelName {
  ...
}
  • name: プロファイル名(PascalCase 推奨)。メソッド名と型名に使用される
  • pick: 含めるフィールド名のリスト(omit と排他)
  • omit: 除外するフィールド名のリスト(pick と排他)

使用例

/// @dto.profile(name: Public, pick: [id, email, name])
/// @dto.profile(name: Admin, omit: [password])
model User {
  id       Int    @id @default(autoincrement())
  email    String @unique
  name     String?
  password String /// @dto(hidden: true)

  posts Post[]
}

生成結果

// デフォルト DTO — @dto(hidden: true) により password が除外
export type UserModelDto = {
  id: number;
  email: string;
  name?: string | null;
  posts: PostWithIncludes[];
};

// Public プロファイル — pick で指定した 3 フィールドのみ
export type UserPublicDto = {
  id: number;
  email: string;
  name?: string | null;
};

// Admin プロファイル — omit で password のみ除外(リレーション含む全フィールド)
export type UserAdminDto = {
  id: number;
  email: string;
  name?: string | null;
  posts: PostWithIncludes[];
};

export class UserModel {
  // デフォルト DTO
  toDto(): UserModelDto { ... }

  // プロファイル DTO
  toPublicDto(): UserPublicDto { ... }
  toAdminDto(): UserAdminDto { ... }
}

プロファイルと hidden の関係

プロファイルは @dto(hidden: true)override します。プロファイルで明示的に pick したフィールドは、そのフィールドが hidden であっても含まれます。

/// @dto.profile(name: Debug, pick: [id, password])
model User {
  id       Int    @id
  password String /// @dto(hidden: true)
}

この場合:

  • toDto()password除外される(hidden が適用される)
  • toDebugDto()password含まれる(プロファイルが hidden を override)

バリデーション

  • pickomit を同一プロファイルで同時に指定するとそのプロファイルは無視されます
  • 存在しないフィールド名を指定すると警告が出力されスキップされます
  • 同じプロファイル名が重複する場合、最初の定義のみ使用されます
  • pick / omit のリストが空の場合、そのプロファイルは生成されません

アノテーションの組み合わせ

複数のアノテーションを同じフィールドに指定できます:

model Config {
  id       Int  @id
  settings Json /// @json(type: [SettingsObject]) @dto(hidden: true)
}

この場合:

  • settingsSettingsObject 型として内部保持される
  • toDto()ConfigModelDto からは除外される
  • getter (config.settings) では SettingsObject 型でアクセス可能

Builder パターン

各モデルに {Model}ModelBuilder クラスが生成されます。Builder は以下を提供します:

  • リレーション省略 — 不要なリレーションをスキップ(リスト系は [] デフォルト)
  • 個別スカラーセッター — Faker と組み合わせたテスト fixture に最適
  • 拡張性 — Builder をサブクラス化してカスタムフィールドを追加

注意: fromPrismaValue() は厳密なまま変更されません(全リレーション必須)。Builder は柔軟性を選ぶための別経路です。

基本的な使い方 — 不要なリレーションをスキップ

// Before (fromPrismaValue — 全リレーション必須):
const user = UserModel.fromPrismaValue({
  self: prismaUser,
  posts: loadedPosts,
  books: [],                         // 使わなくても必須
  initiatorUserNotification: [],      // 使わなくても必須
  receivingUserNotification: [],      // 使わなくても必須
});

// After (Builder — 必要なものだけ):
const user = UserModel.builder()
  .fromPrisma(prismaUser)            // スカラーフィールドを一括セット
  .posts(loadedPosts)                // ロードしたリレーションだけ
  .build();                          // books, notifications → []

テスト fixture — Faker と組み合わせ

個別スカラーセッターにより、テストデータ生成に最適です:

import { faker } from "@faker-js/faker";

function createUserFixture(overrides?: Partial<{
  id: number;
  email: string;
  name: string | null;
  password: string;
}>) {
  return UserModel.builder()
    .id(overrides?.id ?? faker.number.int())
    .email(overrides?.email ?? faker.internet.email())
    .name(overrides?.name ?? faker.person.fullName())
    .password(overrides?.password ?? faker.internet.password())
    .build();
  // リレーションは全て [] デフォルト — スカラーだけ指定
}

// テストで:
const user = createUserFixture();
const admin = createUserFixture({ email: "[email protected]" });

モデル拡張 — カスタムフィールド追加

生成されたモデルと Builder をサブクラス化してカスタムフィールドを追加:

import {
  UserModel,
  UserModelBuilder,
  UserModelConstructorArgs,
} from "./__generated__/model/User.model";

// 1. モデルを拡張
type AppUserArgs = UserModelConstructorArgs & { fullName: string };

class AppUser extends UserModel {
  private readonly _fullName: string;

  constructor(args: AppUserArgs) {
    super(args);
    this._fullName = args.fullName;
  }

  get fullName() { return this._fullName; }

  override toDto() {
    return { ...super.toDto(), fullName: this._fullName };
  }
}

// 2. Builder を拡張
class AppUserBuilder extends UserModelBuilder {
  private _fullName?: string;

  fullName(value: string): this {
    this._fullName = value;
    return this;
  }

  override build(): AppUser {
    if (!this._fullName) throw new Error('"fullName" is required');
    return new AppUser({
      ...this.buildArgs(),   // protected — サブクラスからアクセス可能
      fullName: this._fullName,
    });
  }
}

// 使用例:
const appUser = new AppUserBuilder()
  .fromPrisma(prismaUser)
  .fullName("John Doe")
  .posts(loadedPosts)
  .build();

appUser.toDto();
// → { id, email, name, posts, ..., fullName }

モデル拡張 — DTO 出力のカスタマイズ

toDto() をオーバーライドしてネストしたリレーションをリッチな DTO に変換:

class UserWithNestedDtos extends UserModel {
  override toDto() {
    return {
      ...super.toDto(),
      posts: this.posts.map((post) =>
        PostModel.builder().fromPrisma(post).build().toDto()
      ),
    };
  }
}

生成される Builder API リファレンス

スカラーフィールド(id, email, name, password)とリスト系リレーション(posts, books)を持つモデルの場合:

export class UserModelBuilder {
  protected _args: Partial<UserModelConstructorArgs>;

  // Prisma レコードからスカラーを一括セット(型変換あり)
  fromPrisma(value: PrismaUser): this;

  // 個別スカラーセッター
  id(value: number): this;
  email(value: string): this;
  name(value: string | null): this;
  password(value: string): this;

  // リレーションセッター
  posts(value: PostWithIncludes[]): this;
  books(value: BookWithIncludes[]): this;

  // バリデーション + デフォルト値で引数を解決(サブクラス用 protected)
  protected buildArgs(): UserModelConstructorArgs;

  // モデルインスタンスを構築
  build(): UserModel;
}

buildArgs() のデフォルト値

| フィールド種別 | デフォルト | |--------------|---------| | 必須スカラー | 未セット時 Error を throw | | オプショナルスカラー | null | | リスト系リレーション | [] | | オプショナル単一リレーション | undefined | | 必須単一リレーション | 未セット時 Error を throw |