frourio-framework-prisma-generators
v7.8.1
Published
A Prisma generator that produces immutable, type-safe TypeScript model classes from your Prisma schema.
Maintainers
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.tsspec file (recommended) — single source of truth for DTO shape:hide/nested/profiles/jsonType/ per-viewselect,transforms,computed,raw. Preferred over///schema annotations.- Custom typing for
Jsonfields (@jsonannotation, orjsonTypein spec) - Field hiding from DTO output (
@dto(hidden: true)annotation, orhide: truein spec) - Custom DTO profiles with pick/omit (
@dto.profileannotation, orprofilesin 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, andrawviews in the spec; the generator emits per-view types, classes, and repository methods
Requirements
- prisma, @prisma/[email protected] or later (both must be the same version!)
Install
npm install -D frourio-framework-prisma-generatorsSetup
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
repositorygenerator 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.tsis preferred over///schema annotations: it is type-checked against the Prisma schema, supports IDE autocomplete, allows arrow functions / static maps fortransformsandcomputed(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/@jsonannotations 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 mapfields[name].jsonType— custom TS type forJsonfields (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 ownSelect,Row,Dto, andViewclass.
View types
Generated per view (for defineModelDto("Post", { views: { detail: {...} } })):
postDetailSelect— typedPrisma.PostSelectconst forprisma.post.findMany({ select })PostDetailRow— raw row shape returned by PrismaPostDetailDto— flattened DTO type (after transforms / computed applied)PostDetailView— class withstatic fromPrismaValue(row)andtoDto()
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.tsviabase.profilesinstead — 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 withomit).omit: List of field names to exclude (mutually exclusive withpick).
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()—passwordis excluded (hidden applies)toDebugDto()—passwordis included (profile overrides hidden)
Validation
- Using both
pickandomiton 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/omitlist 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:
settingsis stored internally as theSettingsObjecttype- Excluded from
toDto()andConfigModelDto - Accessible via getter (
config.settings) as theSettingsObjecttype
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.tsspec ファイル(推奨) — DTO 形状の単一情報源:hide/nested/profiles/jsonType/ view ごとのselect・transforms・computed・raw。///スキーマアノテーションより優先推奨。Json型フィールドのカスタム型指定(@jsonアノテーション、または spec のjsonType)- フィールド非表示機能(
@dto(hidden: true)アノテーション、または spec のhide: true) - カスタム DTO プロファイル(
@dto.profileアノテーション、または spec のprofiles— spec 推奨) - リレーションフィールドの自動型生成(
WithIncludes型) - 外部キーフィールドの自動除外(リレーションフィールドが存在する場合)
- リポジトリ生成(ベータ) —
findBy、paginate、CRUD 付きリポジトリクラスを自動生成 - View 駆動 DTO 生成 — spec ファイルに
select・transforms・computed・rawview を宣言すると、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 | fromPrismaValue と toDto — クエリ、変換、出力 |
| 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 補完が効き、transforms・computedでアロー関数や静的マップを使える(アノテーションでは表現不能)。DTO 関連設定をスキーマファイルから分離できる。新規プロジェクトは spec で DTO を設定すること。既存の@dto(hidden: true)/@dto(nested: true)/@dto.profile/@jsonアノテーションも後方互換のため引き続きサポート。
view の形状・transform・計算フィールドを単一 spec ファイルに宣言します。モデルジェネレーターが view ごとの型と View クラスを生成し、リポジトリジェネレーターが findById{View} / findMany{View} / paginate{View} メソッドを追加します。モデル全体のベース設定(hide・nested・profiles・jsonType)も 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].jsonType—Jsonフィールドのカスタム TS 型(@json(type: [...])と同じ)profiles—@dto.profileと同じ({ name, pick?, omit? }の配列)
views— view ごとのクエリ形状 + マッピング。各キーがSelect・Row・Dto・Viewクラスを持つ view として生成される。
View の種別
view ごとの生成物(例: defineModelDto("Post", { views: { detail: {...} } })):
postDetailSelect—prisma.post.findMany({ select })で使う型付きPrisma.PostSelect定数PostDetailRow— Prisma が返す生のロウ型PostDetailDto— transforms / computed 適用後のフラット DTO 型PostDetailView—static 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 ベースの生成をバイパスします。prisma は PrismaClient 型付き、args は明示注釈、row は raw の戻り値から推論。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() の戻り値型です。DateTime は string に変換されます。外部キーフィールド(authorId など)はリレーションフィールドがある場合、自動的に除外されます。
export type PostModelDto = {
id: number;
createdAt: string; // DateTime → string (ISO 8601)
title: string;
content?: string | null;
author?: UserWithIncludes | null;
};コンストラクタ引数型 ({Model}ModelConstructorArgs)
モデルのコンストラクタ引数型です。DateTime は Date 型のまま保持されます。
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.tsのbase.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)
バリデーション
pickとomitを同一プロファイルで同時に指定するとそのプロファイルは無視されます- 存在しないフィールド名を指定すると警告が出力されスキップされます
- 同じプロファイル名が重複する場合、最初の定義のみ使用されます
pick/omitのリストが空の場合、そのプロファイルは生成されません
アノテーションの組み合わせ
複数のアノテーションを同じフィールドに指定できます:
model Config {
id Int @id
settings Json /// @json(type: [SettingsObject]) @dto(hidden: true)
}この場合:
settingsはSettingsObject型として内部保持される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 |
