4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Prismaジェネレーターは簡単に作れる

Posted at

はじめに

Prismaではスキーマからジェネレーターという機能を使ってコードやドキュメントを自動生成することができます。例えば、公式が提供するprisma-client-jsをはじめとする各言語のクライアントはジェネレーターを使って生成されています。また、TypeGraphQLのリゾルバを生成するtypegraphql-prismaや、ER図を生成するprisma-erd-generatorなどもあります。個人的なおすすめはダミーデータを作ってくれる@quramy/prisma-fabbricaです。

Prismaジェネレーター自体は世の中にたくさんあるのですが、出力される形式はほぼ固定されているものが多く、要求に完璧にマッチするものは案外見つからなかったりします。また、古いまま放置されているために動かないものも多いです。

そこで、Prismaジェネレーターを一から作ってみました。公式ドキュメントにはジェネレーターに関する記述はほとんどなく、断片的な記述と既存のジェネレーターのコードが頼りでしたが、意外と簡単に求めているものを出力するジェネレーターが作れたので、作り方や注意点などをまとめました。

作り方

実行環境

PrismaジェネレーターはPrisma CLIと標準入出力上でJSON PRCを使ってやり取りするので、標準入出力を扱える任意の実行環境で作成できます。

後述する通りPrisma公式から提供されている@prisma/generator-helperはNPMパッケージなので、NPMパッケージが扱える環境で作るのが一番楽です。以下はNode.jsで作る前提で進めます。1

依存関係

@prisma/generator-helperがJSON RPC回りのハンドリングと型定義の提供をしてくれます。

また、Prisma CLIdevDependenciesとして入れておくと、手動ですがその場でE2Eテストできるので便利です。2

$ npm install @prisma/generator-helper
$ npm install -D prisma

あとは使いたいライブラリや必要なツールを適宜インストールしてください。

エントリーポイント

エントリーポイントで@prisma/generator-helpergeneratorHandlerを呼び出します。

onManifestはPrisma CLIがコンソールに出力する内容やジェネレーターの実行順序を決めるためのマニフェストです。他のジェネレーターに特に依存していない場合はdefaultOutput, prettyName, versionだけ返しておけば十分です。

onGenerateは実際の生成処理です。今回はPrismaスキーマを解析したASTであるDMMF (Data Model meta format) が含まれているoptions.dmmfの内容を、outputで指定されたJSONファイルにそのまま吐き出します。

index.js
#!/usr/bin/env node
import fs from 'node:fs/promises'
import path from 'node:path'
import helper from '@prisma/generator-helper'

helper.generatorHandler({
	onManifest(config) {
		return {
			defaultOutput: 'dmmf.json',
			prettyName: 'DMMF JSON',
			version: '1.2.3',
		}
	},
	async onGenerate(options) {
		const path = options.generator.output?.value
		assert(path != null, 'output is required.')
		const content = JSON.stringify(options.dmmf, null, '\t')
		await fs.writeFile(path, content)
	},
})

実行

Prismaスキーマにジェネレーターを追加します。

schema.prisma
generator awesome {
	provider = "node ./index.js"
}

prisma generateを実行すると、次のような結果になります。

$ npx prisma generate
Prisma schema loaded from schema.prisma

✓ Generated DMMF JSON (1.2.3) to ./dmmf.json in 12ms
dmmf.json
{
	"datamodel": {
		"enums": [...],
		"models": [...],
		"types": [...]
	},
	"schema": {
		"inputObjectTypes": {
			"prisma": [...]
		},
		"outputObjectTypes": {
			"prisma": [...],
			"model": [...]
		},
		"enumTypes": {
			"prisma": [...],
			"model": [...]
		},
		"fieldRefTypes": {
			"prisma": [...],
		}
	},
	"mappings": {
		"modelOperations": [...],
		"otherOperations": [...]
	}
}

これで最低限の構成は完了です。あとはDMMFから必要な情報を読み取って、ほしい形式に変換すれば目的のジェネレーターが完成します。

設定

Prismaスキーマからジェネレーターに設定を渡すことができます。型は常にstring | string[] | undefinedになります。

