📌

Prismaのgeneratorを独自で開発する3つの方法

2024/11/21に公開

はじめに

本記事ではNode.js向けORMのPrismaのCustom Generatorの作成方法について紹介します

Prismaにはgeneratorという機能があり、定義しておいたスキーマ定義を利用してDBに関するドキュメントを自動生成したり、CRUD処理のためのGraphQL向けのソースコードを自動生成したりといったことが可能となっています
上記のような動きを行うgeneratorが有志で作例されて以下のように公開されているため、利用しようと思えばこちらをインストールすれば使えるような状態となっています
https://www.prisma.io/docs/orm/prisma-schema/overview/generators#community-generators

こちらは非常に便利ではあるのですが、自分が思っているものと少し違うなと思うことがあったりしたり、独自で作りたいと考えることがあります

独自に作れるのか?という部分についてですが、当然自分自身でもgeneratorを開発することは可能です。
私も実際に作ってみたことがあるのですが、私が把握する限りでは大きくわけて3種類の開発方法というか方針あるのかなと思っております。
その方法によってメリット・デメリットがあるため本記事ではそちらをまとめています

3種類の方法

  1. custom generatorを開発してnpmパッケージとして公開する
  2. custom generatorを開発してローカルに配置する
  3. 独自のscriptを開発して、prisma generateとは別で実行する

1と2はPrismaのgeneratorとして開発する方法
3.は厳密にはPrismaのgeneratorとは外れた方法で、同等の情報を使って独自に処理を行う方法

というようなイメージになります

実装の手間としては上から順に手間がかかるなと思います。2と3はほぼ誤差になるとは思いますが

次からそれぞれの方法について記載していきます

custom generatorを開発してnpmパッケージとして公開する

まずは一番綺麗ではあるが最も手間のかかる方法についてです

対応内容

一言で言うと、以下に記載されているようなcommunity generatorのようにnpmに公開してしまう方法です

https://www.prisma.io/docs/orm/prisma-schema/overview/generators#community-generators

利用したい場合は通常のnpmパッケージと同様に、npmコマンドでインストールして

$ npm i -D my-prisma-generator

schema.prismaに利用の設定を記載するのみです

generator my_generator {
  provider = "my-prisma-generator"
}

この状態で通常通りprisma generateをすることで、prisma clientの生成と同時に自身のgeneratorも起動してくれます

$ prisma generate

どのようなコードを書く必要があるかという肝心な部分については、他の方が書いた記事等が参考になったため、本記事では省略しようかと思います
コードの概要と参考リンクについては以下に記載しました
@prisma/generator-helperを利用したcustom generatorの作成

メリット・デメリット

  1. メリット
    1. 複数のプロジェクトに対して適用したい場合であっても、npm installを行うのみで利用することができる
    2. 世界中の人に利用してもらえる(かも?)
  2. デメリット
    1. 開発の手間がかかる
    2. プロジェクト固有の処理などは入れづらい

手間はかかるものの、運用上可能であればこちらの方法はおすすめではあります

公開するという都合上、あまり適当にはできないのである程度真面目に対応する必要はあるのですが、導入したいと思ったプロジェクトに即時適用できると言うのはかなり便利です

対応例

ちなみに以下は私で開発して公開したものになります。

https://www.npmjs.com/package/prisma-generator-plantuml-erd

schema.prismaの情報を元にPlantUMLのER図のpumlファイルを生成するgeneratorです。
生成されたファイルをvscodeプレビューしたり、Github Actionsでsvgに変換してドキュメントとして利用したりしています。

ER図の生成としては以下のmermaid向けのgeneratorが有名ですが、mermaidでは日本語を記載することができなかったりと、機能に不足が出たためPlantUML向けのものを自作したという背景となります
https://www.npmjs.com/package/prisma-erd-generator

個人的にはかなり気に入っておりまして
ER図を用意したいというモチベーションはどのプロジェクトでも思うことではあったので
参加するプロジェクト内で利用可能であれば、サッと導入してすぐに使えるというのが非常に嬉しいポイントでした

公開方法

作成と公開にあたっては
community-generatorのページにリンクがある、以下のテンプレートを利用しました

https://github.com/YassinEldeeb/create-prisma-generator

こちらを利用することで、generatorとしての最低限必要な以下のコードを生成してくれてます

  1. generatorのプロダクションコード
  2. テストコード
  3. npm公開用のsemantic-releaseの設定
  4. npm公開用のGithub Actions設定

こちらをベースに必要な認証情報をnpm上で発行してGithubに設定すれば、あとはmainへのマージをトリガーに自動でリリースしてくれます

作成したgeneratorをCommunity generatorsのページへの記載依頼をする場合

https://www.prisma.io/docs/orm/prisma-schema/overview/generators#community-generators

有志で作成されたgeneratorは上記のページに公開されているのですが
自身で作成したgeneratorを掲載してもらいたいなと思った場合はGithub上でmarkdownの編集PRを作成して、マージされたら公開される形となります

その依頼をした時の依頼が以下のPRになるのですが、結構軽いノリでマージされました。

https://github.com/prisma/docs/pull/6224

参考にした他のPRを見ると、まだメンテナンスされているかどうかと prisma-generator-<name>などが推奨などの命名部分でのレビューは受けていたようです
https://github.com/prisma/docs/pull/5742#issuecomment-2023430774
ここを抑えておけば比較的スムーズにマージしてもらえるものと思います

custom generatorを開発してローカルに配置する

次に、custom generatorを開発するがnpmへの公開まではしない方法についてです

対応内容

最初の方法とは違って公開はしていないのでnpm installはできません

