Libra AI 技术开发规范
September 2, 2025 · View on GitHub
目录
- Libra AI 技术开发规范
1. 项目概述
Libra AI 是一个现代化的开源 AI 驱动开发平台,提供类似 V0/Lovable 的智能代码生成和项目构建能力。该项目基于 Turborepo Monorepo 架构构建,采用最新的 Web 技术栈,为开发者提供强大的 AI 辅助开发体验。
1.1 核心功能
- 🎯 AI 代码生成:基于多 AI 提供商(Anthropic、Azure AI、OpenRouter、xAI)的智能代码生成
- 🏗️ 项目构建:支持 Vite、React 等主流框架的项目模板
- 🎨 可视化编辑:提供 IDE 级别的代码编辑(基于 shikicode)和实时预览功能
- 👥 团队协作:支持组织管理、项目分享和团队协作功能
- 🔗 集成生态:与 GitHub、Cloudflare、E2B、Daytona 沙盒等平台深度集成
- 🚀 即时部署:一键部署到 Cloudflare Workers,支持自定义域名
1.2 技术特色
- 🔒 端到端类型安全:从数据库到前端的完整 TypeScript 类型覆盖
- 🎨 现代化设计系统:基于 Tailwind CSS v4 + Radix UI 的组件系统
- ⚡ 高性能架构:React Server Components + 流式渲染 + React 19 优化
- 🛡️ 企业级安全:better-auth + Cloudflare 插件的认证系统,集成 Stripe 计费
- 🌍 国际化支持:基于 Paraglide.js 的类型安全多语言系统
- ☁️ 云原生部署:完整的 Cloudflare Workers 部署方案,支持 OpenNext.js
1.3 架构原则
- 📦 Monorepo 优先:使用 Turborepo 统一代码管理、依赖共享、工具链一致性
- 🔄 关注点分离:应用层、业务逻辑层、数据层明确分离
- 🏗️ 类型安全优先:端到端 TypeScript 类型覆盖,使用 Zod 验证
- 🎯 开发体验优先:Bun 包管理器、Biome 代码格式化、热重载支持
- 📈 可观测性驱动:结构化日志、Posthog 分析、性能追踪
- 🔧 工具链标准化:统一的 Biome linting/formatting、Vitest 测试配置
2. 架构设计
2.1 Monorepo 组织结构
项目采用 Turborepo 构建的 Monorepo 架构,清晰分离应用和共享包:
libra/
├── apps/ # 应用程序
│ ├── auth-studio/ # better-auth 管理界面
│ ├── builder/ # Vite 构建工具 (独立构建环境)
│ ├── cdn/ # Hono CDN 服务 (文件上传/图片处理)
│ ├── deploy/ # 部署服务 (Cloudflare Workers)
│ ├── deploy-workflow/ # 部署工作流服务 (deprecated)
│ ├── dispatcher/ # 请求路由服务 (认证中间件)
│ ├── docs/ # 文档站点 (Next.js + FumaDocs)
│ ├── email/ # React Email 开发环境
│ ├── opennext-cache/ # OpenNext 缓存服务
│ ├── proxy/ # 代理与容器 (WIP)
│ ├── screenshot/ # 截图生成服务
│ ├── vite-shadcn-template/# Vite 项目模板
│ └── web/ # Next.js 15 主应用 (React 19)
├── packages/ # 共享包
│ ├── api/ # tRPC API 层 (类型安全)
│ ├── auth/ # better-auth 认证系统,使用 Cloudflare D1
│ ├── better-auth-cloudflare/ # Cloudflare 适配器
│ ├── better-auth-stripe/ # Stripe 集成
│ ├── common/ # 通用工具和类型
│ ├── db/ # 业务数据库层 (Drizzle ORM + Neon/Hyperdrive)
│ ├── email/ # React Email 模板
│ ├── middleware/ # Cloudflare Workers 中间件
│ ├── sandbox/ # E2B 沙盒集成
│ ├── shikicode/ # 代码高亮组件
│ ├── templates/ # 项目脚手架模板
│ └── ui/ # 设计系统 (基于 Radix UI + Tailwind CSS v4)
├── scripts/ # 构建脚本和工具
├── tooling/ # 开发工具配置
│ └── typescript-config/ # 共享 TypeScript 配置
├── biome.json # Biome 配置
├── bun.lock # Bun 锁定文件
├── package.json # 根级依赖管理 (Bun workspace)
└── turbo.json # Turborepo 构建配置
核心原则:
- 单一职责:每个包负责特定功能领域
- 依赖管理:共享包避免重复,减少 bundle 大小
- 类型共享:跨包的类型定义统一管理
- 构建优化:Turborepo 并行构建和智能缓存
2.2 应用程序架构
apps/web - 主 Web 应用
基于 Next.js 15 的主应用,采用 App Router 架构:
apps/web/
├── app/ # Next.js App Router
│ ├── (frontend)/ # 前端路由组
│ │ ├── (dashboard)/ # 控制台页面
│ │ │ ├── dashboard/ # 用户仪表板
│ │ │ └── project/ # 项目管理
│ │ └── (marketing)/ # 营销页面
│ └── api/ # API 路由
│ ├── ai/ # AI 相关 API
│ ├── auth/ # 认证 API
│ ├── trpc/ # tRPC 端点
│ └── webhooks/ # Webhook 处理
├── components/ # UI 组件
│ ├── ide/ # IDE 编辑器组件
│ ├── dashboard/ # 控制台组件
│ ├── marketing/ # 营销组件
│ └── ui/ # 基础 UI 组件
├── ai/ # AI 功能模块
│ ├── models.ts # 模型管理
│ ├── generate.ts # 生成逻辑
│ └── prompts/ # 提示词模板
├── lib/ # 工具函数
├── trpc/ # tRPC 客户端
└── env.mjs # 环境变量
apps/builder - Vite 构建工具
独立的 Vite + React 应用,用于项目构建:
apps/builder/
├── src/
│ ├── components/ # 构建工具 UI
│ ├── lib/ # 构建逻辑
│ └── utils/ # 工具函数
├── vite.config.ts # Vite 配置
└── wrangler.jsonc # Cloudflare Workers 配置
apps/cdn - CDN 服务
基于 Hono 的内容分发服务:
apps/cdn/
├── src/
│ ├── routes/ # API 路由
│ ├── middleware/ # 中间件
│ └── utils/ # 工具函数
├── wrangler.jsonc # Workers 配置
└── package.json
apps/dispatcher - 请求路由
基于 Hono 的请求分发服务:
apps/dispatcher/
├── src/
│ ├── middleware/ # 认证中间件
│ ├── routes/ # 路由逻辑
│ └── utils/ # 工具函数
├── wrangler.jsonc # Cloudflare Workers 配置
└── package.json
apps/auth-studio - 数据库管理
基于 Drizzle Studio 的数据库管理界面:
apps/auth-studio/
├── package.json # 启动脚本配置
├── DEV.md # 开发文档
└── DEV_ZH.md # 中文开发文档
功能特点:
- 提供认证数据库的可视化管理界面
- 支持数据表的 CRUD 操作
- 集成 Drizzle ORM 的 Studio 工具
- 运行在端口 3002
apps/docs - 文档站点
基于 Fumadocs + Next.js 的文档系统:
apps/docs/
├── app/ # Next.js App Router
│ ├── [lang]/ # 多语言路由
│ ├── layout.tsx # 布局组件
│ └── page.tsx # 首页
├── components/ # 文档组件
│ ├── language-switcher.tsx # 语言切换器
│ ├── heading.tsx # 标题组件
│ └── scroller.tsx # 滚动组件
├── content/ # 文档内容
│ ├── meta.json # 英文元数据
│ ├── meta.zh.json # 中文元数据
│ ├── opensource/ # 开源相关文档
│ └── platform/ # 平台相关文档
├── lib/ # 工具函数
│ ├── i18n.ts # 国际化配置
│ └── translations.ts # 翻译管理
├── source.config.ts # Fumadocs 配置
└── wrangler.jsonc # Cloudflare Workers 配置
技术特点:
- 基于 Fumadocs 的现代文档框架
- 支持 MDX 格式的文档编写
- 内置多语言支持(中英文)
- 集成 GitHub Stars 显示
- 部署到 Cloudflare Workers
apps/email - 邮件模板开发
基于 React Email 的邮件模板开发环境:
apps/email/
├── emails/ # 邮件模板
│ ├── welcomeEmail.tsx # 欢迎邮件
│ ├── emailVerification.tsx # 邮箱验证
│ ├── organizationInvitation.tsx # 组织邀请
│ ├── signIn.tsx # 登录邮件
│ └── cancellationEmail.tsx # 取消订阅
├── package.json # 依赖配置
└── tsconfig.json # TypeScript 配置
功能特点:
- 提供邮件模板的实时预览环境
- 支持 React 组件化的邮件开发
- 集成 @libra/email 包的模板
apps/vite-shadcn-template - 项目模板
基于 Vite + React + TypeScript 的项目脚手架:
apps/vite-shadcn-template/
├── src/ # 源代码
│ ├── components/ # UI 组件
│ ├── lib/ # 工具函数
│ └── utils/ # 辅助工具
├── public/ # 静态资源
├── components.json # 组件配置
├── e2b.Dockerfile # E2B 容器配置
├── e2b.toml # E2B 模板配置
├── vite.config.ts # Vite 配置
├── wrangler.jsonc # Cloudflare Workers 配置
└── fileStructure.ts # 文件结构定义
技术特点:
- 预配置的 Vite + React + TypeScript 环境
- 集成自定义 UI 组件库
- 支持 E2B、Daytona 沙盒环境部署
- 提供完整的项目脚手架模板
- 可部署到 Cloudflare Workers
apps/deploy - 部署服务
基于 Hono 的 Cloudflare Workers 部署服务:
apps/deploy/
├── src/
│ ├── index.ts # 主入口
│ ├── handlers/ # 请求处理器
│ └── utils/ # 工具函数
└── wrangler.jsonc # Workers 配置
功能特点:
- 处理项目的自动化部署
- 管理 Cloudflare Pages 部署
- 处理构建产物上传
- 集成 GitHub 部署状态更新
apps/deploy-workflow - 部署工作流
基于 Cloudflare Workflows 的异步部署处理服务:
apps/deploy-workflow/
├── src/
│ ├── index.ts # 工作流入口
│ ├── steps/ # 工作流步骤
│ └── types/ # 类型定义
└── wrangler.jsonc # Workers 配置
功能特点:
- 异步处理长时间部署任务
- 支持多步骤部署流程
- 集成 E2B 沙盒环境
- 部署状态实时更新
apps/screenshot - 截图服务
基于 Playwright 的网页截图生成服务:
apps/screenshot/
├── src/
│ ├── index.ts # 服务入口
│ ├── browser/ # 浏览器控制
│ └── utils/ # 工具函数
└── wrangler.jsonc # Workers 配置
功能特点:
- 生成项目预览截图
- 支持不同设备尺寸
- 自动等待页面加载
- 缓存优化减少重复截图
2.3 共享包架构
@libra/ui - 设计系统
基于 Radix UI 和 CVA 的自定义组件库:
// packages/ui/src/components/button.tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '../lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground',
outline: 'border border-input bg-background hover:bg-accent',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive.Slot : 'button'
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
)
}
)
@libra/api - tRPC API 层
类型安全的 API 层:
// packages/api/src/router/project.ts
export const projectRouter = {
create: organizationProcedure
.input(projectSchema)
.mutation(async ({ ctx, input }) => {
const { orgId, userId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
// 配额验证
const quotaDeducted = await checkAndUpdateProjectUsage(orgId)
if (!quotaDeducted) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Project quota exceeded'
})
}
// 创建项目
const [newProject] = await db.insert(project).values({
name: input.name ?? 'My First Project',
templateType: input.templateType ?? 'default',
userId,
organizationId: orgId,
}).returning()
return newProject
}),
} satisfies TRPCRouterRecord
@libra/db - 数据库层
基于 Drizzle ORM 的数据库层,提供完整的项目管理、AI 功能统计和订阅管理功能:
// packages/db/schema/project-schema.ts
import { createId } from '@paralleldrive/cuid2'
import { sql } from 'drizzle-orm'
import { boolean, integer, pgTable, text, timestamp, varchar, uniqueIndex } from 'drizzle-orm/pg-core'
// 项目主表 - 核心项目信息管理
export const project = pgTable('project', {
// 主键标识
id: text('id')
.$defaultFn(() => createId())
.primaryKey()
.unique(),
// 基础项目信息
name: text('name').notNull(), // 项目名称
templateType: text('template_type').notNull(), // 模板类型
url: text('url'), // 项目访问 URL
// Git 集成字段
gitUrl: text('git_url'), // Git 仓库地址
gitBranch: text('git_branch'), // Git 分支
// 部署和预览
previewImageUrl: text('preview_image_url'), // 预览图片 URL
productionDeployUrl: text('production_deploy_url'), // 生产部署 URL
workflowId: text('workflow_id'), // Cloudflare Workflow ID deprecated
deploymentStatus: varchar('deployment_status', {
enum: ['idle', 'preparing', 'deploying', 'deployed', 'failed']
}), // 部署状态
// 自定义域名管理
customDomain: text('custom_domain'), // 自定义域名
customDomainStatus: varchar('custom_domain_status', {
enum: ['pending', 'verified', 'active', 'failed']
}), // 域名状态
customDomainVerifiedAt: timestamp('custom_domain_verified_at', {
withTimezone: true,
mode: 'string'
}), // 域名验证时间
customHostnameId: text('custom_hostname_id'), // Cloudflare 主机名 ID
ownershipVerification: text('ownership_verification'), // 所有权验证
// SSL 证书管理
sslStatus: varchar('ssl_status', {
enum: ['pending', 'pending_validation', 'active', 'failed']
}), // SSL 状态
// 项目设置
visibility: varchar('visibility', { enum: ['public', 'private'] }), // 可见性
isActive: boolean('is_active').notNull().default(true), // 是否活跃
// 关联关系
userId: text('user_id').notNull(), // 用户 ID
organizationId: text('organization_id').notNull(), // 组织 ID
containerId: text('container_id'), // 容器 ID
// AI 功能字段
initialMessage: text('initial_message'), // 初始消息
knowledge: text('knowledge'), // 知识库内容
messageHistory: text('message_history').notNull().default('[]'), // 消息历史
// 时间戳
createdAt: timestamp('created_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp('updated_at', {
withTimezone: true,
mode: 'string',
})
.defaultNow()
.notNull()
.$onUpdate(() => sql`now()`),
})
// 项目 AI 使用统计表
export const projectAIUsage = pgTable('project_ai_usage', {
id: text('id')
.$defaultFn(() => createId())
.primaryKey(),
projectId: text('project_id')
.notNull()
.references(() => project.id, { onDelete: 'cascade' }), // 外键关联
organizationId: text('organization_id').notNull(),
totalAIMessageCount: integer('total_ai_message_count').notNull().default(0), // AI 消息总数
lastUsedAt: timestamp('last_used_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`), // 最后使用时间
createdAt: timestamp('created_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp('updated_at', {
withTimezone: true,
mode: 'string',
})
.defaultNow()
.notNull()
.$onUpdate(() => sql`now()`),
})
// 订阅资源限制表
export const subscriptionLimit = pgTable('subscription_limit', {
id: text('id')
.$defaultFn(() => createId())
.primaryKey(),
organizationId: text('organization_id').notNull(),
stripeCustomerId: text('stripe_customer_id'), // Stripe 客户 ID
planName: text('plan_name').notNull(), // 计划名称
planId: text('plan_id').notNull(), // 计划 ID
aiNums: integer('ai_nums').notNull(), // AI 使用次数限制
enhanceNums: integer('enhance_nums').notNull(), // 增强功能次数限制
seats: integer('seats').notNull().default(1), // 席位数
projectNums: integer('project_nums').notNull().default(1), // 项目数限制
isActive: boolean('is_active').notNull().default(true), // 是否活跃
periodStart: timestamp('period_start', {
withTimezone: true,
mode: 'string'
}).notNull(), // 计费周期开始
periodEnd: timestamp('period_end', {
withTimezone: true,
mode: 'string'
}).notNull(), // 计费周期结束
createdAt: timestamp('created_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp('updated_at', {
withTimezone: true,
mode: 'string',
})
.defaultNow()
.notNull()
.$onUpdate(() => sql`now()`),
}, (table) => ({
// 唯一约束:每个组织只能有一个活跃的计划
uniqueOrgPlanActive: uniqueIndex('subscription_limit_org_plan_active_idx')
.on(table.organizationId, table.planName)
.where(sql`${table.isActive} = true`)
}))
// 项目资产表 - 管理项目相关的附件文件
export const projectAsset = pgTable('project_asset', {
id: text('id')
.$defaultFn(() => createId())
.primaryKey(),
organizationId: text('organization_id').notNull(),
projectId: text('project_id')
.notNull()
.references(() => project.id, { onDelete: 'cascade' }), // 级联删除
planId: text('plan_id').notNull(), // 关联计划 ID
attachmentKey: text('attachment_key').notNull(), // 附件存储键
createdAt: timestamp('created_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp('updated_at', {
withTimezone: true,
mode: 'string',
})
.defaultNow()
.notNull()
.$onUpdate(() => sql`now()`),
})
// TypeScript 类型推断
type Project = typeof project.$inferSelect
type InsertProject = typeof project.$inferInsert
type ProjectAIUsage = typeof projectAIUsage.$inferSelect
type SubscriptionLimit = typeof subscriptionLimit.$inferSelect
type ProjectAsset = typeof projectAsset.$inferSelect
tRPC 集成使用示例:
// packages/api/src/router/project.ts - tRPC Query 示例
export const projectRouter = {
// 查询组织下的所有项目
list: organizationProcedure.query(async ({ ctx }) => {
const { orgId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
return await db
.select({
id: project.id,
name: project.name,
templateType: project.templateType,
url: project.url,
visibility: project.visibility,
isActive: project.isActive,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
})
.from(project)
.where(and(
eq(project.organizationId, orgId),
eq(project.isActive, true)
))
.orderBy(desc(project.createdAt))
}),
// 查询单个项目详情(包含 AI 使用统计)
getById: organizationProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ ctx, input }) => {
const { orgId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
const { projectId } = input
const projectData = await fetchProject(db, projectId)
ensureOrgAccess(projectData, orgId, 'read')
// 联表查询项目和 AI 使用统计
const [result] = await db
.select({
project: project,
aiUsage: projectAIUsage,
})
.from(project)
.leftJoin(projectAIUsage, eq(project.id, projectAIUsage.projectId))
.where(eq(project.id, projectId))
return result
}),
// tRPC Mutation 示例 - 创建项目
create: organizationProcedure
.input(projectSchema)
.mutation(async ({ ctx, input }) => {
const { orgId, userId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
const { name, initialMessage, visibility, templateType } = input
log.project('info', 'Project creation started', {
orgId,
userId,
projectName: name,
templateType,
visibility,
})
// 配额检查和扣减
const quotaDeducted = await checkAndUpdateProjectUsage(orgId)
if (!quotaDeducted) {
log.project('warn', 'Project creation failed - quota exceeded', {
orgId,
userId,
})
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Project quota exceeded'
})
}
// 事务处理:创建项目并初始化相关记录
const result = await db.transaction(async (tx) => {
// 创建项目
const [newProject] = await tx
.insert(project)
.values({
name: name ?? 'My First Project',
templateType: templateType ?? 'default',
visibility: (visibility as 'public' | 'private') ?? 'private',
initialMessage,
messageHistory: initialMessage
? JSON.stringify([{
type: 'user',
message: initialMessage,
planId: 'initial'
}])
: '[]',
userId,
organizationId: orgId,
})
.returning()
if (!newProject) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create project'
})
}
// 初始化 AI 使用统计记录
await tx.insert(projectAIUsage).values({
projectId: newProject.id,
organizationId: orgId,
totalAIMessageCount: 0,
})
log.project('info', 'Project created successfully', {
orgId,
userId,
projectId: newProject.id,
projectName: newProject.name,
})
return newProject
})
return result
}),
// 更新项目配置
updateConfig: organizationProcedure
.input(updateProjectConfigSchema)
.mutation(async ({ ctx, input }) => {
const { projectId, name, knowledge } = input
const { orgId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
const projectData = await fetchProject(db, projectId)
ensureOrgAccess(projectData, orgId, 'update')
const updates: Partial<typeof project.$inferInsert> = {}
if (name !== undefined) updates.name = name
if (knowledge !== undefined) updates.knowledge = knowledge
if (Object.keys(updates).length === 0) {
return projectData
}
const [updatedProject] = await db
.update(project)
.set(updates)
.where(eq(project.id, projectId))
.returning()
if (!updatedProject) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to update project'
})
}
return updatedProject
}),
// 删除项目(软删除)
delete: organizationProcedure
.input(z.object({ projectId: z.string() }))
.mutation(async ({ ctx, input }) => {
const { projectId } = input
const { orgId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
const projectData = await fetchProject(db, projectId)
ensureOrgAccess(projectData, orgId, 'delete')
// 事务处理:软删除项目并恢复配额
await db.transaction(async (tx) => {
// 软删除项目
await tx
.update(project)
.set({ isActive: false })
.where(eq(project.id, projectId))
// 恢复项目配额
await restoreProjectQuotaOnDeletion(orgId)
})
log.project('info', 'Project deleted successfully', {
orgId,
projectId,
})
return { success: true }
}),
} satisfies TRPCRouterRecord
// 错误处理和类型安全实践
import { tryCatch } from '@libra/common'
// 使用 tryCatch 进行安全的数据库操作
export async function safeProjectOperation(projectId: string) {
const [result, error] = await tryCatch(async () => {
const db = await getDbAsync()
return await db
.select()
.from(project)
.where(eq(project.id, projectId))
})
if (error) {
log.project('error', 'Database operation failed', {
projectId,
error: error.message,
})
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Database operation failed',
})
}
return result
}
tRPC 客户端使用示例:
// apps/web/trpc/client.tsx - 客户端配置
'use client'
import { createTRPCReact } from '@trpc/react-query'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink, loggerLink } from '@trpc/client'
import { useState } from 'react'
import superjson from 'superjson'
import type { AppRouter } from '@libra/api'
// 创建 tRPC React 客户端
export const api = createTRPCReact<AppRouter>()
export function TRPCReactProvider(props: { children: React.ReactNode }) {
const [queryClient] = useState(() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000, // 30秒
retry: (failureCount, error) => {
// 对于认证错误不重试
if (error?.data?.code === 'UNAUTHORIZED') return false
return failureCount < 3
},
},
},
})
)
const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
httpBatchLink({
transformer: superjson,
url: `${getBaseUrl()}/api/trpc`,
headers() {
const headers = new Map<string, string>()
headers.set('x-trpc-source', 'nextjs-react')
return Object.fromEntries(headers)
},
}),
],
})
)
return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{children}
</api.Provider>
</QueryClientProvider>
)
}
// apps/web/app/layout.tsx - 应用级集成
import { TRPCReactProvider } from '@/trpc/client'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<TRPCReactProvider>
{children}
</TRPCReactProvider>
</body>
</html>
)
}
// 组件中的查询使用示例
'use client'
import { api } from '@/trpc/client'
import { Button } from '@libra/ui/components/button'
import { Card, CardContent, CardHeader, CardTitle } from '@libra/ui/components/card'
import { Loader2, Plus } from 'lucide-react'
export function ProjectList() {
// 查询数据 - 自动类型推导
const {
data: projects,
isLoading,
error,
refetch
} = api.project.list.useQuery()
// 创建项目的变更操作
const createProject = api.project.create.useMutation({
onSuccess: (newProject) => {
// 自动类型推导:newProject 类型为 Project
console.log('项目创建成功:', newProject.id)
// 手动使缓存失效,触发重新获取
utils.project.list.invalidate()
},
onError: (error) => {
// 类型安全的错误处理
console.error('创建失败:', error.message)
if (error.data?.code === 'FORBIDDEN') {
alert('项目配额已用完,请升级套餐')
}
},
})
// 获取 tRPC utils 用于缓存操作
const utils = api.useUtils()
const handleCreateProject = () => {
createProject.mutate({
name: '新项目',
templateType: 'nextjs',
visibility: 'private',
initialMessage: '创建一个待办事项应用',
})
}
if (isLoading) {
return (
<div className="flex items-center justify-center p-8">
<Loader2 className="h-8 w-8 animate-spin" />
<span className="ml-2">加载项目中...</span>
</div>
)
}
if (error) {
return (
<div className="p-4 text-red-600">
<p>加载失败: {error.message}</p>
<Button onClick={() => refetch()} variant="outline" className="mt-2">
重试
</Button>
</div>
)
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">我的项目</h2>
<Button
onClick={handleCreateProject}
disabled={createProject.isPending}
>
{createProject.isPending ? (
<Loader2 className="h-4 w-4 animate-spin mr-2" />
) : (
<Plus className="h-4 w-4 mr-2" />
)}
创建项目
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{projects?.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</div>
</div>
)
}
3. 核心技术栈
3.1 前端技术栈
核心框架
- Next.js 15.3.5:App Router 架构,React Server Components,实验性 React 编译器
- React 19.1.1:最新 React 版本,支持 Server/Client Components 分离
- TypeScript 5.8.3:严格模式,完整类型覆盖
用户界面框架
- Tailwind CSS 4.1.11:原子化 CSS,CSS-in-CSS 新语法,CSS 变量支持
- Radix UI:无样式的可访问性原语组件(accordion、dialog、tooltip 等)
- Class Variance Authority (CVA) 0.7.1:组件变体管理
- 基于 shadcn/ui 设计模式:自定义组件实现,遵循 shadcn/ui 方法论
- Lucide React 0.486.0:图标库
- next-themes 0.4.6:主题切换支持
状态管理
- Zustand 5.0.6:轻量级客户端状态管理
- TanStack Query (React Query) 5.83.0:服务端状态和缓存管理
- React Hook Form 7.61.1:高性能表单状态管理
开发工具
- Vite:快速开发服务器(builder、template 应用)
- Turborepo 2.5.5:Monorepo 构建工具
- Biome ^2.2.2:代码格式化和 Lint 工具(替代 ESLint + Prettier)
3.2 后端技术栈
接口层
- tRPC 11.4.3:端到端类型安全 API
- Zod 4.0.14:运行时数据验证和类型推导
- Hono 4.8.10:轻量级 Web 框架(CDN/dispatcher 服务)
数据库
- PostgreSQL:主业务数据库(通过 Neon + Hyperdrive)
- SQLite:认证数据库(Cloudflare D1)
- Drizzle ORM 0.44.4:类型安全的数据库 ORM
- Hyperdrive:Cloudflare 数据库连接池服务
认证与支付
- better-auth 1.3.4:现代认证解决方案
- Stripe:支付和订阅管理
- GitHub OAuth:社交登录(Octokit 22.0.0)
- Resend:邮件发送服务
文件存储
- Cloudflare R2:对象存储
- Cloudflare KV:键值存储
- E2B 1.2.0-beta.5:沙盒代码执行环境
3.3 AI 集成技术
人工智能提供商
- Anthropic Claude:Claude 4.0 Sonnet,通过 @ai-sdk/anthropic 集成
- Azure OpenAI:GPT-4.1、GPT-4.1 Mini,通过 @ai-sdk/azure 集成
- Google Gemini:Gemini 2.5 Pro,通过 AI SDK 集成
- xAI Grok:通过 @ai-sdk/xai 集成
人工智能工具
- Vercel AI SDK 4.3.19:流式响应和统一 API
- E2B 1.2.0-beta.5:沙盒代码执行环境(Docker 容器)
- Shiki 3.8.1:代码语法高亮
生成功能
// apps/web/ai/models.ts
export const selectModel = (
userPlan: string,
selectedModelId?: string,
isFileEdit = false
): string => {
let modelToUse = selectedModelId
if (isFileEdit) {
modelToUse = DEFAULT_MODELS.FILE_EDIT
} else if (!modelToUse) {
const defaultModel = getDefaultModelForPlan(userPlan)
modelToUse = defaultModel.id
} else {
// 严格的访问控制
if (!canAccessModel(userPlan, modelToUse)) {
const requestedModel = findModelById(modelToUse)
throw new Error(`Access denied: ${requestedModel.name} requires ${requestedModel.requiredPlan} subscription. Current plan: ${userPlan}`)
}
}
return MODEL_MAPPING[modelToUse] || (isFileEdit ? DEFAULT_MODELS.FILE_EDIT_FALLBACK : DEFAULT_MODELS.FALLBACK)
}
3.4 部署与基础设施
云服务提供商
- Cloudflare:完整的云原生部署
- Workers:无服务器计算
- D1:边缘 SQLite 数据库
- KV:边缘键值存储
- R2:对象存储
- Hyperdrive:数据库连接池服务
- Workers For Platform:边缘计算平台
- Cloudflare for SaaS:Custom domain, SSL, WAF, DDoS 保护
- Cloudflare AI Gateway:AI 请求代理
部署工具
- @opennextjs/cloudflare 1.6.2:Next.js 到 Cloudflare 的适配器
- Wrangler:Cloudflare Workers 部署工具
- Bun 1.2.19:JavaScript 运行时和包管理器(首选)
开发运维
// package.json scripts
{
"dev": "turbo dev --parallel",
"build": "turbo build --concurrency=100%",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"lint": "turbo lint --continue --",
"typecheck": "turbo typecheck"
}
监控与分析
- Posthog:产品分析和用户行为追踪
- 结构化日志:自定义日志系统(组件级别分类)
- 错误跟踪:集成错误监控和边界处理
- OpenTelemetry:可观测性数据收集
4. 设计系统与 UI 组件
4.1 设计系统架构
项目采用基于 OKLCH 色彩空间 的现代设计系统,结合 Tailwind CSS v4 的 CSS-in-CSS 新特性:
/* packages/ui/src/styles/variables.css */
:root {
/* 品牌色彩 - OKLCH 格式 */
--brand: oklch(66.5% 0.1804 47.04);
--brand-foreground: oklch(75.77% 0.159 55.91);
/* 基础色彩 */
--background: oklch(98% 0.01 95.1);
--foreground: oklch(34% 0.03 95.72);
--background-landing: oklch(100% 0.01 97.5);
--foreground-landing: oklch(32% 0.03 95.72);
/* 组件色彩 */
--card: oklch(98% 0.01 95.1);
--card-foreground: oklch(19% 0 106.59);
--popover: oklch(100% 0 0);
--popover-foreground: oklch(27% 0.02 98.94);
--primary: oklch(62% 0.14 39.04);
--primary-foreground: oklch(100% 0 0);
--secondary: oklch(92% 0.01 92.99);
--secondary-foreground: oklch(43% 0.02 98.6);
--muted: oklch(93% 0.02 90.24);
--muted-foreground: oklch(61% 0.01 97.42);
--accent: oklch(92% 0.01 92.99);
--accent-foreground: oklch(27% 0.02 98.94);
--destructive: oklch(19% 0 106.59);
--destructive-foreground: oklch(100% 0 0);
--border: oklch(88% 0.01 97.36);
--input: oklch(76% 0.02 98.35);
--ring: oklch(87% 0.0671 252);
/* 图表色彩 */
--chart-1: oklch(56% 0.13 43);
--chart-2: oklch(69% 0.16 290.41);
--chart-3: oklch(88% 0.03 93.13);
--chart-4: oklch(88% 0.04 298.18);
--chart-5: oklch(56% 0.13 42.06);
/* 侧边栏色彩 */
--sidebar: oklch(97% 0.01 98.88);
--sidebar-foreground: oklch(36% 0.01 106.65);
--sidebar-primary: oklch(62% 0.14 39.04);
--sidebar-primary-foreground: oklch(99% 0 0);
--sidebar-accent: oklch(92% 0.01 92.99);
--sidebar-accent-foreground: oklch(33% 0 0);
--sidebar-border: oklch(94% 0 0);
--sidebar-ring: oklch(77% 0 0);
/* 字体系统 */
--font-sans: Inter, sans-serif;
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: Mona_Sans, monospace;
/* 布局变量 */
--radius: 0.625rem;
--layout-nav-height: 3.5rem; /* 56px */
/* 阴影系统 */
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 5%);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 5%);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 10%), 0 1px 2px -1px hsl(0 0% 0% / 10%);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 10%), 0 1px 2px -1px hsl(0 0% 0% / 10%);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 10%), 0 2px 4px -1px hsl(0 0% 0% / 10%);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 10%), 0 4px 6px -1px hsl(0 0% 0% / 10%);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 10%), 0 8px 10px -1px hsl(0 0% 0% / 10%);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 25%);
/* 插图色彩 */
--light: var(--brand);
--light-foreground: var(--brand-foreground);
--color-1: hsl(0 100% 63%);
--color-2: hsl(270 100% 63%);
--color-3: hsl(210 100% 63%);
--color-4: hsl(195 100% 63%);
--color-5: hsl(90 100% 63%);
}
.dark {
/* 品牌色彩 */
--brand: oklch(83.6% 0.1177 66.87);
--brand-foreground: oklch(75.77% 0.159 55.91);
/* 特殊背景 */
--background-hero: oklch(14.1% 0.005 285.823);
--foreground-hero: oklch(98.5% 0 0);
/* 基础色彩 */
--background: oklch(27% 0 106.64);
--foreground: oklch(81% 0.01 93.01);
--background-landing: oklch(18% 0.01 260);
--foreground-landing: oklch(85% 0.01 95.01);
/* 组件色彩 */
--card: oklch(27% 0 106.64);
--card-foreground: oklch(98% 0.01 95.1);
--popover: oklch(31% 0 106.6);
--popover-foreground: oklch(92% 0 106.48);
--primary: oklch(67% 0.13 38.76);
--primary-foreground: oklch(100% 0 0);
--secondary: oklch(98% 0.01 95.1);
--secondary-foreground: oklch(31% 0 106.6);
--muted: oklch(22% 0 106.71);
--muted-foreground: oklch(77% 0.02 99.07);
--accent: oklch(21% 0.01 95.42);
--accent-foreground: oklch(97% 0.01 98.88);
--destructive: oklch(64% 0.21 25.33);
--destructive-foreground: oklch(100% 0 0);
--border: oklch(36% 0.01 106.89);
--input: oklch(43% 0.01 100.22);
--ring: oklch(36% 0.0728 251.11);
/* 图表色彩 */
--chart-1: oklch(56% 0.13 43);
--chart-2: oklch(69% 0.16 290.41);
--chart-3: oklch(21% 0.01 95.42);
--chart-4: oklch(31% 0.05 289.32);
--chart-5: oklch(56% 0.13 42.06);
/* 侧边栏色彩 */
--sidebar: oklch(24% 0 67.71);
--sidebar-foreground: oklch(81% 0.01 93.01);
--sidebar-primary: oklch(33% 0 0);
--sidebar-primary-foreground: oklch(99% 0 0);
--sidebar-accent: oklch(17% 0 106.62);
--sidebar-accent-foreground: oklch(81% 0.01 93.01);
--sidebar-border: oklch(94% 0 0);
--sidebar-ring: oklch(77% 0 0);
/* 插图色彩 */
--light: var(--foreground);
--light-foreground: var(--foreground);
}
4.2 组件开发模式
所有 UI 组件遵循 shadcn/ui 设计模式,结合 radix-ui 原语组件:
// packages/ui/src/components/button.tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { Slot as SlotPrimitive } from 'radix-ui'
import * as React from 'react'
import { cn } from '../lib/utils'
const buttonVariants = cva(
// 基础样式
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? SlotPrimitive : 'button'
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
)
}
)
Button.displayName = 'Button'
export { Button, buttonVariants }
组件开发原则:
- CVA 变体管理:使用 class-variance-authority 统一管理组件变体
- asChild 模式:通过 Radix UI Slot 支持组件组合
- forwardRef:正确转发 ref 以支持命令式操作
- 类型安全:完整 TypeScript 类型定义,支持自动推导
- 类型安全:完整的 TypeScript 类型定义和推导
4.3 样式系统规范
Tailwind CSS v4 配置
项目使用 Tailwind CSS v4 的 CSS-in-CSS 新特性:
/* packages/ui/src/styles/globals.css */
@import "tailwindcss";
@import "./deployment-tokens.css";
@import "./utils.css";
@import "./theme.css";
@import "./variables.css";
@import "./quota.css";
@import "tw-animate-css";
@plugin 'tailwind-scrollbar';
@source "../../../../packages/ui/src/**/*.{js,ts,jsx,tsx}";
@custom-variant dark (&:where(.dark, .dark *));
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
PostCSS 配置:
// packages/ui/postcss.config.mjs
const config = {
plugins: ['@tailwindcss/postcss'],
}
export default config
CSS 变量使用规范
- 必须使用 CSS 变量进行颜色定义,禁止硬编码颜色值
- 语义化命名:使用 primary、secondary、accent 等语义名称
- 主题支持:所有颜色变量支持亮色/暗色模式切换
/* ✅ 正确:使用语义化 CSS 变量 */
.card {
background-color: var(--card);
border: 1px solid var(--border);
color: var(--card-foreground);
}
/* ❌ 错误:硬编码颜色值 */
.card {
background-color: #ffffff;
border: 1px solid #e2e8f0;
color: #1e293b;
}
Tailwind 工具类规范
- 优先使用 语义化工具类(bg-primary、text-muted-foreground)
- 避免使用 任意值(bg-[#fff]、mt-[12px])
- 组合使用 cn() 工具函数处理条件类名
// ✅ 推荐:语义化类名 + 条件处理
<button className={cn(
'bg-primary text-primary-foreground',
'hover:bg-primary/90 transition-colors',
disabled && 'opacity-50 cursor-not-allowed',
className
)}>
{children}
</button>
// ❌ 避免:任意值和硬编码
<button className="bg-[#3b82f6] text-white hover:bg-[#2563eb] disabled:opacity-50">
{children}
</button>
自定义工具类
项目定义了多个自定义工具类:
/* packages/ui/src/styles/utils.css */
@utility glass-1 {
backdrop-filter: blur(64px) saturate(200%);
background-color: rgb(0 0 0 / 1%);
}
@utility glass-2 {
backdrop-filter: blur(128px) saturate(100%);
background-color: rgb(255 255 255 / 2%);
}
@utility fade-x {
@apply relative overflow-hidden;
&::before,
&::after {
@apply pointer-events-none absolute z-10 h-full w-8 content-[''];
}
&::before {
@apply left-0 bg-gradient-to-r from-background to-transparent;
}
&::after {
@apply right-0 bg-gradient-to-l from-background to-transparent;
}
}
4.4 主题系统实现
项目使用 next-themes 实现主题切换,支持系统主题检测:
// apps/web/app/(frontend)/layout.tsx
import { ThemeProvider } from 'next-themes'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang={locale} suppressHydrationWarning>
<body className={cn(inter.className, 'antialiased')}>
<ThemeProvider attribute='class' defaultTheme='dark' enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}
主题切换组件实现:
// components/theme-toggle.tsx
'use client'
import { useTheme } from 'next-themes'
import { Button } from '@libra/ui'
import { Moon, Sun } from 'lucide-react'
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">切换主题</span>
</Button>
)
}
5. API 开发规范
5.1 tRPC 实现模式
项目使用 tRPC 实现端到端类型安全的 API,包含完整的认证、权限验证和错误处理机制:
// packages/api/src/trpc.ts
export const createTRPCContext = async (opts: { headers: Headers }) => {
const auth = await initAuth()
const session = await auth.api.getSession({ headers: opts.headers })
const db = await getAuthDb()
return { db, session, ...opts }
}
// 基础过程定义
export const publicProcedure = t.procedure
export const protectedProcedure = publicProcedure.use(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
})
})
export const organizationProcedure = protectedProcedure
.input(orgSchema)
.use(({ ctx, next }) => {
const activeOrganizationId = ctx.session?.session?.activeOrganizationId
if (!activeOrganizationId) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Organization ID is required',
})
}
return next({
ctx: { ...ctx, orgId: activeOrganizationId, session: ctx.session },
})
})
路由实现示例
// packages/api/src/router/project/basic-operations.ts
export const basicOperations = {
create: organizationProcedure.input(projectSchema).mutation(async ({ ctx, input }) => {
const { orgId, userId } = await requireOrgAndUser(ctx)
const { name, initialMessage, visibility, templateType } = input
// 配额验证
const quotaDeducted = await checkAndUpdateProjectUsage(orgId)
if (!quotaDeducted) {
log.project('warn', 'Project creation failed - quota exceeded', {
orgId,
userId,
operation: 'create',
})
throw new TRPCError({ code: 'FORBIDDEN', message: 'Project quota exceeded' })
}
// 使用模块化的项目创建工具
return await withDbCleanup(async (db) => {
return await createProjectWithHistory(db, {
orgId,
userId,
operation: 'create',
}, {
name,
templateType,
visibility,
initialMessage,
templateType: templateType ?? 'default',
visibility: (visibility as 'public' | 'private') ?? 'private',
initialMessage,
userId,
organizationId: orgId,
}).returning()
if (!newProject) {
log.project('error', 'Database operation failed - project creation', {
orgId, userId, operation: 'create', projectName: name,
})
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create project'
})
}
log.project('info', 'Project created successfully', {
orgId, userId, projectId: newProject.id, operation: 'create',
})
return newProject
}),
list: organizationProcedure.query(async ({ ctx }) => {
const { orgId } = await requireOrgAndUser(ctx)
const db = await getDbAsync()
return await db.query.project.findMany({
where: eq(project.organizationId, orgId),
orderBy: [desc(project.updatedAt)],
})
}),
} satisfies TRPCRouterRecord
5.2 数据验证与类型安全
所有 API 输入使用 Zod 进行验证,确保运行时类型安全:
// packages/api/src/schemas/project-schema.ts
export const projectSchema = z.object({
name: z.string().min(1, 'Project name is required').optional(),
initialMessage: z.string().optional(),
visibility: z.enum(['public', 'private']).optional(),
templateType: z.string().optional(),
attachment: z.object({
name: z.string(),
type: z.string(),
content: z.string(),
}).optional(),
planId: z.string().optional(),
})
export const updateProjectSchema = z.object({
projectId: z.string().cuid2('Invalid project ID'),
name: z.string().min(1).optional(),
visibility: z.enum(['public', 'private']).optional(),
})
export type ProjectInput = z.infer<typeof projectSchema>
export type UpdateProjectInput = z.infer<typeof updateProjectSchema>
客户端类型推导
// apps/web/trpc/client.ts
import { type AppRouter } from '@libra/api'
import { createTRPCReact } from '@trpc/react-query'
export const api = createTRPCReact<AppRouter>()
// 使用示例 - 完全类型安全
export function ProjectList() {
const { data: projects, isLoading, error } = api.project.list.useQuery()
const createProject = api.project.create.useMutation({
onSuccess: (data) => {
// data 的类型自动推导为 Project
console.log('Created project:', data.id)
},
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{projects?.map(project => (
<div key={project.id}>{project.name}</div>
))}
</div>
)
}
5.3 错误处理模式
项目使用统一的错误处理模式,包含 tryCatch 工具函数和结构化错误响应:
// packages/common/src/error.ts
type Success<T> = readonly [T, null]
type Failure<E> = readonly [null, E]
type ResultSync<T, E> = Success<T> | Failure<E>
type ResultAsync<T, E> = Promise<ResultSync<T, E>>
type Operation<T> = Promise<T> | (() => T) | (() => Promise<T>)
export function tryCatch<T, E = Error>(operation: Promise<T>): ResultAsync<T, E>
export function tryCatch<T, E = Error>(operation: () => Promise<T>): ResultAsync<T, E>
export function tryCatch<T, E = Error>(operation: () => T): ResultSync<T, E>
export function tryCatch<T, E = Error>(
operation: Operation<T>
): ResultSync<T, E> | ResultAsync<T, E> {
if (operation instanceof Promise) {
return operation
.then((data: T) => [data, null] as const)
.catch((error: E) => [null, error as E] as const)
}
try {
const result = operation()
if (result instanceof Promise) {
return result
.then((data: T) => [data, null] as const)
.catch((error: E) => [null, error as E] as const)
}
return [result, null] as const
} catch (error) {
return [null, error as E] as const
}
}
错误处理使用示例
// API 路由中的错误处理
export const aiRouter = {
generate: protectedProcedure
.input(generateSchema)
.mutation(async ({ ctx, input }) => {
const [result, error] = await tryCatch(async () => {
// 验证配额
await validateAIQuota(ctx.session.user.id)
// 生成内容
return await generateAIResponse(input)
})
if (error) {
log.ai('error', 'AI generation failed', {
userId: ctx.session.user.id,
error: error instanceof Error ? error.message : String(error),
})
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to generate AI response',
cause: error,
})
}
return result
}),
}
5.4 日志记录规范
项目实现结构化日志系统,支持组件级别的日志分类和上下文传递:
// packages/common/src/logger.ts
export const log = {
// 组件特定的日志记录器
auth: (level: LogLevel, message: string, context?: LogContext, error?: Error) =>
logger.auth(level, message, context, error),
project: (level: LogLevel, message: string, context?: LogContext, error?: Error) =>
logger.project(level, message, context, error),
ai: (level: LogLevel, message: string, context?: LogContext, error?: Error) =>
logger.ai(level, message, context, error),
billing: (level: LogLevel, message: string, context?: LogContext, error?: Error) =>
logger.billing(level, message, context, error),
}
// 使用示例
log.project('info', 'Project creation started', {
orgId, userId, projectName: name, templateType,
})
log.ai('error', 'Model selection failed', {
userId, requestedModel, userPlan,
error: error instanceof Error ? error.message : String(error),
})
日志上下文结构
interface LogContext {
// 用户相关
userId?: string
orgId?: string
// 操作相关
operation?: string
resource?: string
resourceId?: string
// 业务相关
projectId?: string
modelId?: string
quotaType?: string
// 技术相关
duration?: number
error?: string
// 其他业务特定字段
[key: string]: unknown
}
6. 数据库设计与操作
6.1 双数据库架构
项目采用 双数据库架构 来优化性能和功能分离:
业务数据库 - PostgreSQL (Hyperdrive)
- 用途:存储核心业务数据(项目、文件、AI 对话等)
- 连接方式:通过 Cloudflare Hyperdrive 连接池
- 优势:高性能、复杂查询支持、事务完整性
- 访问函数:
getDbAsync(),getDbForHono()
认证数据库 - SQLite (Cloudflare D1)
- 用途:存储用户认证、会话、组织权限数据
- 连接方式:直接连接 Cloudflare D1
- 优势:低延迟、边缘计算友好、better-auth 原生支持
- 访问函数:
getAuthDb()
数据库选择指南
// 业务数据操作 - 使用 PostgreSQL
const db = await getDbAsync()
const projects = await db.query.project.findMany()
// 认证相关操作 - 使用 SQLite D1
const authDb = await getAuthDb()
const user = await authDb.query.user.findFirst()
6.2 PostgreSQL 连接配置(Hyperdrive)
项目使用 Cloudflare Hyperdrive 进行 PostgreSQL 连接池管理,优化连接性能:
// packages/db/index.ts
import { getCloudflareContext } from '@opennextjs/cloudflare'
import { drizzle as drizzleNode } from 'drizzle-orm/node-postgres'
import pg from 'pg'
import { cache } from 'react'
// 用于 React Server Components 的异步数据库连接
export const getDbAsync = async () => {
let connectionString: string | undefined
if ((process.env['NODE_ENV'] as string) === 'development') {
connectionString = env.POSTGRES_URL
} else {
const { env: cfEnv } = await getCloudflareContext({ async: true })
// Type assertion for HYPERDRIVE property
const hyperdrive = (cfEnv as any).HYPERDRIVE
connectionString = hyperdrive?.connectionString
}
if (!connectionString) {
throw new Error('Database connection string not found for async.')
}
const pool = new pg.Pool({
connectionString: connectionString,
maxUses: 1, // 适配无服务器环境,每个连接只使用一次
})
return drizzleNode({ client: pool, schema: schema })
}
// 用于 Hono 应用(Cloudflare Workers)的数据库连接
export async function getDbForHono(c: HonoDbContext) {
let connectionString: string | undefined
const nodeEnv: string = c.env.NODE_ENV || 'production'
// 开发环境:使用直连 PostgreSQL
if (nodeEnv === 'development') {
connectionString = c.env?.POSTGRES_URL
} else {
// 生产环境:使用 HYPERDRIVE 连接池
const hyperdrive = c.env?.HYPERDRIVE
connectionString = hyperdrive?.connectionString
}
if (!connectionString) {
throw new Error(`No database connection string available. Environment: ${nodeEnv}`)
}
const pool = new pg.Pool({ connectionString, maxUses: 1 })
return drizzleNode({ client: pool, schema })
}
Hyperdrive 配置优势:
- 连接池管理:自动管理数据库连接池,减少连接开销
- 全球分布:在 Cloudflare 边缘网络中缓存连接
- 自动重试:内置连接失败重试机制
- 性能优化:减少 TCP 握手延迟
6.3 Schema 设计规范
项目使用 Drizzle ORM 与 PostgreSQL/SQLite 数据库,遵循严格的 Schema 设计规范:
// packages/db/schema/project-schema.ts
import { createId } from '@paralleldrive/cuid2'
import { sql } from 'drizzle-orm'
import { boolean, integer, pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core'
export const project = pgTable('project', {
// 主键:使用 CUID2 生成唯一标识符
id: text('id').$defaultFn(() => createId()).primaryKey().unique(),
// 基础字段
name: text('name').notNull(),
templateType: text('template_type').notNull(),
url: text('url'),
gitUrl: text('git_url'),
previewImageUrl: text('preview_image_url'),
// 枚举字段:使用 varchar 约束
visibility: varchar('visibility', { enum: ['public', 'private'] }),
// 布尔字段:提供默认值
isActive: boolean('is_active').notNull().default(true),
// 外键关联
userId: text('user_id').notNull(),
organizationId: text('organization_id').notNull(),
containerId: text('container_id'),
// JSON 字段
initialMessage: text('initial_message'),
messageHistory: text('message_history').notNull().default('[]'),
// 审计字段:自动管理创建和更新时间
createdAt: timestamp('created_at', {
withTimezone: true,
mode: 'string'
}).default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp('updated_at', {
withTimezone: true,
mode: 'string',
}).defaultNow().notNull().$onUpdate(() => sql`now()`),
})
// 类型导出
export type Project = typeof project.$inferSelect
export type NewProject = typeof project.$inferInsert
Schema 设计原则:
- CUID2 主键:所有表使用 CUID2 作为主键,保证全局唯一性
- 审计字段:每个表包含 createdAt 和 updatedAt 字段
- 枚举约束:使用 varchar enum 限制字段值
- 非空约束:合理使用 notNull() 约束
- 默认值:为布尔字段和状态字段提供默认值
- 时区支持:时间戳字段启用时区支持
6.4 查询模式
基础查询操作
// packages/api/src/router/project.ts
import { db } from '@libra/db'
import { project } from '@libra/db/schema'
import { eq, desc, and, isNotNull } from 'drizzle-orm'
// 查询用户的活跃项目
export async function getUserActiveProjects(userId: string, orgId: string) {
return await db.query.project.findMany({
where: and(
eq(project.userId, userId),
eq(project.organizationId, orgId),
eq(project.isActive, true)
),
orderBy: [desc(project.updatedAt)],
limit: 20,
})
}
// 关联查询示例
export async function getProjectWithFiles(projectId: string) {
return await db.query.project.findFirst({
where: eq(project.id, projectId),
with: {
files: {
orderBy: [desc(files.updatedAt)],
},
owner: {
columns: {
id: true,
name: true,
email: true,
},
},
},
})
}
事务处理
// 复杂操作使用事务确保数据一致性
export async function createProjectWithFiles(
projectData: NewProject,
initialFiles: NewFile[]
) {
return await db.transaction(async (tx) => {
// 创建项目
const [newProject] = await tx
.insert(project)
.values(projectData)
.returning()
if (!newProject) {
throw new Error('Failed to create project')
}
// 创建初始文件
if (initialFiles.length > 0) {
await tx.insert(files).values(
initialFiles.map(file => ({
...file,
projectId: newProject.id,
}))
)
}
return newProject
})
}
6.5 数据完整性
外键约束和级联删除
// 定义关系和约束
export const projectRelations = relations(project, ({ one, many }) => ({
owner: one(user, {
fields: [project.userId],
references: [user.id],
}),
organization: one(organization, {
fields: [project.organizationId],
references: [organization.id],
}),
files: many(files),
aiUsage: many(aiUsage),
}))
// 软删除模式
export async function softDeleteProject(projectId: string) {
const [deletedProject] = await db
.update(project)
.set({
isActive: false,
updatedAt: new Date().toISOString(),
})
.where(eq(project.id, projectId))
.returning()
return deletedProject
}
7. 认证与权限系统
7.1 better-auth 集成
项目使用 better-auth 实现现代化认证系统,支持多种认证方式和 Cloudflare 集成:
// packages/auth/auth-server.ts
import { betterAuth } from 'better-auth'
import { organization, emailOTP, admin, bearer } from 'better-auth/plugins'
import { stripe } from '@libra/better-auth-stripe'
import { withCloudflare } from '@libra/better-auth-cloudflare'
import { emailHarmony } from 'better-auth-harmony'
async function authBuilder() {
const dbInstance = await getAuthDb()
const { env } = await getCloudflareContext({ async: true })
return betterAuth(
withCloudflare(
{
autoDetectIpAddress: true,
geolocationTracking: true,
d1: {
db: dbInstance,
options: {
// usePlural: true,
// debugLogs: true,
},
},
kv: env.KV,
},
{
databaseHooks: {
session: {
create: {
before: async (session: Session) => {
try {
const organization = await getActiveOrganization(session.userId)
return {
data: {
...session,
activeOrganizationId: organization.id,
},
}
} catch (error) {
// 错误处理和日志记录
throw new Error('Failed to create session')
}
},
},
},
},
socialProviders: {
github: {
clientId: envs.BETTER_GITHUB_CLIENT_ID,
clientSecret: envs.BETTER_GITHUB_CLIENT_SECRET,
},
},
plugins: [
admin({
defaultRole: 'user',
adminRoles: ['admin', 'superadmin'],
}),
organization(),
emailOTP(),
stripe(),
emailHarmony(),
bearer()
]
})
)
}
7.2 组织权限模式
项目实现多租户组织架构,支持层级权限管理:
// packages/auth/organization.ts
export interface OrganizationMember {
id: string
userId: string
organizationId: string
role: 'owner' | 'admin' | 'member'
permissions: Permission[]
invitedAt: Date
joinedAt?: Date
}
export const organizationPermissions = {
'project:create': 'Create new projects',
'project:read': 'View projects',
'project:update': 'Edit projects',
'project:delete': 'Delete projects',
'member:invite': 'Invite new members',
'member:manage': 'Manage member roles',
'billing:view': 'View billing information',
'billing:manage': 'Manage subscriptions',
} as const
export async function checkPermission(
userId: string,
orgId: string,
permission: keyof typeof organizationPermissions
): Promise<boolean> {
const member = await getOrganizationMember(userId, orgId)
if (!member) return false
// 所有者拥有所有权限
if (member.role === 'owner') return true
// 检查特定权限
return member.permissions.includes(permission)
}
7.3 API 权限验证
// packages/api/src/auth-utils.ts
export async function requireOrgAndUser(ctx: Context) {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
const activeOrganizationId = ctx.session.session?.activeOrganizationId
if (!activeOrganizationId) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'No active organization'
})
}
return {
userId: ctx.session.user.id,
orgId: activeOrganizationId,
}
}
export async function requirePermission(
ctx: Context,
permission: Permission
) {
const { userId, orgId } = await requireOrgAndUser(ctx)
const hasPermission = await checkPermission(userId, orgId, permission)
if (!hasPermission) {
throw new TRPCError({
code: 'FORBIDDEN',
message: `Missing permission: ${permission}`
})
}
return { userId, orgId }
}
8. Stripe 支付集成
8.1 Stripe 插件配置
项目使用自定义的 @libra/better-auth-stripe 插件集成 Stripe 支付功能:
// packages/auth/plugins/stripe-plugin.ts
import { stripe } from '@libra/better-auth-stripe'
import { env } from '../env.mjs'
import { stripeClient } from '../utils/stripe-config'
export const stripePlugin = stripeClient
? [
stripe({
stripeClient,
stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET || '',
createCustomerOnSignUp: true,
subscription: {
enabled: true,
getCheckoutSessionParams,
plans: getPlans,
authorizeReference,
onSubscriptionComplete,
onSubscriptionUpdate,
onSubscriptionCancel,
onSubscriptionDeleted,
},
onEvent,
onCustomerCreate,
})
]
: []
8.2 订阅计划管理
// packages/auth/utils/subscription-limits/types.ts
export const PLAN_TYPES = {
FREE: 'libra free',
PRO: 'libra pro',
MAX: 'libra max'
} as const
export interface PlanLimits {
aiNums: number
seats: number
projectNums: number
uploadLimit?: number
deployLimit?: number
}
// 创建或更新订阅限制
export async function createOrUpdateSubscriptionLimit(
organizationId: string,
stripeCustomerId: string | null,
plan: string,
periodStart: Date,
periodEnd: Date,
customLimits?: { aiNums?: number; seats?: number; projectNums?: number }
) {
// 实现订阅限制的创建和更新逻辑
}
8.3 Webhook 处理
// packages/auth/webhooks/stripe-handler.ts
export async function handleSubscriptionUpdated(event: Stripe.Event) {
const subscription = event.data.object as Stripe.Subscription
// 更新组织的订阅状态
await updateOrganizationSubscription(subscription)
// 发送确认邮件
await sendSubscriptionUpdateEmail(subscription)
}
export async function handleSubscriptionDeleted(event: Stripe.Event) {
const subscription = event.data.object as Stripe.Subscription
// 降级到免费计划
await downgradeToFreePlan(subscription.metadata.organizationId)
}
9. AI 功能开发
9.1 AI 模型管理
项目支持多个 AI 提供商,实现基于用户计划的模型访问控制:
// apps/web/configs/ai-models.ts
export const PLAN_TYPES = {
FREE: 'libra free',
PRO: 'libra pro',
MAX: 'libra max',
} as const
export type AIModel = {
id: string
name: string
icon: string
provider: 'anthropic' | 'openai' | 'google'
requiredPlan: PlanType
}
export const AI_MODELS: AIModel[] = [
{
id: 'gpt-4-1',
name: 'GPT 4.1',
icon: '/openai.svg',
provider: 'openai',
requiredPlan: PLAN_TYPES.FREE,
},
{
id: 'claude-4-0-sonnet',
name: 'Claude 4.0 Sonnet',
icon: '/anthropic.svg',
provider: 'anthropic',
requiredPlan: PLAN_TYPES.PRO,
},
{
id: 'gemini-2-5-pro',
name: 'Gemini 2.5 Pro',
icon: '/google.svg',
provider: 'google',
requiredPlan: PLAN_TYPES.PRO,
},
]
// 文件编辑操作使用优化的小模型
const DEFAULT_MODELS = {
FILE_EDIT: 'gpt-4-1-mini',
FALLBACK: 'chat-model-reasoning-azure',
FILE_EDIT_FALLBACK: 'chat-model-reasoning-azure-mini',
} as const
export const selectModel = (
userPlan: string,
selectedModelId?: string,
isFileEdit = false
): string => {
let modelToUse = selectedModelId
// 文件编辑操作使用优化的小模型
if (isFileEdit) {
modelToUse = DEFAULT_MODELS.FILE_EDIT
} else if (!modelToUse) {
const defaultModel = getDefaultModelForPlan(userPlan)
modelToUse = defaultModel.id
} else {
// 严格的访问控制
if (!canAccessModel(userPlan, modelToUse)) {
const requestedModel = MODELS[modelToUse]
throw new Error(
`Access denied: ${requestedModel.name} requires ${requestedModel.requiredPlan} subscription`
)
}
}
return MODEL_MAPPING[modelToUse] || DEFAULT_MODELS.FALLBACK
}
export function canAccessModel(userPlan: string, modelId: string): boolean {
const model = MODELS[modelId]
if (!model) return false
const planHierarchy = ['free', 'basic', 'pro', 'enterprise']
const userPlanIndex = planHierarchy.indexOf(userPlan)
const requiredPlanIndex = planHierarchy.indexOf(model.requiredPlan)
return userPlanIndex >= requiredPlanIndex
}
9.2 配额与计费系统
实现基于用户计划的 AI 使用配额管理:
// packages/api/src/utils/quota.ts
export async function validateAIQuota(
organizationId: string,
quotaType: 'ai' | 'reasoning' = 'ai'
): Promise<void> {
const [quotaInfo, error] = await tryCatch(async () => {
return await getCombinedProjectQuota(organizationId)
})
if (error || !quotaInfo) {
log.ai('error', 'AI quota validation failed', {
organizationId, quotaType,
error: error instanceof Error ? error.message : String(error),
})
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to validate AI quota',
})
}
const { current, limit } = quotaInfo[quotaType]
if (current >= limit) {
log.ai('warn', 'AI quota exceeded', {
organizationId, quotaType, current, limit,
})
throw new TRPCError({
code: 'FORBIDDEN',
message: `${quotaType} quota exceeded. Current: ${current}, Limit: ${limit}`,
})
}
log.ai('info', 'AI quota validated', {
organizationId, quotaType, current, limit, remaining: limit - current,
})
}
export async function updateAIUsage(
organizationId: string,
usage: {
quotaType: 'ai' | 'reasoning'
tokensUsed: number
modelId: string
cost?: number
}
): Promise<void> {
const db = await getDbAsync()
await db.insert(aiUsage).values({
organizationId,
quotaType: usage.quotaType,
tokensUsed: usage.tokensUsed,
modelId: usage.modelId,
cost: usage.cost,
createdAt: new Date().toISOString(),
})
}
9.3 生成与流式响应
实现 AI 内容生成的完整流程,包括上下文构建、模型选择和流式响应:
// apps/web/ai/generate.ts
export const generateStreamResponse = async (
prompt: string,
projectId: string,
selectedItems: SelectedItem[] = [],
imageData?: ImageData | null,
abortSignal?: AbortSignal,
config: GenerationConfig = { isFileEdit: false, quotaType: 'ai' }
) => {
const [result, error] = await tryCatch(async () => {
validateAbortSignal(abortSignal)
// 获取项目数据和用户信息
const projectData = await fetchProjectData(projectId)
// 验证配额
await validateQuota(projectData.organizationId, config.quotaType)
// 构建生成上下文
const context = await buildGenerationContext(projectData, config)
// 选择模型
const selectedModel = selectModel(
context.userPlan,
config.modelId,
config.isFileEdit
)
// 构建消息历史
const messages = await buildMessageHistory(
prompt,
selectedItems,
imageData,
context
)
// 生成响应
const streamResult = streamText({
model: myProvider.languageModel(selectedModel),
system: systemPromptText,
messages,
abortSignal,
providerOptions: buildProviderOptions(config),
})
return streamResult.textStream
})
if (error) {
const operation = config.isFileEdit ? 'streamGenerateAppForFileEdit' : 'streamGenerateApp'
console.error(`Error in ${operation}:`, error)
throw error
}
return result
}
9.4 提示词工程
实现结构化的提示词管理系统:
// apps/web/ai/prompts/system-prompt.ts
export const buildSystemPrompt = (context: GenerationContext): string => {
const {
projectFiles,
selectedItems,
projectName,
templateType,
hasReasoningEnabled,
capabilities,
} = context
return `
<libra_info>
你是 Libra,一个专业的全栈开发 AI 助手。你可以查看、创建和编辑文件,在浏览器中运行代码,并为用户构建完整的 Web 应用程序。
<capabilities>
${capabilities.map(cap => `- ${cap}`).join('\n')}
</capabilities>
<project_context>
项目名称: ${projectName}
模板类型: ${templateType}
推理模式: ${hasReasoningEnabled ? '启用' : '禁用'}
</project_context>
${projectFiles ? `
<current_files>
${Object.entries(projectFiles)
.map(([path, content]) => `
<file path="${path}">
${content}
</file>
`).join('')}
</current_files>
` : ''}
${selectedItems.length > 0 ? `
<selected_context>
用户选择了以下内容进行重点关注:
${selectedItems.map(item => `
<selected_item type="${item.type}" file="${item.file}">
${item.content}
</selected_item>
`).join('')}
</selected_context>
` : ''}
</libra_info>
请根据用户的请求提供准确、实用的帮助。当创建或修改文件时,请确保代码质量和最佳实践。
`
}
9.5 沙盒执行环境
项目集成 E2B 提供安全的代码执行环境,支持在 Docker 容器中运行用户代码:
容器配置
// packages/sandbox/src/config/index.ts
import { SandboxConfig } from '../types'
export const DEFAULT_SANDBOX_CONFIGS: Record<string, Partial<SandboxConfig>> = {
'vite-shadcn-template-libra': {
template: 'vite-shadcn-template-libra',
timeoutMs: 300000, // 5 分钟
resources: {
memory: 1024, // MB
cpu: 2,
disk: 2048, // MB
},
network: {
enabled: true,
allowedDomains: ['*.npmjs.org', '*.unpkg.com', '*.jsdelivr.net'],
blockedPorts: [22, 3306, 5432],
},
},
'vite-shadcn-template-builder-libra': {
template: 'vite-shadcn-template-builder-libra',
timeoutMs: 300000, // 5 分钟
resources: {
memory: 2048, // MB
cpu: 2,
disk: 4096, // MB
},
network: {
enabled: true,
allowedDomains: ['*.npmjs.org', '*.unpkg.com', '*.jsdelivr.net', '*.cloudflare.com'],
blockedPorts: [22, 3306, 5432],
},
},
}
// 创建沙盒实例
export async function createSandbox(config: SandboxConfig) {
const factory = getSandboxFactory()
return factory.createSandbox(config)
}
Docker 模板配置
# e2b/Dockerfile.vite
FROM oven/bun:slim
WORKDIR /app
# 安装依赖
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# 复制项目文件
COPY . .
# 暴露端口
EXPOSE 5173
# 启动命令
CMD ["bun", "run", "dev", "--host"]
配置文件 (e2b.toml)
# apps/web/e2b.toml
[template]
id = "vite-react-template"
title = "Vite React Template"
description = "React + TypeScript + Vite development environment"
[template.dockerfile]
path = "./Dockerfile.vite"
[template.resources]
memory = 512
cpus = 1
disk = 1024 # MB
沙盒操作 API
// lib/e2b/operations.ts
export class SandboxOperations {
private sandbox: E2B.Sandbox
constructor(sandbox: E2B.Sandbox) {
this.sandbox = sandbox
}
// 执行命令
async exec(command: string) {
const result = await this.sandbox.exec(command)
return {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
}
}
// 写入文件
async writeFile(path: string, content: string) {
await this.sandbox.filesystem.write(path, content)
}
// 读取文件
async readFile(path: string): Promise<string> {
return await this.sandbox.filesystem.read(path)
}
// 获取预览 URL
async getPreviewUrl(): Promise<string> {
const ports = await this.sandbox.ports.list()
const previewPort = ports.find(p => p.port === 5173 || p.port === 3000)
if (!previewPort) {
throw new Error('No preview port found')
}
return `https://${this.sandbox.id}-${previewPort.port}.e2b.dev`
}
// 清理资源
async cleanup() {
await this.sandbox.close()
}
}
安全最佳实践
// lib/e2b/security.ts
export const sandboxSecurityConfig = {
// 禁止的命令
blockedCommands: [
'rm -rf /',
'dd if=/dev/zero',
'fork bomb',
':(){ :|:& };:',
],
// 文件系统限制
fsLimits: {
maxFileSize: 10 * 1024 * 1024, // 10MB
maxFiles: 1000,
allowedExtensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.html', '.json'],
},
// 网络限制
networkPolicy: {
allowedDomains: ['*.npmjs.org', '*.unpkg.com', '*.jsdelivr.net'],
blockedPorts: [22, 3306, 5432],
},
}
// 命令验证
export function validateCommand(command: string): boolean {
return !sandboxSecurityConfig.blockedCommands.some(blocked =>
command.includes(blocked)
)
}
9. 状态管理模式
9.1 客户端状态管理
项目使用 Zustand 管理客户端状态,提供轻量级、类型安全的状态管理:
// lib/stores/project-store.ts
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface ProjectState {
// 当前项目状态
currentProject: Project | null
selectedFiles: string[]
isPreviewOpen: boolean
isSidebarCollapsed: boolean
// Actions
setCurrentProject: (project: Project | null) => void
toggleFile: (filePath: string) => void
togglePreview: () => void
toggleSidebar: () => void
// Computed values
hasSelectedFiles: () => boolean
}
export const useProjectStore = create<ProjectState>()(
devtools(
(set, get) => ({
// Initial state
currentProject: null,
selectedFiles: [],
isPreviewOpen: false,
isSidebarCollapsed: false,
// Actions
setCurrentProject: (project) =>
set({ currentProject: project }, false, 'setCurrentProject'),
toggleFile: (filePath) =>
set((state) => ({
selectedFiles: state.selectedFiles.includes(filePath)
? state.selectedFiles.filter(f => f !== filePath)
: [...state.selectedFiles, filePath]
}), false, 'toggleFile'),
togglePreview: () =>
set((state) => ({ isPreviewOpen: !state.isPreviewOpen }), false, 'togglePreview'),
toggleSidebar: () =>
set((state) => ({ isSidebarCollapsed: !state.isSidebarCollapsed }), false, 'toggleSidebar'),
// Computed values
hasSelectedFiles: () => get().selectedFiles.length > 0,
}),
{ name: 'project-store' }
)
)
9.2 服务端状态管理
使用 TanStack Query (React Query) 管理服务端状态和缓存:
// lib/query-client.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 分钟
gcTime: 1000 * 60 * 30, // 30 分钟
retry: (failureCount, error) => {
// 不重试认证错误
if (error?.message?.includes('UNAUTHORIZED')) return false
return failureCount < 3
},
},
mutations: {
retry: false,
onError: (error) => {
// 全局错误处理
console.error('Mutation error:', error)
},
},
},
})
// hooks/use-projects.ts
export function useProjects() {
return api.project.list.useQuery(undefined, {
select: (data) => data.sort((a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
),
})
}
export function useCreateProject() {
const utils = api.useUtils()
return api.project.create.useMutation({
onSuccess: (newProject) => {
// 更新项目列表缓存
utils.project.list.setData(undefined, (old) =>
old ? [newProject, ...old] : [newProject]
)
// 预取新项目详情
utils.project.getById.setData({ id: newProject.id }, newProject)
},
onError: (error) => {
console.error('Failed to create project:', error)
},
})
}
9.3 缓存策略
智能缓存失效
// utils/cache.ts
export function invalidateProjectCache(projectId: string) {
const utils = api.useUtils()
// 失效相关查询
utils.project.getById.invalidate({ id: projectId })
utils.project.list.invalidate()
utils.file.getProjectFiles.invalidate({ projectId })
}
export function updateProjectCache(projectId: string, updates: Partial<Project>) {
const utils = api.useUtils()
// 乐观更新
utils.project.getById.setData({ id: projectId }, (old) =>
old ? { ...old, ...updates } : undefined
)
utils.project.list.setData(undefined, (old) =>
old?.map(p => p.id === projectId ? { ...p, ...updates } : p)
)
}
10. 邮件系统
10.1 React Email 集成
项目使用 React Email 和 Resend 构建现代化邮件系统:
// packages/email/index.ts
import { Resend } from 'resend';
import {env} from "./env.mjs";
export const resend = new Resend(env.RESEND_API_KEY);
export * from './utils/email'
// packages/email/env.mjs
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const env = createEnv({
server: {
RESEND_FROM: z.string().min(1).email(),
RESEND_API_KEY: z.string().min(1),
},
runtimeEnv: {
RESEND_FROM: process.env['RESEND_FROM'],
RESEND_API_KEY: process.env['RESEND_API_KEY'],
},
})
10.2 邮件模板系统
项目提供完整的邮件模板库,使用 React 组件构建,支持 Tailwind CSS 样式:
// packages/email/templates/emails/index.tsx
// Export all email templates for easier imports
export { CancellationEmailTemplate } from './cancellation-email'
export { ContactTemplate } from './contact'
export { EmailVerificationTemplate } from './email-verification'
export { OrganizationInvitationTemplate } from './organization-invitation'
export { SignInTemplate } from './sign-in'
export { WelcomeEmailTemplate } from './welcomeEmail'
// Template subject mapping
export const emailSubjects = {
'sign-in': 'Libra - Sign-in Verification Code',
'email-verification': 'Libra - Email Verification Code',
welcome: 'Welcome to Libra!',
cancellation: 'Libra Subscription Cancelled',
'organization-invitation': 'Invitation to join a team on Libra',
}
邮件模板组件示例:
// packages/email/templates/emails/welcomeEmail.tsx
import {
Heading,
Text,
Section,
Link,
} from '@react-email/components';
import type * as React from 'react';
import {
Button,
ContentSection,
EmailContainer,
InfoBox
} from '../../components';
interface WelcomeEmailTemplateProps {
planName: string;
}
export const WelcomeEmailTemplate: React.FC<WelcomeEmailTemplateProps> = ({
planName
}) => {
return (
<EmailContainer
title={`Welcome to the ${planName} Plan`}
previewText={`Welcome to the ${planName} Plan!`}
>
<ContentSection>
<Heading as="h2" className="mt-0 text-gray-800 text-xl font-semibold">
Welcome to the {planName} Plan!
</Heading>
<Text className="mb-6 text-gray-600 leading-relaxed">
Thank you for subscribing to the {planName} Plan! We're excited to have you on board and can't wait to see what you'll accomplish with Libra.
</Text>
{/* Plan Benefits */}
<InfoBox title={`Your ${planName} Plan Benefits`} variant="info">
<ul className="text-gray-600 m-0 list-disc pl-5">
<li className="mb-2">Access to all premium features</li>
<li className="mb-2">Priority customer support</li>
<li className="mb-2">Advanced analytics and reporting</li>
<li className="m-0">Unlimited team members</li>
</ul>
</InfoBox>
<Text className="text-gray-600 mb-6 leading-relaxed">
Your subscription is now active. Here's how to get started:
</Text>
{/* Call to Action Button */}
<Section className="text-center mb-7">
<Button href="#" variant="primary">
Start Using Libra Now
</Button>
</Section>
{/* Help Center */}
<Text className="text-gray-600 mb-2 leading-relaxed">
Need help? Check out our {" "}
<Link href="#" className="text-brand no-underline hover:underline">
Help Center
</Link>{" "}
or contact our support team.
</Text>
</ContentSection>
</EmailContainer>
);
};
登录验证邮件示例:
// packages/email/templates/emails/sign-in.tsx
import {
Heading,
Text,
Section,
} from '@react-email/components';
import type * as React from 'react';
import {
Button,
ContentSection,
EmailContainer,
OtpCode
} from '../../components';
interface SignInTemplateProps {
otp: string;
}
export const SignInTemplate: React.FC<SignInTemplateProps> = ({ otp }) => {
return (
<EmailContainer
title="Libra Sign-in Verification"
previewText={`Libra verification code: ${otp}`}
>
<ContentSection>
<Heading as="h2" className="mt-0 text-gray-800 text-xl font-semibold">
Verify Your Sign-in
</Heading>
<Text className="mb-6 text-gray-600 leading-relaxed">
Hello! We received a request to sign in to your Libra account. Please use the following verification code to complete the sign-in process:
</Text>
{/* 使用 OtpCode 组件显示验证码 */}
<OtpCode code={otp} />
<Text className="text-gray-600 mb-2 leading-relaxed">
This verification code will expire in <strong>10 minutes</strong>.
</Text>
<Section className="text-center mb-7">
<Button href="#" variant="primary">
Visit Libra
</Button>
</Section>
<Section className="border-t border-gray-200 pt-5 mt-2">
<Text className="text-sm text-gray-500 mb-0 leading-relaxed">
<strong>Security Tip:</strong> Libra will never ask for your password or financial information via email.
</Text>
</Section>
</ContentSection>
</EmailContainer>
);
};
10.3 邮件发送工具
// packages/email/utils/email.ts
export async function sendWelcomeEmail(userEmail: string, planName: string) {
try {
await resend.emails.send({
from: env.RESEND_FROM,
to: [userEmail],
subject: `Welcome to the Libra ${planName} Plan`,
react: React.createElement(WelcomeEmailTemplate, { planName }) as any,
})
} catch (error) {
throw error
}
}
export async function sendOrganizationInvitation(options: {
email: string
invitedByUsername: string
teamName: string
inviteLink: string
}) {
const { email, invitedByUsername, teamName, inviteLink } = options
await resend.emails.send({
from: env.RESEND_FROM,
to: [email],
subject: 'Invitation to join a team on Libra',
react: React.createElement(OrganizationInvitationTemplate, {
invitedByUsername,
teamName,
inviteLink
}) as any,
})
}
11. 错误处理与日志
11.1 统一错误处理
项目实现了完整的错误处理体系,包含 tryCatch 模式和结构化错误响应:
// packages/common/src/error.ts
type Success<T> = readonly [T, null]
type Failure<E> = readonly [null, E]
type ResultSync<T, E> = Success<T> | Failure<E>
type ResultAsync<T, E> = Promise<ResultSync<T, E>>
type Operation<T> = Promise<T> | (() => T) | (() => Promise<T>)
export function tryCatch<T, E = Error>(operation: Promise<T>): ResultAsync<T, E>
export function tryCatch<T, E = Error>(operation: () => Promise<T>): ResultAsync<T, E>
export function tryCatch<T, E = Error>(operation: () => T): ResultSync<T, E>
export function tryCatch<T, E = Error>(
operation: Operation<T>
): ResultSync<T, E> | ResultAsync<T, E> {
if (operation instanceof Promise) {
return operation
.then((data: T) => [data, null] as const)
.catch((error: E) => [null, error as E] as const)
}
try {
const result = operation()
if (result instanceof Promise) {
return result
.then((data: T) => [data, null] as const)
.catch((error: E) => [null, error as E] as const)
}
return [result, null] as const
} catch (error) {
return [null, error as E] as const
}
}
// 使用示例
const [data, error] = await tryCatch(async () => {
return await fetchUserData(userId)
})
if (error) {
log.auth('error', 'Failed to fetch user data', { userId })
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR' })
}
return data
11.2 结构化日志记录
实现组件级别的结构化日志系统:
// packages/common/src/logger.ts
interface LogContext {
// 用户上下文
userId?: string
orgId?: string
sessionId?: string
// 业务上下文
projectId?: string
operation?: string
resource?: string
// 技术上下文
duration?: number
requestId?: string
userAgent?: string
// 错误上下文
error?: string
stackTrace?: string
[key: string]: unknown
}
export const log = {
auth: (level: 'info' | 'warn' | 'error', message: string, context?: LogContext) => {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level,
component: 'auth',
message,
...context,
}))
},
project: (level: 'info' | 'warn' | 'error', message: string, context?: LogContext) => {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level,
component: 'project',
message,
...context,
}))
},
ai: (level: 'info' | 'warn' | 'error', message: string, context?: LogContext) => {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level,
component: 'ai',
message,
...context,
}))
},
}
11.3 错误监控
前端错误边界
// components/error-boundary.tsx
'use client'
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary'
import { Button } from '@libra/ui'
function ErrorFallback({ error, resetErrorBoundary }: {
error: Error
resetErrorBoundary: () => void
}) {
return (
<div className="flex flex-col items-center justify-center min-h-[400px] p-8">
<h2 className="text-2xl font-bold text-destructive mb-4">出现错误</h2>
<p className="text-muted-foreground mb-6 text-center max-w-md">
抱歉,应用程序遇到了意外错误。请尝试刷新页面或联系支持团队。
</p>
<div className="flex gap-4">
<Button onClick={resetErrorBoundary}>重试</Button>
<Button variant="outline" onClick={() => window.location.reload()}>
刷新页面
</Button>
</div>
{process.env.NODE_ENV === 'development' && (
<details className="mt-8 w-full max-w-2xl">
<summary className="cursor-pointer text-sm font-medium">
错误详情 (开发模式)
</summary>
<pre className="mt-4 p-4 bg-muted rounded text-xs overflow-auto">
{error.stack}
</pre>
</details>
)}
</div>
)
}
export function ErrorBoundary({ children }: { children: React.ReactNode }) {
return (
<ReactErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
// 发送错误到监控服务
console.error('Error caught by boundary:', error, errorInfo)
}}
>
{children}
</ReactErrorBoundary>
)
}
12. 国际化实现
12.1 Paraglide.js 集成
项目使用 Paraglide.js 实现类型安全的国际化:
Paraglide 配置
// apps/web/project.inlang/settings.json
{
"$schema": "https://inlang.com/schema/project-settings",
"baseLocale": "en",
"locales": [
"en",
"zh"
],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
],
"plugin.inlang.messageFormat": {
"pathPattern": "./messages/{locale}.json"
}
}
语言切换实现
// apps/web/components/language-switcher.tsx
"use client";
import { useState, useEffect } from "react";
import { Globe } from "lucide-react";
import { Button } from "@libra/ui/components/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@libra/ui/components/dropdown-menu";
import { getLocale, setLocale, type Locale } from "../paraglide/runtime";
import * as m from "@/paraglide/messages";
const getLanguages = (isHydrated: boolean) => [
{ code: "en" as Locale, name: isHydrated ? m["common.languages.english"]() : "English" },
{ code: "zh" as Locale, name: isHydrated ? m["common.languages.chinese"]() : "中文" },
];
export function LanguageSwitcher() {
const [isHydrated, setIsHydrated] = useState(false);
const currentLocale = getLocale();
useEffect(() => {
setIsHydrated(true);
}, []);
const handleLanguageChange = (locale: Locale) => {
setLocale(locale);
};
const languages = getLanguages(isHydrated);
const currentLanguage = languages.find(lang => lang.code === currentLocale);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="gap-2">
<Globe className="h-4 w-4" />
<span>{currentLanguage?.name}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{languages.map((language) => (
<DropdownMenuItem
key={language.code}
onClick={() => handleLanguageChange(language.code)}
className={currentLocale === language.code ? "bg-accent" : ""}
>
{language.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
Next.js 中间件配置
// apps/web/middleware.ts
import { paraglideMiddleware } from '@/paraglide/server'
import { type NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
return paraglideMiddleware(request, ({ request, locale }) => {
request.headers.set('x-paraglide-locale', locale)
request.headers.set('x-paraglide-request-url', request.url)
return NextResponse.rewrite(request.url, request)
})
}
export const config = {
matcher: [
// Match all request paths except for the ones starting with:
// - api (API routes)
// - _next/static (static files)
// - _next/image (image optimization files)
// - favicon.ico (favicon file)
'/((?!api|monitoring|_next/static|_next/image|favicon.ico|.*\\.svg$|.*\\.json$|sentry-example-page).*)',
],
}
12.2 多语言内容管理
消息文件组织和管理:
// messages/en.json
{
"auth": {
"login": "Sign In",
"logout": "Sign Out",
"email": "Email address",
"password": "Password",
"forgotPassword": "Forgot password?",
"createAccount": "Create an account"
},
"project": {
"create": "Create Project",
"name": "Project Name",
"description": "Description",
"visibility": {
"public": "Public",
"private": "Private"
}
},
"ai": {
"generating": "Generating...",
"modelSelect": "Select Model",
"quotaExceeded": "AI quota exceeded",
"reasoning": "Reasoning Mode"
}
}
// messages/zh.json
{
"auth": {
"login": "登录",
"logout": "退出登录",
"email": "邮箱地址",
"password": "密码",
"forgotPassword": "忘记密码?",
"createAccount": "创建账户"
},
"project": {
"create": "创建项目",
"name": "项目名称",
"description": "描述",
"visibility": {
"public": "公开",
"private": "私有"
}
},
"ai": {
"generating": "生成中...",
"modelSelect": "选择模型",
"quotaExceeded": "AI 配额已用尽",
"reasoning": "推理模式"
}
}
12.3 本地化最佳实践
组件中使用国际化
// components/project/create-project-dialog.tsx
import * as m from '@/paraglide/messages'
import { Link } from '@/lib/i18n'
export function CreateProjectDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>{m.project_create()}</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{m.project_create()}</DialogTitle>
</DialogHeader>
<form>
<div className="space-y-4">
<div>
<Label htmlFor="name">{m.project_name()}</Label>
<Input id="name" placeholder={m.project_name()} />
</div>
<div>
<Label htmlFor="description">{m.project_description()}</Label>
<Textarea id="description" placeholder={m.project_description()} />
</div>
</div>
</form>
</DialogContent>
</Dialog>
)
}
// 语言切换组件
export function LanguageSwitcher() {
const pathname = usePathname()
const currentLocale = getCurrentLocale()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<Globe className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{availableLanguageTags.map(locale => (
<DropdownMenuItem key={locale} asChild>
<Link
href={pathname}
locale={locale}
className={cn(
"w-full",
currentLocale === locale && "font-semibold"
)}
>
{locale === 'en' ? 'English' : '中文'}
</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
动态内容处理
// 带参数的消息
// messages/en.json
{
"welcome": "Welcome {name}!",
"items_count": "{count, plural, =0 {no items} one {# item} other {# items}}"
}
// 使用
const welcomeMessage = m.welcome({ name: user.name })
const itemsMessage = m.items_count({ count: items.length })
开发工作流
# 添加新的翻译字符串
1. 编辑 messages/en.json 添加新键
2. 运行 paraglide-js compile 生成类型
3. 使用机器翻译初始化其他语言
bun run translate
# 检查翻译完整性
bun run paraglide-js validate
13. GitHub 集成
13.1 GitHub App 配置
项目使用 GitHub App 模式实现与 GitHub 的深度集成:
// apps/web/app/api/github/setup/route.ts
import { App } from '@octokit/app'
const app = new App({
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n'),
})
// 获取安装详情
const installation = await app.octokit.request('GET /app/installations/{installation_id}', {
installation_id: Number.parseInt(installationId, 10),
})
13.2 双重认证架构
项目支持两种 GitHub 认证方式:
GitHub App 安装认证
- 用途:组织级别的仓库管理
- 权限:读写仓库、管理 Issues、Webhooks
- 令牌类型:Installation Access Token(1小时有效期)
OAuth 用户认证
- 用途:个人账户的仓库创建
- 权限:用户授权的个人仓库访问
- 令牌类型:User Access Token(可刷新)
// packages/api/src/utils/github-auth.ts
export async function getGitHubAuthToken(db: any, organizationId: string) {
const installation = await db.query.githubInstallation.findFirst({
where: eq(githubInstallation.organizationId, organizationId),
})
if (installation) {
if (installation.githubAccountType === 'User') {
// 使用用户令牌
const userToken = await db.query.githubUserToken.findFirst({
where: eq(githubUserToken.organizationId, organizationId),
})
return { token: userToken.accessToken, type: 'user' }
}
// 使用安装令牌
const installationToken = await generateInstallationToken(installation.installationId)
return { token: installationToken, type: 'installation' }
}
}
13.3 仓库管理功能
// packages/api/src/router/github.ts
export const githubRouter = {
// 创建项目仓库
createProjectRepository: organizationProcedure
.input(z.object({
projectId: z.string().min(1),
description: z.string().optional(),
private: z.boolean().default(true),
}))
.mutation(async ({ ctx, input }) => {
const authResult = await getGitHubAuthToken(ctx.db, ctx.orgId)
// 生成唯一仓库名
const now = new Date()
const dateString = now.toISOString().slice(0, 10).replace(/-/g, '')
const repoName = `${baseRepoName}-${dateString}`
// 创建仓库
const createRepoResponse = await fetch(apiUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${authResult.token}`,
Accept: 'application/vnd.github.v3+json',
},
body: JSON.stringify({
name: repoName,
description: input.description,
private: input.private,
auto_init: true,
gitignore_template: 'Node',
license_template: 'mit',
}),
})
}),
}
14. 开发工具与工作流
13.1 Bun 包管理器使用规范
项目使用 Bun v1.2.19 作为包管理器和 JavaScript 运行时:
Bun 优势
- 极速安装:
- 内置 TypeScript:无需额外配置直接运行 TS 文件
- 原生 ESM 支持:完全支持 ES 模块
- 工作区支持:Monorepo 的完美支持
- 内置工具:集成了测试、打包等工具
安装 Bun
# macOS/Linux
curl -fsSL https://bun.sh/install | bash
# Windows (WSL)
curl -fsSL https://bun.sh/install | bash
# 验证安装
bun --version
常用命令
# 安装依赖
bun install # 等同于 npm install
bun i # 简写
# 添加依赖
bun add react # 等同于 npm install react
bun add -d vitest # 开发依赖
# 移除依赖
bun remove react # 等同于 npm uninstall
# 运行脚本
bun run dev # 等同于 npm run dev
bun dev # 简写(如果没有同名文件)
# 执行文件
bun run index.ts # 直接运行 TypeScript
# 工作区命令
bun --filter @libra/api dev # 运行特定工作区
Bun 配置文件
# bunfig.toml
[install]
# 使用硬链接加速
strategy = "hardlink"
# 自动安装 peer dependencies
auto = "auto"
# 禁用遗留模式
legacy = false
[install.scopes]
# 自定义注册表
"@company" = { token = "$npm_token", url = "https://registry.company.com/" }
[test]
# 测试配置
preload = ["./test/setup.ts"]
coverage = true
工作区配置
// package.json
{
"name": "libra",
"private": true,
"workspaces": [
"apps/*",
"packages/*",
"tooling/*",
"scripts"
],
"scripts": {
"dev": "turbo dev --parallel",
"build": "turbo build",
"test": "turbo test"
}
}
Bun 特有功能
// 使用 Bun 内置 API
import { $ } from 'bun'
// Shell 命令执行
const result = await $`ls -la`.text()
// 文件操作
const file = Bun.file('./config.json')
const content = await file.json()
// HTTP 服务器
Bun.serve({
port: 3000,
fetch(req) {
return new Response('Hello from Bun!')
},
})
// 环境变量
const apiKey = Bun.env.API_KEY
迁移指南
# 从 npm/yarn 迁移
1. 删除 node_modules 和 lock 文件
rm -rf node_modules package-lock.json yarn.lock
2. 使用 Bun 安装
bun install
3. 更新 CI/CD 配置
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
13.2 开发环境配置
环境变量与 Secrets 管理
// apps/web/env.mjs - 使用 @t3-oss/env-nextjs 进行类型安全的环境变量验证
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const env = createEnv({
server: {
// 数据库(必需)
POSTGRES_URL: z.string().min(1),
// AI 提供商(可选)
ANTHROPIC_API_KEY: z.string().optional(),
AZURE_OPENAI_API_KEY: z.string().optional(),
XAI_API_KEY: z.string().optional(),
OPENROUTER_API_KEY: z.string().optional(),
// 认证(必需)
BETTER_GITHUB_CLIENT_ID: z.string().optional(),
BETTER_GITHUB_CLIENT_SECRET: z.string().optional(),
GITHUB_CLIENT_ID: z.string().optional(),
GITHUB_CLIENT_SECRET: z.string().optional(),
// 支付(必需)
STRIPE_SECRET_KEY: z.string().min(1),
STRIPE_WEBHOOK_SECRET: z.string().min(1),
// Cloudflare(必需)
CLOUDFLARE_API_TOKEN: z.string().min(1),
DATABASE_ID: z.string().min(1),
// 功能标志(布尔值)
ENHANCED_PROMPT: z.boolean().optional(),
REASONING_ENABLED: z.boolean().optional(),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_TURNSTILE_SITE_KEY: z.string().min(1),
NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
},
runtimeEnv: {
// 服务端变量
POSTGRES_URL: process.env.POSTGRES_URL,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
// 布尔值需要特殊处理
ENHANCED_PROMPT: process.env['ENHANCED_PROMPT'] === 'TRUE',
REASONING_ENABLED: process.env['REASONING_ENABLED'] === 'TRUE',
// ... 其他变量根据实际需要映射
// 客户端变量
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
},
})
// 模块化的环境变量验证
// packages/auth/env.mjs - 认证相关
// packages/db/env.mjs - 数据库相关
13.3 代码质量工具
Biome 配置
// biome.json
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"include": ["**/*.{js,jsx,ts,tsx,json,jsonc}"],
"ignore": [
"node_modules/**",
".next/**",
"dist/**",
"build/**",
"coverage/**"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"recommended": true
},
"complexity": {
"recommended": true,
"noExcessiveCognitiveComplexity": "error"
},
"correctness": {
"recommended": true,
"noUnusedVariables": "error"
},
"suspicious": {
"recommended": true,
"noExplicitAny": "warn",
"noConsole": "warn"
},
"style": {
"recommended": true,
"useImportType": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "es5"
}
},
"typescript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "es5"
}
}
}
13.4 构建与部署
Turborepo 配置
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": [
"$TURBO_DEFAULT$",
".env",
".env.local",
".env.production"
],
"outputs": [
".next/**",
"!.next/cache/**",
"dist/**"
]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^topo"],
"inputs": [
"$TURBO_DEFAULT$",
".eslintrc*",
"biome.json"
]
},
"typecheck": {
"dependsOn": ["^topo"],
"inputs": [
"$TURBO_DEFAULT$",
"tsconfig*.json"
],
"outputs": [
"node_modules/.cache/tsbuildinfo.json"
]
},
"test": {
"dependsOn": ["^build"],
"inputs": [
"$TURBO_DEFAULT$",
"vitest.config.*"
],
"outputs": [
"coverage/**"
]
}
}
}
部署脚本
// package.json
{
"scripts": {
"dev": "turbo dev --parallel",
"dev:web": "turbo dev --parallel --filter !stripe",
"build": "turbo build --concurrency=100%",
"lint": "turbo lint --continue --",
"lint:fix": "turbo lint --continue -- --apply",
"typecheck": "turbo typecheck",
"test": "turbo test",
"clean": "turbo clean && rm -rf node_modules/.cache",
"clean:workspaces": "turbo clean",
"deploy": "cd apps/web && opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"migration:generate": "cd packages/db && drizzle-kit generate",
"migration:local": "cd packages/db && drizzle-kit migrate"
}
}
13.5 Cloudflare 部署配置
项目使用 @opennextjs/cloudflare 实现 Next.js 到 Cloudflare 的完整部署:
OpenNext 配置
// apps/web/open-next.config.ts
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
import { withRegionalCache } from "@opennextjs/cloudflare/overrides/incremental-cache/regional-cache";
import doShardedTagCache from "@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache";
import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue";
export default defineCloudflareConfig({
incrementalCache: withRegionalCache(r2IncrementalCache, { mode: "short-lived" }),
queue: doQueue,
// This is only required if you use On-demand revalidation
tagCache: doShardedTagCache({ baseShardSize: 12 }),
// Disable this if you want to use PPR
enableCacheInterception: true,
});
Wrangler 配置
// wrangler.jsonc
{
"name": "libra",
"main": ".open-next/worker.js",
"compatibility_date": "2025-07-17",
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"minify": true,
// 静态资源配置
"assets": {
"binding": "ASSETS",
"directory": ".open-next/assets"
},
// 自定义域名路由
"routes": [
{
"pattern": "libra.dev",
"custom_domain": true
}
],
// 智能部署(自动选择最佳边缘位置)
"placement": { "mode": "smart" },
// D1 数据库
"d1_databases": [
{
"binding": "DB",
"database_name": "libra-auth",
"database_id": "your-d1-database-id"
}
],
// Hyperdrive 配置
"hyperdrive": {
"binding": "HYPERDRIVE",
"id": "your-hyperdrive-config-id"
},
// 环境变量
"vars": {
"ENVIRONMENT": "production"
},
// 机密管理
"secrets": [
"DATABASE_URL",
"ANTHROPIC_API_KEY",
"STRIPE_SECRET_KEY"
]
}
部署流程
# 1. 构建应用
bun run build
# 2. 使用 OpenNext 构建
cd apps/web
bun run deploy
CI/CD 配置
# .github/workflows/deploy.yml
name: Deploy to Cloudflare
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build application
run: bun run build
- name: Deploy to Cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
cd apps/web
bunx @opennextjs/cloudflare@latest deploy
环境变量管理
# 设置机密
wrangler secret put DATABASE_URL
wrangler secret put ANTHROPIC_API_KEY
wrangler secret put STRIPE_SECRET_KEY
# 查看机密列表
wrangler secret list
# 删除机密
wrangler secret delete API_KEY
自定义域名配置
// lib/custom-domain.ts
export async function setupCustomDomain(domain: string) {
// 创建 Cloudflare Workers 路由
const route = {
pattern: `${domain}/*`,
zone_name: domain,
script_name: 'libra-web',
}
// 设置 DNS 记录
const dnsRecords = [
{
type: 'A',
name: '@',
content: '192.0.2.1', // Cloudflare 代理 IP
proxied: true,
},
{
type: 'AAAA',
name: '@',
content: '100::', // Cloudflare 代理 IPv6
proxied: true,
},
]
return { route, dnsRecords }
}
性能优化配置
// apps/web/next.config.mjs
import { paraglideWebpackPlugin } from "@inlang/paraglide-js";
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
// 启用 Cloudflare 开发环境支持
initOpenNextCloudflareForDev();
// Bundle Analyzer 配置
const withBundleAnalyzer = (await import('@next/bundle-analyzer')).default({
enabled: process.env.ANALYZE === 'true',
openAnalyzer: true,
});
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// 包转译配置
transpilePackages: [
"@libra/ui",
"@libra/auth",
"@libra/db",
"@libra/api",
"@libra/common",
"@libra/better-auth-cloudflare",
"@libra/email",
"@libra/better-auth-stripe",
"@libra/shikicode",
"@libra/sandbox"
],
pageExtensions: ['ts', 'tsx'],
// 实验性功能
experimental: {
reactCompiler: true,
useCache: true,
// ppr: true, // 可选启用 PPR
},
// 图像配置 - 使用自定义 loader
images: {
loader: 'custom',
loaderFile: './imageLoader.ts',
remotePatterns: [
{
protocol: 'http',
hostname: 'localhost',
port: '3004',
pathname: '/image/**',
},
{
protocol: 'https',
hostname: 'cdn.libra.dev',
pathname: '/image/**',
}
],
},
// Webpack 配置
webpack: (config) => {
config.plugins.push(
paraglideWebpackPlugin({
outdir: "./paraglide",
project: "./project.inlang",
strategy: ["cookie", "baseLocale"],
experimentalMiddlewareLocaleSplitting: false,
// Cookie 域名配置
cookieDomain: process.env.NODE_ENV === 'production' ? '.libra.dev' : 'localhost'
})
);
return config;
},
// 服务端外部包
serverExternalPackages: [
"@prisma/client",
".prisma/client",
"postgres",
"@libsql/isomorphic-ws",
"jose"
],
}
export default withBundleAnalyzer(nextConfig)
13.6 GitHub API 集成
项目使用 Octokit 实现 GitHub API 集成,支持代码提交、仓库管理和 OAuth 认证:
GitHub 客户端配置
// lib/github/client.ts
import { Octokit } from '@octokit/rest'
import { createAppAuth } from '@octokit/auth-app'
export function createGitHubClient(accessToken: string) {
return new Octokit({
auth: accessToken,
// 使用 GitHub API v3
baseUrl: 'https://api.github.com',
// 请求超时设置
request: {
timeout: 30000,
},
})
}
// GitHub App 认证
export function createGitHubAppClient() {
return new Octokit({
authStrategy: createAppAuth,
auth: {
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
installationId: process.env.GITHUB_APP_INSTALLATION_ID,
},
})
}
仓库操作
// lib/github/repository.ts
export class GitHubRepository {
private octokit: Octokit
private owner: string
private repo: string
constructor(octokit: Octokit, owner: string, repo: string) {
this.octokit = octokit
this.owner = owner
this.repo = repo
}
// 创建仓库
async create(options: {
name: string
description?: string
private?: boolean
autoInit?: boolean
}) {
const { data } = await this.octokit.repos.createForAuthenticatedUser({
name: options.name,
description: options.description,
private: options.private ?? true,
auto_init: options.autoInit ?? true,
default_branch: 'main',
})
return data
}
// 提交文件
async commitFiles(files: Array<{ path: string; content: string }>, message: string) {
// 获取当前分支
const { data: ref } = await this.octokit.git.getRef({
owner: this.owner,
repo: this.repo,
ref: 'heads/main',
})
const currentCommitSha = ref.object.sha
// 获取当前树
const { data: currentCommit } = await this.octokit.git.getCommit({
owner: this.owner,
repo: this.repo,
commit_sha: currentCommitSha,
})
// 创建 blobs
const blobs = await Promise.all(
files.map(async (file) => {
const { data } = await this.octokit.git.createBlob({
owner: this.owner,
repo: this.repo,
content: Buffer.from(file.content).toString('base64'),
encoding: 'base64',
})
return { path: file.path, sha: data.sha }
})
)
// 创建新树
const { data: newTree } = await this.octokit.git.createTree({
owner: this.owner,
repo: this.repo,
base_tree: currentCommit.tree.sha,
tree: blobs.map(blob => ({
path: blob.path,
mode: '100644',
type: 'blob',
sha: blob.sha,
})),
})
// 创建新提交
const { data: newCommit } = await this.octokit.git.createCommit({
owner: this.owner,
repo: this.repo,
message,
tree: newTree.sha,
parents: [currentCommitSha],
})
// 更新引用
await this.octokit.git.updateRef({
owner: this.owner,
repo: this.repo,
ref: 'heads/main',
sha: newCommit.sha,
})
return newCommit
}
// 创建拉取请求
async createPullRequest(options: {
title: string
body: string
head: string
base?: string
}) {
const { data } = await this.octokit.pulls.create({
owner: this.owner,
repo: this.repo,
title: options.title,
body: options.body,
head: options.head,
base: options.base ?? 'main',
})
return data
}
}
认证流程集成
// lib/github/oauth.ts
export class GitHubOAuth {
private clientId: string
private clientSecret: string
private redirectUri: string
constructor() {
this.clientId = process.env.GITHUB_CLIENT_ID!
this.clientSecret = process.env.GITHUB_CLIENT_SECRET!
this.redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/github/callback`
}
// 生成授权 URL
getAuthorizationUrl(state: string, scopes: string[] = ['repo', 'user']) {
const params = new URLSearchParams({
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: scopes.join(' '),
state,
})
return `https://github.com/login/oauth/authorize?${params.toString()}`
}
// 交换访问令牌
async exchangeCode(code: string) {
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret,
code,
redirect_uri: this.redirectUri,
}),
})
if (!response.ok) {
throw new Error('Failed to exchange code for access token')
}
const data = await response.json()
return data.access_token
}
}
项目同步功能
// lib/github/sync.ts
export async function syncProjectToGitHub(
projectId: string,
githubToken: string
) {
const project = await api.project.getById({ id: projectId })
const files = await api.file.getProjectFiles({ projectId })
const octokit = createGitHubClient(githubToken)
const repo = new GitHubRepository(
octokit,
project.githubOwner,
project.githubRepo
)
// 准备文件内容
const filesToCommit = files.map(file => ({
path: file.path,
content: file.content,
}))
// 添加 README
filesToCommit.push({
path: 'README.md',
content: `# ${project.name}\n\n${project.description || ''}【\n\nCreated with [Libra AI](https://libra.dev)`,
})
// 提交到 GitHub
const commit = await repo.commitFiles(
filesToCommit,
`Update project: ${project.name}`
)
// 更新项目状态
await api.project.update({
id: projectId,
gitUrl: `https://github.com/${project.githubOwner}/${project.githubRepo}`,
lastSyncedAt: new Date().toISOString(),
})
return commit
}
13. 性能优化指南
13.1 前端性能优化
React Server Components 优化
// 默认情况下,app 目录中的组件都是服务端组件
// 仅在需要客户端功能时添加 'use client' 指令
// app/(frontend)/layout.tsx - 服务端组件示例
export default function Layout({ children }: { children: React.ReactNode }) {
// 服务端组件可以直接访问数据库和执行异步操作
return (
<div className="min-h-screen">
<Suspense fallback={<div>加载中...</div>}>
{children}
</Suspense>
</div>
)
}
流式渲染优化
// app/api/ai/route.ts - AI 响应流式传输
import { streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
// 使用流式响应提供实时反馈
const result = streamText({
model: openai('gpt-4'),
messages,
})
return result.toDataStreamResponse()
}
性能监控配置
// next.config.mjs - 启用的性能优化
export default {
reactCompiler: true, // React 编译器优化
useCache: true, // 启用缓存
bundleAnalyzer: { // Bundle 分析工具
enabled: process.env.ANALYZE === 'true',
},
images: {
loader: 'custom', // 自定义图片加载器
loaderFile: './imageLoader.ts',
},
}
13.2 数据库性能优化
查询优化
// 使用索引优化查询
export const projectQueries = {
// 分页查询优化
getProjectsPaginated: async (orgId: string, page = 1, limit = 20) => {
const offset = (page - 1) * limit
return await db.query.project.findMany({
where: and(
eq(project.organizationId, orgId),
eq(project.isActive, true)
),
orderBy: [desc(project.updatedAt)], // 使用索引排序
limit,
offset,
with: {
owner: {
columns: { id: true, name: true, email: true },
},
},
})
},
// 批量查询优化
getProjectsByIds: async (projectIds: string[]) => {
return await db.query.project.findMany({
where: inArray(project.id, projectIds),
with: {
files: {
orderBy: [desc(files.updatedAt)],
limit: 10, // 限制关联数据量
},
},
})
},
}
13.3 AI 性能优化
流式响应优化
// 优化 AI 响应流
export async function optimizedStreamGeneration(
prompt: string,
context: GenerationContext
) {
// 1. 预处理提示词以减少 token 使用
const optimizedPrompt = compressPrompt(prompt, context)
// 2. 使用适合的模型
const model = selectOptimalModel(context.complexity, context.userPlan)
// 3. 设置合适的流参数
const stream = await streamText({
model,
messages: [{ role: 'user', content: optimizedPrompt }],
temperature: 0.1, // 较低温度提高一致性
maxTokens: calculateOptimalMaxTokens(context),
stream: true,
})
// 4. 流式处理和缓存
return stream.textStream.pipe(
new TransformStream({
transform(chunk, controller) {
// 实时处理和验证
const processedChunk = processChunk(chunk)
controller.enqueue(processedChunk)
}
})
)
}