name: supabase-patterns description: “适用于所有项目的Supabase最佳实践,包括行级安全、实时订阅、存储和边缘函数。不依赖于特定框架。”
Supabase Patterns 技能
适用于任何项目的通用Supabase模式。涵盖RLS策略、实时性、存储、边缘函数和迁移。
设计原则
这项技能是框架通用的。它提供通用的Supabase模式:
- 不针对Book-Vetting、ocr-service或任何特定项目定制
- 涵盖适用于所有Supabase项目通用模式
- 项目特定的配置放在项目特定的技能中
变量
| 变量 | 默认值 | 描述 |
|---|---|---|
| SUPABASE_DIR | supabase | Supabase配置目录 |
| ENFORCE_RLS | true | 要求所有表都使用RLS |
| REALTIME_ENABLED | auto | 自动检测实时表 |
指南
强制性 - 按照以下工作流程顺序进行。
- 检查Supabase项目配置
- 查看现有的RLS策略
- 遵循安全第一的模式
- 保持迁移有序
红旗 - 停止并重新考虑
如果你即将:
- 无RLS策略创建表
- 在客户端代码中使用服务角色密钥
- 跳过迁移进行架构更改
- 在实时中暴露敏感数据
停止 -> 添加RLS策略 -> 使用适当的密钥 -> 然后继续
食谱书
RLS策略
- 如果:创建或修改RLS策略
- 那么:阅读并执行
./cookbook/rls-policies.md
实时订阅
- 如果:设置实时功能
- 那么:阅读并执行
./cookbook/realtime-subscriptions.md
存储模式
- 如果:使用Supabase存储
- 那么:阅读并执行
./cookbook/storage-patterns.md
快速参考
项目结构
supabase/
├── config.toml # 项目配置
├── migrations/ # SQL迁移
│ ├── 20231201000000_initial.sql
│ └── 20231202000000_add_users.sql
├── seed.sql # 种子数据
└── functions/ # 边缘函数
└── hello/
└── index.ts
关键命令
# 初始化项目
supabase init
# 开始本地开发
supabase start
# 生成迁移
supabase migration new my_migration
# 推送到远程
supabase db push
# 生成类型
supabase gen types typescript --local > types/supabase.ts
RLS策略模式
-- 启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 用户拥有行
CREATE POLICY "Users can view own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);
-- 用户可以插入自己的
CREATE POLICY "Users can create posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- 公共读取
CREATE POLICY "Public read"
ON posts FOR SELECT
USING (is_public = true);
客户端模式
// 初始化客户端
import { createClient } from '@supabase/supabase-js';
import type { Database } from './types/supabase';
const supabase = createClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
// 使用类型查询
const { data, error } = await supabase
.from('posts')
.select('*')
.eq('user_id', userId);
// 插入
const { data, error } = await supabase
.from('posts')
.insert({ title, content, user_id: userId })
.select()
.single();
实时模式
// 订阅更改
const subscription = supabase
.channel('posts')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
console.log('Change:', payload);
}
)
.subscribe();
// 清理
subscription.unsubscribe();
存储模式
// 上传文件
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.png`, file, {
upsert: true,
contentType: 'image/png'
});
// 获取公共URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(`${userId}/avatar.png`);
安全清单
生产前
- [ ] 所有表都启用了RLS
- [ ] 服务角色密钥不在客户端代码中
- [ ] 仅用于公共操作的匿名密钥
- [ ] 存储桶有策略
- [ ] 从实时中排除敏感列
- [ ] 配置了API速率限制
- [ ] 正确配置了CORS
RLS清单
- [ ] 每个表都启用了RLS
- [ ] 定义了SELECT策略
- [ ] 定义了INSERT/UPDATE/DELETE策略
- [ ] 使用不同角色测试了策略
- [ ] 没有过于宽松的策略
集成
与架构对齐
Supabase迁移应与ORM模型对齐:
-- supabase/migrations/20231201000000_users.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
应匹配:
# SQLAlchemy模型
class User(Base):
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
email: Mapped[str] = mapped_column(unique=True)
name: Mapped[str | None]
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
类型生成
# 从本地架构生成TypeScript类型
supabase gen types typescript --local > types/supabase.ts
# 在客户端使用
import type { Database } from './types/supabase';
type Post = Database['public']['Tables']['posts']['Row'];
最佳实践
- RLS优先:创建表时始终添加RLS策略
- 迁移一切:永远不要直接修改架构
- 类型安全:生成并使用TypeScript类型
- 密钥卫生:客户端使用匿名密钥,服务密钥仅在服务器端使用
- 测试策略:使用实际用户上下文测试RLS
- 谨慎使用实时:仅对需要的表启用