Wasp Entities 数据模型指南:用 Prisma Schema 定义数据库实体并实现端到端类型安全
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
导读
Entity(实体)是 Wasp 应用数据模型的基石:在 Wasp 中,一个 Entity 就对应数据库中的一张表(model)。Wasp 借助 Prisma ORM 实现全部数据库功能,并以schema.prisma文件作为唯一的数据模型定义来源。读完本文,你将掌握如何在 Wasp 项目中声明 Entity、如何把模型变更同步到数据库、如何借助wasp/entities获得端到端类型安全,以及在 Operation 和自定义代码中读写实体的两种标准姿势。
Entities 是什么:Entity 即数据库模型
简单来说,Entity 定义了应用数据库中的一个模型(model)。Wasp 使用 Prisma ORM 来实现所有数据库功能,并在此基础上提供了一层薄薄的抽象。这意味着你只需要在schema.prisma文件中定义数据库模型和关系,Wasp 会自动读取该文件并识别其中定义的所有模型。
在 Wasp 项目的根目录中,你会看到schema.prisma文件,其所在的项目结构大致如下(以 0.14 版本为例):
. ├── main.wasp ... ├── schema.prisma ├── src ├── tsconfig.json └── vite.config.ts从当前仓库的实际示例看,这一约定贯穿始终:无论是 examples/kitchen-sink/schema.prisma、examples/ask-the-documents/schema.prisma 还是 examples/waspello/schema.prisma,schema.prisma都位于示例项目的根目录,与main.wasp.ts平级。
Prisma 使用专门的Prisma Schema Language(PSL)来描述模型,这是一种声明式、非常直观的定义语言。官方资料可参考 Prisma Schema 概览与语言规范,不过在阅读本文的示例之后,你基本可以直接上手,无需预先系统学习 PSL。
关于 Wasp 与 Prisma 文件的协同关系,可进一步阅读仓库中的 Prisma Schema 文件详解。
Entity 与 Model 的区别
你可能会疑惑:既然 Wasp Entity 和 Prisma model 目前本质上是同一回事,为什么还要区分这两个概念?
- Entity 是 Wasp 的概念:目前定义 Prisma model 是创建 Entity 的唯一途径,但 Entity 是一个更高层级的抽象,Wasp 计划在未来扩展 Entity 的定义方式与能力;
- Model 是 Prisma 的概念:在 Prisma 世界中,model 就是数据库表的声明。
当前所有 Prisma model 都是 Wasp Entity,反之亦然;但随着 Wasp 的发展,这种对应关系可能演进。从仓库源码看,Wasp 的编译器会解析schema.prisma并生成对应的实体类型与数据库访问代码,这正是这一抽象层背后的实现机制。
定义一个 Entity:从 Task 模型开始
schema.prisma文件中的每一个 Prismamodel声明,都代表一个 Wasp Entity。例如,下面这个模型定义了一个表示任务的 Entity:
model Task { id String @id @default(uuid()) description String isDone Boolean @default(false) }上述model定义告诉 Wasp:创建一个用于存储任务的表(即tasks表,包含三列):
| 字段 | 类型 | 含义 |
|---|---|---|
id | String | 主键,数据库自动生成随机唯一 ID(@default(uuid())) |
description | String | 存储任务描述 |
isDone | Boolean | 表示任务完成状态,创建时未赋值则默认false(@default(false)) |
真实仓库中的 Entity 形态
仓库中的示例项目展示了更贴近实战的模型定义。例如 examples/kitchen-sink/schema.prisma 中的Task与User、TaskVote构成了典型的一对多关系,并使用了枚举与多种默认值:
enum TaskVisibility { PRIVATE LINK_ONLY PUBLIC } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User @relation(fields: [userId], references: [id]) userId Int votes TaskVote[] visibility TaskVisibility @default(PRIVATE) }可以看到,除了String @id @default(uuid()),你还可以使用Int @id @default(autoincrement())自增主键、@relation声明外键关系、DateTime @default(now())记录创建时间、@updatedAt自动维护更新时间(见 examples/ask-the-documents/schema.prisma 中的Document模型),以及用enum约束字段取值。只要符合 Prisma Schema 语法,Wasp 都能正常处理。
类型安全的实体类型:wasp/entities
在 TypeScript 项目中,Wasp 会为每个创建的 Entity 暴露一个类型,可以直接导入使用:
import { Task } from 'wasp/entities' const task: Task = { ... } // 你也可以定义操作实体的函数 function getInfoMessage(task: Task): string { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` }在getInfoMessage的参数类型中使用Task,就把参数与 Task 实体耦合在一起了。这种耦合消除了重复,并确保即使你修改了实体,函数也能保持正确的签名——当然,修改方式不当时函数可能会抛类型错误,而这恰恰是你想要的效果。
实体类型在任何地方都可用,包括客户端代码:
import { Task } from "wasp/entities" export function ExamplePage() { const task: Task = { id: "some-uuid-1234", description: "Some random task", isDone: false, } return <div>{task.description}</div> }同样的类型安全机制在这里也生效:修改schema.prisma中的实体,会同步改变导入的类型,从而通过类型错误提醒你更新过时的任务定义。仓库中 examples/kitchen-sink/src/features/operations/queries.ts 的开头正是import { type Task } from "wasp/entities",并使用Pick<Task, "id">这类工具类型对 Operation 的入参做精确约束,是该机制的典型用法。
使用实体的标准工作流
定义和操作 Wasp Entity 的完整流程如下:
- 在
schema.prisma文件中创建/更新若干 Entity; - 运行
wasp db migrate-dev:该命令将数据库模型与schema.prisma中的 Entity 定义同步,方式是生成迁移脚本; - 迁移脚本会自动放入
migrations/文件夹,务必把该文件夹提交到版本控制; - 实现 Operation 时使用 Wasp 的 JavaScript API 访问数据库(详见 Operations 概览)。
迁移脚本的实际形态可以参考仓库:examples/kitchen-sink/migrations/20240516082146_add_votes/migration.sql 就是一条真实的迁移,它创建了TaskVote表,并为userId、taskId添加了指向User与Task的外键约束:
-- CreateTable CREATE TABLE "TaskVote" ( "id" TEXT NOT NULL, "userId" INTEGER NOT NULL, "taskId" INTEGER NOT NULL, CONSTRAINT "TaskVote_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "TaskVote" ADD CONSTRAINT "TaskVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;在 Operation 中使用实体
绝大多数情况下,你会在Operation(Query 与 Action)的上下文中使用 Entity——这是 Wasp 推荐的主要访问方式。Operation 会在运行时把实体委托模型注入到context.entities中,你只需按实体名小写访问即可。仓库中的 getTasks 查询实现 展示了完整写法:
export const getTasks = (async (_args, context) => { if (!context.user) { throw new HttpError(401); } const Task = context.entities.Task; const tasks = await Task.findMany({ where: { user: { id: context.user.id } }, orderBy: { id: "asc" }, }); return tasks; }) satisfies GetTasks<void>;context.entities.Task本质上就是 Prisma Client 的委托模型(delegate),因此findMany、findUnique、count、create等全部 Prisma CRUD 能力都可用。关于 Operation 的完整讲解见 Queries 与 Actions 文档。
直接使用 Prisma Client
当需要更细粒度的控制时,你可以直接导入并使用Prisma Client与实体交互。Wasp 官方建议优先使用其内置机制,仅在需要 Wasp 未提供的特性时才直接使用 Prisma Client。
需要注意:Prisma Client 只能用于 Wasp 服务端代码。导入方式如下:
import { prisma } from 'wasp/server' prisma.task.create({ description: "Read the Entities doc", isDone: true // almost :) })补充说明:虽然客户端代码中不能使用 Prisma Client 访问数据库,但你仍然可以在客户端导入
@prisma/client以获取类型定义(尤其是enum)。详见 Prisma Schema 文件详解 中关于 enum 块的说明。
与 Prisma 文件协同的 Wasp 专属规则
虽然 Wasp 基本允许你像普通 JS/TS 项目一样使用schema.prisma,但存在几条 Wasp 专属规则需要遵守(详见 Prisma Schema 文件详解):
datasource块:provider只能使用"postgresql"或"sqlite"(Wasp 目前仅支持这两种数据库);url必须设置为env("DATABASE_URL"),Wasp 才能正常工作;generator块:必须存在一个provider = "prisma-client-js"的 generator;你可以在其基础上添加previewFeatures以启用 Prisma 预览特性(例如postgresqlExtensions,见 examples/ask-the-documents/schema.prisma 对 pgvector 扩展的实际使用);model块:只要符合 Prisma Schema 语法即可;enum块:Prisma 支持的枚举,Wasp 同样支持;服务端与客户端均可从@prisma/client导入枚举值使用。
这些规则本质上保证了 Wasp 编译器能够稳定解析数据模型,并将其转化为生成的实体类型与数据库访问代码。
小结与下一步
至此,你已经掌握了 Wasp Entities 的完整使用链路:在schema.prisma中声明模型 → 用wasp db migrate-dev同步数据库并提交migrations/→ 在 Operation 中通过context.entities访问实体,或通过wasp/entities获得端到端类型安全,必要时在服务端直接用wasp/server暴露的 Prisma Client。
下一步,建议继续阅读仓库中的 Operations 概览,了解如何让 Query 与 Action 充分调用这些实体,构建出完整的数据库读写能力。
【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考