📜

Prisma SchemaからGraphQL SDLを生成する

2023/01/29に公開

背景

Prismaを使っていて、GraphQLはSDL Firstな形で実装している。
SDLは完全にPrismaの定義と一致している必然性はないけど、だいたいいっしょの定義をちくちく書いていかないといけなくて不毛でめんどくさい。

とりあえずのベースとなるSDLを出力して省力化を図る。

実装

Prisma本体でパースした結果を得られる getDMMF という関数がある。

import {getDMMF} from '@prisma/internals';

今回のケースではこれで十分できそうだけど、得られる情報が一部限定されているので、他のケースも含めて以下の非公式のパーサーを利用させてもらっている。
純粋にパースされた結果をTypeScriptでNarrowingしながら使いやすようになっていて便利。

https://github.com/loancrate/prisma-schema-parser

以下実装

import {readFileSync} from 'fs';
import {join} from 'path';
import {parsePrismaSchema} from '@loancrate/prisma-schema-parser';

const SCHEMA_PATH = join(__dirname, 'prisma', 'schema.prisma');

const main = () => {
  const s = readFileSync(SCHEMA_PATH, {encoding: 'utf-8'});
  const ast = parsePrismaSchema(s);

  const lines = ast.declarations.flatMap(d => {
    if (d.kind === 'model') {
      return [
        `type ${d.name.value} {`,
        ...d.members
          .map(m => {
            if (m.kind === 'field') {
              const gqlType = (() => {
                switch (m.type.kind) {
                  case 'typeId': {
                    return m.type.name.value + '!';
                  }
                  case 'required': {
                    if (m.type.type.kind === 'typeId') {
                      return m.type.type.name.value + '!';
                    } else {
                      return undefined;
                    }
                  }
                  case 'optional': {
                    if (m.type.type.kind === 'typeId') {
                      return m.type.type.name.value;
                    } else {
                      return undefined;
                    }
                  }
                  default:
                    return undefined;
                }
              })();
              if (gqlType) {
                return `  ${m.name.value}: ${gqlType}`;
              } else {
                return '';
              }
            }
            return '';
          })
          .filter(s => !!s),
        `}`,
        '', // 空白行
      ];
    } else if (d.kind === 'enum') {
      return [
        `enum ${d.name.value} {`,
        ...d.members
          .map(m => {
            if (m.kind === 'enumValue') {
              return `  ${m.name.value}`;
            }
            return '';
          })
          .filter(s => !!s),
        `}`,
        '', // 空白行
      ];
    }
    return [];
  });

  console.log(lines.join('\n'));
};

main();

Discussion