@undefineds.co/linx 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/version.js +3 -0
- package/dist/generated/version.js.map +1 -0
- package/dist/index.js +10 -17
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-command.js +169 -301
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/login-command.js +30 -22
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +59 -12
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +36 -7
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +215 -5
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +25 -1
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +219 -137
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-tools.js +104 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +207 -23
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +63 -182
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/web-fetch.js +154 -0
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -0
- package/dist/lib/pod-chat-store.js +40 -30
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +275 -303
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +37 -35
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/skills/drizzle-solid/SKILL.md +340 -0
- package/dist/skills/pod-storage/SKILL.md +60 -0
- package/dist/skills/solid-modeling/SKILL.md +274 -0
- package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
- package/package.json +2 -2
- package/dist/lib/pi-adapter/pod-native.js +0 -478
- package/dist/lib/pi-adapter/pod-native.js.map +0 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: drizzle-solid
|
|
3
|
+
description: Drizzle Solid ORM 专家,处理 Pod 数据 CRUD、Schema 定义、查询优化、SPARQL 端点配置等问题
|
|
4
|
+
allowed-tools: Read, Write, Edit, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Drizzle Solid ORM 专家
|
|
8
|
+
|
|
9
|
+
你是 XPod 项目的 Drizzle Solid ORM 专家。帮助设计和实现基于 drizzle-solid 的 Pod 数据访问层。
|
|
10
|
+
|
|
11
|
+
## 核心概念
|
|
12
|
+
|
|
13
|
+
### drizzle-solid 是什么
|
|
14
|
+
|
|
15
|
+
drizzle-solid 是一个为 Solid Pod 设计的类型安全 ORM,基于 Drizzle ORM 构建,让你能够像操作传统数据库一样操作 Solid Pod 中的 RDF 数据。
|
|
16
|
+
|
|
17
|
+
### 服务器支持
|
|
18
|
+
|
|
19
|
+
| 能力 | 原生 CSS | xpod |
|
|
20
|
+
|------|----------|------|
|
|
21
|
+
| **基础 CRUD** | ✅ LDP 模式 | ✅ LDP 模式 |
|
|
22
|
+
| **SPARQL SELECT** | ❌ 不支持(Comunica 客户端执行) | ✅ 服务端索引下推 |
|
|
23
|
+
| **SPARQL UPDATE** | ⚠️ 仅 BGP 写入 | ✅ 完整支持 |
|
|
24
|
+
| **条件查询** (where) | Comunica 读文件到内存 | 索引下推(单 Pod) |
|
|
25
|
+
| **聚合函数** (count/sum/avg) | Comunica 读文件到内存 | 索引下推(单 Pod) |
|
|
26
|
+
| **SPARQL 端点** | ❌ 不支持 | ✅ `/-/sparql` Sidecar |
|
|
27
|
+
|
|
28
|
+
## Schema 定义
|
|
29
|
+
|
|
30
|
+
### podTable 配置
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { podTable, string, int, datetime, uri } from '@undefineds.co/drizzle-solid';
|
|
34
|
+
|
|
35
|
+
const userTable = podTable('users', {
|
|
36
|
+
id: string('id').primaryKey(),
|
|
37
|
+
name: string('name'),
|
|
38
|
+
email: string('email'),
|
|
39
|
+
age: int('age'),
|
|
40
|
+
createdAt: datetime('createdAt'),
|
|
41
|
+
}, {
|
|
42
|
+
base: '/data/users/', // 容器或资源路径
|
|
43
|
+
type: 'https://schema.org/Person', // RDF 类型
|
|
44
|
+
namespace: UDFS_NAMESPACE, // 自定义命名空间
|
|
45
|
+
subjectTemplate: '{id}.ttl#this', // subject URI 模板
|
|
46
|
+
sparqlEndpoint: '/data/users/-/sparql', // xpod SPARQL 端点
|
|
47
|
+
typeIndex: 'private', // TypeIndex 注册
|
|
48
|
+
autoRegister: true, // 自动注册到 TypeIndex
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### subjectTemplate 模式
|
|
53
|
+
|
|
54
|
+
#### Document 模式(每条记录独立文件)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// 每个用户一个文件: /users/alice.ttl, /users/bob.ttl
|
|
58
|
+
const userTable = podTable('users', { ... }, {
|
|
59
|
+
base: '/users/',
|
|
60
|
+
subjectTemplate: '{id}.ttl', // 或 '{id}.ttl#this'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 按日期分片: /logs/2026/01/17/log-001.ttl
|
|
64
|
+
const logTable = podTable('logs', { ... }, {
|
|
65
|
+
base: '/logs/',
|
|
66
|
+
subjectTemplate: '{yyyy}/{MM}/{dd}/{id}.ttl',
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Fragment 模式(多条记录共享文件)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// 所有消息在同一文件: /chat/room.ttl#msg-1, /chat/room.ttl#msg-2
|
|
74
|
+
const messageTable = podTable('messages', { ... }, {
|
|
75
|
+
base: '/chat/room.ttl',
|
|
76
|
+
subjectTemplate: '#{id}', // 强制 fragment 模式
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 关联表设计(同文件存储)
|
|
81
|
+
|
|
82
|
+
当需要将关联数据存储在同一文件时:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Thread 和 Message 存储在同一文件
|
|
86
|
+
const ChatThread = podTable('ChatThread', {
|
|
87
|
+
id: string('id').primaryKey(),
|
|
88
|
+
title: string('title'),
|
|
89
|
+
}, {
|
|
90
|
+
base: '/chat/',
|
|
91
|
+
subjectTemplate: '{id}.ttl#this', // /chat/thread-1.ttl#this
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const ChatMessage = podTable('ChatMessage', {
|
|
95
|
+
id: string('id').primaryKey(),
|
|
96
|
+
threadId: string('threadId'),
|
|
97
|
+
content: string('content'),
|
|
98
|
+
}, {
|
|
99
|
+
base: '/chat/',
|
|
100
|
+
subjectTemplate: '{threadId}.ttl#{id}', // /chat/thread-1.ttl#msg-1
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**注意**:使用 `{threadId}` 等外键变量时,查询需要配合 `sparqlEndpoint` 才能跨文件查询。
|
|
105
|
+
|
|
106
|
+
## SPARQL 端点配置(xpod 专用)
|
|
107
|
+
|
|
108
|
+
### Fragment Mode(推荐)
|
|
109
|
+
|
|
110
|
+
LDP 和 SPARQL 完全兼容:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const postsTable = podTable('posts', { ... }, {
|
|
114
|
+
base: '/data/posts.ttl',
|
|
115
|
+
subjectTemplate: '#{id}',
|
|
116
|
+
sparqlEndpoint: '/data/posts.ttl/-/sparql',
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Document Mode + 容器端点
|
|
121
|
+
|
|
122
|
+
用于跨文件查询:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const usersTable = podTable('users', { ... }, {
|
|
126
|
+
base: '/data/users/',
|
|
127
|
+
subjectTemplate: '{id}.ttl#this',
|
|
128
|
+
sparqlEndpoint: '/data/users/-/sparql', // 容器级 SPARQL 端点
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**重要**:Document Mode 下,SPARQL 写操作与 LDP 文件视图不兼容!
|
|
133
|
+
- SPARQL INSERT 不会创建 LDP 文件
|
|
134
|
+
- SPARQL UPDATE 不会更新 LDP 文件
|
|
135
|
+
- 建议:写操作用 LDP,读操作可用 SPARQL 聚合查询
|
|
136
|
+
|
|
137
|
+
## 查询操作
|
|
138
|
+
|
|
139
|
+
### 基础 CRUD
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { drizzle, eq, and, gte } from '@undefineds.co/drizzle-solid';
|
|
143
|
+
|
|
144
|
+
const db = drizzle(session, { schema });
|
|
145
|
+
|
|
146
|
+
// 查询所有
|
|
147
|
+
const users = await db.select().from(userTable);
|
|
148
|
+
|
|
149
|
+
// 条件查询
|
|
150
|
+
const adults = await db.select()
|
|
151
|
+
.from(userTable)
|
|
152
|
+
.where(gte(userTable.age, 18));
|
|
153
|
+
|
|
154
|
+
// 复合条件
|
|
155
|
+
const result = await db.select()
|
|
156
|
+
.from(userTable)
|
|
157
|
+
.where(and(
|
|
158
|
+
eq(userTable.status, 'active'),
|
|
159
|
+
gte(userTable.age, 18)
|
|
160
|
+
));
|
|
161
|
+
|
|
162
|
+
// 插入
|
|
163
|
+
await db.insert(userTable).values({
|
|
164
|
+
id: 'user-1',
|
|
165
|
+
name: 'Alice',
|
|
166
|
+
email: 'alice@example.com',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// 更新
|
|
170
|
+
await db.update(userTable)
|
|
171
|
+
.set({ name: 'Alice Smith' })
|
|
172
|
+
.where(eq(userTable.id, 'user-1'));
|
|
173
|
+
|
|
174
|
+
// 删除
|
|
175
|
+
await db.delete(userTable)
|
|
176
|
+
.where(eq(userTable.id, 'user-1'));
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Drizzle 风格查询
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const db = drizzle(session, { schema });
|
|
183
|
+
|
|
184
|
+
// findMany
|
|
185
|
+
const users = await db.query.users.findMany({
|
|
186
|
+
where: { verified: true },
|
|
187
|
+
orderBy: [{ column: schema.users.name, direction: 'asc' }],
|
|
188
|
+
with: { posts: true }, // 关联查询
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// findFirst
|
|
192
|
+
const user = await db.query.users.findFirst({
|
|
193
|
+
where: eq(schema.users.id, 'user-1'),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// findByIri
|
|
197
|
+
const alice = await db.findByIri(schema.users, 'https://pod.example/data/users.ttl#alice');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 聚合查询
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { count, max, sum, avg } from '@undefineds.co/drizzle-solid';
|
|
204
|
+
|
|
205
|
+
const stats = await db
|
|
206
|
+
.select({
|
|
207
|
+
totalUsers: count(),
|
|
208
|
+
oldestAge: max(userTable.age),
|
|
209
|
+
})
|
|
210
|
+
.from(userTable);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 初始化与容器创建
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// 初始化表(创建容器、资源,注册 TypeIndex)
|
|
217
|
+
await db.init([userTable, postTable]);
|
|
218
|
+
|
|
219
|
+
// 手动确保容器存在
|
|
220
|
+
// drizzle-solid 会自动处理,但如果需要手动控制:
|
|
221
|
+
// 1. 先 HEAD 检查容器是否存在
|
|
222
|
+
// 2. 不存在则 PUT 创建
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 常见问题
|
|
226
|
+
|
|
227
|
+
### 1. 查询返回空数组
|
|
228
|
+
|
|
229
|
+
**可能原因**:
|
|
230
|
+
- Document Mode 下没有配置 `sparqlEndpoint`
|
|
231
|
+
- 容器不存在
|
|
232
|
+
- 数据存储在不同文件,但没有使用容器级 SPARQL 端点
|
|
233
|
+
|
|
234
|
+
**解决方案**:
|
|
235
|
+
```typescript
|
|
236
|
+
// 配置容器级 SPARQL 端点
|
|
237
|
+
const table = podTable('items', { ... }, {
|
|
238
|
+
base: '/data/items/',
|
|
239
|
+
sparqlEndpoint: '/data/items/-/sparql',
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2. 写入后查询不到数据
|
|
244
|
+
|
|
245
|
+
**可能原因**:Document Mode 下混用了 SPARQL 写入和 LDP 读取
|
|
246
|
+
|
|
247
|
+
**解决方案**:
|
|
248
|
+
- 使用 Fragment Mode(推荐)
|
|
249
|
+
- 或者统一使用 LDP 操作
|
|
250
|
+
|
|
251
|
+
### 3. subjectTemplate 中的变量不生效
|
|
252
|
+
|
|
253
|
+
**正确用法**:
|
|
254
|
+
```typescript
|
|
255
|
+
// 使用 {id} 引用主键
|
|
256
|
+
subjectTemplate: '{id}.ttl#this'
|
|
257
|
+
|
|
258
|
+
// 使用其他字段(如 threadId)
|
|
259
|
+
subjectTemplate: '{threadId}.ttl#{id}'
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**注意**:变量必须是 schema 中定义的字段名。
|
|
263
|
+
|
|
264
|
+
### 4. TypeIndex 注册失败
|
|
265
|
+
|
|
266
|
+
**检查**:
|
|
267
|
+
- 确保 `typeIndex: 'private'` 或 `'public'` 已设置
|
|
268
|
+
- 确保 `autoRegister: true`(默认)
|
|
269
|
+
- 确保用户有权限写入 TypeIndex
|
|
270
|
+
|
|
271
|
+
## 最佳实践
|
|
272
|
+
|
|
273
|
+
### 1. 优先使用 Fragment Mode
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// 推荐:所有数据在一个文件
|
|
277
|
+
const table = podTable('items', { ... }, {
|
|
278
|
+
base: '/data/items.ttl',
|
|
279
|
+
subjectTemplate: '#{id}',
|
|
280
|
+
sparqlEndpoint: '/data/items.ttl/-/sparql',
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 2. 需要文件隔离时使用 Document Mode + SPARQL 端点
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// 每条记录独立文件,但通过 SPARQL 端点聚合查询
|
|
288
|
+
const table = podTable('items', { ... }, {
|
|
289
|
+
base: '/data/items/',
|
|
290
|
+
subjectTemplate: '{id}.ttl#this',
|
|
291
|
+
sparqlEndpoint: '/data/items/-/sparql',
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 3. 关联数据存储在同一文件
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Thread 和 Message 在同一文件,便于原子操作
|
|
299
|
+
const ChatThread = podTable('ChatThread', { ... }, {
|
|
300
|
+
base: '/chat/',
|
|
301
|
+
subjectTemplate: '{id}.ttl#this',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const ChatMessage = podTable('ChatMessage', { ... }, {
|
|
305
|
+
base: '/chat/',
|
|
306
|
+
subjectTemplate: '{threadId}.ttl#{id}',
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 4. 使用 namespace 统一管理自定义谓词
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { UDFS_NAMESPACE } from '@/vocab';
|
|
314
|
+
|
|
315
|
+
const table = podTable('items', {
|
|
316
|
+
status: string('status'), // 映射到 udfs:status
|
|
317
|
+
}, {
|
|
318
|
+
namespace: UDFS_NAMESPACE,
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## 参考资料
|
|
323
|
+
|
|
324
|
+
- drizzle-solid README: `node_modules/@undefineds.co/drizzle-solid/README.md`
|
|
325
|
+
- 项目 vocab 定义: `src/vocab/`
|
|
326
|
+
- 现有 schema 示例: `src/api/chatkit/schema.ts`, `src/credential/schema/tables.ts`
|
|
327
|
+
|
|
328
|
+
## 问题反馈
|
|
329
|
+
|
|
330
|
+
如果发现 drizzle-solid 设计不合理或存在 bug,通过 git MCP 向 drizzle-solid 仓库提 issue:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
仓库: undefinedsco/drizzle-solid
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
提 issue 时请包含:
|
|
337
|
+
1. 问题描述
|
|
338
|
+
2. 复现步骤
|
|
339
|
+
3. 期望行为 vs 实际行为
|
|
340
|
+
4. 相关代码片段
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pod-storage
|
|
3
|
+
description: Pod 文件系统操作 — 使用 pod_read/pod_write 读写用户 Pod 中的数据。
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob, pod_read, pod_write
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pod 文件系统
|
|
8
|
+
|
|
9
|
+
用户的数据存储在 Pod 中。访问 Pod 文件使用 `pod_read` / `pod_write`(不是本地 read/write)。
|
|
10
|
+
|
|
11
|
+
## 路径约定
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/alice/
|
|
15
|
+
├── settings/ # 配置和凭据
|
|
16
|
+
│ ├── credentials.ttl # 第三方 API key
|
|
17
|
+
│ └── preferences.ttl # 用户偏好
|
|
18
|
+
├── logs/ # 工具/Agent 输出日志
|
|
19
|
+
│ └── tasks/ # 按任务组织
|
|
20
|
+
├── repos/ # git 仓库工作区
|
|
21
|
+
└── data/ # 用户自己的数据
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 读写
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pod_read /alice/settings/credentials.ttl
|
|
28
|
+
pod_write /alice/settings/credentials.ttl "content"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
用 `pod_read` 读 Pod 文件,`pod_write` 写 Pod 文件。Content-Type 由扩展名自动推断(.ttl → text/turtle)。
|
|
32
|
+
|
|
33
|
+
## 凭据约定
|
|
34
|
+
|
|
35
|
+
第三方 API key 存在 `/alice/settings/credentials.ttl`,每条一个节点:
|
|
36
|
+
|
|
37
|
+
```turtle
|
|
38
|
+
@prefix xpod: <https://undefineds.co/xpod#> .
|
|
39
|
+
|
|
40
|
+
<#jina> xpod:apiKey "jina_xxx" .
|
|
41
|
+
<#openai> xpod:apiKey "sk-xxx" .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
需要 key 时先用 `pod_read` 读这个文件。没有就引导用户去对应网站注册,拿到后用 `pod_write` 写入。
|
|
45
|
+
|
|
46
|
+
写入时注意保留已有内容,只修改目标凭据行。
|
|
47
|
+
|
|
48
|
+
## 查找
|
|
49
|
+
|
|
50
|
+
不知道文件在哪时用 `bash` 配合 Pod HTTP API,或用 Grep 搜索本地 Pod mirror。
|
|
51
|
+
|
|
52
|
+
## 日志
|
|
53
|
+
|
|
54
|
+
工具执行结果写入 `/alice/logs/`,按任务 ID 分目录:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
/alice/logs/tasks/task-001/
|
|
58
|
+
├── stdout.log
|
|
59
|
+
└── result.json
|
|
60
|
+
```
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: solid-modeling
|
|
3
|
+
description: Solid/RDF 数据建模专家,处理 Pod 数据结构设计、类继承、属性定义、命名空间等问题
|
|
4
|
+
allowed-tools: Read, Write, Edit, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Solid/RDF 数据建模专家
|
|
8
|
+
|
|
9
|
+
你是 XPod 项目的 Solid/RDF 数据建模专家。帮助设计符合 Solid 规范和 RDF 最佳实践的数据模型。
|
|
10
|
+
|
|
11
|
+
## 核心原则
|
|
12
|
+
|
|
13
|
+
### 数据主权
|
|
14
|
+
|
|
15
|
+
用户数据存储在用户自己的 Pod 中,服务器不存储用户数据。
|
|
16
|
+
|
|
17
|
+
### 标准词汇表优先
|
|
18
|
+
|
|
19
|
+
优先复用已有的标准词汇表,只在必要时定义自定义词汇。
|
|
20
|
+
|
|
21
|
+
| 用途 | 词汇表 | 前缀 | 导入 |
|
|
22
|
+
|------|--------|------|------|
|
|
23
|
+
| 自定义 | Undefineds Namespace | `udfs:` | `import { UDFS } from '@/vocab'` |
|
|
24
|
+
| RDF 基础 | RDF/RDFS | `rdf:`, `rdfs:` | `import { RDF, RDFS } from '@/vocab'` |
|
|
25
|
+
| 时间/元数据 | Dublin Core | `dc:` | `import { DCTerms } from '@/vocab'` |
|
|
26
|
+
| 容器/资源 | LDP | `ldp:` | `import { LDP } from '@/vocab'` |
|
|
27
|
+
| 个人信息 | FOAF | `foaf:` | `import { FOAF } from '@/vocab'` |
|
|
28
|
+
| 访问控制 | ACL | `acl:` | `import { ACL } from '@/vocab'` |
|
|
29
|
+
| 数据类型 | XSD | `xsd:` | `import { XSD } from '@/vocab'` |
|
|
30
|
+
|
|
31
|
+
## 命名规范
|
|
32
|
+
|
|
33
|
+
### 词汇表命名
|
|
34
|
+
|
|
35
|
+
| 类型 | 格式 | 示例 |
|
|
36
|
+
|------|------|------|
|
|
37
|
+
| **Class** | PascalCase (大写开头) | `Credential`, `Provider`, `Model` |
|
|
38
|
+
| **Property** | camelCase (小写开头) | `apiKey`, `baseUrl`, `createdAt` |
|
|
39
|
+
| **实例 ID** | kebab-case | `#my-entity`, `#instance-001` |
|
|
40
|
+
|
|
41
|
+
### 使用 Vocab 定义
|
|
42
|
+
|
|
43
|
+
项目使用 `src/vocab/` 统一管理词汇表:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/vocab/udfs.ts - UDFS 词汇表
|
|
47
|
+
export const UDFS = createNamespace('udfs', 'https://undefineds.co/ns#', {
|
|
48
|
+
// Classes (大写)
|
|
49
|
+
Credential: 'Credential',
|
|
50
|
+
Provider: 'Provider',
|
|
51
|
+
Model: 'Model',
|
|
52
|
+
|
|
53
|
+
// Properties (小写)
|
|
54
|
+
apiKey: 'apiKey',
|
|
55
|
+
baseUrl: 'baseUrl',
|
|
56
|
+
status: 'status',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**使用方式**:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { UDFS, UDFS_NAMESPACE } from '@/vocab';
|
|
64
|
+
|
|
65
|
+
// 使用 Class
|
|
66
|
+
const type = UDFS.Credential; // 'https://undefineds.co/ns#Credential'
|
|
67
|
+
|
|
68
|
+
// 使用 Property
|
|
69
|
+
const prop = UDFS.apiKey; // 'https://undefineds.co/ns#apiKey'
|
|
70
|
+
|
|
71
|
+
// 动态构建 URI
|
|
72
|
+
const custom = UDFS('CustomTerm'); // 'https://undefineds.co/ns#CustomTerm'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## drizzle-solid Schema 定义
|
|
76
|
+
|
|
77
|
+
项目级业务语义不要写进这个 skill 文件。
|
|
78
|
+
|
|
79
|
+
- 这类定义应放在仓库的 models/schema/docs 里,由代码和 shared docs 作为单一真相。
|
|
80
|
+
- 这个 skill 只保留通用 Solid/RDF 建模原则、drizzle-solid 约束和可复用的模式。
|
|
81
|
+
- 如果某个产品需要定义 `chat` / `thread` / `session` 的具体含义,应写回对应 package 的 schema 注释和 shared docs,而不是放到 skill。
|
|
82
|
+
|
|
83
|
+
### 基本结构
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { podTable, string, uri, datetime, int } from 'drizzle-solid';
|
|
87
|
+
import { UDFS, UDFS_NAMESPACE } from '../vocab';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Credential - 凭据
|
|
91
|
+
*
|
|
92
|
+
* 存储位置: /settings/credentials.ttl
|
|
93
|
+
*/
|
|
94
|
+
export const Credential = podTable(
|
|
95
|
+
'Credential', // 表名用 PascalCase
|
|
96
|
+
{
|
|
97
|
+
id: string('id').primaryKey(),
|
|
98
|
+
provider: uri('provider'),
|
|
99
|
+
apiKey: string('apiKey'),
|
|
100
|
+
status: string('status'),
|
|
101
|
+
createdAt: datetime('createdAt'),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
base: '/settings/credentials.ttl',
|
|
105
|
+
type: UDFS.Credential, // 使用 vocab 而不是硬编码字符串
|
|
106
|
+
namespace: UDFS_NAMESPACE,
|
|
107
|
+
subjectTemplate: '#{id}',
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 关系定义
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { relations } from 'drizzle-solid';
|
|
116
|
+
|
|
117
|
+
export const CredentialRelations = relations(Credential, ({ one }) => ({
|
|
118
|
+
provider: one(Provider, {
|
|
119
|
+
fields: [Credential.provider],
|
|
120
|
+
references: [Provider.id],
|
|
121
|
+
}),
|
|
122
|
+
}));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 类设计
|
|
126
|
+
|
|
127
|
+
### 使用 Class 继承表达用途分类
|
|
128
|
+
|
|
129
|
+
当实体有共同特征但不同用途时,使用 `rdfs:subClassOf`:
|
|
130
|
+
|
|
131
|
+
```turtle
|
|
132
|
+
# 基类
|
|
133
|
+
udfs:Provider a rdfs:Class ;
|
|
134
|
+
rdfs:label "Provider" ;
|
|
135
|
+
rdfs:comment "服务供应商基类" .
|
|
136
|
+
|
|
137
|
+
# 子类 - 按用途区分
|
|
138
|
+
udfs:AgentProvider rdfs:subClassOf udfs:Provider ;
|
|
139
|
+
rdfs:label "Agent Provider" .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 用属性区分实现细节
|
|
143
|
+
|
|
144
|
+
具体实现方式用属性表达,不用子类:
|
|
145
|
+
|
|
146
|
+
```turtle
|
|
147
|
+
# 正确:用属性区分实现类型
|
|
148
|
+
<#provider-a> a udfs:Provider ;
|
|
149
|
+
udfs:executorType "claude" .
|
|
150
|
+
|
|
151
|
+
<#provider-b> a udfs:Provider ;
|
|
152
|
+
udfs:executorType "openai" .
|
|
153
|
+
|
|
154
|
+
# 错误:不要为每种实现创建子类
|
|
155
|
+
# udfs:ClaudeProvider rdfs:subClassOf udfs:Provider . ❌
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**规则**:
|
|
159
|
+
- Class 继承区分**用途/功能**
|
|
160
|
+
- 属性区分**具体实现**
|
|
161
|
+
|
|
162
|
+
### 定义与实例分离
|
|
163
|
+
|
|
164
|
+
静态定义(模板)和运行时实例分开建模:
|
|
165
|
+
|
|
166
|
+
```turtle
|
|
167
|
+
# 定义(模板) - 静态配置,描述"是什么"
|
|
168
|
+
<#agent-config> a udfs:AgentConfig ;
|
|
169
|
+
udfs:displayName "Indexing Agent" ;
|
|
170
|
+
udfs:systemPrompt "..." .
|
|
171
|
+
|
|
172
|
+
# 实例 - 运行时状态,描述"正在做什么"
|
|
173
|
+
<#agent-status> a udfs:AgentStatus ;
|
|
174
|
+
udfs:agentId "indexing" ;
|
|
175
|
+
udfs:status "running" ;
|
|
176
|
+
udfs:currentTaskId "task-123" .
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 属性设计
|
|
180
|
+
|
|
181
|
+
### 使用 URI 引用关联实体
|
|
182
|
+
|
|
183
|
+
实体间关系用 URI 引用,不用字符串:
|
|
184
|
+
|
|
185
|
+
```turtle
|
|
186
|
+
# 正确:URI 引用
|
|
187
|
+
<#credential> a udfs:Credential ;
|
|
188
|
+
udfs:provider </settings/ai/providers.ttl#google> .
|
|
189
|
+
|
|
190
|
+
# 错误:字符串值
|
|
191
|
+
<#credential> a udfs:Credential ;
|
|
192
|
+
udfs:provider "google" . ❌
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 时间字段统一用 datetime
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// 正确
|
|
199
|
+
createdAt: datetime('createdAt'),
|
|
200
|
+
updatedAt: datetime('updatedAt'),
|
|
201
|
+
|
|
202
|
+
// 错误 - 不要用 string 存时间
|
|
203
|
+
startedAt: string('startedAt'), // ❌
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 布尔值
|
|
207
|
+
|
|
208
|
+
drizzle-solid 目前用 string 存储布尔值:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
enabled: string('enabled'), // 存储 "true" / "false"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
代码中需要手动比较:`enabled === 'true'`
|
|
215
|
+
|
|
216
|
+
## 文件组织
|
|
217
|
+
|
|
218
|
+
### 按功能分文件
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
pod:/settings/
|
|
222
|
+
├── ai/
|
|
223
|
+
│ ├── providers.ttl # AI 供应商
|
|
224
|
+
│ ├── models.ttl # AI 模型
|
|
225
|
+
│ ├── agent-providers.ttl # Agent 供应商
|
|
226
|
+
│ ├── agents.ttl # Agent 配置
|
|
227
|
+
│ ├── agent-status.ttl # Agent 状态
|
|
228
|
+
│ ├── config.ttl # Pod 级 AI 配置
|
|
229
|
+
│ ├── vector-stores.ttl # 向量知识库
|
|
230
|
+
│ └── indexed-files.ttl # 已索引文件
|
|
231
|
+
├── credentials.ttl # 凭据(敏感信息单独存放)
|
|
232
|
+
└── prefs.ttl # 用户偏好设置
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 文件引用规则
|
|
236
|
+
|
|
237
|
+
同文件用 `#fragment`,跨文件用完整路径:
|
|
238
|
+
|
|
239
|
+
```turtle
|
|
240
|
+
# 同文件引用
|
|
241
|
+
<#entity-a> udfs:relatedTo <#entity-b> .
|
|
242
|
+
|
|
243
|
+
# 跨文件引用
|
|
244
|
+
<#credential> udfs:provider </settings/ai/providers.ttl#google> .
|
|
245
|
+
|
|
246
|
+
# 跨 Pod 引用
|
|
247
|
+
<#entity-a> udfs:relatedTo <https://other.pod/file.ttl#entity-b> .
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 检查清单
|
|
251
|
+
|
|
252
|
+
设计新数据模型时:
|
|
253
|
+
|
|
254
|
+
- [ ] 是否有可复用的标准词汇表?
|
|
255
|
+
- [ ] 新词汇是否已添加到 `src/vocab/udfs.ts`?
|
|
256
|
+
- [ ] Class 名是否大写开头?Property 名是否小写开头?
|
|
257
|
+
- [ ] 类继承是否按用途区分(不是按实现)?
|
|
258
|
+
- [ ] 定义和实例是否分离?
|
|
259
|
+
- [ ] 实体关系是否用 URI 引用(不是字符串)?
|
|
260
|
+
- [ ] 时间字段是否用 `datetime()` 类型?
|
|
261
|
+
- [ ] 敏感数据是否单独存放?
|
|
262
|
+
- [ ] Schema 是否使用 `UDFS.ClassName` 而不是硬编码字符串?
|
|
263
|
+
|
|
264
|
+
## 参考文件
|
|
265
|
+
|
|
266
|
+
- **Vocab 定义**: `src/vocab/udfs.ts`, `src/vocab/external.ts`
|
|
267
|
+
- **Credential Schema**: `src/credential/schema/tables.ts`
|
|
268
|
+
- **Embedding Schema**: `src/embedding/schema/tables.ts`
|
|
269
|
+
- **Agent Schema**: `src/agents/schema/`
|
|
270
|
+
- **Task Schema**: `src/task/schema.ts`
|
|
271
|
+
- **Credential Schema**: `src/credential/schema/tables.ts`
|
|
272
|
+
- **Embedding Schema**: `src/embedding/schema/tables.ts`
|
|
273
|
+
- **Agent Schema**: `src/agents/schema/`
|
|
274
|
+
- **Task Schema**: `src/task/schema.ts`
|