| name | drizzle-solid |
| description | Drizzle Solid ORM 专家,处理 Pod 数据 CRUD、Schema 定义、查询优化、SPARQL 端点配置等问题 |
| allowed-tools | Read, Write, Edit, Grep, Glob |
Drizzle Solid ORM 专家
你是 XPod 项目的 Drizzle Solid ORM 专家。帮助设计和实现基于 drizzle-solid 的 Pod 数据访问层。
核心概念
drizzle-solid 是什么
drizzle-solid 是一个为 Solid Pod 设计的类型安全 ORM,基于 Drizzle ORM 构建,让你能够像操作传统数据库一样操作 Solid Pod 中的 RDF 数据。
服务器支持
| 能力 | 原生 CSS | xpod |
|---|
| 基础 CRUD | ✅ LDP 模式 | ✅ LDP 模式 |
| SPARQL SELECT | ❌ 不支持(Comunica 客户端执行) | ✅ 服务端索引下推 |
| SPARQL UPDATE | ⚠️ 仅 BGP 写入 | ✅ 完整支持 |
| 条件查询 (where) | Comunica 读文件到内存 | 索引下推(单 Pod) |
| 聚合函数 (count/sum/avg) | Comunica 读文件到内存 | 索引下推(单 Pod) |
| SPARQL 端点 | ❌ 不支持 | ✅ /-/sparql Sidecar |
Schema 定义
podTable 配置
import { podTable, string, int, datetime, uri } from '@undefineds.co/drizzle-solid';
const userTable = podTable('users', {
id: string('id').primaryKey(),
name: string('name'),
email: string('email'),
age: int('age'),
createdAt: datetime('createdAt'),
}, {
base: '/data/users/',
type: 'https://schema.org/Person',
namespace: UDFS_NAMESPACE,
subjectTemplate: '{id}.ttl#this',
sparqlEndpoint: '/data/users/-/sparql',
typeIndex: 'private',
autoRegister: true,
});
subjectTemplate 模式
Document 模式(每条记录独立文件)
const userTable = podTable('users', { ... }, {
base: '/users/',
subjectTemplate: '{id}.ttl',
});
const logTable = podTable('logs', { ... }, {
base: '/logs/',
subjectTemplate: '{yyyy}/{MM}/{dd}/{id}.ttl',
});
Fragment 模式(多条记录共享文件)
const messageTable = podTable('messages', { ... }, {
base: '/chat/room.ttl',
subjectTemplate: '#{id}',
});
关联表设计(同文件存储)
当需要将关联数据存储在同一文件时:
const ChatThread = podTable('ChatThread', {
id: string('id').primaryKey(),
title: string('title'),
}, {
base: '/chat/',
subjectTemplate: '{id}.ttl#this',
});
const ChatMessage = podTable('ChatMessage', {
id: string('id').primaryKey(),
threadId: string('threadId'),
content: string('content'),
}, {
base: '/chat/',
subjectTemplate: '{threadId}.ttl#{id}',
});
注意:使用 {threadId} 等外键变量时,查询需要配合 sparqlEndpoint 才能跨文件查询。
SPARQL 端点配置(xpod 专用)
Fragment Mode(推荐)
LDP 和 SPARQL 完全兼容:
const postsTable = podTable('posts', { ... }, {
base: '/data/posts.ttl',
subjectTemplate: '#{id}',
sparqlEndpoint: '/data/posts.ttl/-/sparql',
});
Document Mode + 容器端点
用于跨文件查询:
const usersTable = podTable('users', { ... }, {
base: '/data/users/',
subjectTemplate: '{id}.ttl#this',
sparqlEndpoint: '/data/users/-/sparql',
});
重要:Document Mode 下,SPARQL 写操作与 LDP 文件视图不兼容!
- SPARQL INSERT 不会创建 LDP 文件
- SPARQL UPDATE 不会更新 LDP 文件
- 建议:写操作用 LDP,读操作可用 SPARQL 聚合查询
查询操作
基础 CRUD
import { drizzle, eq, and, gte } from '@undefineds.co/drizzle-solid';
const db = drizzle(session, { schema });
const users = await db.select().from(userTable);
const adults = await db.select()
.from(userTable)
.where(gte(userTable.age, 18));
const result = await db.select()
.from(userTable)
.where(and(
eq(userTable.status, 'active'),
gte(userTable.age, 18)
));
await db.insert(userTable).values({
id: 'user-1',
name: 'Alice',
email: 'alice@example.com',
});
await db.update(userTable)
.set({ name: 'Alice Smith' })
.where(eq(userTable.id, 'user-1'));
await db.delete(userTable)
.where(eq(userTable.id, 'user-1'));
Drizzle 风格查询
const db = drizzle(session, { schema });
const users = await db.query.users.findMany({
where: { verified: true },
orderBy: [{ column: schema.users.name, direction: 'asc' }],
with: { posts: true },
});
const user = await db.query.users.findFirst({
where: eq(schema.users.id, 'user-1'),
});
const alice = await db.findByIri(schema.users, 'https://pod.example/data/users.ttl#alice');
聚合查询
import { count, max, sum, avg } from '@undefineds.co/drizzle-solid';
const stats = await db
.select({
totalUsers: count(),
oldestAge: max(userTable.age),
})
.from(userTable);
初始化与容器创建
await db.init([userTable, postTable]);
常见问题
1. 查询返回空数组
可能原因:
- Document Mode 下没有配置
sparqlEndpoint
- 容器不存在
- 数据存储在不同文件,但没有使用容器级 SPARQL 端点
解决方案:
const table = podTable('items', { ... }, {
base: '/data/items/',
sparqlEndpoint: '/data/items/-/sparql',
});
2. 写入后查询不到数据
可能原因:Document Mode 下混用了 SPARQL 写入和 LDP 读取
解决方案:
- 使用 Fragment Mode(推荐)
- 或者统一使用 LDP 操作
3. subjectTemplate 中的变量不生效
正确用法:
subjectTemplate: '{id}.ttl#this'
subjectTemplate: '{threadId}.ttl#{id}'
注意:变量必须是 schema 中定义的字段名。
4. TypeIndex 注册失败
检查:
- 确保
typeIndex: 'private' 或 'public' 已设置
- 确保
autoRegister: true(默认)
- 确保用户有权限写入 TypeIndex
最佳实践
1. 优先使用 Fragment Mode
const table = podTable('items', { ... }, {
base: '/data/items.ttl',
subjectTemplate: '#{id}',
sparqlEndpoint: '/data/items.ttl/-/sparql',
});
2. 需要文件隔离时使用 Document Mode + SPARQL 端点
const table = podTable('items', { ... }, {
base: '/data/items/',
subjectTemplate: '{id}.ttl#this',
sparqlEndpoint: '/data/items/-/sparql',
});
3. 关联数据存储在同一文件
const ChatThread = podTable('ChatThread', { ... }, {
base: '/chat/',
subjectTemplate: '{id}.ttl#this',
});
const ChatMessage = podTable('ChatMessage', { ... }, {
base: '/chat/',
subjectTemplate: '{threadId}.ttl#{id}',
});
4. 使用 namespace 统一管理自定义谓词
import { UDFS_NAMESPACE } from '@/vocab';
const table = podTable('items', {
status: string('status'),
}, {
namespace: UDFS_NAMESPACE,
});
参考资料
- drizzle-solid README:
node_modules/@undefineds.co/drizzle-solid/README.md
- 项目 vocab 定义:
src/vocab/
- 现有 schema 示例:
src/api/chatkit/schema.ts, src/credential/schema/tables.ts
问题反馈
如果发现 drizzle-solid 设计不合理或存在 bug,通过 git MCP 向 drizzle-solid 仓库提 issue:
仓库: undefinedsco/drizzle-solid
提 issue 时请包含:
- 问题描述
- 复现步骤
- 期望行为 vs 实际行为
- 相关代码片段