@ailnaf/zoddb 0.0.0-stage → 0.2.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 +73 -0
- package/LICENSE +15 -0
- package/README.md +440 -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 +1063 -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 +67 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +230 -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 +128 -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 +476 -0
- package/package.json +70 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
graph query 2.0:补关系型后台/内容应用的高频查询表达。
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `every` 关系过滤:空关系为 true,存在不满足(含 NULL 谓词)为 false;支持 forward / reverse / M2M,与 `some` / `none` 组合
|
|
10
|
+
- `with.aggregate`:关系级 `count` / `sum` / `max`
|
|
11
|
+
- 空关系 `count=0`、`sum/max=null`
|
|
12
|
+
- `sum` 仅数值列;`max` 保留列类型
|
|
13
|
+
- 支持一对多、反向、M2M through
|
|
14
|
+
- 聚合复用关系 `where`,不受 `limit/offset` 影响
|
|
15
|
+
- 结果挂在 `<relation>Aggregate`
|
|
16
|
+
- 三层 `with` 加载:每层支持 `where` / `orderBy` / `limit` / `offset`;第 4 层在运行时和类型层显式拒绝
|
|
17
|
+
- 查询数回归:20 父行下 `every`=1、aggregate=3、三层 `with`=3,锁定无 N+1
|
|
18
|
+
- Studio / 外部 dogfood 覆盖新查询能力
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `with` 关系加载上限从文档承诺的 2 层提升到 3 层;关系过滤仍为深度 2
|
|
23
|
+
- 文档明确批量查询数边界和新能力限制
|
|
24
|
+
|
|
25
|
+
### Known limitations
|
|
26
|
+
|
|
27
|
+
- 仅支持 PostgreSQL
|
|
28
|
+
- 关系过滤最大深度 2
|
|
29
|
+
- 关系聚合仅 `count` / `sum` / `max`;不做 `avg` / `min` / 关系 groupBy / having / 嵌套聚合
|
|
30
|
+
- M2M 行级 `where` / `orderBy` / `limit` 仍不支持
|
|
31
|
+
- forward-only migration;不支持 down migration、shadow DB、零停机在线 DDL
|
|
32
|
+
|
|
33
|
+
[0.2.0]: https://www.npmjs.com/package/@ailnaf/zoddb/v/0.2.0
|
|
34
|
+
|
|
35
|
+
## [0.1.0] - 2026-10-18
|
|
36
|
+
|
|
37
|
+
首个可发布预览版。
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- zod-first graph 建模:`defineSchema`、`node`、`edge`、`manyToMany`
|
|
42
|
+
- graph 运行时 `zoddb`:类型化 CRUD、关系加载、关系过滤、嵌套写入
|
|
43
|
+
- 多对多 through 写入:`connect` / `set` / `disconnect`
|
|
44
|
+
- `relationGraph(schema)` 纯 JSON 关系图
|
|
45
|
+
- better-auth 子路径:`authSchema` / `zodDbAdapter`
|
|
46
|
+
- 生产迁移 CLI:advisory lock、checksum、历史完整性、baseline、dry-run、JSON status
|
|
47
|
+
- CLI 自动加载项目根 `.env`
|
|
48
|
+
- `zoddb studio`:本地 schema explorer + 数据浏览(表/列/PK/唯一约束/关系;分页/总数/排序/过滤/投影、`with` 关系预览、`some/none`、FK 跳转、M2M through;仅使用公开 graph IR 与 `zoddb` runtime)
|
|
49
|
+
- `zoddb init` 生成 `.mts` scaffold,避免 CommonJS 项目的首次 `dev` 加载失败
|
|
50
|
+
- `zoddb studio --write`:受控写模式(create/update/delete/upsert、嵌套 create、M2M 写入、transaction;默认只读、同源 token、删除确认)
|
|
51
|
+
- Studio 只读迁移状态面板:调用公开 `zoddb status --json` 展示 applied/pending/missing/out-of-order/empty/tampered/drift
|
|
52
|
+
- `verify:pack` 覆盖 tarball 安装后的 Studio schema / rows / migrations 请求
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- npm 包名为 `@ailnaf/zoddb`(npm 拒绝无 scope 的 `zoddb`);CLI 命令仍为 `zoddb`
|
|
57
|
+
- 公开运行时面收口为 **15 个 graph/error exports**
|
|
58
|
+
- `table()`、低层 DDL、`defineDb`、`relations`、`pg`、迁移函数不再从主入口导出
|
|
59
|
+
- 迁移改为 CLI 工作流;低层能力保留在内部模块
|
|
60
|
+
|
|
61
|
+
### Breaking
|
|
62
|
+
|
|
63
|
+
- 旧的低层 `table()` / DDL / `defineDb` / `relations` 导入会失败
|
|
64
|
+
- 需要低层建表或迁移脚本的用户应改用 `zoddb init/dev/generate/migrate`
|
|
65
|
+
|
|
66
|
+
### Known limitations
|
|
67
|
+
|
|
68
|
+
- 仅支持 PostgreSQL
|
|
69
|
+
- 关系过滤最大深度 2;不支持 `every`、关系排序/聚合全套
|
|
70
|
+
- forward-only migration;不支持 down migration、shadow DB、零停机在线 DDL
|
|
71
|
+
- better-auth 承诺 core + admin + organization,其他插件需自行验证
|
|
72
|
+
|
|
73
|
+
[0.1.0]: https://www.npmjs.com/package/@ailnaf/zoddb/v/0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zoddb contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,441 @@
|
|
|
1
|
-
#
|
|
1
|
+
# zoddb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Postgres 的 zod-first ORM:**只写 graph 建模,不写 SQL**。表、类型、关系、DDL 和迁移都从同一个 schema 推导;没有 codegen,没有低层表构造器。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const adults = await db.users.findMany({
|
|
7
|
+
where: { age: { gte: 18 } },
|
|
8
|
+
orderBy: { createdAt: 'desc' },
|
|
9
|
+
limit: 20,
|
|
10
|
+
})
|
|
11
|
+
// adults: { id: string; email: string; name: string | null; ... }[]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i @ailnaf/zoddb postgres zod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
CLI 命令仍然是 `zoddb`。也可以从 tarball 安装:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm pack
|
|
24
|
+
npm i ./ailnaf-zoddb-0.2.0.tgz postgres zod
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
要求 Node.js **>= 22.18**。
|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx zoddb init
|
|
33
|
+
|
|
34
|
+
# init 生成 .env.example;复制为 .env 并填入 DATABASE_URL
|
|
35
|
+
cp .env.example .env
|
|
36
|
+
# 编辑 .env:DATABASE_URL=postgres://postgres:postgres@localhost:5432/zoddb
|
|
37
|
+
|
|
38
|
+
npx zoddb dev --once
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`init` 生成 `zoddb.config.mts`、`schema.mts`、`.env.example`;`.mts` 在 CommonJS 和 ESM 项目里都能被 Node 直接加载。已有文件默认不覆盖,`--force` 才覆盖。
|
|
42
|
+
|
|
43
|
+
`dev --once` 会读取 graph schema,生成 additive 迁移并应用;删列/改类型默认拒绝。
|
|
44
|
+
|
|
45
|
+
## 建模:一个 graph
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// src/schema.ts
|
|
49
|
+
import { z } from 'zod'
|
|
50
|
+
import { defineSchema, edge, manyToMany, node } from '@ailnaf/zoddb'
|
|
51
|
+
|
|
52
|
+
export const schema = defineSchema({
|
|
53
|
+
nodes: {
|
|
54
|
+
users: node(z.object({
|
|
55
|
+
id: z.uuid(),
|
|
56
|
+
email: z.email(),
|
|
57
|
+
name: z.string().nullable(),
|
|
58
|
+
createdAt: z.date(),
|
|
59
|
+
}), { unique: [['email']] }),
|
|
60
|
+
|
|
61
|
+
posts: node(z.object({
|
|
62
|
+
id: z.uuid(),
|
|
63
|
+
authorId: z.uuid(),
|
|
64
|
+
title: z.string(),
|
|
65
|
+
published: z.boolean().default(false),
|
|
66
|
+
createdAt: z.date(),
|
|
67
|
+
})),
|
|
68
|
+
|
|
69
|
+
tags: node(z.object({
|
|
70
|
+
id: z.uuid(),
|
|
71
|
+
slug: z.string(),
|
|
72
|
+
}), { unique: [['slug']] }),
|
|
73
|
+
|
|
74
|
+
postTags: node(z.object({
|
|
75
|
+
postId: z.uuid(),
|
|
76
|
+
tagId: z.uuid(),
|
|
77
|
+
}), { primaryKey: ['postId', 'tagId'] }),
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
edges: {
|
|
81
|
+
users: {
|
|
82
|
+
posts: edge('posts', { from: 'id', to: 'authorId', kind: 'many' }),
|
|
83
|
+
},
|
|
84
|
+
posts: {
|
|
85
|
+
author: edge('users', { from: 'authorId', to: 'id', onDelete: 'cascade' }),
|
|
86
|
+
tags: manyToMany('tags', { through: 'postTags', from: 'postId', to: 'tagId' }),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
约定:
|
|
93
|
+
|
|
94
|
+
- `users` → 表 `users`,`createdAt` → 列 `created_at`
|
|
95
|
+
- `id: z.uuid()` → 主键 + `gen_random_uuid()`
|
|
96
|
+
- `createdAt` / `updatedAt` → `timestamptz default now()`
|
|
97
|
+
- `z.email()` → text,`z.int()` → int8,`z.date()` → timestamptz,`z.enum()` → text
|
|
98
|
+
- 关系只写在 `edges[源表]`;不要在 node 里写 relations
|
|
99
|
+
- `reverse` 可自动生成反向关系;反向关系只读
|
|
100
|
+
- M2M 必须显式声明 through 表
|
|
101
|
+
|
|
102
|
+
## 连接
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { zoddb } from '@ailnaf/zoddb'
|
|
106
|
+
import { schema } from './schema.js'
|
|
107
|
+
|
|
108
|
+
export const db = zoddb({
|
|
109
|
+
schema,
|
|
110
|
+
url: process.env.DATABASE_URL!,
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
> 应用进程不会自动读 `.env`;Node 22.18+ 可用 `node --env-file=.env app.mts` 或 `process.loadEnvFile()`。CLI 会自动读取项目根 `.env`。
|
|
115
|
+
|
|
116
|
+
`db` 按表名提供命名空间:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
db.users
|
|
120
|
+
db.posts
|
|
121
|
+
db.tags
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
每个命名空间支持:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
create / insert / insertMany / seed / update / delete / upsert
|
|
128
|
+
count / aggregate / groupBy / findMany / findOne
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 写入
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const ada = await db.users.create({
|
|
135
|
+
email: 'ada@acme.dev',
|
|
136
|
+
name: 'Ada',
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const [updated] = await db.users.update(
|
|
140
|
+
{ id: ada.id },
|
|
141
|
+
{ name: 'Ada Lovelace' },
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
await db.users.upsert(
|
|
145
|
+
{ email: 'ada@acme.dev', name: 'Ada' },
|
|
146
|
+
{ conflict: ['email'], update: ['name'] },
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
await db.users.delete({ id: ada.id })
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- 写入先过 zod;`id` / `createdAt` / `updatedAt` 自动生成
|
|
153
|
+
- 读出的行默认也校验;失败抛 `RowValidationError`
|
|
154
|
+
- `update` / `delete` 条件不能为空;存在 `updatedAt` 时自动刷新
|
|
155
|
+
|
|
156
|
+
嵌套 create:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const grace = await db.users.create({
|
|
160
|
+
email: 'grace@acme.dev',
|
|
161
|
+
name: 'Grace',
|
|
162
|
+
posts: {
|
|
163
|
+
create: [
|
|
164
|
+
{ title: 'Type-safe SQL', published: true },
|
|
165
|
+
{ title: 'Draft' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
// grace.posts: Post[]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
父行和子行同一事务;many 支持 `create` / `connect`,one 只支持 `connect`。
|
|
173
|
+
|
|
174
|
+
## 查询
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const rows = await db.users.findMany({
|
|
178
|
+
where: {
|
|
179
|
+
email: { contains: '@acme', mode: 'insensitive' },
|
|
180
|
+
name: { isNull: false },
|
|
181
|
+
},
|
|
182
|
+
orderBy: [{ createdAt: 'desc' }, { id: 'asc' }],
|
|
183
|
+
limit: 20,
|
|
184
|
+
offset: 0,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const one = await db.users.findOne({
|
|
188
|
+
where: { email: 'ada@acme.dev' },
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
支持 `eq` / `ne` / `lt` / `lte` / `gt` / `gte` / `in` / `notIn` / `contains` / `startsWith` / `endsWith` / `isNull`,以及 `AND` / `OR` / `NOT`。
|
|
193
|
+
|
|
194
|
+
`select` 只取需要的列;`cursor` 做 keyset 分页,必须覆盖 `orderBy`。
|
|
195
|
+
|
|
196
|
+
## 关系读取
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const users = await db.users.findMany({
|
|
200
|
+
orderBy: { createdAt: 'desc' },
|
|
201
|
+
with: {
|
|
202
|
+
posts: {
|
|
203
|
+
where: { published: true },
|
|
204
|
+
orderBy: { createdAt: 'desc' },
|
|
205
|
+
limit: 2,
|
|
206
|
+
with: { author: true },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
关系聚合:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const users = await db.users.findMany({
|
|
216
|
+
with: {
|
|
217
|
+
posts: {
|
|
218
|
+
aggregate: {
|
|
219
|
+
count: true,
|
|
220
|
+
sum: { views: true },
|
|
221
|
+
max: { createdAt: true },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
users[0].postsAggregate
|
|
228
|
+
// { count: number, sum: { views: number | null }, max: { createdAt: Date | null } }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- `count` 空关系为 0;`sum` / `max` 空关系为 null
|
|
232
|
+
- 普通关系聚合复用 `where`,但不受 `limit/offset` 影响;M2M 行级 `where` 仍不支持
|
|
233
|
+
|
|
234
|
+
- 每个父行独立分页(窗口函数),不是全局 LIMIT
|
|
235
|
+
- 1 条主查询 + 每条关系 1 条批量查询,不做 N+1
|
|
236
|
+
- 关系过滤内联为主查询的 `EXISTS`;每个 aggregate 关系额外 1 条聚合查询;三层 `with` 每层 1 条
|
|
237
|
+
- 支持最多 3 层 `with`;每层 `where/orderBy/limit` 独立生效,第 4 层运行时和类型层都拒绝
|
|
238
|
+
- 关系名和返回类型从 graph 推导;拼错会在编译期报错
|
|
239
|
+
|
|
240
|
+
关系过滤:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const authors = await db.users.findMany({
|
|
244
|
+
where: {
|
|
245
|
+
posts: {
|
|
246
|
+
some: {
|
|
247
|
+
published: true,
|
|
248
|
+
tags: { some: { slug: 'orm' } },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const quiet = await db.users.findMany({
|
|
255
|
+
where: { posts: { none: { published: true } } },
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const allPublished = await db.users.findMany({
|
|
259
|
+
where: { posts: { every: { published: true } } },
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
编译为 `EXISTS` / `NOT EXISTS`,内联进主查询,不按父行查询;`every` 空关系为 `true`,存在一个不满足为 `false`;支持 forward / reverse / M2M through,最大深度 2。
|
|
264
|
+
|
|
265
|
+
## 多对多
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const post = await db.posts.create({
|
|
269
|
+
authorId: ada.id,
|
|
270
|
+
title: 'Why zoddb',
|
|
271
|
+
tags: {
|
|
272
|
+
connect: [{ id: orm.id }, { id: pg.id }],
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
await db.posts.update(
|
|
277
|
+
{ id: post.id },
|
|
278
|
+
{ tags: { set: [{ id: pg.id }] } },
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
await db.posts.update(
|
|
282
|
+
{ id: post.id },
|
|
283
|
+
{ tags: { disconnect: [{ id: pg.id }] } },
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- create 只支持 `connect`
|
|
288
|
+
- update 支持 `set` / `connect` / `disconnect`
|
|
289
|
+
- 只管理 through link,不创建 target 行
|
|
290
|
+
- update 的 where 必须命中恰好一个父行
|
|
291
|
+
|
|
292
|
+
## 事务
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
await db.transaction(async tx => {
|
|
296
|
+
const user = await tx.users.create({
|
|
297
|
+
email: 'tx@acme.dev',
|
|
298
|
+
name: 'TX',
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
await tx.posts.create({
|
|
302
|
+
authorId: user.id,
|
|
303
|
+
title: 'created in tx',
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
回调抛错自动回滚;`tx` 有同样的 graph 命名空间。
|
|
309
|
+
|
|
310
|
+
## Studio(本地只读 / 可选写模式)
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npx zoddb studio
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
CLI 会读取 `zoddb.config.ts`/`zoddb.config.mts` 的 schema,在 `127.0.0.1` 启动本地 Studio,并输出带 token 的 URL。
|
|
317
|
+
|
|
318
|
+
- 不配置数据库 URL 时:只展示表、列、PK、唯一约束和关系
|
|
319
|
+
- 配置 `DATABASE_URL` / `--url` 时:增加只读数据浏览(分页、总数、排序、操作符过滤、`with` 关系预览、`some/none` 关系过滤、FK 跳转、M2M through)
|
|
320
|
+
- 配置迁移目录时:侧栏只读展示 `zoddb status --json` 的 applied / pending / missing / out-of-order / empty / tampered / drift 快照
|
|
321
|
+
- 不在 Studio 中执行迁移、DDL 或 SQL 控制台;迁移状态通过公开 CLI 子进程读取
|
|
322
|
+
|
|
323
|
+
Studio 的数据层只使用公开的 `relationGraph(schema)` 和 `zoddb` runtime,因此它也是公开接口的验证器。
|
|
324
|
+
|
|
325
|
+
### 写模式
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npx zoddb studio --write
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- 默认只读;只有 `--write` 才显示新建/编辑/删除/事务入口
|
|
332
|
+
- 写请求走 `POST /api/write`,要求本地 token 和同源请求
|
|
333
|
+
- 支持 create / update / delete / upsert / 嵌套 create / M2M 写入 / transaction
|
|
334
|
+
- 删除需要二次确认;不提供 SQL、DDL 或迁移 apply
|
|
335
|
+
- `npm run verify:pack` 会从 tarball 安装后启动 Studio,验证 schema / rows / migrations 三类请求
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
## 迁移
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
zoddb generate --schema ./src/schema.ts --dir ./migrations --name init
|
|
342
|
+
zoddb migrate --dir ./migrations
|
|
343
|
+
zoddb status --schema ./src/schema.ts --dir ./migrations
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- `generate` 只写文件,不执行;破坏性变更默认拒绝,`--allow-destructive` 显式放行
|
|
347
|
+
- `migrate` 用 advisory lock 串行化,每个文件一个事务
|
|
348
|
+
- 历史记录 `_zoddb_migrations`;缺失、乱序、空文件、checksum 变化默认拒绝
|
|
349
|
+
- 破坏性变更默认拒绝
|
|
350
|
+
|
|
351
|
+
CI / 存量库:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
zoddb migrate --dir ./migrations --dry-run
|
|
355
|
+
zoddb status --schema ./src/schema.ts --dir ./migrations --json
|
|
356
|
+
zoddb baseline --schema ./src/schema.ts --dir ./migrations --through <file>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- `--dry-run` 只读,不写历史、不建业务表
|
|
360
|
+
- `--json` 输出稳定字段;待应用/异常 exit 1
|
|
361
|
+
- `baseline` 只标记 through 及之前的迁移,不执行文件 SQL;默认要求 schema 与库一致
|
|
362
|
+
|
|
363
|
+
## 关系图 IR
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { relationGraph } from '@ailnaf/zoddb'
|
|
367
|
+
|
|
368
|
+
const graph = relationGraph(schema)
|
|
369
|
+
// { nodes, edges };包含反向关系和 M2M through
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
不连接数据库、不执行 SQL,适合可视化与审计。
|
|
373
|
+
|
|
374
|
+
## better-auth
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { betterAuth } from 'better-auth'
|
|
378
|
+
import { authSchema, zodDbAdapter } from '@ailnaf/zoddb/better-auth'
|
|
379
|
+
import { defineSchema, zoddb } from '@ailnaf/zoddb'
|
|
380
|
+
|
|
381
|
+
const auth = authSchema({ plugins: [] })
|
|
382
|
+
const schema = defineSchema({
|
|
383
|
+
nodes: auth.nodes,
|
|
384
|
+
edges: auth.edges,
|
|
385
|
+
})
|
|
386
|
+
const db = zoddb({ schema, url: process.env.DATABASE_URL! })
|
|
387
|
+
|
|
388
|
+
const handler = betterAuth({
|
|
389
|
+
database: zodDbAdapter(db, { schema }),
|
|
390
|
+
emailAndPassword: { enabled: true },
|
|
391
|
+
})
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
- `authSchema` 生成 core 四表;插件表由 plugins 推导
|
|
395
|
+
- `auth.nodes` / `auth.edges` 可直接展开
|
|
396
|
+
- adapter 按 schema 模型名自动映射
|
|
397
|
+
|
|
398
|
+
## 错误
|
|
399
|
+
|
|
400
|
+
| 错误 | 场景 |
|
|
401
|
+
|---|---|
|
|
402
|
+
| `ZodError` | 写入值不符合 zod |
|
|
403
|
+
| `RowValidationError` | 读出的行校验失败 |
|
|
404
|
+
| `UniqueViolationError` | 23505,带 table / columns |
|
|
405
|
+
| `NotNullViolationError` / `ForeignKeyViolationError` | 23502 / 23503 |
|
|
406
|
+
| `NoRowsError` | `.one()` 无结果 |
|
|
407
|
+
| `UndefinedParamError` | SQL 参数是 undefined |
|
|
408
|
+
| `PgError` | 其它 PG 错误 |
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { UniqueViolationError } from '@ailnaf/zoddb'
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
await db.users.create({ email: 'ada@acme.dev', name: 'Ada' })
|
|
415
|
+
} catch (e) {
|
|
416
|
+
if (e instanceof UniqueViolationError) {
|
|
417
|
+
console.log(e.table, e.columns)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## 边界
|
|
423
|
+
|
|
424
|
+
- 公开运行时只有 graph 建模、`zoddb`、`relationGraph` 和错误类型
|
|
425
|
+
- 低层 `table` / DDL / `defineDb` / `relations` / `pg` 是内部实现,不对外
|
|
426
|
+
- 不支持 query builder、懒加载、任意深度递归、关系排序/聚合全套
|
|
427
|
+
- 不支持 down migration、shadow DB、零停机在线 DDL
|
|
428
|
+
- 仅 Postgres
|
|
429
|
+
|
|
430
|
+
公开契约见 [`docs/api.md`](./docs/api.md);发布准备见 [`RELEASING.md`](./RELEASING.md)、变更见 [`CHANGELOG.md`](./CHANGELOG.md)。
|
|
431
|
+
|
|
432
|
+
## 开发
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npm run check # typecheck + 390 个测试
|
|
436
|
+
npm run build # tsc → dist
|
|
437
|
+
npm run verify:pack # 打包安装 smoke + 类型消费
|
|
438
|
+
npm run verify:dogfood # 独立 app 干净安装 + 全链路 dogfood
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
需要本地 PG(默认 `postgres://postgres@127.0.0.1:55432/zoddb_test`,或设 `DATABASE_URL`)。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type postgres from 'postgres';
|
|
2
|
+
import { type WhereFilter } from './filter.js';
|
|
3
|
+
import { type RowOf, type SqlFragment, type Table } from './table.js';
|
|
4
|
+
export type AggKey<T extends Table> = keyof RowOf<T> & string;
|
|
5
|
+
type SumFields<S extends readonly string[]> = S extends readonly [] ? {} : {
|
|
6
|
+
[K in S[number] as `sum_${K}`]: string | null;
|
|
7
|
+
};
|
|
8
|
+
type AvgFields<A extends readonly string[]> = A extends readonly [] ? {} : {
|
|
9
|
+
[K in A[number] as `avg_${K}`]: string | null;
|
|
10
|
+
};
|
|
11
|
+
type MinFields<T extends Table, N extends readonly string[]> = N extends readonly [] ? {} : {
|
|
12
|
+
[K in N[number] as `min_${K}`]: RowOf<T>[Extract<K, keyof RowOf<T>>] | null;
|
|
13
|
+
};
|
|
14
|
+
type MaxFields<T extends Table, X extends readonly string[]> = X extends readonly [] ? {} : {
|
|
15
|
+
[K in X[number] as `max_${K}`]: RowOf<T>[Extract<K, keyof RowOf<T>>] | null;
|
|
16
|
+
};
|
|
17
|
+
/** 聚合结果:分组键 + count + 只包含请求了的聚合字段 */
|
|
18
|
+
export type AggRow<T extends Table, G extends readonly string[] = [], S extends readonly string[] = [], A extends readonly string[] = [], N extends readonly string[] = [], X extends readonly string[] = []> = Pick<RowOf<T>, Extract<G[number], keyof RowOf<T>>> & {
|
|
19
|
+
count: number;
|
|
20
|
+
} & SumFields<S> & AvgFields<A> & MinFields<T, N> & MaxFields<T, X>;
|
|
21
|
+
export interface AggregateOptions<T extends Table> {
|
|
22
|
+
where?: WhereFilter<T>;
|
|
23
|
+
sum?: readonly AggKey<T>[];
|
|
24
|
+
avg?: readonly AggKey<T>[];
|
|
25
|
+
min?: readonly AggKey<T>[];
|
|
26
|
+
max?: readonly AggKey<T>[];
|
|
27
|
+
/** 高级逃生口:如 sql`count(*) > 1` */
|
|
28
|
+
having?: SqlFragment;
|
|
29
|
+
/** 高级逃生口:如 sql`count(*) desc` */
|
|
30
|
+
orderBy?: SqlFragment;
|
|
31
|
+
limit?: number;
|
|
32
|
+
offset?: number;
|
|
33
|
+
}
|
|
34
|
+
export type AggregateRunner = (sql: string, params: readonly unknown[], meta: {
|
|
35
|
+
operation: string;
|
|
36
|
+
table?: string;
|
|
37
|
+
}) => Promise<unknown[]>;
|
|
38
|
+
export declare function aggregateImpl(raw: postgres.Sql, table: Table, opts: AggregateOptions<Table> & {
|
|
39
|
+
groupBy?: readonly string[];
|
|
40
|
+
}, requireGroup: boolean, run?: AggregateRunner): Promise<Array<Record<string, unknown>>>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=aggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { compileWhere } from './filter.js';
|
|
2
|
+
import { isSqlFragment } from './table.js';
|
|
3
|
+
const quoteIdent = (s) => `"${s.replace(/"/g, '""')}"`;
|
|
4
|
+
export async function aggregateImpl(raw, table, opts, requireGroup, run) {
|
|
5
|
+
const sum = opts.sum ?? [];
|
|
6
|
+
const avg = opts.avg ?? [];
|
|
7
|
+
const min = opts.min ?? [];
|
|
8
|
+
const max = opts.max ?? [];
|
|
9
|
+
const groupBy = opts.groupBy ?? [];
|
|
10
|
+
if (requireGroup && groupBy.length === 0)
|
|
11
|
+
throw new Error('groupBy: groupBy 不能为空');
|
|
12
|
+
const planOf = (k) => {
|
|
13
|
+
const p = table.__plan.find(p => p.outKey === k);
|
|
14
|
+
if (!p)
|
|
15
|
+
throw new Error(`aggregate: 未知列 ${table.name}.${k}`);
|
|
16
|
+
return p;
|
|
17
|
+
};
|
|
18
|
+
for (const k of [...groupBy, ...sum, ...avg, ...min, ...max])
|
|
19
|
+
planOf(k);
|
|
20
|
+
const where = opts.where ? compileWhere(table, opts.where) : { text: '', params: [] };
|
|
21
|
+
const params = [...where.params];
|
|
22
|
+
const fragmentSQL = (frag) => {
|
|
23
|
+
if (!frag)
|
|
24
|
+
return '';
|
|
25
|
+
if (!isSqlFragment(frag))
|
|
26
|
+
throw new Error('aggregate: having/orderBy 必须是 sql 片段');
|
|
27
|
+
let out = frag.strings[0] ?? '';
|
|
28
|
+
frag.params.forEach((p, i) => {
|
|
29
|
+
params.push(p);
|
|
30
|
+
out += `$${params.length}${frag.strings[i + 1] ?? ''}`;
|
|
31
|
+
});
|
|
32
|
+
return out;
|
|
33
|
+
};
|
|
34
|
+
const selects = [];
|
|
35
|
+
const groupCols = [];
|
|
36
|
+
for (const k of groupBy) {
|
|
37
|
+
const col = quoteIdent(planOf(k).dbKey);
|
|
38
|
+
groupCols.push(col);
|
|
39
|
+
selects.push(`${col} as ${quoteIdent(k)}`);
|
|
40
|
+
}
|
|
41
|
+
selects.push('count(*)::int as "count"');
|
|
42
|
+
for (const k of sum)
|
|
43
|
+
selects.push(`sum(${quoteIdent(planOf(k).dbKey)})::text as ${quoteIdent(`sum_${k}`)}`);
|
|
44
|
+
for (const k of avg)
|
|
45
|
+
selects.push(`avg(${quoteIdent(planOf(k).dbKey)})::text as ${quoteIdent(`avg_${k}`)}`);
|
|
46
|
+
for (const k of min)
|
|
47
|
+
selects.push(`min(${quoteIdent(planOf(k).dbKey)}) as ${quoteIdent(`min_${k}`)}`);
|
|
48
|
+
for (const k of max)
|
|
49
|
+
selects.push(`max(${quoteIdent(planOf(k).dbKey)}) as ${quoteIdent(`max_${k}`)}`);
|
|
50
|
+
let text = `select ${selects.join(', ')} from ${quoteIdent(table.name)}`;
|
|
51
|
+
if (where.text)
|
|
52
|
+
text += ` where ${where.text}`;
|
|
53
|
+
if (groupCols.length > 0)
|
|
54
|
+
text += ` group by ${groupCols.join(', ')}`;
|
|
55
|
+
const having = fragmentSQL(opts.having);
|
|
56
|
+
if (having)
|
|
57
|
+
text += ` having ${having}`;
|
|
58
|
+
const order = fragmentSQL(opts.orderBy);
|
|
59
|
+
if (order)
|
|
60
|
+
text += ` order by ${order}`;
|
|
61
|
+
if (opts.limit !== undefined) {
|
|
62
|
+
if (!Number.isInteger(opts.limit) || opts.limit < 0)
|
|
63
|
+
throw new Error('limit 必须是非负整数');
|
|
64
|
+
params.push(opts.limit);
|
|
65
|
+
text += ` limit $${params.length}`;
|
|
66
|
+
}
|
|
67
|
+
if (opts.offset !== undefined) {
|
|
68
|
+
if (!Number.isInteger(opts.offset) || opts.offset < 0)
|
|
69
|
+
throw new Error('offset 必须是非负整数');
|
|
70
|
+
params.push(opts.offset);
|
|
71
|
+
text += ` offset $${params.length}`;
|
|
72
|
+
}
|
|
73
|
+
if (run)
|
|
74
|
+
return await run(text, params, { operation: 'aggregate', table: table.name });
|
|
75
|
+
return await raw.unsafe(text, params);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"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"}
|