schema.prisma
generator awesome {
	provider = "node ./index.js"
	output   = "generated"
	string   = "any string"
	array    = ["foo", "bar"]
}

provider, output, binaryTargets, previewFeaturesの4つのキーは予約されており、generator[key].valueから取得することになっています。その他のキーについてはgenerator.configから取得できます。3

outputに相対パスを渡した場合、schema.prismaからの相対パスとして扱われ、Prisma CLI側で絶対パスに変換されます。また、schema.prismaoutputを渡さなかった場合、マニフェストのdefaultOutputが同様に変換されて使われます。

options
{
	generator: {
		name: 'awesome',
		provider: {
			value: 'node index.js',
		},
		output: {
			value: '/absolute/path/to/output',
		},
		config: {
			string: 'any string',
			array: ['foo', 'bar']
		}
	}
}

string | string[] | undefined以外の型は扱えないため、これより複雑な型を扱いたい場合などは、generator.configで設定ファイルのパスを渡す形にするのがよいかと思います。

注意点

DMMFの互換性は保証されない

DMMFはPrismaの内部で使われるASTのため、メジャーバージョン間で互換性がありません。また、マイナーバージョン・パッチバージョン間での互換性も保証されません。4よって、利用側でPrismaのバージョンを上げる際には、ジェネレーターが動作するかどうかを確かめ、必要に応じてジェネレーター側もバージョンを上げなければなりません。

最新の型定義についてはここで確認できます。

ジェネレーターから標準エラー出力は使えない

前述の通り、PrismaジェネレーターはPrisma CLIと標準入出力を使ってやり取りしますが、そのうちPrismaジェネレーターからPrisma CLIへのレスポンスの受け渡しは標準エラー出力を使って行われます。また、JSON RPC以外の標準エラー出力はすべて無視されます。よって、エラーログを出力したい場合はその場でErrorを投げて@prisma/generator-helperからPrisma CLIにエラーレスポンスを返してもらうか、標準出力の方にエラーログを出力する必要があります。

また、Prisma CLIはv5.13.0時点でエラーのスタックトレースをログに出してくれないので、スタックトレースが欲しい場合は自前でどこかに吐き出す必要があります。

作ったもの

最初はテーブル定義書とかER図とかGraphQLスキーマとかを出力するジェネレーターを1つずつ書いていたんですが、コードをいちいち書くのが面倒でメンテナンスコストも高そうだったので、DMMFをHandlebarsテンプレートに丸投げしてくれるジェネレーターを作りました。

こうやってテンプレートを書いたら、

templates/models.md.handlebars
Models:

{{#each datamodel.models}}
- {{name}}
	{{#each fields}}
	- {{name}}
	{{/each}}
{{/each}}

Enums:

{{#each datamodel.enums}}
- {{name}}
	{{#each values}}
	- {{name}}
	{{/each}}
{{/each}}

こういうのが出力できます。

models.md
Models:

- User
	- id
	- email
	- type
	- name
	- posts
- Post
	- id
	- createdAt
	- updatedAt
	- title
	- content
	- published
	- viewCount
	- author
	- authorId

Enums:

- UserType
	- ADMIN
	- USER

ファイルの読み書きとHandlebarsへの受け渡し以外はほとんど何もしていないので、ジェネレーターというよりはジェネレーターヘルパーと言った方が正しいかもしれません。

テンプレートを書かなければいけないので手軽ではないですが、Partialsによるテンプレートの分割・再利用やHelpersによる関数の埋め込みにも対応しているので、汎用性は非常に高いと思います。よろしければぜひ利用してください。

おわりに

Prismaジェネレーターの作り方でした。やり方さえわかれば簡単に望み通りのものが作れるようになるので、作ってみてはいかがでしょうか。

参考

  1. node@20.10.0を使用しました。検証していないですが、DenoやBunでも作れると思います。

  2. @prisma/generator-helper@5.13.0およびprisma@5.13.0を使用しました。

  3. 実はPrismaスキーマに書かれた予約キーもgenerator.configに入りますが、Prisma CLIによる変換がされていない生の値なので、使わずに捨てた方が無難です。

  4. これが理由で公式ドキュメントにはPrismaジェネレーターのドキュメントが置かれてないらしいです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?