Keystone Query API 完全指南用context.query以编程方式执行 GraphQL CRUD 操作【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址: https://gitcode.com/gh_mirrors/key/keystone导读Keystone 的 Query API 是一套面向程序员的 CRUD 操作接口它让你不必手写 GraphQL 文档字符串就能以context.query.listName的方式对系统中的每个列表执行增删改查并精确控制返回字段。本文以官方参考文档为主体结合仓库中 Query API 的实现源码 与 GraphQL 执行层、schema 生成代码 以及 API 测试用例完整讲解九个操作方法、query参数语义、底层执行链路以及它在 hooks、访问控制、脚本与测试中的典型用法。读完本文你可以在不启动 HTTP 服务的情况下熟练使用 Query API 完成数据读写、批量操作与字段级投影。认识 Query API位于 Context 之上的编程式 CRUD 入口在 Keystone 中Context对象是系统所有运行时功能的主要 API 入口。它面向 GraphQL 的每个 resolver 提供包含query、db、graphql、session、sudo、internal、withRequest、transaction、prisma等一系列能力详见 Context 总览文档。其中的query即 Query API对于系统中定义的每一个列表List它都在context.query.listName上暴露一组 CRUD 方法。例如定义了User、Post两个列表后你可以直接调用context.query.User.*与context.query.Post.*。官方参考文档给出如下完整签名{ findOne({ where: { id }, query }), findMany({ where, take, skip, orderBy, query }), count({ where }), createOne({ data, query }), createMany({ data, query }), updateOne({ where: { id }, data, query }), updateMany({ data, query }), deleteOne({ where: { id }, query }), deleteMany({ where, query }), }这些函数的参数与它们对应的 GraphQL API 高度一致因此你可以在“编程式 API”和“GraphQL API”之间轻松切换——同样的where过滤、take/skip分页、orderBy排序、嵌套关系写入语法在两种形态下几乎一一对应。底层原理Query API 如何映射到 GraphQL schema官方文档指出“该 API 中的函数通过直接对 GraphQL API 执行查询queries和变更mutations来工作。”这一点在源码中得到完全印证。在 packages/core/src/lib/context/api.ts 中getQueryFactory从 schema 中取出 Query 与 Mutation 根类型把每个 Query API 操作绑定到列表对应的 GraphQL 字段名上Query API 方法绑定的 GraphQL 字段取自已初始化列表的graphql.namesfindOneitemQueryName单条查询findManylistQueryName列表查询count手工拼接listQueryCountName(where: $where)并调用context.graphql.runcreateOnecreateMutationNamecreateManycreateManyMutationNameupdateOneupdateMutationNameupdateManyupdateManyMutationNamedeleteOnedeleteMutationNamedeleteManydeleteManyMutationName从源码结构看这是 Query API 与context.dbAPI 最大的设计差异之一getQueryFactory走的是 makeContextQueryFn而context.db走的是makeContextDbFn。对于 Query APImakeContextQueryFn会把你传入的query字段选择字符串解析成 GraphQL fragmentparse(fragment x on RootType {...})连同由字段参数生成的 variable definitions 一起拼装成完整的 GraphQL 操作文档随后执行validate(schema, document)做合法性校验再通过execute把args作为变量值传给当前context执行最终返回result.data[fieldName]。这意味着你传入的query选择集必须对目标返回类型合法否则会抛出 schema 校验错误args中的undefined值会被过滤掉再传给 GraphQL 执行层避免干扰默认值源码注释说明了这一点如果某个操作因graphql.omit或 access control 被禁用对应字段在 schema 上不存在getQueryFactory会返回一个抛错函数“This operation is not supported by the GraphQL schema: fieldName()”。对应的 GraphQL schema 字段本身由 getQueriesForList 生成——它依据list.graphql.isEnabledquery.one/query.many/query.count决定是否注册findOne、findMany与count字段并在withSpan中记录 OpenTelemetry spanquery ${info.fieldName}附带keystone.list标签便于观测每个查询的执行。findOne按唯一条件取单条记录findOne通过where定位一条记录where通常使用主键idconst user await context.query.User.findOne({ where: { id: ... }, query: id name posts { id title }, });需要注意where使用的是列表的uniqueWhere输入类型见 getQueriesForList 中对g.nonNull(list.graphql.types.uniqueWhere)的使用即必须提供能唯一定位的字段如id或配置为唯一的字段对 singleton 列表该参数有默认值{ id: 1 }。若查询不到记录返回值为null。findMany过滤、分页与排序findMany返回匹配条件的列表支持where过滤、take/skip分页和orderBy排序const users await context.query.User.findMany({ where: { name: { startsWith: A } }, take: 10, skip: 20, orderBy: [{ name: asc }], query: id name posts { id title }, });其中where使用列表的where 输入类型非 uniqueWhere支持字段级条件操作符如startsWith、equals、contains等以及 AND/OR/NOT 组合take、skip控制返回条数与偏移量用于分页orderBy为数组可指定多个排序键如[{ name: asc }, { createdAt: desc }]。若列表未启用query.many即graphql.query.many为false调用findMany会因 schema 中不存在对应字段而抛出上文提到的“not supported”错误。count统计符合条件的记录数count仅接受where返回满足条件的记录总数数字类型const count await context.query.User.count({ where: { name: { startsWith: A } }, });从源码看count的实现与其它方法略有不同它直接调用context.graphql.run执行query ($where: WhereInput!) { count: listQueryCountName(where: $where) }并返回count数值见 api.ts。当where省略时默认为空对象即统计整张表。createOne / createMany创建单条与批量创建createOne创建一条记录data中可直接使用嵌套关系写入语法const user await context.query.User.createOne({ data: { name: Alice, posts: { create: [{ title: My first post }] }, }, query: id name posts { id title }, });createMany接收data数组一次创建多条const users await context.query.User.createMany({ data: [ { name: Alice, posts: { create: [{ title: Alices first post }] }, }, { name: Bob, posts: { create: [{ title: Bobs first post }] }, }, ], query: id name posts { id title }, });这段嵌套写法与 GraphQL mutation 的create/connect语义完全一致——你可以在创建父记录的同时嵌套创建关联子记录query字段随后会按照你声明的结构返回嵌套结果。在仓库测试中可以看到大量同样的用法例如 many-to-many 关系测试 中await context.query.Company.createOne({ ... })直接驱动测试夹具数据的构建说明这是测试与脚本中初始化数据的标准姿势。updateOne / updateMany更新单条与批量更新updateOne用whereuniqueWhere定位记录再用data描述变更const user await context.query.User.updateOne({ where: { id: ... }, data: { name: Alice, posts: { create: [{ title: My first post }] }, }, query: id name posts { id title }, });updateMany则是“where data”对的数组const users await context.query.User.updateMany({ data: [ { where: { id: ... }, data: { name: Alice, posts: { create: [{ title: Alices first post }] }, }, }, { where: { id: ... }, data: { name: Bob, posts: { create: [{ title: Bobs first post }] }, }, }, ], query: id name posts { id title }, });与createMany不同updateMany的data数组中的每一项都包含whereuniqueWhere与data两部分分别标识要更新的记录及其新值。data中同样支持嵌套关系操作create、connect、disconnect、set等。deleteOne / deleteMany删除单条与批量删除deleteOne删除where.id指定的记录并返回被删除记录的数据由query决定返回哪些字段const user await context.query.User.deleteOne({ where: { id: ... }, query: id name posts { id title }, });deleteMany的where是一个 uniqueWhere 对象数组一次删除多条const users await context.query.User.deleteMany({ where: [{ id: ... }, { id: ... }], query: id name posts { id title }, });注意这里的差异findMany/count的where是单个过滤条件对象而deleteMany的where是 uniqueWhere 数组——每个元素用唯一字段如id标识一条待删除记录。这与 GraphQL 的deleteMany(where: [UserWhereUniqueInput!]!)签名一一对应。query参数字段投影与嵌套选择所有操作除count外都接受query参数。它是一个字符串指明该操作应返回哪些字段默认值为id该默认值由 makeContextQueryFn 中的query ?? id实现。因此即使你不传query返回值也至少包含id。query的写法与 GraphQL 内联选择集语法一致标量字段直接写名字id name嵌套对象/关系字段用花括号展开posts { id title }嵌套可继续向下展开任意深度如posts { id title author { id name } }。由于query最终被解析为 fragment 选择集并经过 schema 校验见上文底层原理拼写错误的字段名会立即抛出校验错误这为编程式调用提供了类似 GraphQL 的静态安全感。如何获得一个可用的contextQuery API 的典型使用场景包括access control访问控制、hooks钩子、测试、GraphQL schema 扩展、数据迁移脚本。获取context有几种途径1. 在 resolver / hooks / 自定义扩展中直接使用Keystone 会把Context作为所有 resolver 的context参数传入因此在 hooks如beforeOperation、访问控制函数、schema 扩展的 resolver 里直接使用context.query.listName即可。此时 access control 与 session 信息会随当前context一并传递。2. 使用getContext脱离 HTTP 服务运行官方 get-context 文档 提供了keystone-6/core/context导出的getContext函数只要此前运行过keystone build或keystone dev配置变更后需先重新构建就可以不启动 HTTP 服务、不触发构建流程直接获得 context——非常适合数据填充脚本、自定义协议如小型 REST API以及单元测试import { getContext } from keystone-6/core/context import config from ./keystone.ts import * as PrismaModule from ./generated/prisma/client.ts const context getContext(config, PrismaModule) // ... 接下来即可使用 context.query.User.* 等 API使用getContext时有两点值得注意这样创建的 context既没有隐式 session也不是sudo()context——它不会绕过访问控制getContext每次调用都会实例化一个新的 Prisma Client并非全局单例使用不当可能触发“too many instances of Prisma Client”警告。仓库中的examples/script示例项目正是利用 Node.js 内置 TypeScript 支持需要 Node.js 22.18 及以上并在tsconfig.json中开启allowImportingTsExtensions: true以getContext完成数据库种子数据的写入也可用tsx等工具替代。访问控制、session 与sudoQuery API 的权限语义由context.query、context.graphql.run、context.graphql.raw发起的调用都会把当前context上的访问控制与 session 信息透传过去见 Context 总览。也就是说context.query默认受你的 access control 配置约束——它和 GraphQL API 走的是同一套访问控制、同一套校验。当你需要临时绕过这些约束时Context提供了派生新 context 的方法sudo()返回绕过 access control 的新 context适用于context.query、context.db或context.graphqlinternal()返回绕过graphql.omit列表/字段的 API 隐藏配置的新 context可读写本应从 GraphQL API 中隐藏的数据withRequest(req, res)基于请求对象重建 context其.session由sessionStrategy.get决定withSession(newSession)用指定 session 替换当前 context 的.session。与之形成对照的是context.prisma它直接暴露底层 Prisma Client始终绕过 GraphQL schema 与访问控制等价于总是处于.sudo()状态官方文档明确给出了这一警告。因此在需要走完整业务规则访问控制、hooks、校验的代码路径中应优先使用context.query而把context.prisma留给原始数据库操作。与context.db的对比何时选谁context.dbDatabase API详见 db-items 文档与context.query拥有几乎相同的九个方法签名但存在两个关键差异维度context.queryQuery APIcontext.dbDB API执行目标直接对 GraphQL API 执行 query/mutation走完整 schema 与访问控制直接执行内部 GraphQL resolver返回对象由query字段选择集投影出的“查询值”内部 item 对象适合从 schema 扩展的 mutation resolver 中直接返回从源码看context.db走makeContextDbFn见 graphql.ts它构造了一个特殊的包装 schema把字段返回值包进ReturnRawValue类型并解包还原从而把数据库行对象原样返回给调用方。因此官方建议在 GraphQL schema 扩展中编写 mutation 需要把数据行作为返回值时使用context.db其余面向业务逻辑的读写使用context.query。测试中的实战验证Query API 不仅是运行时 API也是仓库自动化测试的主力。以 many-to-many 关系测试 为例测试用例通过context.query.Company.createOne({ data: {...}, query: ... })准备数据再通过findMany、updateMany、deleteMany等断言关系型 CRUD 的行为tests/api-tests 目录下大量测试如defaults.test.ts、field-groups.test.ts等均依赖context.query完成数据存取。这意味着 Query API 具备足够的稳定性和表达力可以直接在你的项目测试与数据脚本中复用相同的模式。小结context.query是 Keystone 在“手写 GraphQL 字符串”与“原始数据库驱动”之间提供的第三态它拥有 GraphQL 的字段投影、校验与访问控制能力同时保持 JavaScript/TypeScript 调用的简洁与类型可读性。九个方法覆盖了单条/批量/计数的全部 CRUD 形态query参数提供嵌套选择能力配合getContext可在脚本与测试中脱离 HTTP 服务直接使用结合sudo、internal、withSession等 context 派生方法你可以精确控制每次调用的权限边界。理解 api.ts 与 graphql.ts 的执行链路后你也能在遇到“operation not supported by the GraphQL schema”或字段校验错误时快速定位是访问控制、graphql.omit还是字段名拼写导致的问题。【免费下载链接】keystoneThe superpowered headless CMS for Node.js — built with GraphQL and React项目地址: https://gitcode.com/gh_mirrors/key/keystone创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
bpftrace 贡献指南:从编写工具、RFC 提案到代码合入的完整实践 可观测性性能剖析eBPF 【免费下载链接】bpftrace High-level tracing language for Linux 项目地址: https://gitcode.com/gh_mirrors/bp/bpftrace 点击查看 免费下载 bpftrace 是一个面向 Linux 的高层跟踪语言,致力于让开发者用极简的单行命令快速编写… · 2026/9/24 17:22:20
NetApp FAS8300更换控制器启动中没有发现硬盘 本文章介绍NetApp FAS8300更换控制器后,启动的时候未发现硬盘,反复重启。本文章适用于FAS8300和A400,
1、更换控制器后,启动过程未发现硬盘,告警信息如下:
WARNING: 0 disks found!Storage Adapters found:
0 Fibre Channel Storage Adapters found!
4 SAS Adapters fo… · 2026/9/24 17:22:20
SpaceX-API 发射场(Launchpads)接口全解析:使用 v4/launchpads 获取全部发射场数据 后端API设计 【免费下载链接】SpaceX-API :rocket: Open Source REST API for SpaceX launch, rocket, core, capsule, starlink, launchpad, and landing pad data. 项目地址: https://gitcode.com/gh_mirrors/spa/SpaceX-API 点击查看 免费下载 本篇技术指南围绕… · 2026/9/24 17:22:14
边缘AI应用:事关家庭隐私,家用AI摄像头的哪些AI任务在本地完成 家用AI摄像头的隐私风险不在于有没有AI,而是要弄清楚AI推理发生在哪里——是在摄像头本地芯片上完成,还是把视频传到云端处理。而本地推理的任务之所以安全,是因为摄像头拍到的原始视频没有离开设备,你的摄像头安装在家里… · 2026/9/24 17:53:30
第六篇:《Codex 桌面应用实战:多智能体并行协作》 如果说 Codex CLI 是“一个智能体在终端里干活”,那么 Codex 桌面应用就是“一个指挥中心,同时调度多个智能体并行工作”。2026 年 2 月,OpenAI 推出了 Codex macOS 桌面应用,将其定位为 “代理指挥中心”(Command Cen… · 2026/9/24 17:53:23
第五篇:《Codex CLI 安装与快速上手:从零到第一个任务》 Codex 的桌面应用提供了直观的图形界面,但如果你想在终端中直接委托任务,或者需要在 CI/CD 流水线中集成 AI 编码能力,Codex CLI 才是真正的起点。它是 OpenAI 官方开源的终端 AI 编程智能体,用 Rust 实现,能在你的项目… · 2026/9/24 17:53:23
开工才发现缺料,BOM、库存、采购怎么串起来?物料齐套系统选型 制造业里最让人头疼的场面之一,就是工单已经下发了、工人已经到位了,开工才发现——缺料。A料库存不够,B料采购还没到,C料在仓库里但没人知道在哪。生产计划被迫中断,交期一拖再拖。 这个问题的根源,不是库… · 2026/9/24 17:52:58
设备不会说话:一天里的七层证据 设备不会说话:一天里的七层证据标签:有限状态机、互斥租约、生产者-消费者、内存池、构建指纹、软限位、图像显示链路、统计基线、git 语义
引言:设备不会说话。它不会告诉你"我现在很忙",也不会解释"刚才那次为什… · 2026/9/24 17:52:58
毕业设计说明书和毕业论文:图纸说明先落在谁那里,改一次要回头动几处 说明书与论文这两份文本的分工,不看你把哪段写进哪一本,而看这一处内容改一次要连带动几份。图纸说明大多两条船都踩:动一处就得回头核另一处。文末两项能力可以免费用起来:一项先把章节骨架整段搭起来,一项把图表公式… · 2026/9/24 17:52:58
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44