Prisma

Prisma ORM凭借其直观的数据模型、自动迁移、类型安全和自动完成功能,使开发人员在使用数据库时获得了全新的体验。

@gqloom/prisma 提供了 GQLoom 与 Prisma 的集成:

  • 从 Prisma Schema 生成丝线;
  • 从 Prisma 快速生成 CRUD 操作。

安装

npm
yarn
pnpm
bun
npm install -D prisma
npm
yarn
pnpm
bun
npm install @gqloom/core @gqloom/prisma

Prisma 文档中,你可以找到更多关于安装的信息。

配置

prisma/schema.prisma 文件中定义你的 Prisma Schema:

generator client { provider = "prisma-client-js" } generator gqloom { provider = "prisma-gqloom" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) email String @unique name String? posts Post[] } model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId Int }

generator 参数

generator 接受以下参数:

参数说明默认值
gqloomPathGQLoom 包的路径。@gqloom/prisma
clientOutputPrisma 客户端的路径。node_modules/@prisma/client
output生成的文件所在的文件夹路径。node_modules/@gqloom/prisma/generated
commonjsFileCommonJS 文件的文件名。使用 "" 将跳过 CommonJS 文件的生成。index.cjs
moduleFileES 模块文件的文件名。使用 "" 将跳过 ES 模块文件的生成。index.js
typesFilesTypeScript 声明文件的文件名。使用 [] 将跳过 TypeScript 声明文件的生成。["index.d.ts"]

生成丝线

npx prisma generate

使用丝线

在生成丝线后,我们可以在 resolver 中使用:

import { resolver, query, field, weave } from '@gqloom/valibot' import * as p from '@gqloom/prisma/generated' import * as v from 'valibot' import { PrismaClient } from '@prisma/client' const db = new PrismaClient({}) const userResolver = resolver.of(p.User, { user: query(p.User.nullable(), { input: { id: v.number() }, resolve: ({ id }) => { return db.user.findUnique({ where: { id } }) }, }), posts: field(p.Post.list(), async (user) => { const posts = await db.user.findUnique({ where: { id: user.id } }).posts() return posts ?? [] }), }) const postResolver = resolver.of(p.Post, { author: field(p.User, async (post) => { const author = await db.post.findUnique({ where: { id: post.id } }).author() return author! }), }) export const schema = weave(userResolver, postResolver)

隐藏字段

@gqloom/prisma 默认将暴露所有字段。如果你希望隐藏某些字段,你可以使用 field.hidden

const postResolver = resolver.of(p.Post, { author: field(p.User, async (post) => { const author = await db.post.findUnique({ where: { id: post.id } }).author() return author! }), authorId: field.hidden, })

在上面的代码中,我们隐藏了 authorId 字段,这意味着它将不会出现在生成的 GraphQL Schema 中。

使用线筒

@gqloom/prisma 提供了 PrismaModelBobbin 来帮助你创建线筒。
使用线筒,你可以快速定义常用的查询、变更和字段,线筒还预置了常见操作输入的输入类型,使用线筒可以大大减少样板代码,这在快速开发时非常有用。

import * as p from '@gqloom/prisma/generated' import { PrismaModelBobbin } from '@gqloom/prisma' import { PrismaClient } from '@prisma/client' const db = new PrismaClient({}) const userBobbin = new PrismaModelBobbin(p.User, db) const postBobbin = new PrismaModelBobbin(p.Post, db)

在上面的代码中,我们创建了 UserPost 模型的线筒。PrismaModelBobbin 接受两个参数,第一个是作为丝线的模型,第二个是 PrismaClient 实例。

关系字段

线筒提供了 relationField 方法来定义关系字段:

const userResolver = resolver.of(p.User, { user: query(p.User.nullable(), { input: { id: v.number() }, resolve: ({ id }) => { return db.user.findUnique({ where: { id } }) }, }), posts: field(p.Post.list(), async (user) => { const posts = await db.user.findUnique({ where: { id: user.id } }).posts() return posts ?? [] }), posts: userBobbin.relationField('posts'), }) const postResolver = resolver.of(p.Post, { author: field(p.User, async (post) => { const author = await db.post.findUnique({ where: { id: post.id } }).author() return author! }), author: postBobbin.relationField('author'), authorId: field.hidden, })