しかし、schema.prismaファイル内のgenerator設定のproviderに対して実行コマンドを記載しておけばそのコマンドを実行してくれる
という割とシンプルな作りなので、公開していない自作のスクリプトを呼び出すことも特に問題ありません

例えば以下のようにgenerator用のファイルを作成した場合であれば

$ mkdir prisma/my-generator
$ touch prisma/my-generator/index.js

以下のように実行コマンドを指定することで、generatorとして機能させることができます

generator my_generator {
  provider = "node ./prisma/my-generator/index.js"
}

この状態まで来れば、最初の方法と同じようにprisma generateをすることで、prisma clientの生成と同時に自身のgeneratorも起動してくれます

$ prisma generate

prisma generateの仕組みに乗せているため、作成するファイルについは最初の方式と全く同じで、以下の方法です
@prisma/generator-helperを利用したcustom generatorの作成
公開する or ローカルにファイルを配置するのみかどうかと言うのが差分となります

メリット・デメリット

  1. メリット
    1. npmの公開手順を踏まなくても良い
    2. プロジェクトに閉じた生成処理なども特に気にせず実装できる
  2. デメリット
    1. 同じgeneratorを他プロジェクトで利用したいと思った場合はコピー or 何かしらの工夫が必要になる

generatorをTypescriptで書きたい場合

記載した例はシンプルにnodeコマンドで実行するパターンでしたが、おそらくPrismaを導入しているプロジェクトはTypescriptで開発しているのではないでしょうか
となればもちろんgeneratorのスクリプトもTypescriptで書きたいはず

その場合、プロジェクトで利用している設定次第ではありますが次のような方法が選択可能です
同じことの繰り返しになってはしましますが、providerは実行コマンドを記載しているだけなのでCLIで実行できさえすれば割となんでもOKだったりします

tsファイルをビルドしてjsファイルにする

tsファイルをビルドしておく

$ npx tsc prisma/my-generator/index.ts
generator my_generator {
  provider = "node ./prisma/my-generator/index.js"
}

providerにts-nodeを指定して直接tsファイルを起動する

あるいはts-nodeなどを利用して直接実行することも可能です

generator my_generator {
  provider = "npx ts-node ./prisma/my-generator/index.ts"
}

providerにbunを指定する

こう言うシチュエーションがどれくらいあるかはわかりませんが
bunを指定して実行するのも問題なく動作しました

generator my_generator {
  provider = "bun ./prisma/my-generator/index.ts"
}

独自のscriptを開発して、prisma generateとは別で実行する

最後はprisma generateの枠とは外れて、完全独自のスクリプトとして作成するパターンについてです

対応内容

最初2つの方式はprismaのgeneratorとして用意されている仕組みに則り、渡されるスキーマ情報を元にgenerateする方式でしたが
この時に渡されるスキーマ情報というのは他の方法でも取得可能なため、そちらを利用して独自にスクリプトを組むという方式になります

例えば最小のサンプルで言えば
以下のようなコードでgenerate相当の処理を実現可能です

import { Prisma } from "@prisma/client";
import { writeFile } from "fs/promises";

async function main() {
  const models = Prisma.dmmf.datamodel.models;
  const result = "";
  for (const model of models) {
    for (const field of model.fields) {
      // some code
    }
    await writeFile(`/path/to/output.txt`, result);
  }
}
main();

@prisma/generator-helperを利用したcustom generatorの作成

prisma generateを行う場合では上記に記載の通り @prisma/generator-helperから渡される options.dmmf.datamodel.modelsを利用する形でした
この情報と同等の情報が Prisma.dmmf.datamodel.modelsでも取得可能なため
prisma generateのフローに乗せていない独自のスクリプトであっても同じことができるという背景になります

メリット・デメリット

  1. メリット
    1. npmの公開手順を踏まなくても良い
    2. プロジェクトに閉じた生成処理なども特に気にせず実装できる
    3. prisma generateの度に実行したくないものを除外しやすい
  2. デメリット
    1. 同じgeneratorを他プロジェクトで利用したいと思った場合はコピー or 何かしらの工夫が必要になる
    2. prisma generateのタイミングで自動実行されるわけではないので、追従漏れが起こる可能性がある

基本的には一つ前の方式と同じではありますが
prisma generateとは外れた形で実装する形となるため、メリット・デメリットともにそこに引きずられる形になります

備考

@prisma/generator-helperを利用したcustom generatorの作成

generatorを作成するためには@prisma/generator-helperというパッケージを利用して実行する形となります
例えば以下のように実装した上でgeneratorのproviderに設定すると
prisma generate実行時に呼び出されて、options引数に対してDBのスキーマを表すDMMF(Data Model Meta Format)が渡されるため
その情報を利用して、必要となるファイルをよしなに作成する
というような動きになります

import { generatorHandler } from "@prisma/generator-helper";
import { writeFile } from "fs/promises";

generatorHandler({
  onManifest: () => ({
    defaultOutput: "/path/to/default/output",
    prettyName: "test",
    requiresEngines: ["queryEngine"],
    version: "0.0.0",
  }),
  async onGenerate(options) {
    const models = options.dmmf.datamodel.models;
    const result: string[] = [];
    for (const model of models) {
      for (const field of model.fields) {
        // some code
      }
      await writeFile(options.generator.output.value, result.join('\n'));
    }
  },
});

generatorの作り方については以下の記事などが非常に参考になりました

おわりに

PrismaのCustom Generatorを作成する方式3つについて記載しました

シチュエーションにもよりますが、基本的には2つ目のcustom generatorを開発してローカルに配置するが手軽に実施できておすすめかなと思っています
その後の状況次第で、npmに公開を検討することがあるような形かなと思います

Micoworks株式会社

Discussion