@ailnaf/zoddb 0.0.0-stage → 0.1.0
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/CHANGELOG.md +41 -0
- package/LICENSE +15 -0
- package/README.md +412 -2
- package/dist/aggregate.d.ts +42 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +77 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/auth-schema.d.ts +26 -0
- package/dist/auth-schema.d.ts.map +1 -0
- package/dist/auth-schema.js +139 -0
- package/dist/auth-schema.js.map +1 -0
- package/dist/better-auth-adapter.d.ts +21 -0
- package/dist/better-auth-adapter.d.ts.map +1 -0
- package/dist/better-auth-adapter.js +175 -0
- package/dist/better-auth-adapter.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +341 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +52 -0
- package/dist/config.js.map +1 -0
- package/dist/db.d.ts +183 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +946 -0
- package/dist/db.js.map +1 -0
- package/dist/ddl.d.ts +21 -0
- package/dist/ddl.d.ts.map +1 -0
- package/dist/ddl.js +127 -0
- package/dist/ddl.js.map +1 -0
- package/dist/dev.d.ts +9 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +21 -0
- package/dist/dev.js.map +1 -0
- package/dist/errors.d.ts +63 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +122 -0
- package/dist/errors.js.map +1 -0
- package/dist/filter.d.ts +66 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +219 -0
- package/dist/filter.js.map +1 -0
- package/dist/graph.d.ts +54 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +52 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +12 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +62 -0
- package/dist/init.js.map +1 -0
- package/dist/migrate.d.ts +58 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +447 -0
- package/dist/migrate.js.map +1 -0
- package/dist/migrations.d.ts +80 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +281 -0
- package/dist/migrations.js.map +1 -0
- package/dist/relation-graph.d.ts +50 -0
- package/dist/relation-graph.d.ts.map +1 -0
- package/dist/relation-graph.js +93 -0
- package/dist/relation-graph.js.map +1 -0
- package/dist/relations.d.ts +94 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +21 -0
- package/dist/relations.js.map +1 -0
- package/dist/schema.d.ts +247 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +584 -0
- package/dist/schema.js.map +1 -0
- package/dist/studio/page.d.ts +3 -0
- package/dist/studio/page.d.ts.map +1 -0
- package/dist/studio/page.js +708 -0
- package/dist/studio/page.js.map +1 -0
- package/dist/studio/server.d.ts +26 -0
- package/dist/studio/server.d.ts.map +1 -0
- package/dist/studio/server.js +415 -0
- package/dist/studio/server.js.map +1 -0
- package/dist/table.d.ts +156 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +165 -0
- package/dist/table.js.map +1 -0
- package/dist/where.d.ts +18 -0
- package/dist/where.d.ts.map +1 -0
- package/dist/where.js +76 -0
- package/dist/where.js.map +1 -0
- package/docs/api.md +444 -0
- package/package.json +70 -4
package/docs/api.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# zoddb API 契约
|
|
2
|
+
|
|
3
|
+
> 本文件是公开语义的唯一契约来源。公开运行时 API 只有 graph 建模、`zoddb` 运行时和错误类型;低层 DDL/迁移/表构造器不对外。
|
|
4
|
+
> 所有行为以本文件为准,`test/contract.test.ts` 与 `test/api-surface.test.ts` 钉住契约。
|
|
5
|
+
|
|
6
|
+
## 入口
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import {
|
|
10
|
+
defineSchema, node, edge, manyToMany, zoddb, relationGraph,
|
|
11
|
+
ZodDbError, UndefinedParamError, NoRowsError, RowValidationError, IncludeAliasError,
|
|
12
|
+
PgError, UniqueViolationError, NotNullViolationError, ForeignKeyViolationError,
|
|
13
|
+
} from '@ailnaf/zoddb'
|
|
14
|
+
import { authSchema, zodDbAdapter } from '@ailnaf/zoddb/better-auth'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
公开运行时导出共 **15 个**(主入口)+ **2 个**(`zoddb/better-auth` 子路径);完整清单见文末。
|
|
18
|
+
|
|
19
|
+
## zod-first
|
|
20
|
+
|
|
21
|
+
### `defineSchema({ nodes, edges })`
|
|
22
|
+
|
|
23
|
+
一个对象完成表与关系建模:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { z } from 'zod'
|
|
27
|
+
import { defineSchema, edge, manyToMany, node } from '@ailnaf/zoddb'
|
|
28
|
+
|
|
29
|
+
const schema = defineSchema({
|
|
30
|
+
nodes: {
|
|
31
|
+
users: node(z.object({
|
|
32
|
+
id: z.uuid(),
|
|
33
|
+
email: z.email(),
|
|
34
|
+
name: z.string().nullable(),
|
|
35
|
+
createdAt: z.date(),
|
|
36
|
+
}), { unique: [['email']] }),
|
|
37
|
+
|
|
38
|
+
posts: node(z.object({
|
|
39
|
+
id: z.uuid(),
|
|
40
|
+
authorId: z.uuid(),
|
|
41
|
+
title: z.string(),
|
|
42
|
+
published: z.boolean().default(false),
|
|
43
|
+
createdAt: z.date(),
|
|
44
|
+
})),
|
|
45
|
+
|
|
46
|
+
tags: node(z.object({
|
|
47
|
+
id: z.uuid(),
|
|
48
|
+
slug: z.string(),
|
|
49
|
+
}), { unique: [['slug']] }),
|
|
50
|
+
|
|
51
|
+
postTags: node(z.object({
|
|
52
|
+
postId: z.uuid(),
|
|
53
|
+
tagId: z.uuid(),
|
|
54
|
+
}), { primaryKey: ['postId', 'tagId'] }),
|
|
55
|
+
},
|
|
56
|
+
edges: {
|
|
57
|
+
users: {
|
|
58
|
+
posts: edge('posts', { from: 'id', to: 'authorId', kind: 'many' }),
|
|
59
|
+
},
|
|
60
|
+
posts: {
|
|
61
|
+
author: edge('users', { from: 'authorId', to: 'id', onDelete: 'cascade' }),
|
|
62
|
+
tags: manyToMany('tags', { through: 'postTags', from: 'postId', to: 'tagId' }),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `nodes` 的 key 默认是 schema key;`node` 第二参数可设置 `name`、`primaryKey`、`unique`、`indexes`、`checks`、`defaults`
|
|
69
|
+
- 列名默认 camelCase → snake_case
|
|
70
|
+
- `id: z.uuid()` 自动主键 + `gen_random_uuid()`;`createdAt` / `updatedAt` 自动 `now()`
|
|
71
|
+
- `z.string()` / `z.email()` → text,`z.uuid()` → uuid,`z.int()` → int8,`z.date()` → timestamptz
|
|
72
|
+
- 复杂类型用 `.meta({ sqlType })` 覆盖;无法推断的 zod 类型在构建期报错
|
|
73
|
+
- 关系只写在 `edges[源表]`;不要在 node 里写 `relations`
|
|
74
|
+
|
|
75
|
+
## 表定义
|
|
76
|
+
|
|
77
|
+
### `node(shape, options?)`
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const posts = node(z.object({
|
|
81
|
+
id: z.uuid(),
|
|
82
|
+
title: z.string(),
|
|
83
|
+
published: z.boolean().default(false),
|
|
84
|
+
}), {
|
|
85
|
+
name: 'posts',
|
|
86
|
+
unique: [['title']],
|
|
87
|
+
indexes: [{ columns: ['published'] }],
|
|
88
|
+
checks: { posts_title_check: "length(title) > 0" },
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `primaryKey: ['orgId', 'userId']` 生成复合主键
|
|
93
|
+
- `.nullable()` 表示 DB 可空;`.default()` 表示插入可省略
|
|
94
|
+
- 唯一/索引/CHECK 由 DDL 与迁移统一管理
|
|
95
|
+
- 不要从公开入口导入 `table()`;它只是内部构造器
|
|
96
|
+
|
|
97
|
+
### `edge(targetTable, options)` / `manyToMany(targetTable, options)`
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
edges: {
|
|
101
|
+
posts: {
|
|
102
|
+
author: edge('users', {
|
|
103
|
+
from: 'authorId',
|
|
104
|
+
reverse: 'posts',
|
|
105
|
+
onDelete: 'cascade',
|
|
106
|
+
}),
|
|
107
|
+
tags: manyToMany('tags', {
|
|
108
|
+
through: 'postTags',
|
|
109
|
+
from: 'postId',
|
|
110
|
+
to: 'tagId',
|
|
111
|
+
}),
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- `from` 是源表列;`to` 默认是目标表的 `id`
|
|
117
|
+
- `reverse` 自动生成反向关系;反向关系只读,不重复生成 FK
|
|
118
|
+
- `onDelete`: `cascade` / `restrict` / `set null` / `no action` / `set default`
|
|
119
|
+
- M2M 必须显式声明 through 表;through 表用复合主键
|
|
120
|
+
- 普通 graph 的 target 在编译期校验;auth graph 的动态表名在运行时校验
|
|
121
|
+
- 关系深度最多 2;不支持 `every`、关系排序和关系聚合
|
|
122
|
+
|
|
123
|
+
## 连接
|
|
124
|
+
|
|
125
|
+
### `zoddb(options)`
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { zoddb } from '@ailnaf/zoddb'
|
|
129
|
+
|
|
130
|
+
const db = zoddb({
|
|
131
|
+
schema,
|
|
132
|
+
url: process.env.DATABASE_URL!,
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- 返回按表命名的客户端:`db.users`、`db.posts`、`db.tags`
|
|
137
|
+
- 每个客户端支持 `create`、`insert`、`insertMany`、`seed`、`update`、`delete`、`upsert`、`count`、`aggregate`、`groupBy`、`findMany`、`findOne`
|
|
138
|
+
- `db.transaction(fn)` 提供同一个 graph 命名空间的事务句柄
|
|
139
|
+
- `db.close()` 关闭连接
|
|
140
|
+
- 也可以用 `connection` 传入已有 postgres.js 连接
|
|
141
|
+
|
|
142
|
+
## 查询
|
|
143
|
+
|
|
144
|
+
### `db.<table>.findMany(options?)` / `findOne(options?)`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const rows = await db.users.findMany({
|
|
148
|
+
where: {
|
|
149
|
+
email: { contains: '@acme', mode: 'insensitive' },
|
|
150
|
+
name: { isNull: false },
|
|
151
|
+
},
|
|
152
|
+
orderBy: [{ createdAt: 'desc' }, { id: 'asc' }],
|
|
153
|
+
limit: 20,
|
|
154
|
+
offset: 0,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const one = await db.users.findOne({ where: { email: 'ada@acme.dev' } })
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
操作符:
|
|
161
|
+
|
|
162
|
+
| 操作符 | 语义 |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `eq` / `ne` | 等值/不等;`null` → `is [not] null` |
|
|
165
|
+
| `lt` / `lte` / `gt` / `gte` | 比较 |
|
|
166
|
+
| `in` / `notIn` | 数组;空数组分别为 false / true |
|
|
167
|
+
| `contains` / `startsWith` / `endsWith` | 自动转义 `% _ \` |
|
|
168
|
+
| `mode: 'insensitive'` | 模糊匹配改用 `ilike` |
|
|
169
|
+
| `AND` / `OR` / `NOT` | 组合条件 |
|
|
170
|
+
|
|
171
|
+
- 直接写值等价于 `eq`;写 `null` 等价于 `is null`
|
|
172
|
+
- `select` 只取指定列
|
|
173
|
+
- `cursor` 做 keyset 分页,必须覆盖 `orderBy` 的所有列
|
|
174
|
+
- `update` / `delete` 不接受关系过滤
|
|
175
|
+
|
|
176
|
+
### `with` 关系读取
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const users = await db.users.findMany({
|
|
180
|
+
orderBy: { createdAt: 'desc' },
|
|
181
|
+
with: {
|
|
182
|
+
posts: {
|
|
183
|
+
where: { published: true },
|
|
184
|
+
orderBy: { createdAt: 'desc' },
|
|
185
|
+
limit: 2,
|
|
186
|
+
with: { author: true },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- 每个父行独立分页(窗口函数);不是全局 LIMIT
|
|
193
|
+
- 1 条主查询 + 每条关系 1 条批量查询,不做 N+1
|
|
194
|
+
- 支持嵌套 `with`;关系名和返回类型从 schema 推导
|
|
195
|
+
- `with` 的键拼错会在编译期报错
|
|
196
|
+
|
|
197
|
+
## 关系
|
|
198
|
+
|
|
199
|
+
### `some` / `none` 关系过滤
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const authors = await db.users.findMany({
|
|
203
|
+
where: {
|
|
204
|
+
posts: {
|
|
205
|
+
some: {
|
|
206
|
+
published: true,
|
|
207
|
+
tags: { some: { slug: 'orm' } },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const quiet = await db.users.findMany({
|
|
214
|
+
where: { posts: { none: { published: true } } },
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- 编译为 `EXISTS` / `NOT EXISTS` 子查询
|
|
219
|
+
- 支持 forward / reverse / M2M through
|
|
220
|
+
- 最大关系深度 2;超深路径在类型和运行时都拒绝
|
|
221
|
+
- 每层可混用列过滤、`AND` / `OR` / `NOT`
|
|
222
|
+
|
|
223
|
+
### 多对多写入
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const post = await db.posts.create({
|
|
227
|
+
authorId: user.id,
|
|
228
|
+
title: 'Why zoddb',
|
|
229
|
+
tags: {
|
|
230
|
+
connect: [{ id: tag.id }],
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
await db.posts.update(
|
|
235
|
+
{ id: post.id },
|
|
236
|
+
{ tags: { set: [{ id: otherTag.id }] } },
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
await db.posts.update(
|
|
240
|
+
{ id: post.id },
|
|
241
|
+
{ tags: { disconnect: [{ id: otherTag.id }] } },
|
|
242
|
+
)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- `create` 的 M2M 关系只支持 `connect`
|
|
246
|
+
- `update` 支持 `set` / `connect` / `disconnect`;同事务
|
|
247
|
+
- `set` 替换全部 link;只改 link,不创建 target 行
|
|
248
|
+
- `update` 的 where 必须命中恰好一个父行
|
|
249
|
+
- through 额外业务列不支持
|
|
250
|
+
|
|
251
|
+
## 写
|
|
252
|
+
|
|
253
|
+
### create / update / delete / upsert
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const ada = await db.users.create({
|
|
257
|
+
email: 'ada@acme.dev',
|
|
258
|
+
name: 'Ada',
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const [updated] = await db.users.update(
|
|
262
|
+
{ id: ada.id },
|
|
263
|
+
{ name: 'Ada Lovelace' },
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
await db.users.upsert(
|
|
267
|
+
{ email: 'ada@acme.dev', name: 'Ada' },
|
|
268
|
+
{ conflict: ['email'], update: ['name'] },
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
await db.users.delete({ id: ada.id })
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- 写入值先过 zod;`id` / `createdAt` / `updatedAt` 自动生成
|
|
275
|
+
- 读出的行默认也过 zod;失败抛 `RowValidationError`
|
|
276
|
+
- `update` / `delete` 条件不能为空
|
|
277
|
+
- 更新会自动刷新 `updatedAt`(存在该列时)
|
|
278
|
+
|
|
279
|
+
### 嵌套 create
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const grace = await db.users.create({
|
|
283
|
+
email: 'grace@acme.dev',
|
|
284
|
+
name: 'Grace',
|
|
285
|
+
posts: {
|
|
286
|
+
create: [
|
|
287
|
+
{ title: 'Type-safe SQL', published: true },
|
|
288
|
+
{ title: 'Draft' },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
// grace.posts: Post[]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- 父行和子行同一事务
|
|
296
|
+
- many 关系支持 `create` / `connect`;one 关系只支持 `connect`
|
|
297
|
+
- 反向关系只读,不支持 nested create
|
|
298
|
+
- 父键支持数据库默认值
|
|
299
|
+
- 嵌套写只做一层
|
|
300
|
+
|
|
301
|
+
## 事务
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
await db.transaction(async tx => {
|
|
305
|
+
const user = await tx.users.create({
|
|
306
|
+
email: 'tx@acme.dev',
|
|
307
|
+
name: 'TX',
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
await tx.posts.create({
|
|
311
|
+
authorId: user.id,
|
|
312
|
+
title: 'created in tx',
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- 回调抛错 → 回滚
|
|
318
|
+
- `tx` 有同样的 graph 命名空间
|
|
319
|
+
- 隔离级别通过 `{ isolation: 'serializable' }` 传入
|
|
320
|
+
|
|
321
|
+
## 迁移
|
|
322
|
+
|
|
323
|
+
迁移是 graph 驱动的 CLI 工作流;公开运行时不导出 DDL/迁移函数。
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# CLI 自动读取项目根 .env;也可显式 export 或传 --url
|
|
327
|
+
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/zoddb
|
|
328
|
+
|
|
329
|
+
npx zoddb generate --schema ./src/schema.ts --dir ./migrations --name init
|
|
330
|
+
npx zoddb migrate --dir ./migrations
|
|
331
|
+
npx zoddb status --schema ./src/schema.ts --dir ./migrations
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
- `generate` 只写文件,不执行;破坏性变更默认拒绝,`--allow-destructive` 显式放行
|
|
335
|
+
- `migrate` 用 advisory lock 串行化,每个文件一个事务
|
|
336
|
+
- 历史记录在 `_zoddb_migrations`;缺失、乱序、空文件、checksum 变化默认拒绝
|
|
337
|
+
- `dev --once` 在本地生成并应用 additive 迁移
|
|
338
|
+
|
|
339
|
+
### 只读 CI / 存量库
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npx zoddb migrate --dir ./migrations --dry-run
|
|
343
|
+
npx zoddb status --schema ./src/schema.ts --dir ./migrations --json
|
|
344
|
+
npx zoddb baseline --schema ./src/schema.ts --dir ./migrations --through <file>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
- `migrate --dry-run` 不加锁、不写历史、不建业务表;有待应用/异常时 exit 1
|
|
348
|
+
- `status --json` 只输出 JSON,exit code 与文本模式一致
|
|
349
|
+
- `baseline` 只标记 through 及之前的文件,不执行文件 SQL;默认要求 schema 与库一致
|
|
350
|
+
- CI 应依赖 exit code/JSON,不要解析日志
|
|
351
|
+
|
|
352
|
+
## Studio(本地只读 / 可选写模式)
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
npx zoddb studio
|
|
356
|
+
# 或 npx zoddb studio --schema ./src/schema.ts --port 5555
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- 由 CLI 读取 config/schema,绑定 `127.0.0.1`,输出带本地 token 的 URL
|
|
360
|
+
- 不配置数据库 URL 时只展示 schema 表、列、PK、唯一列和关系边
|
|
361
|
+
- 配置 `--url` / `DATABASE_URL` 时提供只读数据浏览:`limit/offset`、总数、排序、操作符过滤、`with` 关系预览、`some/none` 关系过滤、FK 跳转、M2M through;`select` 投影已由 `/api/rows` 支持
|
|
362
|
+
- 默认不写数据;`--write` 开启受控写模式(需数据库 URL、本地 token、同源请求)
|
|
363
|
+
- 写模式支持 create / update / delete / upsert / 嵌套 create / M2M 写入 / transaction;删除需二次确认
|
|
364
|
+
- 配置迁移目录时,侧栏只读展示 `status --json` 的 applied / pending / missing / out-of-order / empty / tampered / drift 快照;不执行迁移
|
|
365
|
+
- 迁移状态由 Studio 调用当前 CLI 的公开 `status --json` 获取,不导入内部迁移模块
|
|
366
|
+
- Studio 的 schema 数据只来自公开 `relationGraph(schema)`;不导入内部 table/db/migrations,因此它也是公开接口的验证器
|
|
367
|
+
- 不提供 SQL 控制台,也不在 Studio 中 apply 迁移
|
|
368
|
+
|
|
369
|
+
## 关系图 IR
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import { relationGraph } from '@ailnaf/zoddb'
|
|
373
|
+
|
|
374
|
+
const graph = relationGraph(schema)
|
|
375
|
+
// { nodes: [...], edges: [...] }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
- 纯 JSON,包含自动反向关系和 M2M through 边
|
|
379
|
+
- 不连接数据库、不执行 SQL
|
|
380
|
+
- 适合可视化、审计和外部工具
|
|
381
|
+
|
|
382
|
+
## 错误
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { UniqueViolationError } from '@ailnaf/zoddb'
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
await db.users.create({ email: 'ada@acme.dev', name: 'Ada' })
|
|
389
|
+
} catch (e) {
|
|
390
|
+
if (e instanceof UniqueViolationError) {
|
|
391
|
+
console.log(e.table, e.columns)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
| 错误 | 场景 |
|
|
397
|
+
|---|---|
|
|
398
|
+
| `ZodDbError` | 库内错误基类 |
|
|
399
|
+
| `UndefinedParamError` | SQL 参数含 `undefined` |
|
|
400
|
+
| `NoRowsError` | `.one()` 无结果 |
|
|
401
|
+
| `RowValidationError` | 读出的行校验失败 |
|
|
402
|
+
| `IncludeAliasError` | include 别名不在结果里 |
|
|
403
|
+
| `PgError` | 其它 PG 错误 |
|
|
404
|
+
| `UniqueViolationError` | 23505 |
|
|
405
|
+
| `NotNullViolationError` | 23502 |
|
|
406
|
+
| `ForeignKeyViolationError` | 23503 |
|
|
407
|
+
|
|
408
|
+
## better-auth
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { betterAuth } from 'better-auth'
|
|
412
|
+
import { authSchema, zodDbAdapter } from '@ailnaf/zoddb/better-auth'
|
|
413
|
+
import { defineSchema, zoddb } from '@ailnaf/zoddb'
|
|
414
|
+
|
|
415
|
+
const auth = authSchema({ plugins: [] })
|
|
416
|
+
const schema = defineSchema({ nodes: auth.nodes, edges: auth.edges })
|
|
417
|
+
const db = zoddb({ schema, url: process.env.DATABASE_URL! })
|
|
418
|
+
|
|
419
|
+
const authHandler = betterAuth({
|
|
420
|
+
database: zodDbAdapter(db, { schema }),
|
|
421
|
+
emailAndPassword: { enabled: true },
|
|
422
|
+
})
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
- `authSchema` 生成 core 四表;插件表由 `plugins` 推导
|
|
426
|
+
- `auth.nodes` / `auth.edges` 可直接展开进 `defineSchema`
|
|
427
|
+
- adapter 按 schema 模型名映射;无需手写低层表
|
|
428
|
+
|
|
429
|
+
## 边界
|
|
430
|
+
|
|
431
|
+
- 低层 `table` / DDL / `defineDb` / `relations` / `pg` 不对外;它们是内部实现
|
|
432
|
+
- 不提供 query builder、懒加载、任意深度递归、`every`、关系聚合全套
|
|
433
|
+
- 不支持 down migration、shadow DB、零停机在线 DDL
|
|
434
|
+
- 仅 Postgres
|
|
435
|
+
|
|
436
|
+
## 导出清单
|
|
437
|
+
|
|
438
|
+
主入口 runtime:
|
|
439
|
+
|
|
440
|
+
`defineSchema` `node` `edge` `manyToMany` `zoddb` `relationGraph` `ZodDbError` `UndefinedParamError` `NoRowsError` `RowValidationError` `IncludeAliasError` `PgError` `UniqueViolationError` `NotNullViolationError` `ForeignKeyViolationError`
|
|
441
|
+
|
|
442
|
+
子路径 `zoddb/better-auth`:
|
|
443
|
+
|
|
444
|
+
`authSchema` `zodDbAdapter`
|
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ailnaf/zoddb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"./better-auth": {
|
|
11
|
+
"types": "./dist/better-auth-adapter.d.ts",
|
|
12
|
+
"import": "./dist/better-auth-adapter.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
18
|
+
"check": "npm run typecheck && npm run test",
|
|
19
|
+
"bench": "vitest run --config vitest.bench.config.ts --reporter=verbose",
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"prepack": "npm run build",
|
|
22
|
+
"verify:pack": "bash scripts/verify-pack.sh",
|
|
23
|
+
"verify:dogfood": "bash scripts/verify-dogfood.sh",
|
|
24
|
+
"prepublishOnly": "npm run check && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"postgres": "^3.4.9",
|
|
28
|
+
"zod": "^4.6.7"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^26.6.5",
|
|
32
|
+
"better-auth": "^1.7.10",
|
|
33
|
+
"expect-type": "^1.4.0",
|
|
34
|
+
"typescript": "^7.0.2",
|
|
35
|
+
"vitest": "^5.0.5"
|
|
36
|
+
},
|
|
37
|
+
"description": "zod-first PostgreSQL ORM with graph schemas, typed CRUD and production migrations",
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"keywords": [
|
|
40
|
+
"postgres",
|
|
41
|
+
"postgresql",
|
|
42
|
+
"orm",
|
|
43
|
+
"zod",
|
|
44
|
+
"typescript",
|
|
45
|
+
"sql",
|
|
46
|
+
"migrations",
|
|
47
|
+
"graph"
|
|
48
|
+
],
|
|
49
|
+
"author": "zoddb contributors",
|
|
50
|
+
"license": "ISC",
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"better-auth": "^1.7.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"better-auth": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"types": "./dist/index.d.ts",
|
|
60
|
+
"files": [
|
|
61
|
+
"dist",
|
|
62
|
+
"docs",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"CHANGELOG.md"
|
|
65
|
+
],
|
|
66
|
+
"bin": {
|
|
67
|
+
"zoddb": "./dist/cli.js"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=22.18"
|
|
71
|
+
}
|
|
72
|
+
}
|