在上面的代码中,我们使用 userBobbin.relationField('posts')postBobbin.relationField('author') 来定义关系字段。 relationField 方法接受一个字符串参数,表示关系字段的名称。

查询

线筒预置了常用的查询,你可以直接使用它们:

const userResolver = resolver.of(p.User, { user: query(p.User.nullable(), { input: { id: v.number() }, resolve: ({ id }) => { return db.user.findUnique({ where: { id } }) }, }), user: userBobbin.findUniqueQuery(), posts: userBobbin.relationField('posts'), })

在上面的代码中,我们使用 userBobbin.findUniqueQuery() 来定义 user 查询。线筒将自动创建输入类型和解析函数。

预置查询

线筒预置了以下查询:

  • countQuery
  • findFirstQuery
  • findManyQuery
  • findUniqueQuery

变更

线筒预置了常用的变更,你可以直接使用它们:

const postResolver = resolver.of(p.Post, { createPost: postBobbin.createMutation(), author: postBobbin.relationField('author'), authorId: field.hidden, })

在上面的代码中,我们使用 postBobbin.createMutation() 来定义 createPost 变更。线筒将自动创建输入类型和解析函数。

预置变更

线筒预置了以下变更:

  • createMutation
  • createManyMutation
  • deleteMutation
  • deleteManyMutation
  • updateMutation
  • updateManyMutation
  • upsertMutation

自定义输入

线筒预置的查询和变更支持自定义输入,你可以通过 input 选项来定义输入类型:

const userResolver = resolver.of(p.User, { user: userBobbin.findUniqueQuery({ input: valibotSilk( v.pipe( v.object({ id: v.number() }), asInputArgs(), v.transform(({ id }) => ({ where: { id } })) ) ), }), posts: userBobbin.relationField('posts'), })

在上面的代码中,我们使用 valibotSilk 来定义输入类型, v.object({ id: v.number() }) 定义了输入对象的类型, asInputArgs() 将这个对象标注为 GraphQL 的输入参数, v.transform(({ id }) => ({ where: { id } })) 将输入参数转换为 Prisma 的查询参数。

添加中间件

线筒预置的查询、变更和字段支持添加中间件,你可以通过 middlewares 选项来定义中间件:

const postResolver = resolver.of(p.Post, { createPost: postBobbin.createMutation({ middlewares: [ async (next) => { const user = await useLoggedUser() if (user == null) throw new GraphQLError('Please login first') return next() }, ], }), author: postBobbin.relationField('author'), authorId: field.hidden, })

在上面的代码中,我们使用 middlewares 选项来定义中间件, async (next) => { ... } 定义了一个中间件, useLoggedUser() 是一个自定义的函数,用于获取当前登录的用户, 如果用户未登录,则抛出一个错误,否则调用 next() 继续执行。

创建 Resolver

你可以从线筒中直接创建一个 Resolver:

const userResolver = userBobbin.resolver()

在上面的代码中,我们使用 userBobbin.resolver() 来创建一个 Resolver。 这个Resolver 将包含线筒中所有的查询、变更和字段。

自定义类型映射

为了适应更多的 Prisma 类型,我们可以拓展 GQLoom 为其添加更多的类型映射。

首先我们使用 PrismaWeaver.config 来定义类型映射的配置。这里我们导入来自 graphql-scalarsGraphQLDateTime,当遇到 DateTime 类型时,我们将其映射到对应的 GraphQL 标量。

import { GraphQLDateTime } from 'graphql-scalars' import { PrismaWeaver } from '@gqloom/prisma' export const prismaWeaverConfig = PrismaWeaver.config({ presetGraphQLType: (type) => { switch (type) { case 'DateTime': return GraphQLDateTime } }, })

在编织 GraphQL Schema 时传入配置到 weave 函数中:

import { weave } from "@gqloom/core" export const schema = weave(prismaWeaverConfig, userResolver, postResolver)

默认类型映射

下表列出了 GQLoom 中 Prisma 类型与 GraphQL 类型之间的默认映射关系:

Prisma 类型GraphQL 类型
Int @idGraphQLID
String @idGraphQLID
BigIntGraphQLInt
IntGraphQLInt
DecimalGraphQLFloat
FloatGraphQLFloat
BooleanGraphQLBoolean
DateTimeGraphQLString
StringGraphQLString