在 Keystone 6 中使用 graphql-ts 扩展 GraphQL Schema:自定义 Query、Mutation 与类型实战
后端【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址https://gitcode.com/gh_mirrors/key/keystone点击查看免费下载本文以仓库中的 extend-graphql-schema-graphql-ts 示例 为核心完整讲解如何通过graphql.extendGraphqlSchema配置项与graphql-ts/schema、graphql-ts/extend为 Keystone 自动生成的 GraphQL API 增加自定义查询、变更与全新对象类型。读完本文你将掌握g.extend的类型安全扩展写法、base.object复用现有类型、context.db与context.query的选型差异以及如何利用base.schema.extensions.scope区分外部 API 与内部 schema。示例项目概览一个最小可运行的 Schema 扩展工程该示例位于 examples/extend-graphql-schema-graphql-ts是一个独立的、可直接运行的 Keystone 6 工程其目录结构如下文件作用keystone.tsKeystone 入口配置声明 SQLite 数据库并在graphql段接入extendGraphqlSchemaschema.ts定义Post/Author两个列表并用g.extend编写全部自定义 Query 与 Mutationschema.graphqlKeystone 自动生成并提交入库的 GraphQL Schema 文件可直接核对扩展结果schema.prismaKeystone 依据列表定义自动生成的 Prisma 模型文件prisma.config.tsPrisma 配置schema 路径、迁移目录、数据源 URLpackage.json工程脚本与依赖声明README 中明确说明该项目建立在 Keystone 官方的 Blog 示例项目之上从 schema.ts 的列表定义可以确认它沿用了博客场景中作者Author— 文章Post的一对多关系模型并在此基础上演示如何为这套模型扩充聚合统计、批量发布等原生 CRUD 之外的能力。环境准备与快速启动按照 README 的 Instructions运行方式如下克隆 Keystone 仓库到本地在仓库根目录执行pnpm install安装依赖仓库使用 pnpm workspace 管理所有 packages 与 examples根目录存在 pnpm-workspace.yaml示例中的keystone-6/core依赖以workspace:^形式指向本地源码包进入示例目录并启动开发服务器cd examples/extend-graphql-schema-graphql-ts pnpm devpnpm dev实际执行的是keystone dev见 package.json它会启动开发服务器并在localhost:3000提供两个入口Admin UIhttp://localhost:3000用于在数据库中创建Post/Author数据方便后续验证自定义接口GraphQL Playgroundhttp://localhost:3000/api/graphql可直接运行 GraphQL 查询与变更是验证自定义 Query / Mutation 的最快途径。工程还提供了完整的构建与运行脚本pnpm build # keystone build生成 Prisma Client、GraphQL Schema 与类型产物 pnpm start # keystone start以生产模式启动 pnpm check # keystone postinstall校验工程配置数据库使用 SQLite数据文件默认为file:./keystone-example.db可通过环境变量DATABASE_URL覆盖见 keystone.ts 与 prisma.config.ts。接入方式graphql.extendGraphqlSchema配置项扩展逻辑的挂载点位于 Keystone 配置的graphql.extendGraphqlSchema选项。完整的配置入口如下// keystone.ts import { PrismaBetterSqlite3 } from prisma/adapter-better-sqlite3 import { config } from keystone-6/core import { lists, extendGraphqlSchema } from ./schema export default config({ db: { provider: sqlite, prismaClientOptions: () ({ adapter: new PrismaBetterSqlite3({ url: process.env.DATABASE_URL || file:./keystone-example.db, }), }), }, graphql: { extendGraphqlSchema, }, lists, })从 Keystone 源码的类型定义看该配置项签名是extendGraphqlSchema?: (schema: GraphQLSchema) GraphQLSchema见 packages/core/src/types/config/index.ts——它接收一个由 Keystone 根据列表定义生成的GraphQLSchema并要求返回一个合法的 GraphQL Schema。在 packages/core/src/lib/graphql.ts 中createGraphQLSchema先基于列表构建基础 schema再执行// merge in the user defined graphQL API return config.graphql?.extendGraphqlSchema?.(graphQLSchema) ?? graphQLSchema也就是说无论你使用g.extend、graphql-tools/schema的mergeSchemas还是其他第三方 schema 工具只要传入一个「输入基础 schema、输出新 schema」的函数即可完成扩展。本示例选择的是 Keystone 官方推荐的graphql-ts方案即g.extend(base ({ query, mutation }))。数据模型Post 与 Author 列表扩展逻辑作用的对象是 schema.ts 中定义的两个列表export const lists { Post: list({ access: allowAll, fields: { title: text({ validation: { isRequired: true } }), status: select({ type: enum, options: [ { label: Draft, value: draft }, { label: Published, value: published }, { label: Banned, value: banned }, ], }), content: text(), publishDate: timestamp(), author: relationship({ ref: Author.posts, many: false }), }, }), Author: list({ access: allowAll, fields: { name: text({ validation: { isRequired: true } }), email: text({ isIndexed: unique, validation: { isRequired: true } }), posts: relationship({ ref: Post.author, many: true }), }, }), } satisfies Lists值得注意的点status是一个select枚举字段取值draft/published/banned后续publishPost、banPost两个自定义 Mutation 正是围绕它实现的satisfies Lists让lists接受Lists类型检查同时保留字面量类型推断便于g.extend内联扩展时对base.object(Post)等字符串引用保持精确对应的 Prisma 模型schema.prisma由 Keystone 自动生成Post.author与Author.posts通过关系字段Post_author关联。g.extend 的类型安全机制从gWithContext到g示例中扩展代码的头部有一行容易被忽略的关键声明import { gWithContext, list } from keystone-6/core import type { Context, Lists } from ./generated/keystone/types const g gWithContextContext() type gT gWithContext.inferT这里的Context来自 Keystone 生成的./generated/keystone/typeskeystone dev/build时自动生成它描述了每个列表的精确返回类型。gWithContext的底层实现在 packages/core/src/types/schema/gWithContext.ts它本质上是graphql-ts/schema的GWithContext、Keystone 标量集合与graphql-ts/extend的extend函数的组合export function gWithContextContext extends KeystoneContextany(): GWithContextContext typeof scalars { extend: typeof extend } { return { ...baseGWithContextContext(), ...scalars, extend, } }而 Keystone 默认导出的g则是预先绑定到 Keystone 默认KeystoneContext的单例见 packages/core/src/types/schema/g.tsexport const g gWithContextKeystoneContext()用法要点如果你的 resolver 只需使用 Keystone 标准的Context直接import { g } from keystone-6/core即可只有当你的应用给 context 注入了自定义字段、需要更精确的类型时才用gWithContextMyContext()重新绑定。本示例采用后者展示了面向自定义 context 的推荐写法。g.extend的回调参数base提供两个核心能力base.object(Post)从基础 schema 中按名称取出已有对象类型不存在则抛错用于复用 Keystone 生成的列表类型作为返回类型base.schema.extensions.scope读取基础 schema 上携带的public | internal标记来源见下文用于区分当前构建的是对外 API 还是内部 schema。自定义类型 Statistics聚合统计的三种字段写法示例用g.object定义了一个全新的Statistics对象类型演示定义新类型 在 resolver 中组合查询const Statistics g.object{ authorId: string }()({ name: Statistics, fields: { draft: g.field({ type: g.Int, resolve({ authorId }, args, context) { return context.query.Post.count({ where: { author: { id: { equals: authorId } }, status: { equals: draft } }, }) }, }), published: g.field({ type: g.Int, resolve({ authorId }, args, context) { return context.query.Post.count({ where: { author: { id: { equals: authorId } }, status: { equals: published } }, }) }, }), latest: g.field({ type: base.object(Post), async resolve({ authorId }, args, context) { const [post] await context.db.Post.findMany({ take: 1, orderBy: { publishDate: desc }, where: { author: { id: { equals: authorId } } }, }) return post }, }), }, })三个字段分别示范了三种典型写法draft/published使用context.query.Post.count统计某个作者对应状态的文章数。context.query返回的是普通数据对象适合用于Int这类标量结果latest使用context.db.Post.findMany取该作者publishDate最新的一篇文章返回类型声明为base.object(Post)即复用 Keystone 的Post类型。这里必须使用context.db——因为返回类型是 Keystone 的列表对象context.db提供的是符合 GraphQL 输出格式的内部对象若误用context.query会因字段格式不符而在客户端解析时出错源码注释明确警告了这一点g.object{ authorId: string }()的泛型参数声明了该类型的 source 形状——stats查询的 resolver 只返回{ authorId: id }真正的聚合计算被下放到各字段的 resolver 中执行这是 GraphQL 对象类型「按需懒解析」的典型用法。自定义 MutationpublishPost 与仅内部可见的 banPostreturn { mutation: { publishPost: g.field({ type: base.object(Post), args: { id: g.arg({ type: g.nonNull(g.ID) }) }, resolve(source, { id }, context) { return context.db.Post.updateOne({ where: { id }, data: { status: published, publishDate: new Date().toISOString() }, }) }, }), // only add this mutation to the internal schema (this is not usable from the API) ...(base.schema.extensions.scope internal ? { banPost: g.field({ type: base.object(Post), args: { id: g.arg({ type: g.nonNull(g.ID) }) }, resolve(source, { id }, context) { return context.db.Post.updateOne({ where: { id }, data: { status: banned }, }) }, }), } : {}), }, ... }publishPost接受一个非空的ID参数g.nonNull(g.ID)把对应文章的状态改为published并写入当前时间返回更新后的Post。它直接调用context.db.Post.updateOne与Statistics.latest同理因为返回类型是base.object(Post)。banPost演示了一个进阶技巧通过base.schema.extensions.scope internal判断当前构建的 schema 是内部还是公开版本从而把某些变更只暴露给内部 schema。其原理在 packages/core/src/lib/graphql.ts 与 packages/core/src/lib/graphql.tscreateGraphQLSchema会以scope: public | internal两种 scope 分别构建 schema并把 scope 写入GraphQLSchema的extensions。因此banPost会被合并进内部 schema供 Admin UI 等内部场景使用却不会出现在对外 API 中。这一点在 tests/api-tests/extend-graphql-schema.test.ts 中有直接验证it(Identifies whether the schema is public or internal, runner(async () { expect(observedSchemaExtensions.slice(-2)).toEqual([ { scope: public }, { scope: internal }, ]) }))自定义 QueryrecentPosts 与 statsquery: { recentPosts: g.field({ type: g.list(g.nonNull(base.object(Post))), args: { id: g.arg({ type: g.nonNull(g.ID) }), seconds: g.arg({ type: g.nonNull(g.Int), defaultValue: 600 }), }, resolve(source, { id, seconds }, context) { const cutoff new Date(Date.now() - seconds * 1000) return context.db.Post.findMany({ where: { author: { id: { equals: id } }, publishDate: { gt: cutoff } }, }) }, }), stats: g.field({ type: Statistics, args: { id: g.arg({ type: g.nonNull(g.ID) }) }, resolve(source, { id }) { return { authorId: id } }, }), },recentPosts返回「某个作者最近seconds秒内发布publishDate大于截止时间的文章列表」返回类型是g.list(g.nonNull(base.object(Post)))——非空元素的Post列表。seconds参数带defaultValue: 600调用方不传时默认取最近 10 分钟stats返回上节定义的Statistics类型resolver 只负责把id塞进 source{ authorId: id }具体聚合交给字段级 resolver。这两条查询是标准的 GraphQL 分层 resolver 实践外层查询只做参数透传真正的数据获取与过滤下沉到字段或依赖 Keystone 内置 API 完成。验证扩展结果自动生成的 schema.graphql运行pnpm dev后Keystone 会在工程根目录生成/更新schema.graphql。在该示例提交的 schema.graphql 中可以直观核对上述扩展是否生效type Mutation { createPost(data: PostCreateInput!): Post # ... 其余 CRUD 变更 publishPost(id: ID!): Post } type Query { # ... 其余列表查询 recentPosts(id: ID!, seconds: Int! 600): [Post!] stats(id: ID!): Statistics } type Statistics { draft: Int published: Int latest: Post }注意两点文件头部注释明确写着「This file is automatically generated by Keystone, do not modify it manually」因此扩展 API 的唯一正确入口是g.extend代码而不是直接编辑schema.graphqlMutation中只有publishPost没有banPost——与「内部 schema 专属」的设计一致Query中新增的recentPosts、stats以及Statistics类型与源码定义一一对应说明g.extend的扩展结果会被完整并入最终 schema。配套测试与延伸阅读仓库的 API 测试 tests/api-tests/extend-graphql-schema.test.ts 为extendGraphqlSchema提供了行为级佐证除了上文提到的 scope 断言外还覆盖了自定义 Query 正常执行double(x: 10)返回 20自定义 Mutation 正常执行triple(x: 10)返回 30扩展字段同样受 access control 约束quads因访问函数返回 false 而抛出Access deniedKeystone 内置 resolver 不受扩展影响createUser照常工作。这四条用例可以当作你为自定义扩展编写测试时的参照模板。若想进一步深入仓库内还有两处高价值资料GraphQL Schema 扩展指南系统讲解g.extend与第三方工具graphql-tools/schema的mergeSchemas两种路线并提示keystone-6/core3.0 之前由graphQLSchemaExtension导出的能力已改为直接使用第三方工具config.md 中 extendGraphqlSchema 章节给出配置项的类型签名与基础示例同目录下的姊妹示例 extend-graphql-schema-graphql-tools 与 extend-graphql-schema-nexus分别演示使用graphql-tools/schema和 Nexus 完成同样的扩展目标方便对比不同工具链的取舍。总的来说本示例是理解 Keystone GraphQL 扩展机制的理想起点g.extend提供了与graphql-ts/schema一脉相承的类型安全体验base.object打通了自定义类型与既有列表类型的复用而scope机制则让你能够精细控制扩展能力在对外 API 与内部 schema 之间的暴露范围。赞分享后端【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址https://gitcode.com/gh_mirrors/key/keystone点击查看免费下载相关推荐使用 Nexus 扩展 Keystone 的 GraphQL API自定义 Query/Mutation 实战指南使用 Nexus 扩展 Keystone 的 GraphQL API自定义 Query/Mutation 实战指南 导读 Keystone 会自动根据你的列表后端使用 graphql-tools/schema 扩展 Keystone GraphQL API自定义查询、变更与类型的完整实战使用 graphql tools/schema 扩展 Keystone GraphQL API自定义查询、变更与类型的完整实战 导读 Keystone 会后端免费 macOS 录屏教程QuickRecorder 7 种录制模式快速上手指南免费 macOS 录屏教程QuickRecorder 7 种录制模式快速上手指南 录制一堂网课、做一段产品演示或者想保存别人屏幕上的操作往往会被商业录屏软桌面应用音视频屏幕录制上一篇Chewie性能优化实战解决Android缓冲状态问题的终极方案下一篇Ruby定时任务的分布式锁基于Whenever的并发控制方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

