尧图精选

Cal.diy 数据访问规范:Prisma 查询为何必须用 Select 代替 Include

🕒 发布时间:2026/9/10 16:08:26 📁 来源:尧图网络
Cal.diy 数据访问规范Prisma 查询为何必须用 Select 代替 Include【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy导读本篇文章围绕 Cal.diy 仓库中的工程规范>const booking await prisma.booking.findFirst({ include: { user: true, // 取回 User 的 ALL 字段其中包含敏感字段 } });这段代码的问题在于include: { user: true }会返回user表的所有列。而 Cal.diy 的User模型背后是一套庞大的用户体系很多字段本不该流经预订查询链路一旦这些数据被序列化返回给客户端就构成潜在的安全与性能隐患。正例用嵌套 Select 做精确投影规范推荐的做法是显式声明每一层需要的字段const booking await prisma.booking.findFirst({ select: { id: true, title: true, user: { select: { id: true, name: true, email: true, } } } });关键差异在于内层从user: true变成了user: { select: { id: true, name: true, email: true } }。select的嵌套语法支持对关联relation逐层下钻因此它可以做到“只取 booking 的 id/title再只取 user 的 id/name/email”完全跳过用户记录中的其他几十个字段。投影能力是递归的这正是它比 include 精细的地方。三大收益与适用例外规范明确列出了改用select的三点收益维度说明Performance性能更小的数据载荷、更快的查询执行降低数据库到应用之间的数据传输量Security安全从源头阻止敏感字段例如credential.key被意外序列化并暴露到前端Clarity清晰让数据需求显式化代码即文档维护者一眼看出该查询真正依赖哪些列规范同时给出了唯一的例外只有当确实需要某个关联的全部字段时才使用include——而这种场景在实践中非常罕见。换言之select是默认值include是需要写注释说明理由的特例。仓库实践预置 Select 的集中管理规范并不是停留在理念层面。Cal.diy 在 packages/prisma/selects/ 目录下集中维护了一组可复用的预置 Select 对象目录结构如下packages/prisma/selects/ ├── app.ts # safeAppSelect ├── booking.ts # bookingMinimalSelect / bookingDetailsSelect / bookingWithUserAndEventDetailsSelect ├── credential.ts # credentialForCalendarServiceSelect / safeCredentialSelect ├── event-types.ts # baseEventTypeSelect / bookEventTypeSelect / availiblityPageEventTypeSelect ├── user.ts # availabilityUserSelect / baseUserSelect / userSelect └── index.ts # 统一导出入口统一导出入口 packages/prisma/selects/index.ts 将各模块的投影对象重新导出业务代码只需import { bookingMinimalSelect } from calcom/prisma或从calcom/prisma/selects/booking精确导入。安全投影的典范safeCredentialSelectCredential表中保存着日历/视频/支付等应用的 OAuth 凭据其中key、encryptedKey属于最高敏感级别的字段。在 packages/prisma/selects/credential.ts 中仓库为此准备了两种投影// 服务端内部使用需要拿到 key 做加解密 export const credentialForCalendarServiceSelect { id: true, appId: true, type: true, userId: true, user: { select: { email: true } }, teamId: true, key: true, // ← 服务端场景才出现 encryptedKey: true, // ← 服务端场景才出现 invalid: true, delegationCredentialId: true, } satisfies Prisma.CredentialSelect; // 面向安全导出彻底省略密钥字段 export const safeCredentialSelect { id: true, type: true, /** Omitting to avoid frontend leaks */ // key: true, // encryptedKey: true, userId: true, user: { select: { email: true } }, teamId: true, appId: true, invalid: true, delegationCredentialId: true, } satisfies Prisma.CredentialSelect;safeCredentialSelect用注释Omitting to avoid frontend leaks显式标注了被省略的key/encryptedKey并在select层面直接不取这些列——这正是规范中“防止credential.key意外暴露”的落地形态。同理packages/prisma/selects/app.ts 中的safeAppSelect也通过注释Omitting to avoid frontend leaks排除了 App 的keys字段。跨实体组合投影bookingWithUserAndEventDetailsSelect预订是 Cal.diy 最核心的实体之一其展示链路横跨 User、DestinationCalendar、Attendee、BookingReference、EventType 等 5 个以上关联。若用include依次展开会取回大量无关字段而 packages/prisma/selects/booking.ts 中的bookingWithUserAndEventDetailsSelect只声明了展示确认页真正需要的列export const bookingWithUserAndEventDetailsSelect { title: true, description: true, startTime: true, endTime: true, uid: true, ... destinationCalendar: { select: { id: true, integration: true, externalId: true, ... } }, attendees: { select: { email: true, name: true, timeZone: true, locale: true } }, references: { select: { id: true, type: true, uid: true, ... } }, user: { select: { email: true, name: true, timeZone: true, locale: true, credentials: { select: { id: true, type: true, delegationCredentialId: true } }, ... }, }, eventType: { select: { title: true, metadata: true, recurringEvent: true, ... } }, } satisfies Prisma.BookingSelect;注意user.credentials的内层投影——预订确认页只需要展示「用户接入了哪些凭据类型」因此内层只选id/type/delegationCredentialId连凭据的 key 字段在类型层面都不会进入结果。类型安全的双重保障预置 Select 之所以能被安全地跨文件复用依赖 Prisma 的泛型类型推导satisfies Prisma.XSelect所有预置 Select 都以satisfies Prisma.UserSelect/Prisma.BookingSelect/Prisma.CredentialSelect结尾。satisfies既强制投影键名必须真实存在于 Prisma 生成的模型类型上拼错字段名直接编译报错又不会把对象收窄成固定字面量类型保持可扩展性。Prisma.XGetPayload{ select: typeof ... }投影对象的返回类型可以反向推导。同文件最后一行就导出了精确的结果类型export type BookingWithUserAndEventDetails Prisma.BookingGetPayload{ select: typeof bookingWithUserAndEventDetailsSelect; };这意味着一份投影定义同时约束了“查询怎么写”和“结果长什么样”业务侧直接引用BookingWithUserAndEventDetails类型即可获得完全对齐的字段提示从查询到类型再到 DTO 全程一致。Repository 中的组合用法安全投影 按需扩展预置 Select 不是死板的常量Prisma 的对象展开语法允许在安全基线之上按场景叠加字段。以 packages/features/credentials/repositories/CredentialRepository.ts 为例import { safeCredentialSelect } from calcom/prisma/selects/credential; // 默认场景走 safeCredentialSelect绝不触碰密钥 select: safeCredentialSelect, // 服务端加解密场景在安全投影基础上显式补回 key select: { ...safeCredentialSelect, key: true, encryptedKey: true }, // 需要团队名展示的场景叠加嵌套关系 select: { ...safeCredentialSelect, team: { select: { name: true } } },这个模式完美呼应了规范的三层价值默认安全90% 的调用点直接复用safeCredentialSelect从源头杜绝敏感字段流出按需放行只有真正执行加解密的代码路径才通过展开运算符显式补回key/encryptedKey放行行为一目了然可被 Code Review 精确追踪组合复用预置投影像乐高积木一样可扩展避免每个 Repository 方法重写一整套投影。类似地packages/features/bookings/repositories/BookingRepository.ts 与 packages/features/bookings/lib/ 下的getBooking.ts、getBookingToDelete.ts也大量引用bookingMinimalSelect与bookingDetailsSelect同一个投影在删除校验、支付回调、预订查询等多条业务路径间保持一致。从源码结构看Cal.diy 已把“字段投影集中定义、按需组合”作为数据访问层的默认工程模式。与 Repository 与 DTO 规范的协同select优先原则并不是孤立的一条规则它与仓库内另外两条 HIGH/CRITICAL 级数据规范构成完整闭环规范文件侧重点与本条规则的协作关系data-repository-pattern.md所有数据库访问收敛到 Repository 类Repository 是唯一知道 Prisma 的代码select 投影发生在 Repository 内部ORM 技术细节被封装未来若从 Prisma 迁移到其他 ORM只需改写 Repository 实现与其 select 定义data-dto-boundaries.md数据库类型不得泄漏到前端边界处需经 Zod DTO 校验转换select 先把“数据库侧字段面”收窄到业务所需DTO 再把“跨边界字段面”二次裁剪双层过滤共同守住敏感字段可以这样理解三者的分工select 管“查询阶段少取什么”DTO 管“传输阶段少给什么”Repository 管“这些规则封装在哪里”。前者的性能与安全收益为后两者奠定基础——如果查询阶段就把全部字段取回边界上的 DTO 校验再多也只是补救而非根治。总结与落地建议Cal.diy 的这条 HIGH 影响等级规范传递的信号非常清晰Prisma 查询的字段投影不是优化项而是安全与性能的默认要求。对于在 Cal.diy 仓库中开发或贡献代码的读者落地时可以遵循以下检查清单新增查询时默认写select并对关联逐层声明所需字段优先从 packages/prisma/selects/ 复用既有投影对象而不是每次手写面向客户端返回的查询使用带safe前缀的投影如safeCredentialSelect、safeAppSelect确实需要敏感字段如凭据key时只在 Repository 内部用展开运算符显式补回并确保结果不越过服务端边界用satisfies Prisma.XSelect保证投影键名与模型类型一致用Prisma.XGetPayload导出结果类型供上层引用仅在“确实需要关联全部字段”这一罕见场景下使用include并留下注释说明理由。当遇到“为什么这里不用 include”的疑问时仓库代码本身已经给出了答案——在 Cal.diy 的预订、凭据、应用、事件类型等核心链路中select 优先既是性能纪律更是一条贯穿查询层、Repository 层与 DTO 层的数据安全防线。【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →