@morlay/rdbkit 0.0.0-stage → 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/index.d.mts +518 -0
- package/dist/index.mjs +1422 -0
- package/dist/testing.d.mts +28 -0
- package/dist/testing.mjs +42 -0
- package/package.json +49 -3
- package/src/__tests__/database-introspect.spec.ts +71 -0
- package/src/__tests__/expr-functions.spec.ts +64 -0
- package/src/__tests__/migration.spec.ts +81 -0
- package/src/__tests__/pg.spec.ts +123 -0
- package/src/__tests__/query-join.spec.ts +114 -0
- package/src/__tests__/query-p1.spec.ts +108 -0
- package/src/__tests__/query-write.spec.ts +293 -0
- package/src/__tests__/query.spec.ts +88 -0
- package/src/__tests__/schema.spec.ts +144 -0
- package/src/__tests__/transaction.spec.ts +83 -0
- package/src/catalog.ts +24 -0
- package/src/compound.ts +64 -0
- package/src/database/capabilities.ts +70 -0
- package/src/database/create.ts +119 -0
- package/src/database/index.ts +9 -0
- package/src/database/postgres.ts +133 -0
- package/src/database/queue.ts +24 -0
- package/src/database/sqlite.ts +66 -0
- package/src/database/types.ts +87 -0
- package/src/database/wrap.ts +48 -0
- package/src/defs.ts +82 -0
- package/src/dialect.ts +219 -0
- package/src/error.ts +72 -0
- package/src/expr.ts +346 -0
- package/src/fragment.ts +96 -0
- package/src/index.ts +114 -0
- package/src/migrate.ts +133 -0
- package/src/migration.ts +82 -0
- package/src/table/handle.ts +142 -0
- package/src/table/index.ts +14 -0
- package/src/table/render.ts +140 -0
- package/src/table/select.ts +229 -0
- package/src/table/types.ts +53 -0
- package/src/testing.ts +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 morlay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @morlay/rdbkit
|
|
2
|
+
|
|
3
|
+
**表定义驱动的关系库工具包**:一份表定义同时算 DDL、算启动迁移、算类型化的表句柄;SQLite 与 PostgreSQL
|
|
4
|
+
共用一套代码,业务侧只拿到注入的 `Database`,不接触方言、连接或 SQL。
|
|
5
|
+
|
|
6
|
+
## 用法
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { column, defineTable, ensureSchema, eq, openDatabase, value } from "@morlay/rdbkit";
|
|
10
|
+
|
|
11
|
+
const sessions = defineTable({
|
|
12
|
+
name: "t_sessions",
|
|
13
|
+
columns: {
|
|
14
|
+
f_session_id: { type: "text", primaryKey: true },
|
|
15
|
+
f_title: { type: "text" },
|
|
16
|
+
f_created_at: { type: "integer", notNull: true },
|
|
17
|
+
},
|
|
18
|
+
indexes: { idx_sessions_created: { columns: ["f_created_at"] } },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 装配层:连接的生命周期只在这里(和 Golang 的 session 一样,Database 是注入项)。
|
|
22
|
+
const database = await openDatabase({ dialect: "sqlite", path: "/path/sessions.sqlite" });
|
|
23
|
+
await ensureSchema(database, [sessions]);
|
|
24
|
+
|
|
25
|
+
const table = database.table(sessions);
|
|
26
|
+
await table.insert({ f_session_id: "s1", f_created_at: 1 });
|
|
27
|
+
|
|
28
|
+
// 读是迭代器(对应 Golang 的 iter.Seq2):逐行消费,不一次性物化结果集。
|
|
29
|
+
for await (const row of table.select().where(eq(column("f_session_id"), value("s1")))) {
|
|
30
|
+
console.log(row.f_session_id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await database.transaction(async (tx) => {
|
|
34
|
+
await tx
|
|
35
|
+
.table(sessions)
|
|
36
|
+
.update({ f_title: "标题" }, eq(column("f_session_id"), value("s1")));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await database.close();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 值类型
|
|
43
|
+
|
|
44
|
+
列类型到 JS 值是一一映射的,读回来不跟驱动走(SQLite 的 INTEGER 由驱动给 number,PostgreSQL 的 int8
|
|
45
|
+
默认给字符串):
|
|
46
|
+
|
|
47
|
+
| 列类型 | 读出来的值 | 说明 |
|
|
48
|
+
| --------------------------- | ----------- | ------------------------------------------------------------ |
|
|
49
|
+
| `text` | `string` | |
|
|
50
|
+
| `boolean` | `boolean` | PG `BOOLEAN` / SQLite `INTEGER` |
|
|
51
|
+
| `integer` / `serial` | `number` | |
|
|
52
|
+
| `bigint` | **`bigint`** | 64 位整数(Go 的 int64 / uint64):超过 `2^53` 也不能丢精度 |
|
|
53
|
+
| `count()` / `sum()` | `number \| bigint` | PostgreSQL 返回 int8(BigInt),SQLite 返回 number;要 number 就显式 `Number(x)` |
|
|
54
|
+
|
|
55
|
+
写入时 `bigint` 列也接受 `number`(安全整数范围内两者等价),业务不必到处写 `n` 后缀。
|
|
56
|
+
|
|
57
|
+
## 分层
|
|
58
|
+
|
|
59
|
+
| 文件 | 职责 |
|
|
60
|
+
| ------------- | -------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `defs.ts` | 表 / 列 / 索引 / catalog 的定义(唯一事实来源;`defineTable` 保留字面量类型供推导) |
|
|
62
|
+
| `dialect.ts` | 方言:类型映射、标识符、DDL(建表 / 索引 / 加列)、读现有结构(PRAGMA / information_schema) |
|
|
63
|
+
| `fragment.ts` | SQL 片段与参数;渲染时按方言重编号(SQLite `?`、PostgreSQL `$n`),`only` 限定方言 |
|
|
64
|
+
| `expr.ts` | 条件表达式(`eq` / `inList` / `and` / `or` …)与函数表达式,值一律走绑定参数(见下) |
|
|
65
|
+
| `table/` | 表句柄(业务面):插入 / 插入忽略 / upsert / 更新 / 删除 / 迭代查询 / 计数 / 插入选择(`types` / `handle` / `render` / `select`) |
|
|
66
|
+
| `database/` | `Database`(注入项):`table` / `iterate` / `read` / `run` / `transaction` / 库级操作(`types` / `create` / `sqlite` / `postgres` / `capabilities` / `wrap` / `queue`) |
|
|
67
|
+
| `migrate.ts` | 结构差异:`planSchema`(纯函数)与 `ensureSchema`(缺表建表、缺列补列、缺索引补索引) |
|
|
68
|
+
|
|
69
|
+
## 事务
|
|
70
|
+
|
|
71
|
+
`database.transaction(fn)` 在两种方言上语义一致:**顶层事务按实例串行**(SQLite 单连接、PostgreSQL
|
|
72
|
+
`serializable` 并发会冲突),**事务内再开事务走保存点**(内层失败只回滚内层)。事务内的写自动算这一段事务的
|
|
73
|
+
一部分;事务外的写先排队,不会落进别人未提交的事务里。
|
|
74
|
+
|
|
75
|
+
## 库级操作
|
|
76
|
+
|
|
77
|
+
业务侧不该写 `PRAGMA` / `VACUUM` / `sqlite_master`,这些都收在 `Database` 上:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await database.objectNames(); // 现有的表与视图名
|
|
81
|
+
await database.vacuum(); // SQLite `VACUUM` / PostgreSQL `VACUUM ANALYZE`
|
|
82
|
+
await database.sqlitePragma("user_version"); // 读库身份(仅 SQLite)
|
|
83
|
+
await database.setSqlitePragma("user_version", 4); // 写库身份(值只允许安全字面量)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## 迁移
|
|
88
|
+
|
|
89
|
+
结构(建表 / 补列 / 补索引)由 `ensureSchema` 从表定义算差异;**数据迁移**走注册表,账本是内置的
|
|
90
|
+
`t_schema_migrations`:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const migrations = new MigrationRegistry().register({
|
|
94
|
+
id: "20261103-move-legacy-workspaces",
|
|
95
|
+
up: async (tx) => {
|
|
96
|
+
await tx.table(records).insertMany(rows);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await runMigrations(database, { catalog, migrations }); // 先对齐结构,再按 id 顺序跑没记过的
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
每条迁移在**同一个事务**里执行并记账,失败即回滚、不记账,重跑只跑没记过的。
|
|
104
|
+
|
|
105
|
+
## 表达式(`E`)
|
|
106
|
+
|
|
107
|
+
条件:`E.eq` / `E.ne` / `E.like` / `E.inList` / `E.isNull` / `E.always` / `E.inSubquery` / `E.exists` /
|
|
108
|
+
`E.and` / `E.or` / `E.not` …
|
|
109
|
+
|
|
110
|
+
函数与方言无关写法(业务侧不必再写裸 SQL):
|
|
111
|
+
|
|
112
|
+
| 写法 | 作用 |
|
|
113
|
+
| --------------------- | -------------------------------------------------------- |
|
|
114
|
+
| `E.coalesce` | `COALESCE(a, b, …)` |
|
|
115
|
+
| `E.lower` | `lower(x)` |
|
|
116
|
+
| `E.add(a, b)` | `a + b`(列自增、统计累加) |
|
|
117
|
+
| `E.excluded("f_x")` | upsert 冲突时的 `excluded."f_x"` |
|
|
118
|
+
| `E.toInt(cond)` | 条件当整数列(`CASE WHEN cond THEN 1 ELSE 0 END`) |
|
|
119
|
+
| `E.countIf(cond)` | 条件计数(`SUM(CASE WHEN … THEN 1 ELSE 0 END)`) |
|
|
120
|
+
| `E.maxOf(a, b)` | 只增不减(NULL 视作 0;`CASE` 写法两方言同一份) |
|
|
121
|
+
| `E.localDay(col)` | 毫秒时间戳 → 本地日 `YYYY-MM-DD` |
|
|
122
|
+
| `E.jsonText(col, p)` | JSON 文本提取(SQLite `json_extract`、PG `#>>`) |
|
|
123
|
+
| `E.jsonInt(col, p)` | JSON 整数提取(路径形如 `$.data.usage.inputTokens`) |
|
|
124
|
+
|
|
125
|
+
`raw(sql, params)` 是唯一逃生口,业务包里不该出现它。
|
|
126
|
+
|
|
127
|
+
## PostgreSQL 契约测试
|
|
128
|
+
|
|
129
|
+
`tool/pg` 是本地 dev 实例(compose),根目录 `just pg test` 会先起服务再跑 `src/__tests__/pg.spec.ts`;
|
|
130
|
+
不设 `TEST_PG_URL` 时这些用例自动跳过。
|
|
131
|
+
|
|
132
|
+
## 测试辅助
|
|
133
|
+
|
|
134
|
+
`@morlay/rdbkit/testing` 收着测试需要的驱动能力,业务包不必为测试再依赖 `pg` / `node:sqlite`:
|
|
135
|
+
|
|
136
|
+
- `openSqliteHandle(path, options)`:同步 SQLite 句柄(造损坏数据、读 `PRAGMA`);
|
|
137
|
+
- `createPostgresTestDatabase(options)`:建一个随机名的临时 PG 库,返回连接串与 `drop()`。
|
|
138
|
+
|
|
139
|
+
## 边界
|
|
140
|
+
|
|
141
|
+
- **不写 SQL**:查询、写入、DDL、迁移都从定义算出来;方言相关的东西(JSON 提取、本地日、PRAGMA)由
|
|
142
|
+
rdbkit 渲染,业务侧只调方法。
|
|
143
|
+
- **迁移只做增**:缺表建表、缺列补列、缺索引补索引;删列只在显式 `allowDestructive: true` 时才做,
|
|
144
|
+
不改类型、不删索引(旧库升级不丢数据)。
|
|
145
|
+
- **业务不认数据库**:`Database` 是注入项,方言只在装配层出现;换库不改业务代码。
|
|
146
|
+
- 不做 ORM 的关系映射、不做查询缓存、不做跨库事务编排;`raw(sql, params)` 是唯一的逃生口。
|
|
2
147
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|