MDN混合密度网络:解决多模态回归与不确定性建模

MDN混合密度网络:解决多模态回归与不确定性建模

1. 为什么传统回归模型在“一个输入对应多个合理输出”时会失效?我第一次在工业质检场景里撞上这个问题,是在调试一个金属表面缺陷尺寸预测模型。产线上的同一类划痕,在不同光照角度、不同焦距下,标注员给出的长度值存在0.3mm的合…

2026/9/24 19:40:12 阅读更多 →
Spring Boot 4.0.3与JDK25高并发云原生升级实践指南

Spring Boot 4.0.3与JDK25高并发云原生升级实践指南

Spring Boot 4.0.3 在 2025 年底正式发布的时候,说实话我一开始没太在意,毕竟 3.x 系列已经用得很顺手了。直到我把一个测试项目升级到 4.0.3 并配合 JDK 25 跑起来之后,才意识到这一次升级不是简单换版本号那么简单——模块化重构、云原生适…

2026/9/24 19:40:12 阅读更多 →
IDEA报错Cannot Save Settings:Source root duplicated的排查与修复

IDEA报错Cannot Save Settings:Source root duplicated的排查与修复

每次遇到 "Cannot Save Settings" 我都觉得这东西比编译报错更让人头大——编译错吧,至少有行号有堆栈,你能顺着线索去查。而 "Source root ... is duplicated in module ..." 这个弹窗,只在你想保存设置或者改项目结构的…

