├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── .swcrc ├── README.md ├── example └── test-run.ts ├── jest.config.js ├── package.json ├── prisma ├── dev.db ├── migrations │ ├── 20211201151300_init │ │ └── migration.sql │ └── migration_lock.toml ├── schema.prisma └── seed.ts ├── src ├── args.ts ├── factory │ └── index.ts ├── generator.ts ├── helper.ts ├── index.ts ├── relation.ts └── tests │ ├── __snapshots__ │ ├── defaultValue.test.ts.snap │ ├── interface.test.ts.snap │ ├── sample.test.ts.snap │ └── type.test.ts.snap │ ├── args.test.ts │ ├── defaultValue.test.ts │ ├── helper.test.ts │ ├── interface.test.ts │ ├── relation.test.ts │ ├── sample.test.ts │ └── type.test.ts ├── tsconfig.json └── yarn.lock /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - v*.*.* 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - run: yarn install 11 | - run: yarn build 12 | - run: yarn publish --not-interactive 13 | env: 14 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | jobs: 3 | publish: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - uses: actions/checkout@v2 7 | - run: yarn install 8 | - run: yarn test 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | dmmf.json 5 | *.log 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "parser": { 4 | "syntax": "typescript" 5 | } 6 | }, 7 | "module": { 8 | "type": "commonjs" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Automatically generate a factory from your Prisma Schema. This package contains a prisma generator so reference will automatically update everytime you will run `prisma generate` 2 | 3 | There is a schema that looks like this 4 | ```prisma 5 | datasource db { 6 | provider = "sqlite" 7 | url = "file:./dev.db" 8 | } 9 | 10 | generator client { 11 | provider = "prisma-client-js" 12 | } 13 | 14 | generator factory { 15 | provider = "node ./dist/index.js" 16 | } 17 | 18 | model User { 19 | id Int @id @default(autoincrement()) 20 | email String @unique 21 | userName String @unique 22 | posts Post[] 23 | comments Comment[] 24 | 25 | @@map(name: "users") 26 | } 27 | 28 | model Post { 29 | id Int @id @default(autoincrement()) 30 | user User @relation(fields: [userId], references: [id]) 31 | userId Int @unique @map(name: "user_id") 32 | title String 33 | body String 34 | createdAt DateTime @default(now()) @map(name: "created_at") 35 | updatedAt DateTime @default(now()) @map(name: "updated_at") 36 | comments Comment[] 37 | 38 | @@map("posts") 39 | } 40 | 41 | model Comment { 42 | id Int @id @default(autoincrement()) 43 | post Post @relation(fields: [postId], references: [id]) 44 | postId Int @unique @map(name: "post_id") 45 | user User @relation(fields: [userId], references: [id]) 46 | userId Int @unique @map(name: "user_id") 47 | body String 48 | createdAt DateTime @default(now()) @map(name: "created_at") 49 | approved Boolean @default(false) 50 | 51 | @@map("comments") 52 | } 53 | ``` 54 | After running `npx prisma generate`, the following functions will be available. 55 | - createUser 56 | - createPost 57 | - createComment 58 | 59 | The scalar fields are set to random values by [faker](https://github.com/marak/Faker.js/). 60 | Currently, the relation field needs to be set manually (we would like to create an option for it to be created automatically). 61 | ```typescript 62 | 63 | // Create a user (email and userName are will be created by faker) 64 | const user = await createUser() 65 | console.log(user) 66 | /** 67 | * { 68 | * id: 1, 69 | * email: 'National Directives Orchestrator', 70 | * userName: 'Future Accountability Consultant' 71 | * } 72 | */ 73 | 74 | // Create a post (the title and body will be created by faker, and the user will be connected to the one created above). 75 | const postConnectUser = await createPost({ 76 | user: { 77 | connect: { 78 | id: user.id, 79 | }, 80 | }, 81 | }) 82 | console.log(postConnectUser) 83 | /** 84 | * { 85 | * id: 1, 86 | * userId: 1, 87 | * title: 'Dynamic Research Architect', 88 | * body: 'Corporate Tactics Associate', 89 | * createdAt: 2021-12-02T09:10:06.640Z, 90 | * updatedAt: 2021-12-02T09:10:06.640Z 91 | * } 92 | */ 93 | 94 | // As an alternative, you can create users at the same time. In this case, you can use the function `inputsForUser` to set the parameters needed to create a user. 95 | const postCreateUser = await createPost({ 96 | user: { 97 | create: inputsForUser(), 98 | }, 99 | }) 100 | console.log(postCreateUser) 101 | /** 102 | * { 103 | * id: 6, 104 | * userId: 6, 105 | * title: 'Future Accountability Officer', 106 | * body: 'Lead Usability Specialist', 107 | * createdAt: 2021-12-02T13:28:21.541Z, 108 | * updatedAt: 2021-12-02T13:28:21.541Z 109 | * } 110 | */ 111 | 112 | // Create a comment (the body will be created by faker, the approved will be set to the DB default, and the user and post will be connected to the one created above). 113 | const comment = await createComment({ 114 | user: { 115 | connect: { 116 | id: user.id, 117 | }, 118 | }, 119 | post: { 120 | connect: { 121 | id: post.id, 122 | }, 123 | }, 124 | }) 125 | console.log(comment) 126 | /** 127 | * { 128 | * id: 1, 129 | * postId: 1, 130 | * userId: 1, 131 | * body: 'Human Security Consultant', 132 | * createdAt: 2021-12-02T09:10:06.643Z, 133 | * approved: false 134 | * } 135 | */ 136 | ``` 137 | 138 | ## Getting Started 139 | 140 | 1. Install this package using: 141 | 142 | ```shell 143 | npm install prisma-factory-generator 144 | ``` 145 | or 146 | ```shell 147 | yarn add prisma-factory-generator 148 | ``` 149 | 150 | 2. Add the generator to the schema 151 | 152 | ```prisma 153 | generator factory { 154 | provider = "prisma-factory-generator" 155 | } 156 | ``` 157 | 158 | 3. Run `npx prisma generate` to trigger the generator. This will create a `factory` folder in `node_modules/.prisma/factory` 159 | 160 | ## Seeding data using a factory 161 | 162 | This factory can be used when creating data with the seed function provided in Prisma. 163 | 164 | 1. Prepare the seed script by referring to the Prisma documentation. 165 | [Seeding your database / Prisma documentation](https://www.prisma.io/docs/guides/database/seed-database) 166 | 2. Rewrite the `main` of the seed script to use the factory. Below is an example. 167 | ```typescript 168 | // prisma/seed.ts 169 | import { 170 | createUser, 171 | createPost, 172 | createComment, 173 | inputsForUser, 174 | } from '.prisma/factory' 175 | async function main() { 176 | const user = await createUser() 177 | console.log(user) 178 | 179 | const newUser = await createUser({ 180 | data: { userName: 'hello!' }, 181 | }) 182 | console.log(newUser) 183 | 184 | const postConnectUser = await createPost({ 185 | data: { 186 | user: { 187 | connect: { 188 | id: user.id, 189 | }, 190 | }, 191 | }, 192 | }) 193 | console.log(postConnectUser) 194 | 195 | const postCreateUser = await createPost({ 196 | data: { 197 | user: { 198 | create: inputsForUser(), 199 | }, 200 | }, 201 | }) 202 | console.log(postCreateUser) 203 | 204 | const comment = await createComment({ 205 | data: { 206 | user: { 207 | connect: { 208 | id: user.id, 209 | }, 210 | }, 211 | post: { 212 | connect: { 213 | id: postConnectUser.id, 214 | }, 215 | }, 216 | }, 217 | }) 218 | console.log(comment) 219 | } 220 | 221 | main().catch((e) => console.error(e.toString())) 222 | ``` 223 | 3. Run `npx prisma db seed` to create a seed using the factory 224 | 225 | 226 | ## To Do 227 | - [ ] attributesForUser 228 | - [ ] createUserCollection 229 | 230 | --- 231 | 232 | ### License 233 | 234 | MIT 235 | 236 | (This is not an official Prisma project. It is personally maintained by [me](https://github.com/toyamarinyon) ) 237 | 238 | -------------------------------------------------------------------------------- /example/test-run.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createUser, 3 | createPost, 4 | createComment, 5 | inputsForUser, 6 | } from '.prisma/factory' 7 | async function main() { 8 | const user = await createUser() 9 | console.log(user) 10 | 11 | const newUser = await createUser({ 12 | data: { userName: 'hello!' }, 13 | }) 14 | console.log(newUser) 15 | 16 | const postConnectUser = await createPost({ 17 | data: { 18 | user: { 19 | connect: { 20 | id: user.id, 21 | }, 22 | }, 23 | }, 24 | }) 25 | console.log(postConnectUser) 26 | 27 | const postCreateUser = await createPost({ 28 | data: { 29 | user: { 30 | create: inputsForUser(), 31 | }, 32 | }, 33 | }) 34 | console.log(postCreateUser) 35 | 36 | const comment = await createComment({ 37 | data: { 38 | user: { 39 | connect: { 40 | id: user.id, 41 | }, 42 | }, 43 | post: { 44 | connect: { 45 | id: postConnectUser.id, 46 | }, 47 | }, 48 | }, 49 | }) 50 | console.log(comment) 51 | } 52 | 53 | main() 54 | .then(() => { 55 | console.info('factory created!') 56 | }) 57 | .catch((e) => console.error(e.toString())) 58 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | modulePathIgnorePatterns: ["/dist/"], 3 | transform: { 4 | '^.+\\.(t|j)sx?$': '@swc/jest', 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-factory-generator", 3 | "version": "0.1.7", 4 | "description": "Factory generator for Prisma Schema", 5 | "repository": "https://github.com/toyamarinyon/prisma-factory-generator", 6 | "author": "@toyamarinyon", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "dependencies": { 10 | "@prisma/generator-helper": "^3.5.0", 11 | "@prisma/sdk": "^3.5.0", 12 | "camelcase": "^6.2.1", 13 | "faker": "^5.5.3", 14 | "ts-morph": "^13.0.2" 15 | }, 16 | "bin": { 17 | "prisma-factory-generator": "dist/index.js" 18 | }, 19 | "devDependencies": { 20 | "@prisma/client": "^3.5.0", 21 | "@swc/cli": "^0.1.52", 22 | "@swc/core": "^1.2.114", 23 | "@swc/jest": "^0.2.11", 24 | "@types/faker": "^5.5.9", 25 | "@types/jest": "^27.0.3", 26 | "@types/node": "^16.11.12", 27 | "jest": "^27.4.1", 28 | "prettier": "^2.5.0", 29 | "prisma": "^3.5.0", 30 | "regenerator-runtime": "^0.13.9", 31 | "ts-node": "^10.4.0", 32 | "typescript": "^4.5.2" 33 | }, 34 | "scripts": { 35 | "build": "swc src -d dist", 36 | "test": "jest", 37 | "dev": "ts-node example/test-run.ts" 38 | }, 39 | "prisma": { 40 | "seed": "ts-node prisma/seed.ts" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /prisma/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toyamarinyon/prisma-factory-generator/7049be8d7248163801c8179d73e6b1d07a494181/prisma/dev.db -------------------------------------------------------------------------------- /prisma/migrations/20211201151300_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | "email" TEXT NOT NULL, 5 | "userName" TEXT NOT NULL 6 | ); 7 | 8 | -- CreateTable 9 | CREATE TABLE "posts" ( 10 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 11 | "user_id" INTEGER NOT NULL, 12 | "title" TEXT NOT NULL, 13 | "body" TEXT NOT NULL, 14 | "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 15 | "updated_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 16 | CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 17 | ); 18 | 19 | -- CreateTable 20 | CREATE TABLE "comments" ( 21 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 22 | "post_id" INTEGER NOT NULL, 23 | "user_id" INTEGER NOT NULL, 24 | "body" TEXT NOT NULL, 25 | "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 26 | "approved" BOOLEAN NOT NULL DEFAULT false, 27 | CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, 28 | CONSTRAINT "comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 29 | ); 30 | 31 | -- CreateIndex 32 | CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); 33 | 34 | -- CreateIndex 35 | CREATE UNIQUE INDEX "users_userName_key" ON "users"("userName"); 36 | 37 | -- CreateIndex 38 | CREATE UNIQUE INDEX "posts_user_id_key" ON "posts"("user_id"); 39 | 40 | -- CreateIndex 41 | CREATE UNIQUE INDEX "comments_post_id_key" ON "comments"("post_id"); 42 | 43 | -- CreateIndex 44 | CREATE UNIQUE INDEX "comments_user_id_key" ON "comments"("user_id"); 45 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "sqlite" 6 | url = "file:./dev.db" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | generator factory { 14 | provider = "node ./dist/index.js" 15 | } 16 | 17 | model User { 18 | id Int @id @default(autoincrement()) 19 | email String @unique 20 | userName String @unique 21 | posts Post[] 22 | comments Comment[] 23 | 24 | @@map(name: "users") 25 | } 26 | 27 | model Post { 28 | id Int @id @default(autoincrement()) 29 | user User @relation(fields: [userId], references: [id]) 30 | userId Int @unique @map(name: "user_id") 31 | title String 32 | body String 33 | createdAt DateTime @default(now()) @map(name: "created_at") 34 | updatedAt DateTime @default(now()) @map(name: "updated_at") 35 | comments Comment[] 36 | 37 | @@map("posts") 38 | } 39 | 40 | model Comment { 41 | id Int @id @default(autoincrement()) 42 | post Post @relation(fields: [postId], references: [id]) 43 | postId Int @unique @map(name: "post_id") 44 | user User @relation(fields: [userId], references: [id]) 45 | userId Int @unique @map(name: "user_id") 46 | body String 47 | createdAt DateTime @default(now()) @map(name: "created_at") 48 | approved Boolean @default(false) 49 | 50 | @@map("comments") 51 | } 52 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createUser, 3 | createPost, 4 | createComment, 5 | inputsForUser, 6 | } from '.prisma/factory' 7 | async function main() { 8 | const user = await createUser() 9 | console.log(user) 10 | 11 | const postConnectUser = await createPost({ 12 | user: { 13 | connect: { 14 | id: user.id, 15 | }, 16 | }, 17 | }) 18 | console.log(postConnectUser) 19 | 20 | const postCreateUser = await createPost({ 21 | user: { 22 | create: inputsForUser(), 23 | }, 24 | }) 25 | console.log(postCreateUser) 26 | 27 | const comment = await createComment({ 28 | user: { 29 | connect: { 30 | id: user.id, 31 | }, 32 | }, 33 | post: { 34 | connect: { 35 | id: postConnectUser.id, 36 | }, 37 | }, 38 | }) 39 | console.log(comment) 40 | } 41 | 42 | main().catch((e) => console.error(e.toString())) 43 | -------------------------------------------------------------------------------- /src/args.ts: -------------------------------------------------------------------------------- 1 | import { DMMF } from '@prisma/client/runtime' 2 | import camelcase from 'camelcase' 3 | import { SourceFile } from 'ts-morph' 4 | import { getRelationFields, hasRequiredRelation } from './relation' 5 | 6 | export function factoryArgsTypeName(model: DMMF.Model) { 7 | return camelcase([model.name, 'factory', 'args'], { 8 | pascalCase: true, 9 | }) 10 | } 11 | export function args( 12 | sourceFile: SourceFile, 13 | model: DMMF.Model, 14 | models: DMMF.Model[] 15 | ) { 16 | const createArgsTypeName = camelcase([model.name, 'create', 'args'], { 17 | pascalCase: true, 18 | }) 19 | const createInputTypeName = camelcase([model.name, 'create', 'input'], { 20 | pascalCase: true, 21 | }) 22 | const relationFields = getRelationFields(model, models) 23 | const hasRelationFields = relationFields.length > 0 24 | const data = hasRelationFields 25 | ? `Pick & Partial>` 30 | : `Partial` 31 | sourceFile.addStatements(` 32 | type ${factoryArgsTypeName( 33 | model 34 | )} = Omit & { 35 | data: ${data} 36 | }`) 37 | } 38 | -------------------------------------------------------------------------------- /src/factory/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { Prisma } from ".prisma/client" 3 | import faker from "faker" 4 | const prisma = new PrismaClient() 5 | 6 | export const userDefaultVariables = { 7 | email: faker.name.title(), 8 | userName: faker.name.title() 9 | } 10 | type createUserArgs = RequiredParametersForUserCreation & Partial 11 | 12 | interface RequiredParametersForUserCreation { 13 | posts?: Prisma.PostCreateNestedManyWithoutUserInput 14 | comments?: Prisma.CommentCreateNestedManyWithoutUserInput 15 | } 16 | 17 | export async function createUser(args?: createUserArgs) { 18 | 19 | return await prisma.user.create({ 20 | data: { 21 | ...userDefaultVariables, 22 | ...args, 23 | } 24 | }) 25 | 26 | } 27 | 28 | 29 | export const postDefaultVariables = { 30 | title: faker.name.title(), 31 | body: faker.name.title() 32 | } 33 | type createPostArgs = RequiredParametersForPostCreation & Partial 34 | 35 | interface RequiredParametersForPostCreation { 36 | user: Prisma.UserCreateNestedOneWithoutPostsInput 37 | comments?: Prisma.CommentCreateNestedManyWithoutPostInput 38 | } 39 | 40 | export async function createPost(args: createPostArgs) { 41 | 42 | return await prisma.post.create({ 43 | data: { 44 | ...postDefaultVariables, 45 | ...args, 46 | } 47 | }) 48 | 49 | } 50 | 51 | 52 | export const commentDefaultVariables = { 53 | body: faker.name.title() 54 | } 55 | type createCommentArgs = RequiredParametersForCommentCreation & Partial 56 | 57 | interface RequiredParametersForCommentCreation { 58 | post: Prisma.PostCreateNestedOneWithoutCommentsInput 59 | user: Prisma.UserCreateNestedOneWithoutCommentsInput 60 | } 61 | 62 | export async function createComment(args: createCommentArgs) { 63 | 64 | return await prisma.comment.create({ 65 | data: { 66 | ...commentDefaultVariables, 67 | ...args, 68 | } 69 | }) 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import { DMMF } from '@prisma/client/runtime' 2 | import camelcase from 'camelcase' 3 | import { fakerForField } from './helper' 4 | import { SourceFile } from 'ts-morph' 5 | import { args, factoryArgsTypeName } from './args' 6 | import { hasRequiredRelation } from './relation' 7 | 8 | function getModelFactoryParameterTypeName(model: DMMF.Model) { 9 | return camelcase(['create', model.name, 'Args']) 10 | } 11 | export function addModelFactoryParameterType( 12 | sourceFile: SourceFile, 13 | model: DMMF.Model 14 | ) { 15 | sourceFile.addStatements( 16 | `type ${getModelFactoryParameterTypeName( 17 | model 18 | )} = ${getModelFactoryParameterInterfaceName( 19 | model 20 | )} & Partial` 23 | ) 24 | } 25 | 26 | export function addPrismaImportDeclaration(sourceFile: SourceFile) { 27 | sourceFile.addImportDeclaration({ 28 | moduleSpecifier: '@prisma/client', 29 | namedImports: ['PrismaClient'], 30 | }) 31 | sourceFile.addImportDeclaration({ 32 | moduleSpecifier: '.prisma/client', 33 | namedImports: ['Prisma'], 34 | }) 35 | sourceFile.addImportDeclaration({ 36 | moduleSpecifier: 'faker', 37 | namespaceImport: 'faker', 38 | }) 39 | sourceFile.addStatements(`const prisma = new PrismaClient()`) 40 | } 41 | 42 | export function getModelFactoryParameterInterfaceProperties( 43 | model: DMMF.Model, 44 | models: DMMF.Model[] 45 | ) { 46 | return Object.fromEntries( 47 | model.fields 48 | .filter((field) => field.kind === 'object') 49 | .map((field) => { 50 | const relationKind = field.isList ? 'many' : 'one' 51 | const isOptional = !field.isRequired || field.isList 52 | const relationDest = models.find((m) => { 53 | if (m.name === model.name) { 54 | return false 55 | } 56 | return m.fields.find((f) => f.relationName === field.relationName) 57 | }) 58 | if (!relationDest) { 59 | throw new Error( 60 | `missing relation dest error, model: ${model.name}, field: ${field.name}` 61 | ) 62 | } 63 | 64 | const relationField = relationDest?.fields.find( 65 | (f) => f.relationName === field.relationName 66 | ) 67 | if (!relationField) { 68 | throw new Error('missing relation field error') 69 | } 70 | 71 | return [ 72 | isOptional ? `${field.name}?` : field.name, 73 | `Prisma.${camelcase( 74 | [ 75 | relationKind === 'one' ? field.name : field.type.toString(), 76 | 'CreateNested', 77 | relationKind, 78 | 'Without', 79 | relationField.name, 80 | 'Input', 81 | ], 82 | { 83 | pascalCase: true, 84 | } 85 | )}`, 86 | ] 87 | }) 88 | ) 89 | } 90 | function getModelFactoryParameterInterfaceName(model: DMMF.Model) { 91 | return camelcase(['RequiredParametersFor', model.name, 'creation'], { 92 | pascalCase: true, 93 | }) 94 | } 95 | export function addModelAttributeForFunction( 96 | sourceFile: SourceFile, 97 | model: DMMF.Model 98 | ) { 99 | sourceFile.addFunction({ 100 | isExported: true, 101 | name: getAttributesForFunctionName(model), 102 | statements: (writer) => { 103 | writer.write('return').block(() => { 104 | const initializer = getModelDefaultValueVariableInitializer(model) 105 | Object.keys(getModelDefaultValueVariableInitializer(model)).map( 106 | (key) => { 107 | writer.write(`${key}: ${initializer[key]},`) 108 | } 109 | ) 110 | }) 111 | }, 112 | }) 113 | } 114 | export function addModelFactoryParameterInterface( 115 | sourceFile: SourceFile, 116 | model: DMMF.Model, 117 | models: DMMF.Model[] 118 | ) { 119 | const properties = getModelFactoryParameterInterfaceProperties(model, models) 120 | sourceFile.addInterface({ 121 | name: getModelFactoryParameterInterfaceName(model), 122 | properties: Object.keys(properties).map((key) => ({ 123 | name: key, 124 | type: properties[key], 125 | })), 126 | }) 127 | return Object.keys(properties).some((key) => !key.endsWith('?')) 128 | } 129 | 130 | export function getModelDefaultValueVariableInitializer(model: DMMF.Model) { 131 | return Object.fromEntries( 132 | model.fields 133 | .filter((field) => !field.isId) 134 | .filter((field) => field.kind === 'scalar') 135 | .filter((field) => { 136 | return !model.fields.find((it) => { 137 | return it.relationFromFields?.includes(field.name) 138 | }) 139 | }) 140 | .filter((field) => !field.hasDefaultValue) 141 | .map((field) => [field.name, fakerForField(field)]) 142 | ) 143 | } 144 | 145 | function getAttributesForFunctionName(model: DMMF.Model) { 146 | return camelcase(['inputsFor', model.name]) 147 | } 148 | 149 | export function addModelFactoryDeclaration( 150 | sourceFile: SourceFile, 151 | model: DMMF.Model, 152 | models: DMMF.Model[] 153 | ) { 154 | const modelName = model.name 155 | addModelAttributeForFunction(sourceFile, model) 156 | args(sourceFile, model, models) 157 | const isRequired = hasRequiredRelation(model, models) 158 | sourceFile.addFunction({ 159 | isExported: true, 160 | isAsync: true, 161 | name: camelcase(['create', modelName]), 162 | parameters: [ 163 | { 164 | name: isRequired ? 'args' : 'args?', 165 | type: `${factoryArgsTypeName(model)}`, 166 | }, 167 | ], 168 | statements: ` 169 | return await prisma.${camelcase(modelName)}.create({ 170 | ...args, 171 | data: { 172 | ...${getAttributesForFunctionName(model)}(), 173 | ...args?.data 174 | }, 175 | }) 176 | `, 177 | }) 178 | } 179 | -------------------------------------------------------------------------------- /src/helper.ts: -------------------------------------------------------------------------------- 1 | import * as faker from 'faker' 2 | import { DMMF } from '@prisma/client/runtime' 3 | 4 | export function fakerForStringField(fieldName: string) { 5 | const nameFieldFakers = Object.keys(faker.name) 6 | if (nameFieldFakers.includes(fieldName)) { 7 | return `faker.name.${fieldName}()` 8 | } 9 | const internetFakers = Object.keys(faker.internet) 10 | if (internetFakers.includes(fieldName)) { 11 | return `faker.internet.${fieldName}()` 12 | } 13 | return 'faker.name.title()' 14 | } 15 | 16 | export function fakerForField(field: DMMF.Field) { 17 | const fieldType = field.type 18 | const fieldKind = field.kind 19 | if (fieldType === 'String') { 20 | return fakerForStringField(field.name) 21 | } 22 | if (fieldType === 'Int' || fieldType === 'BigInt') { 23 | return 'faker.datatype.number()' 24 | } 25 | if (fieldType === 'Float') { 26 | return 'faker.datatype.float()' 27 | } 28 | if (fieldType === 'Decimal') { 29 | return 'faker.datatype.hexaDecimal()' 30 | } 31 | if (fieldType === 'DateTime') { 32 | return 'faker.datatype.datetime()' 33 | } 34 | if (fieldType === 'Boolean') { 35 | return (field.default as string) ?? false 36 | } 37 | if (fieldType === 'Json') { 38 | return 'faker.datatype.json()' 39 | } 40 | throw new Error(`${fieldType} isn't supported. kind: ${fieldKind}`) 41 | } 42 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { generatorHandler } from '@prisma/generator-helper' 4 | import { ModuleKind, Project, ts } from 'ts-morph' 5 | import * as fs from 'fs' 6 | import * as path from 'path' 7 | import { 8 | addPrismaImportDeclaration, 9 | addModelFactoryDeclaration, 10 | } from './generator' 11 | 12 | generatorHandler({ 13 | onManifest() { 14 | return { 15 | defaultOutput: 'node_modules/.prisma/factory', 16 | prettyName: 'Prisma Factory Generator', 17 | } 18 | }, 19 | async onGenerate(options) { 20 | const output = options.generator.output?.value 21 | 22 | if (output) { 23 | const project = new Project({ 24 | compilerOptions: { declaration: true, module: ModuleKind.CommonJS }, 25 | }) 26 | const sourceFile = project.createSourceFile( 27 | `${output}/index.ts`, 28 | undefined, 29 | { 30 | overwrite: true, 31 | } 32 | ) 33 | addPrismaImportDeclaration(sourceFile) 34 | const models = options.dmmf.datamodel.models 35 | options.dmmf.datamodel.models.forEach((model) => { 36 | addModelFactoryDeclaration(sourceFile, model, models) 37 | }) 38 | sourceFile.formatText({ 39 | indentSize: 2, 40 | semicolons: ts.SemicolonPreference.Remove, 41 | }) 42 | 43 | try { 44 | await fs.promises.mkdir(output, { 45 | recursive: true, 46 | }) 47 | await sourceFile.emit() 48 | 49 | const packageJsonTargetPath = path.join(output, 'package.json') 50 | const pkgJson = JSON.stringify( 51 | { 52 | name: '.prisma/client', 53 | main: 'index.js', 54 | types: 'index.d.ts', 55 | }, 56 | null, 57 | 2 58 | ) 59 | await fs.promises.writeFile(packageJsonTargetPath, pkgJson) 60 | } catch (e) { 61 | console.error( 62 | 'Error: unable to write files for Prisma Factory Generator' 63 | ) 64 | throw e 65 | } 66 | } else { 67 | throw new Error('No output was specified for Prisma Factory Generator') 68 | } 69 | }, 70 | }) 71 | -------------------------------------------------------------------------------- /src/relation.ts: -------------------------------------------------------------------------------- 1 | import { DMMF } from '@prisma/client/runtime' 2 | 3 | export function getRelationFields(model: DMMF.Model, models: DMMF.Model[]) { 4 | return model.fields 5 | .filter((field) => field.kind === 'object') 6 | .map((field) => { 7 | const relationKind = field.isList ? 'many' : 'one' 8 | const isOptional = !field.isRequired || field.isList 9 | const relationDest = models.find((m) => { 10 | if (m.name === model.name) { 11 | return false 12 | } 13 | return m.fields.find((f) => f.relationName === field.relationName) 14 | }) 15 | if (!relationDest) { 16 | throw new Error( 17 | `missing relation dest error, model: ${model.name}, field: ${field.name}` 18 | ) 19 | } 20 | 21 | const relationField = relationDest?.fields.find( 22 | (f) => f.relationName === field.relationName 23 | ) 24 | if (!relationField) { 25 | throw new Error('missing relation field error') 26 | } 27 | return field.name 28 | }) 29 | } 30 | 31 | export function hasRequiredRelation(model: DMMF.Model, models: DMMF.Model[]) { 32 | return model.fields 33 | .filter((field) => field.kind === 'object') 34 | .some((field) => { 35 | return field.isRequired && !field.isList 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/defaultValue.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot 1`] = ` 4 | "export function inputsForUser() { 5 | return { 6 | email: faker.internet.email(), 7 | } 8 | } 9 | " 10 | `; 11 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/interface.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot 1`] = ` 4 | "interface RequiredParametersForUserCreation { 5 | accessToken?: Prisma.AccessTokenCreateNestedOneWithoutUserInput; 6 | posts?: Prisma.PostCreateNestedManyWithoutUserInput; 7 | } 8 | " 9 | `; 10 | 11 | exports[`snapshot 2`] = ` 12 | "interface RequiredParametersForAccessTokenCreation { 13 | user: Prisma.UserCreateNestedOneWithoutAccessTokenInput; 14 | } 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/sample.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`generate model 1`] = ` 4 | "import { PrismaClient } from \\"@prisma/client\\" 5 | import { Prisma } from \\".prisma/client\\" 6 | import * as faker from \\"faker\\" 7 | const prisma = new PrismaClient() 8 | 9 | export function inputsForUser() { 10 | return { 11 | email: faker.internet.email(), jsonProp: faker.datatype.json(), 12 | } 13 | } 14 | 15 | type UserFactoryArgs = Omit & { 16 | data: Pick & Partial> 17 | } 18 | 19 | export async function createUser(args?: UserFactoryArgs) { 20 | 21 | return await prisma.user.create({ 22 | ...args, 23 | data: { 24 | ...inputsForUser(), 25 | ...args?.data 26 | }, 27 | }) 28 | 29 | } 30 | 31 | export function inputsForAccessToken() { 32 | return { 33 | } 34 | } 35 | 36 | type AccessTokenFactoryArgs = Omit & { 37 | data: Pick & Partial> 38 | } 39 | 40 | export async function createAccessToken(args: AccessTokenFactoryArgs) { 41 | 42 | return await prisma.accessToken.create({ 43 | ...args, 44 | data: { 45 | ...inputsForAccessToken(), 46 | ...args?.data 47 | }, 48 | }) 49 | 50 | } 51 | " 52 | `; 53 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/type.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot 1`] = ` 4 | "type createUserArgs = RequiredParametersForUserCreation & Partial 5 | " 6 | `; 7 | 8 | exports[`snapshot 2`] = ` 9 | "type createAccessTokenArgs = RequiredParametersForAccessTokenCreation & Partial 10 | " 11 | `; 12 | -------------------------------------------------------------------------------- /src/tests/args.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { DMMF } from '@prisma/generator-helper' 3 | import { getRelationFields } from '../relation' 4 | import { args } from '../args' 5 | import { Project } from 'ts-morph' 6 | 7 | const datamodel = /* Prisma */ ` 8 | model User { 9 | id Int @id @default(autoincrement()) 10 | email String @unique 11 | accessToken AccessToken? 12 | posts Post[] 13 | 14 | @@map(name: "users") 15 | } 16 | 17 | model AccessToken { 18 | id Int @id @default(autoincrement()) 19 | user User @relation(fields: [userId], references: [id]) 20 | userId Int @unique @map(name: "user_id") 21 | createdAt DateTime @default(now()) @map(name: "created_at") 22 | isActive Boolean @default(false) 23 | 24 | @@map("access_tokens") 25 | } 26 | 27 | model Post { 28 | id Int @id @default(autoincrement()) 29 | user User @relation(fields: [userId], references: [id]) 30 | userId Int @unique @map(name: "user_id") 31 | body String 32 | 33 | @@map("posts") 34 | } 35 | 36 | model Lecture { 37 | id Int @id @default(autoincrement()) 38 | title String 39 | } 40 | ` 41 | 42 | let dmmf: DMMF.Document 43 | let userModel: DMMF.Model 44 | let accessTokenModel: DMMF.Model 45 | let postModel: DMMF.Model 46 | let lectureModel: DMMF.Model 47 | 48 | beforeAll(async () => { 49 | dmmf = await getDMMF({ datamodel }) 50 | const models = dmmf.datamodel.models 51 | userModel = models[0] 52 | accessTokenModel = models[1] 53 | postModel = models[2] 54 | lectureModel = models[3] 55 | }) 56 | 57 | test('print type definition', () => { 58 | const models = dmmf.datamodel.models 59 | const project = new Project() 60 | 61 | const userSource = project.createSourceFile('tmp1') 62 | const userFactoryArgs = ` 63 | type UserFactoryArgs = Omit & { 64 | data: Pick & Partial> 65 | } 66 | ` 67 | args(userSource, userModel, models) 68 | expect(userSource.getFullText()).toEqual(userFactoryArgs) 69 | 70 | const accessTokenSource = project.createSourceFile('tmp2') 71 | const accessTokenArgs = ` 72 | type AccessTokenFactoryArgs = Omit & { 73 | data: Pick & Partial> 74 | } 75 | ` 76 | args(accessTokenSource, accessTokenModel, models) 77 | expect(accessTokenSource.getFullText()).toEqual(accessTokenArgs) 78 | 79 | const postSource = project.createSourceFile('tmp3') 80 | const postArgs = ` 81 | type PostFactoryArgs = Omit & { 82 | data: Pick & Partial> 83 | } 84 | ` 85 | args(postSource, postModel, models) 86 | expect(postSource.getFullText()).toEqual(postArgs) 87 | 88 | const lectureSource = project.createSourceFile('tmp4') 89 | const lectureArgs = ` 90 | type LectureFactoryArgs = Omit & { 91 | data: Partial 92 | } 93 | ` 94 | args(lectureSource, lectureModel, models) 95 | expect(lectureSource.getFullText()).toEqual(lectureArgs) 96 | 97 | }) 98 | -------------------------------------------------------------------------------- /src/tests/defaultValue.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { 3 | addModelAttributeForFunction, 4 | getModelDefaultValueVariableInitializer, 5 | } from '../generator' 6 | import { DMMF } from '@prisma/generator-helper' 7 | import { Project } from 'ts-morph' 8 | 9 | const datamodel = /* Prisma */ ` 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | email String @unique 13 | 14 | @@map(name: "users") 15 | AccessToken AccessToken[] 16 | } 17 | 18 | model AccessToken { 19 | id Int @id @default(autoincrement()) 20 | user User @relation(fields: [userId], references: [id]) 21 | userId Int @unique @map(name: "user_id") 22 | createdAt DateTime @default(now()) @map(name: "created_at") 23 | isActive Boolean @default(false) 24 | 25 | @@map("access_tokens") 26 | } 27 | ` 28 | 29 | let dmmf: DMMF.Document 30 | let userModel: DMMF.Model 31 | let accessTokenModel: DMMF.Model 32 | let initializer: Record 33 | 34 | beforeAll(async () => { 35 | dmmf = await getDMMF({ datamodel }) 36 | userModel = dmmf.datamodel.models[0] 37 | accessTokenModel = dmmf.datamodel.models[1] 38 | initializer = getModelDefaultValueVariableInitializer(accessTokenModel) 39 | }) 40 | 41 | test('@id field is not generate', () => { 42 | expect(initializer.id).toBeUndefined() 43 | }) 44 | 45 | test('@relation field is not generate', () => { 46 | expect(initializer.user).toBeUndefined() 47 | }) 48 | 49 | test('@relation id field is not generate', () => { 50 | expect(initializer.userId).toBeUndefined() 51 | }) 52 | 53 | test('set @default field is not generate', () => { 54 | expect(initializer.createdAt).toBeUndefined() 55 | expect(initializer.isActive).toBeUndefined() 56 | }) 57 | 58 | test('snapshot', () => { 59 | const project = new Project() 60 | const sourceFile = project.createSourceFile('tmp') 61 | addModelAttributeForFunction(sourceFile, userModel) 62 | expect(sourceFile.getText()).toMatchSnapshot() 63 | }) 64 | -------------------------------------------------------------------------------- /src/tests/helper.test.ts: -------------------------------------------------------------------------------- 1 | import * as faker from 'faker' 2 | import { fakerForStringField } from '../helper' 3 | 4 | test('if field name is `lastName` then `faker.name.lastName`', () => { 5 | expect(fakerForStringField('lastName')).toBe('faker.name.lastName()') 6 | }) 7 | test('if field name is `firstName` then `faker.name.firstName', () => { 8 | expect(fakerForStringField('firstName')).toBe('faker.name.firstName()') 9 | }) 10 | test('if field name is `email` then `faker.internet.email`', () => { 11 | expect(fakerForStringField('email')).toBe('faker.internet.email()') 12 | }) 13 | -------------------------------------------------------------------------------- /src/tests/interface.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { 3 | addModelFactoryParameterInterface, 4 | getModelFactoryParameterInterfaceProperties, 5 | } from '../generator' 6 | import { DMMF } from '@prisma/generator-helper' 7 | import { Project } from 'ts-morph' 8 | 9 | const datamodel = /* Prisma */ ` 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | email String @unique 13 | accessToken AccessToken? 14 | posts Post[] 15 | 16 | @@map(name: "users") 17 | } 18 | 19 | model AccessToken { 20 | id Int @id @default(autoincrement()) 21 | user User @relation(fields: [userId], references: [id]) 22 | userId Int @unique @map(name: "user_id") 23 | createdAt DateTime @default(now()) @map(name: "created_at") 24 | isActive Boolean @default(false) 25 | 26 | @@map("access_tokens") 27 | } 28 | 29 | model Post { 30 | id Int @id @default(autoincrement()) 31 | user User @relation(fields: [userId], references: [id]) 32 | userId Int @unique @map(name: "user_id") 33 | body String 34 | 35 | @@map("posts") 36 | } 37 | ` 38 | 39 | let dmmf: DMMF.Document 40 | let userModel: DMMF.Model 41 | let userInterfaceProperties: Record 42 | let accessTokenModel: DMMF.Model 43 | let accessTokenInterfaceProperties: Record 44 | let postModel: DMMF.Model 45 | let postInterfaceProperties: Record 46 | 47 | beforeAll(async () => { 48 | dmmf = await getDMMF({ datamodel }) 49 | const models = dmmf.datamodel.models 50 | userModel = models[0] 51 | userInterfaceProperties = getModelFactoryParameterInterfaceProperties( 52 | userModel, 53 | models 54 | ) 55 | accessTokenModel = models[1] 56 | accessTokenInterfaceProperties = getModelFactoryParameterInterfaceProperties( 57 | accessTokenModel, 58 | models 59 | ) 60 | postModel = models[2] 61 | postInterfaceProperties = getModelFactoryParameterInterfaceProperties( 62 | postModel, 63 | models 64 | ) 65 | }) 66 | 67 | test('@id field is not generate', () => { 68 | expect(accessTokenInterfaceProperties.id).toBeUndefined() 69 | }) 70 | 71 | test('@relation field is generate', () => { 72 | expect(accessTokenInterfaceProperties.user).toBeDefined() 73 | expect(accessTokenInterfaceProperties.user).toBe( 74 | 'Prisma.UserCreateNestedOneWithoutAccessTokenInput' 75 | ) 76 | }) 77 | 78 | test('Scalar field is not generate', () => { 79 | expect(accessTokenInterfaceProperties.userId).toBeUndefined() 80 | expect(accessTokenInterfaceProperties.createdAt).toBeUndefined() 81 | expect(accessTokenInterfaceProperties.isActive).toBeUndefined() 82 | }) 83 | 84 | test('optional @relation field is generate', () => { 85 | expect(userInterfaceProperties['accessToken?']).toBe( 86 | 'Prisma.AccessTokenCreateNestedOneWithoutUserInput' 87 | ) 88 | }) 89 | 90 | test('list field is generate as optional', () => { 91 | expect(userInterfaceProperties['posts?']).toBe( 92 | 'Prisma.PostCreateNestedManyWithoutUserInput' 93 | ) 94 | }) 95 | 96 | test('list reference field is generate', () => { 97 | expect(postInterfaceProperties['user']).toBe( 98 | 'Prisma.UserCreateNestedOneWithoutPostsInput' 99 | ) 100 | }) 101 | 102 | test('snapshot', () => { 103 | const project = new Project() 104 | const userInterfaceFile = project.createSourceFile('tmp1') 105 | const models = dmmf.datamodel.models 106 | addModelFactoryParameterInterface(userInterfaceFile, userModel, models) 107 | expect(userInterfaceFile.getText()).toMatchSnapshot() 108 | 109 | const accessToken = project.createSourceFile('tmp2') 110 | addModelFactoryParameterInterface(accessToken, accessTokenModel, models) 111 | expect(accessToken.getText()).toMatchSnapshot() 112 | }) 113 | 114 | // test('dmmf snapshot', async () => { 115 | // const dmmf = await getDMMF({ datamodel }) 116 | // expect(dmmf).toMatchSnapshot() 117 | // }) 118 | -------------------------------------------------------------------------------- /src/tests/relation.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { DMMF } from '@prisma/generator-helper' 3 | import { getRelationFields, hasRequiredRelation } from '../relation' 4 | 5 | const datamodel = /* Prisma */ ` 6 | model User { 7 | id Int @id @default(autoincrement()) 8 | email String @unique 9 | accessToken AccessToken? 10 | posts Post[] 11 | 12 | @@map(name: "users") 13 | } 14 | 15 | model AccessToken { 16 | id Int @id @default(autoincrement()) 17 | user User @relation(fields: [userId], references: [id]) 18 | userId Int @unique @map(name: "user_id") 19 | createdAt DateTime @default(now()) @map(name: "created_at") 20 | isActive Boolean @default(false) 21 | 22 | @@map("access_tokens") 23 | } 24 | 25 | model Post { 26 | id Int @id @default(autoincrement()) 27 | user User @relation(fields: [userId], references: [id]) 28 | userId Int @unique @map(name: "user_id") 29 | body String 30 | 31 | @@map("posts") 32 | } 33 | ` 34 | 35 | let dmmf: DMMF.Document 36 | let userModel: DMMF.Model 37 | let accessTokenModel: DMMF.Model 38 | let postModel: DMMF.Model 39 | 40 | beforeAll(async () => { 41 | dmmf = await getDMMF({ datamodel }) 42 | const models = dmmf.datamodel.models 43 | userModel = models[0] 44 | accessTokenModel = models[1] 45 | postModel = models[2] 46 | }) 47 | 48 | test('get relation field', () => { 49 | const models = dmmf.datamodel.models 50 | const userRelationFields = getRelationFields(userModel, models) 51 | expect(userRelationFields.sort()).toEqual(['posts', 'accessToken'].sort()) 52 | 53 | const accessTokenRelationFields = getRelationFields(accessTokenModel, models) 54 | expect(accessTokenRelationFields).toStrictEqual(['user']) 55 | 56 | const postRelationFields = getRelationFields(postModel, models) 57 | expect(postRelationFields).toStrictEqual(['user']) 58 | }) 59 | 60 | test('has required relation', () => { 61 | const models = dmmf.datamodel.models 62 | const userHasRequiredRelation = hasRequiredRelation(userModel, models) 63 | expect(userHasRequiredRelation).toBeFalsy() 64 | 65 | const accessTokenHasRequiredRelation = hasRequiredRelation( 66 | accessTokenModel, 67 | models 68 | ) 69 | expect(accessTokenHasRequiredRelation).toBeTruthy() 70 | 71 | const postHasRequiredRelation = hasRequiredRelation( 72 | postModel, 73 | models 74 | ) 75 | expect(postHasRequiredRelation).toBeTruthy() 76 | }) 77 | -------------------------------------------------------------------------------- /src/tests/sample.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { 3 | addModelFactoryDeclaration, 4 | addPrismaImportDeclaration, 5 | } from '../generator' 6 | import { Project, ts } from 'ts-morph' 7 | const datamodel = /* Prisma */ ` 8 | model User { 9 | id Int @id @default(autoincrement()) 10 | email String @unique 11 | jsonProp Json 12 | accessToken AccessToken? 13 | 14 | @@map(name: "users") 15 | } 16 | 17 | model AccessToken { 18 | id Int @id @default(autoincrement()) 19 | user User @relation(fields: [userId], references: [id]) 20 | userId Int @unique @map(name: "user_id") 21 | 22 | @@map("access_tokens") 23 | } 24 | ` 25 | 26 | // test('dmmf snapshot', async() => { 27 | // const dmmf = await getDMMF({ datamodel }) 28 | // expect(dmmf).toMatchSnapshot() 29 | // }) 30 | test('generate model', async () => { 31 | const project = new Project({}) 32 | const sourceFile = project.createSourceFile('./src/test-src.ts') 33 | const dmmf = await getDMMF({ datamodel }) 34 | addPrismaImportDeclaration(sourceFile) 35 | dmmf.datamodel.models.forEach((model) => { 36 | addModelFactoryDeclaration(sourceFile, model, dmmf.datamodel.models) 37 | }) 38 | 39 | sourceFile.formatText({ 40 | indentSize: 2, 41 | semicolons: ts.SemicolonPreference.Remove, 42 | }) 43 | expect(sourceFile.getFullText()).toMatchSnapshot() 44 | }) 45 | -------------------------------------------------------------------------------- /src/tests/type.test.ts: -------------------------------------------------------------------------------- 1 | import { getDMMF } from '@prisma/sdk' 2 | import { 3 | addModelFactoryParameterType, 4 | getModelFactoryParameterInterfaceProperties, 5 | } from '../generator' 6 | import { DMMF } from '@prisma/generator-helper' 7 | import { Project } from 'ts-morph' 8 | 9 | const datamodel = /* Prisma */ ` 10 | model User { 11 | id Int @id @default(autoincrement()) 12 | email String @unique 13 | accessToken AccessToken? 14 | 15 | @@map(name: "users") 16 | } 17 | 18 | model AccessToken { 19 | id Int @id @default(autoincrement()) 20 | user User @relation(fields: [userId], references: [id]) 21 | userId Int @unique @map(name: "user_id") 22 | createdAt DateTime @default(now()) @map(name: "created_at") 23 | isActive Boolean @default(false) 24 | 25 | @@map("access_tokens") 26 | } 27 | ` 28 | 29 | let dmmf: DMMF.Document 30 | let userModel: DMMF.Model 31 | let accessTokenModel: DMMF.Model 32 | 33 | beforeAll(async () => { 34 | dmmf = await getDMMF({ datamodel }) 35 | const models = dmmf.datamodel.models 36 | userModel = models[0] 37 | accessTokenModel = models[1] 38 | }) 39 | 40 | test('snapshot', () => { 41 | const project = new Project() 42 | const userSourceFile = project.createSourceFile('user') 43 | addModelFactoryParameterType(userSourceFile, userModel) 44 | expect(userSourceFile.getText()).toMatchSnapshot() 45 | 46 | const accessTokenSourceFile = project.createSourceFile('accessToken') 47 | addModelFactoryParameterType(accessTokenSourceFile, accessTokenModel) 48 | expect(accessTokenSourceFile.getText()).toMatchSnapshot() 49 | }) 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "baseUrl": "src", 5 | "outDir": "dist", 6 | "sourceMap": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "lib": ["es2019"], 10 | "strict": true, 11 | "esModuleInterop": true 12 | } 13 | } 14 | --------------------------------------------------------------------------------