Closed13

Prisma ORM の Edge Functions サポートを試す(Cloudflare Workers + GraphQL + Prisma + TiDB Serverless)

HiraiKyoHiraiKyo

Cloudflare WorkersのTCPはリクエストごとに切れるらしいので、Contextに入れる。

src/context.ts
import { PrismaClient } from "@prisma/client";
import { PrismaTiDBCloud } from "@tidbcloud/prisma-adapter";
import { connect } from "@tidbcloud/serverless";

const prisma = (connectionString: string) => {
  const connection = connect({ url: connectionString });
  const adapter = new PrismaTiDBCloud(connection);
  const prisma = new PrismaClient({ 
    adapter
   });
  return prisma;
};
export interface Context {
  db: PrismaClient;
}

export const createContext = (connectionString: string): Context => {
  return {
    db: prisma(connectionString)
  }
}
src/index.ts
import { createYoga } from 'graphql-yoga';
import { schema } from './schema';
import { createContext } from './context';

interface Env {
  DATABASE_URL: string;
}
const yoga = createYoga({ schema });

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const context = createContext(env.DATABASE_URL);
    const response = await yoga(request, ctx);
    await context.db.$disconnect();
    return response;
  },
};

ただし、ContextにPrismaClientを乗っけるのはVSCodeが重くなるので非推奨らしい。
https://pothos-graphql.dev/docs/plugins/prisma#set-up-the-builder
とはいえ一旦これで済ませる。

HiraiKyoHiraiKyo

Pothos導入, PrismaPlugin, RelayPluginを導入してRelay準拠にする。

npm install @pothos/core @pothos/plugin-prisma @pothos/plugin-relay
src/builder.ts
import { type Context } from "./context";
import SchemaBuilder from '@pothos/core';
import PrismaPlugin from "@pothos/plugin-prisma";
import RelayPlugin from "@pothos/plugin-relay";
import type PrismaTypes from "../prisma/generated";
import { DateResolver, JSONResolver } from "graphql-scalars";
import { Prisma } from "@prisma/client";

export const builder = new SchemaBuilder<{
  PrismaTypes: PrismaTypes;
  Scalars: {
    JSON: {
      Input: unknown;
      Output: unknown;
    };
    Date: {
      Input: Date;
      Output: Date;
    };
  };
  Context: Context;
}>({
  plugins: [PrismaPlugin, RelayPlugin],
  relayOptions: {},
  prisma: {
    client: (ctx) => ctx.db,
    dmmf: Prisma.dmmf
  },
});

builder.queryType();
builder.mutationType();
builder.addScalarType('JSON', JSONResolver);
builder.addScalarType('Date', DateResolver);

自分はUserエンティティではなく、公式ドキュメントのLogエンティティを元に作ったので、Modelはこうなった。

src/models/Log.ts
import { builder } from '../builder'

const LevelEnum = builder.enumType('Level', {
  values: ['Info', 'Warn', 'Error'] as const,
});

export const LogType = builder.prismaObject('Log', {
  fields: (t) => ({
    id: t.exposeInt("id"),
    level: t.expose("level", {
      type: LevelEnum
    }),
    message: t.exposeString("message"),
    meta: t.expose("meta", {
      type: 'JSON',
    })
  }),
})
HiraiKyoHiraiKyo
[ERROR] service core:user:test-worker: Uncaught Error: Prisma.dmmf is not available when running in edge runtimes.  

Edge FunctionだとPrisma.dmmfが使えないらしい。
でも、これを消すと型チェックで怒られる。

そもそもdmmfとは何かがわからない…。

SchemaBuilderのプラグインにPrismaPluginを入れたら怒られた。
調べても出てこず、今回のPreviewで発生するようになったエラーかもと思いDiscordで質問してみる。

HiraiKyoHiraiKyo

こちら側で何かできそうにないので、@pothos/plugin-prismaをやめて@pothos/plugin-simple-objectsを入れる事にした。

src/models/Log.ts
export const LogType = builder.simpleObject('Log', {
  fields: (t) => ({
    id: t.int(),
    level: t.field({
      type: LevelEnum
    }),
    message: t.string(),
    meta: t.field({
      type: 'JSON',
    })
  }),
})
src/builder.ts
export const builder = new SchemaBuilder<{
  PrismaTypes: PrismaTypes;
  Scalars: {
    JSON: {
      Input: unknown;
      Output: unknown;
    };
    Date: {
      Input: Date;
      Output: Date;
    };
  };
  Context: Context;
}>({
  plugins: [SimpleObjectsPlugin],
});

いったんこれでクローズ。

HiraiKyoHiraiKyo

Discordで質問したところ、同じ問題に遭遇した方がDiscordの質問までおっかけて解決法を共有してくれました!
ありがたすぎる…とりあえず試す

npm install https://github.com/repository/prisma-dmmf-generator

in schema.prisma

generator dmmf {
  provider = "prisma-dmmf-generator"
  output   = "./generated_dmmf.json"
}
npx prisma generate
src/builder.ts
import dmmf from "../generated/dmmf.json";

const builder = new SchemaBuilder<{
    Context: GraphQLContext;
    PrismaTypes: PrismaTypes;
}>({
    plugins: [PrismaPlugin],
    prisma: {
        client: (context) => context.prisma,
        dmmf: dmmf,
        filterConnectionTotalCount: true,
    },
});

thanks m <3

HiraiKyoHiraiKyo

PothosPrismaGeneratorを使ってQueryとMutationを自動生成する
https://zenn.dev/sora_kumo/articles/pothos-prisma-generator

wrangler devすると以下のエラーが発生。

✘ [ERROR] service core:user:test-worker: Uncaught TypeError: Cannot destructure property 'models' of 'this.getDMMF(...)' as it is undefined.
  at null.<anonymous> (index.js:32522:13) in getModels                                                          at null.<anonymous> (index.js:32462:10) in PrismaSchemaGenerator
  at null.<anonymous> (index.js:32755:22) in PothosPrismaGeneratorPlugin
  at null.<anonymous> (index.js:26627:30)
  at null.<anonymous> (index.js:26622:159) in BuildCache
  at null.<anonymous> (index.js:28164:24) in toSchema
  at null.<anonymous> (index.js:33378:22)

こっちもDMMFまわりでエラー。DMMFファイル自体は生成しているので、これを読み込む方法を調べるため、再オープン。

HiraiKyoHiraiKyo

自動化対応は保留にしたまま開発継続したので、いったんクローズ

HiraiKyoHiraiKyo

Property 'adapterName' is missing in type 'PrismaTiDBCloud' but required in type 'DriverAdapter'

@tidbcloud/prisma-adapterが5.11.0対応しかしてないので、@prisma/clientも5.11.0に揃える必要がある。
https://github.com/tidbcloud/prisma-adapter

npm i --save-exact @prisma/client@5.11.0
このスクラップは2024/04/04にクローズされました