2026/9/24 19:40:12 阅读更多 →

最新新闻

电路板元器件检测:YOLO小目标漏检与密集框调参实战

电路板元器件检测:YOLO小目标漏检与密集框调参实战

简介:本资源面向从事电子制造质检、PCB缺陷检测及YOLO目标检测实战的开发者与研究人员,提供一套可直接用于训练的电路板元器件图像数据集,覆盖目标检测、小目标检测与密集检测等典型场景。压缩包共约2000个文件,以1660个txt标签、…

2026/9/24 22:03:05 阅读更多 →
单片机基础核心知识点汇总(四十三)

单片机基础核心知识点汇总(四十三)

目录 前言 一、软件定时器的核心本质 1、核心工作原理 2、核心特性 二、定时器服务任务:软件定时器的核心载体 1、服务任务的特点 2、核心影响 三、两种工作模式与核心 API 1、两种定时模式 2、核心 API 1. 创建定时器 2. 启动 / 停止 / 重置 3. 回调函数格式 四…

2026/9/24 22:03:05 阅读更多 →
2009年408真题:Cache组相联映射地址计算三步拆解

2009年408真题:Cache组相联映射地址计算三步拆解

最近在复盘408真题的计组部分时,又把2009年第14题翻了出来。这道题本身只有短短几行字,考的是Cache组相联映射中最基础的一类计算:给定Cache总块数、每组路数和块大小,让你算主存某个字节地址会被装入到Cache的哪一个组。题目不长…

2026/9/24 22:03:05 阅读更多 →
车辆检测数据集实战:从VOC转YOLO到yolov5训练避坑指南

车辆检测数据集实战:从VOC转YOLO到yolov5训练避坑指南

简介:这份资源是面向计算机视觉初学者与目标检测实践者的YOLOv5车辆检测数据集,类别聚焦为car,可用于交通监控、自动驾驶、安全驾驶等场景下的模型训练与验证。压缩包共2000个文件,以1285个txt标签、1284张jpg图像和1284个xml标注…

2026/9/24 22:03:05 阅读更多 →
需求获取方法

需求获取方法

2026/9/24 22:03:05 阅读更多 →
Ekko Studio docx Skill 源码级解析:Word 修订(Tracked Changes)与批注(Comments)的 WordprocessingML 处理

Ekko Studio docx Skill 源码级解析:Word 修订(Tracked Changes)与批注(Comments)的 WordprocessingML 处理

AI 应用人工智能AI Agent本地部署前端后端工作流自动化 【免费下载链接】ekko-studio Ekko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web. 项目地址: https://gitcode.com/gh_mirr…

2026/9/24 22:02:05 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →