feat: 完成 B 群組 — 資料層與核心領域模型

- 導入 Prisma 7.9.1 + SQLite(better-sqlite3 driver adapter),prisma.config.ts 管理連線設定
- 完整資料模型:User/Work/Character/CharacterAlias/EpisodicMemory/SemanticMemory/
  ProceduralRule/EmotionState/Relationship/SentimentLedgerEntry
- packages/db:Prisma Client 存取層(ESM,因 Prisma 7 產出的 generated client 僅支援 ESM)
- apps/api 隨之改為 ESM 以相容 packages/db
- packages/shared:新增角色/記憶/情緒/關係共用型別,api 與 web 皆從此匯入
- prisma/seed.ts:建立測試使用者與元氣型測試角色,含完整關係帳本與記憶種子資料
- apps/api:新增 GET /characters
- scripts/smoke/B.mjs:驗證資料表齊全、API 讀出種子角色、關係與記憶可寫入讀出
- 保留 Prisma 官方隨 CLI 附的 agent skill 文件(.agents/skills 等),供後續群組查閱

npm run restart && npm run smoke -- B 皆通過(B-V),A 群組冒煙測試無回歸。
This commit is contained in:
Jeffery
2026-08-13 09:43:15 +08:00
parent ae9c44dde7
commit f44200f543
124 changed files with 13566 additions and 48 deletions
@@ -0,0 +1,276 @@
# Query Options
Options for controlling query behavior.
## select
Choose specific fields to return:
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
id: true,
name: true,
email: true,
// password: false (excluded by not including)
}
})
// Returns: { id: 1, name: 'Alice', email: 'alice@prisma.io' }
```
### Select relations
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
select: {
name: true,
posts: {
select: {
title: true,
published: true
}
}
}
})
```
### Select with include inside
```typescript
const user = await prisma.user.findMany({
select: {
name: true,
posts: {
include: {
comments: true
}
}
}
})
```
### Select relation count
```typescript
const users = await prisma.user.findMany({
select: {
name: true,
_count: {
select: { posts: true }
}
}
})
// Returns: { name: 'Alice', _count: { posts: 5 } }
```
## include
Include related records:
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: true,
profile: true
}
})
```
### Filtered include
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5
}
}
})
```
### Nested include
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
include: {
comments: {
include: {
author: true
}
}
}
}
}
})
```
### Include relation count
```typescript
const users = await prisma.user.findMany({
include: {
_count: {
select: { posts: true, followers: true }
}
}
})
```
## omit
Exclude specific fields:
```typescript
const user = await prisma.user.findUnique({
where: { id: 1 },
omit: {
password: true
}
})
// Returns all fields except password
```
### Omit in relations
```typescript
const users = await prisma.user.findMany({
omit: { password: true },
include: {
posts: {
omit: { content: true }
}
}
})
```
**Note:** Cannot use `select` and `omit` together.
## where
Filter records:
```typescript
const users = await prisma.user.findMany({
where: {
email: { contains: '@prisma.io' },
role: 'ADMIN'
}
})
```
See `filters.md` for detailed filter operators.
## orderBy
Sort results:
```typescript
// Single field
const users = await prisma.user.findMany({
orderBy: { name: 'asc' }
})
// Multiple fields
const users = await prisma.user.findMany({
orderBy: [
{ role: 'desc' },
{ name: 'asc' }
]
})
```
### Order by relation
```typescript
const users = await prisma.user.findMany({
orderBy: {
posts: { _count: 'desc' }
}
})
```
### Null handling
```typescript
const users = await prisma.user.findMany({
orderBy: {
name: { sort: 'asc', nulls: 'last' }
}
})
```
## take & skip
Pagination:
```typescript
// First page
const users = await prisma.user.findMany({
take: 10,
skip: 0
})
// Second page
const users = await prisma.user.findMany({
take: 10,
skip: 10
})
```
### Negative take (reverse)
```typescript
const lastUsers = await prisma.user.findMany({
take: -10,
orderBy: { id: 'asc' }
})
// Returns last 10 users
```
## cursor
Cursor-based pagination:
```typescript
// First page
const firstPage = await prisma.user.findMany({
take: 10,
orderBy: { id: 'asc' }
})
// Next page using cursor
const nextPage = await prisma.user.findMany({
take: 10,
skip: 1, // Skip the cursor record
cursor: { id: firstPage[firstPage.length - 1].id },
orderBy: { id: 'asc' }
})
```
## distinct
Return unique values:
```typescript
const cities = await prisma.user.findMany({
distinct: ['city'],
select: { city: true }
})
```
### Multiple distinct fields
```typescript
const locations = await prisma.user.findMany({
distinct: ['city', 'country']
})
```