jazz-schema-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJazz Data Modelling and Schema Design
Jazz 数据建模与模式设计
When to Use This Skill
何时使用本技能
- Designing data structures for Jazz applications
- Defining CoValue schemas and relationships
- Configuring permissions at the schema level
- Planning schema evolution and migrations
- Choosing between scalar and collaborative types
- Modeling relationships between data entities
- 为Jazz应用设计数据结构
- 定义CoValue模式与关系
- 在模式层面配置权限
- 规划模式演进与迁移
- 在标量类型与协作类型间做选择
- 为数据实体建模关系
Do NOT Use This Skill For
请勿将本技能用于以下场景
- Writing tests for Jazz applications (use the skill)
jazz-testing - General framework integration questions (use the skill)
jazz-ui-development - Permissions topics outside the schema. Use the skill.
jazz-permissions-security
Key Heuristic for Agents: If the user is asking about how to structure their data model, define relationships, configure default permissions, or evolve schemas, use this skill.
- 为Jazz应用编写测试(请使用技能)
jazz-testing - 通用框架集成问题(请使用技能)
jazz-ui-development - 模式以外的权限相关话题,请使用技能
jazz-permissions-security
Agent判断准则:如果用户询问如何构建数据模型、定义关系、配置默认权限或演进模式,请使用本技能。
Core Concepts
核心概念
Jazz models data as an explicitly linked collaborative graph, not traditional tables or collections. Data types are defined as schemas, with CoValues serving as the fundamental building blocks.
Jazz将数据建模为显式关联的协作图,而非传统的表或集合。数据类型通过模式定义,CoValues是其基本构建块。
Schema Definition Basics
模式定义基础
Basic Structure
基本结构
ts
const Author = co.map({
name: z.string()
});
const Post = co.map({
title: z.string(),
content: co.richText(),
});Key Libraries:
- - Zod schemas for primitive types (e.g.,
z.*)z.string() - - Jazz collaborative data types (e.g.,
co.*,co.richText())co.map()
ts
const Author = co.map({
name: z.string()
});
const Post = co.map({
title: z.string(),
content: co.richText(),
});核心库:
- - 用于基本类型的Zod模式(例如
z.*)z.string() - - Jazz协作数据类型(例如
co.*,co.richText())co.map()
Permissions Model
权限模型
Permissions are integral to the data model, not an afterthought. Each CoValue has an ownership group with hierarchical permissions.
权限是数据模型的组成部分,而非事后补充的内容。每个CoValue都有一个具备层级权限的所属组。
Permission Levels (cumulative)
权限级别(累积式)
- none - Cannot read content
- reader - Can read content
- writer - Can update content (overwrite values, modify lists)
- admin - Can grant/revoke permissions plus all writer capabilities
- none - 无法读取内容
- reader - 可读取内容
- writer - 可更新内容(覆盖值、修改列表)
- admin - 可授予/撤销权限,同时拥有writer的所有权限
Critical Permission Rules
关键权限规则
- Only the creator is admin by default - no superuser concept
- Cannot change CoValue ownership group - must modify group membership instead
- Different permissions require separate containers - use distinct CoMaps/CoLists
- Nested CoValues inherit permissions from parent by default
- Define default permissions at schema level during design
- 默认仅创建者为admin - 无超级用户概念
- 无法更改CoValue的所属组 - 必须通过修改组成员身份来实现
- 不同权限需使用独立容器 - 使用不同的CoMap/CoList
- 嵌套CoValue默认继承父级权限
- 在设计阶段于模式层面定义默认权限
Defining Permissions at Schema Level
在模式层面定义权限
Use to set automatic permissions when creating CoValues:
withPermissions()ts
const Dog = co.map({
name: z.string(),
}).withPermissions({
onInlineCreate: "sameAsContainer",
});
const Person = co.map({
pet: Dog,
}).withPermissions({
default: () => Group.create().makePublic(),
});
// Person CoValues are public, Dog shares owner with Person
const person = Person.create({
pet: { name: "Rex" }
});使用在创建CoValue时设置自动权限:
withPermissions()ts
const Dog = co.map({
name: z.string(),
}).withPermissions({
onInlineCreate: "sameAsContainer",
});
const Person = co.map({
pet: Dog,
}).withPermissions({
default: () => Group.create().makePublic(),
});
// Person CoValues是公开的,Dog与Person共享所有者
const person = Person.create({
pet: { name: "Rex" }
});Permission Configuration Options
权限配置选项
default.create()onInlineCreate.create()- (default) - New group includes container owner as member, inheriting permissions
"extendsContainer" - - Reuse container's owner (performance optimization—see below for concerns and considerations)
"sameAsContainer" - - New group with active account as admin
"newGroup" - - Like
{ extendsContainer: "reader" }but override container owner's role"extendsContainer" - Custom callback - Create and configure new group as needed
onCreate.create()default.create()onInlineCreate.create()- (默认)- 新组包含容器所有者作为成员,继承权限
"extendsContainer" - - 复用容器的所有者(性能优化——下文会说明注意事项)
"sameAsContainer" - - 新建一个以当前账户为admin的组
"newGroup" - - 类似
{ extendsContainer: "reader" },但覆盖容器所有者的角色"extendsContainer" - 自定义回调函数 - 根据需要创建并配置新组
onCreate.create()Global Permission Defaults
全局权限默认值
Set defaults for all schemas using :
setDefaultSchemaPermissionsts
import { setDefaultSchemaPermissions } from "jazz-tools";
setDefaultSchemaPermissions({
onInlineCreate: "sameAsContainer", // Performance optimization
});USE EXTREME CAUTION: If you use , you MUST be aware that the child and parent groups are one and the same. Any changes to the child group will affect the parent group, and vice versa. This can lead to unexpected behavior if not handled carefully, where changing permissions on a child group inadvertently results in permissions being granted to the parent group and any other siblings created with the same parent. As ownership cannot be changed, you MUST NOT USE if you AT ANY TIME IN FUTURE may wish to change permissions granularly on the child group.
sameAsContainersameAsContainer使用为所有模式设置默认值:
setDefaultSchemaPermissionsts
import { setDefaultSchemaPermissions } from "jazz-tools";
setDefaultSchemaPermissions({
onInlineCreate: "sameAsContainer", // 性能优化
});务必格外谨慎:如果使用,你必须清楚子项和父项的所属组是同一个。对子项组的任何更改都会影响父项组,反之亦然。如果处理不当,可能会导致意外行为——例如修改子项组的权限会无意中将权限授予父项组以及所有使用同一父项创建的同级项。由于所有权无法更改,如果你未来任何时候可能需要对子项组进行细粒度的权限更改,切勿使用。
sameAsContainersameAsContainerCoValue Types
CoValue类型
| TypeScript Type | CoValue | Use Case |
|---|---|---|
| CoMap | Struct-like objects with predefined keys |
| CoRecord | Dict-like objects with arbitrary string keys |
| CoList | Ordered lists |
| CoFeed | Session-based append-only lists |
| CoPlainText/CoRichText | Collaborative text editing |
| FileStream | File storage |
| ImageDefinition | Image storage |
| CoVector | Embeddings/vector data |
| DiscriminatedUnion | Mixed-type lists |
Use the special types and for user accounts and profiles.
co.account()co.profile()| TypeScript类型 | CoValue | 使用场景 |
|---|---|---|
| CoMap | 具有预定义键的类结构体对象 |
| CoRecord | 具有任意字符串键的类字典对象 |
| CoList | 有序列表 |
| CoFeed | 基于会话的仅追加列表 |
| CoPlainText/CoRichText | 协作式文本编辑 |
| FileStream | 文件存储 |
| ImageDefinition | 图片存储 |
| CoVector | 嵌入/向量数据 |
| DiscriminatedUnion | 混合类型列表 |
使用特殊类型和来处理用户账户和资料。
co.account()co.profile()Choosing Scalar vs Collaborative Types
选择标量类型还是协作类型
Scalar Types (Zod: z.*
)
z.*标量类型(Zod: z.*
)
z.*Use when:
- Full replacement updates expected
- No collaborative editing needed
- Single writer scenario
- Raw performance critical
Examples:
ts
const myCoValue = co.map({
title: z.string() // Replace entire title, no collaboration needed
coords: z.object({
lat: z.number(),
lon: z.number()
}) // Replace entire object, no collaboration needed
});Note: not all Zod types are available in Jazz. Be sure to always , and validate whether the type exists on the export. DO NOT import from .
import { z } from 'jazz-tools';zod使用场景:
- 预期会进行完全替换式更新
- 无需协作编辑
- 单写入者场景
- 对原始性能要求极高
示例:
ts
const myCoValue = co.map({
title: z.string() // 替换整个标题,无需协作
coords: z.object({
lat: z.number(),
lon: z.number()
}) // 替换整个对象,无需协作
});注意:并非所有Zod类型都能在Jazz中使用。请确保始终从导入,并验证导出的类型是否存在。请勿从导入。
jazz-toolszzodCollaborative Types (Jazz: co.*
)
co.*协作类型(Jazz: co.*
)
co.*Use when:
- Multiple users edit simultaneously
- Surgical/granular edits needed
- Full edit history tracking valuable
- Collaborative features required
Examples:
ts
const myCoVal = co.map({
content: co.richText() // Multiple editors
items: co.list(Item) // Add/remove individual items
config: co.map({
settingA: z.boolean(),
settingB: z.number()
}) // Update specific keys
});Trade-off: CoValues track full edit history. Slightly slower for single-writer full-replacement scenarios, but benefits almost always outweigh costs.
使用场景:
- 多用户同时编辑
- 需要精准/细粒度编辑
- 完整编辑历史追踪有价值
- 需要协作功能
示例:
ts
const myCoVal = co.map({
content: co.richText() // 多编辑者场景
items: co.list(Item) // 添加/删除单个项
config: co.map({
settingA: z.boolean(),
settingB: z.number()
}) // 更新特定键
});权衡: CoValues会追踪完整的编辑历史。在单写入者的完全替换场景下,速度略慢,但带来的好处几乎总是超过成本。
Relationship Modeling
关系建模
One-Directional Reference
单向引用
ts
const Post = co.map({
title: z.string(),
author: Author // One-way reference (like foreign key)
});Jazz stores referenced ID. Use resolve queries to control reference traversal depth.
ts
const Post = co.map({
title: z.string(),
author: Author // 单向引用(类似外键)
});Jazz存储引用ID。使用解析查询来控制引用遍历的深度。
Recursive/Forward References
递归/前置引用
Use getters to defer schema evaluation:
ts
const Author = co.map({
name: z.string(),
get posts() {
return co.list(Post); // Deferred evaluation
}
});
const Post = co.map({
title: z.string(),
author: Author
});Important: Jazz doesn't create inferred inverse relationships. Explicitly add both sides for bidirectional traversal.
使用getter来延迟模式求值:
ts
const Author = co.map({
name: z.string(),
get posts() {
return co.list(Post); // 延迟求值
}
});
const Post = co.map({
title: z.string(),
author: Author
});重要提示: Jazz不会自动创建反向关系。如果需要双向遍历,请显式定义双方。
Inverse Relationships (Two-Way)
反向关系(双向)
One-to-One:
ts
const Author = co.map({
name: z.string(),
get post() {
return Post;
}
});
const Post = co.map({
title: z.string(),
author: Author
});One-to-Many:
ts
const Author = co.map({
name: z.string(),
get posts() {
return co.list(Post);
}
});
const Post = co.map({
author: Author
});Many-to-Many:
Use at both ends. Jazz doesn't maintain consistency - manage in application code.
co.list()一对一:
ts
const Author = co.map({
name: z.string(),
get post() {
return Post;
}
});
const Post = co.map({
title: z.string(),
author: Author
});一对多:
ts
const Author = co.map({
name: z.string(),
get posts() {
return co.list(Post);
}
});
const Post = co.map({
author: Author
});多对多:
使用两端的。Jazz不维护一致性——需在应用代码中处理。
co.list()Set-Like Collections (Unique Constraint)
类集合(唯一约束)
CoLists allow duplicates. For uniqueness, use CoRecord keyed on ID:
ts
const Author = co.map({
name: z.string(),
posts: co.record(z.string(), Post)
});
// Usage
author.posts.$jazz.set(newPost.$jazz.id, newPost);Note: CoRecords always use string keys. Validate IDs at application level.
CoList允许重复项。如需唯一性,请使用以ID为键的CoRecord:
ts
const Author = co.map({
name: z.string(),
posts: co.record(z.string(), Post)
});
// 使用方式
author.posts.$jazz.set(newPost.$jazz.id, newPost);注意:CoRecord始终使用字符串键。需在应用层面验证ID。
Data Discovery Pattern
数据发现模式
CoValues are only addressable by unique ID. Discovery without ID requires reference traversal.
Standard pattern:
- Attach 'root' CoValue to user account (entry point to the data graph)
- For global 'roots': hardcode ID or use environment variable
- Build graph from root via references
CoValues仅可通过唯一ID访问。无ID时,需通过引用遍历进行发现。
标准模式:
- 将“根”CoValue附加到用户账户(作为数据图的入口点)
- 对于全局“根”:硬编码ID或使用环境变量
- 从根开始通过引用构建数据图
Schema Evolution
模式演进
Each CoValue copy is authoritative. Users may be on different schema versions simultaneously.
每个CoValue副本都是权威的。用户可能同时使用不同版本的模式。
Best Practices
最佳实践
- Add version field to schema
- Only add fields, never remove
- Never change existing field types
- Make new fields optional (backward compatible)
- Use carefully - runs on every load
withMigration()
- 添加版本字段到模式中
- 仅添加字段,切勿删除
- 切勿更改现有字段类型
- 将新字段设为可选(向后兼容)
- 谨慎使用——它会在每次加载时运行
withMigration()
Migration Example
迁移示例
ts
const Post = co.map({
version: z.number().optional(),
title: z.string(),
content: co.richText(),
tags: z.array(z.string()).optional() // New optional field
}).withMigration((post) => {
// Exit early if already migrated
if (post.version === 2) return;
// Perform migration
if (!post.$jazz.has('tags')) {
post.$jazz.set('tags', []);
}
post.$jazz.set('version', 2);
});Migration warnings:
- Runs every time CoValue loads
- Exit early to avoid unnecessary work
- Poor migrations can significantly slow app
ts
const Post = co.map({
version: z.number().optional(),
title: z.string(),
content: co.richText(),
tags: z.array(z.string()).optional() // 新的可选字段
}).withMigration((post) => {
// 如已迁移则提前退出
if (post.version === 2) return;
// 执行迁移
if (!post.$jazz.has('tags')) {
post.$jazz.set('tags', []);
}
post.$jazz.set('version', 2);
});迁移警告:
- 每次加载CoValue时都会运行
- 尽早退出以避免不必要的工作
- 设计不佳的迁移会显著降低应用速度
Design Checklist
设计检查清单
- Identify which data needs collaborative editing
- Map permissions requirements to CoValue containers
- Choose scalar vs collaborative types appropriately
- Define explicit relationships (both directions if needed)
- Plan root CoValue attachment strategy
- Add version field for future evolution
- Set default permissions at schema level
- Handle recursive references with getters
- Consider migration strategy for schema changes
- Ensure an initial migration exists for the user account to ensure the profile and root are initialized
- Ensure there are no TS or linting errors
- 确定哪些数据需要协作编辑
- 将权限需求映射到CoValue容器
- 合理选择标量类型与协作类型
- 显式定义关系(如需双向则定义双方)
- 规划根CoValue的附加策略
- 添加版本字段以支持未来演进
- 在模式层面设置默认权限
- 使用getter处理递归引用
- 考虑模式变更的迁移策略
- 确保存在针对用户账户的初始迁移,以保证资料和根节点已初始化
- 确保没有TS或语法检查错误
Common Patterns
常见模式
Blog with Authors and Posts
包含作者与文章的博客
ts
const Author = co.map({
name: z.string(),
bio: co.richText(),
get posts() {
return co.list(Post);
}
});
const Post = co.map({
title: z.string(),
content: co.richText(),
author: Author,
publishedAt: z.date().optional()
});ts
const Author = co.map({
name: z.string(),
bio: co.richText(),
get posts() {
return co.list(Post);
}
});
const Post = co.map({
title: z.string(),
content: co.richText(),
author: Author,
publishedAt: z.date().optional()
});Collaborative Task List
协作式任务列表
ts
const Task = co.map({
title: z.string(),
description: co.richText(),
completed: z.boolean(),
assignees: co.list(User)
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task)
});ts
const Task = co.map({
title: z.string(),
description: co.richText(),
completed: z.boolean(),
assignees: co.list(User)
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task)
});User Profile with Settings
带设置的用户资料
ts
const UserRoot = co.map({
theme: z.literal(['light', 'dark']),
});
const UserProfile = co.profile({
name: z.string(),
bio: co.richText(),
posts: co.record(z.string(), Post)
});
const UserAccount = co.account({
profile: UserProfile,
root: UserRoot
}).withMigration((account, creationProps) => {
if (!account.has('root')) {
const root = UserRoot.create({
theme: 'light'
});
account.$jazz.set('root', root)
}
if (!account.has('profile')) {
const profile = UserProfile.create({
name: creationProps?.name ?? 'Anonymous User',
bio: '',
posts: {}
})
}
});ts
const UserRoot = co.map({
theme: z.literal(['light', 'dark']),
});
const UserProfile = co.profile({
name: z.string(),
bio: co.richText(),
posts: co.record(z.string(), Post)
});
const UserAccount = co.account({
profile: UserProfile,
root: UserRoot
}).withMigration((account, creationProps) => {
if (!account.has('root')) {
const root = UserRoot.create({
theme: 'light'
});
account.$jazz.set('root', root)
}
if (!account.has('profile')) {
const profile = UserProfile.create({
name: creationProps?.name ?? 'Anonymous User',
bio: '',
posts: {}
})
}
});Anti-Patterns to Avoid
需避免的反模式
❌ Don't mix permissions in single CoValue - use separate containers
❌ Don't rely on inferred inverse relationships - explicitly define both sides
❌ Don't change field types in schema updates
❌ Don't write expensive migrations - they run on every load
❌ Don't use CoValues everywhere without considering trade-offs
❌ Don't forget to make new fields optional for backward compatibility
❌ 请勿在单个CoValue中混合权限——使用独立容器
❌ 请勿依赖自动推断的反向关系——显式定义双方
❌ 请勿在模式更新中更改字段类型
❌ 请勿编写开销大的迁移——它们会在每次加载时运行
❌ 请勿不考虑权衡就到处使用CoValues
❌ 请勿忘记将新字段设为可选以保证向后兼容
Quick Reference
快速参考
Loading with relationships:
Use resolve queries to control depth when traversing references.
Permission changes:
Admin/manager modifies group membership, not CoValue ownership.
Unique IDs:
Each CoValue has unique ID - only way to directly address without traversal.
Nested CoValues:
Inherit permissions from parent when created inline.
加载带关系的数据:
使用解析查询控制引用遍历的深度。
权限变更:
管理员/经理修改组成员身份,而非CoValue的所有权。
唯一ID:
每个CoValue都有唯一ID——这是无需遍历即可直接访问的唯一方式。
嵌套CoValues:
内联创建时默认继承父级权限。
References
参考资料
Load these on demand, based on need:
- API reference: https://jazz.tools/docs/api-reference.md
- Schema example: https://github.com/garden-co/jazz/blob/main/examples/music-player/src/1_schema.ts
- Connecting CoValues docs https://jazz.tools/docs/core-concepts/schemas/connecting-covalues.md
- Accounts and migrations docs https://jazz.tools/docs/core-concepts/schemas/accounts-and-migrations.md
- Docs for discriminated unions (aka schema unions) https://jazz.tools/docs/core-concepts/schemas/schemaunions.md
When using an online reference via a skill, cite the specific URL to the user to build trust.
根据需求按需加载以下内容:
- API参考:https://jazz.tools/docs/api-reference.md
- 模式示例:https://github.com/garden-co/jazz/blob/main/examples/music-player/src/1_schema.ts
- 连接CoValues文档:https://jazz.tools/docs/core-concepts/schemas/connecting-covalues.md
- 账户与迁移文档:https://jazz.tools/docs/core-concepts/schemas/accounts-and-migrations.md
- 可区分联合(又称模式联合)文档:https://jazz.tools/docs/core-concepts/schemas/schemaunions.md
当通过技能使用在线参考资料时,请向用户提供具体URL以建立信任。