@oj-module/cli 0.2.6 → 0.2.14
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/README.md +1 -1
- package/devkit/README.md +70 -24
- package/devkit/framework-dev.md +14 -5
- package/devkit/manual.md +28 -23
- package/devkit/module-dev-and-cdn-playground.md +197 -0
- package/devkit/sql-builder-missing-capabilities-pr.md +268 -0
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +130 -45
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +64 -0
- package/src/__tests__/versions.test.ts +26 -0
- package/src/build.ts +9 -0
- package/src/bundle.ts +8 -5
- package/src/contract/__tests__/handler-scan.test.ts +29 -0
- package/src/contract/__tests__/market-client.integration.test.ts +170 -0
- package/src/contract/handler-scan.ts +35 -4
- package/src/dev.ts +10 -2
- package/src/index.ts +9 -3
- package/src/init.ts +24 -0
- package/src/lifecycle/__tests__/bundle.test.ts +3 -3
- package/src/market/__tests__/bootstrap.test.ts +204 -0
- package/src/market/__tests__/build-index.test.ts +154 -0
- package/src/market/__tests__/cdn-layout.test.ts +96 -0
- package/src/market/__tests__/failure-playbook.test.ts +33 -0
- package/src/market/__tests__/fixture.test.ts +48 -0
- package/src/market/__tests__/publish.test.ts +109 -0
- package/src/market/__tests__/report.test.ts +51 -0
- package/src/market/__tests__/setup.test.ts +114 -0
- package/src/market/__tests__/verify.test.ts +111 -0
- package/src/market/bootstrap.ts +255 -0
- package/src/market/build-index.ts +159 -0
- package/src/market/cdn-layout.ts +122 -0
- package/src/market/command.ts +139 -0
- package/src/market/failure-playbook.ts +57 -0
- package/src/market/fixture.ts +138 -0
- package/src/market/index-builder.ts +76 -0
- package/src/market/publish.ts +156 -0
- package/src/market/report.ts +67 -0
- package/src/market/scan.ts +99 -0
- package/src/market/setup.ts +144 -0
- package/src/market/trial.ts +121 -0
- package/src/market/verify.ts +298 -0
- package/src/market/walkthrough.ts +506 -0
- package/src/oj.ts +3 -1
- package/src/preview-config.ts +177 -0
- package/src/preview.ts +106 -56
- package/src/sign.ts +21 -6
- package/src/usage.ts +22 -2
- package/src/vendor.ts +4 -2
- package/src/versions.ts +28 -0
- package/templates/api/.ojm-api-exempt.json +6 -2
- package/templates/api/config.yaml +24 -2
- package/templates/api/src/_platform/README.md +31 -0
- package/templates/api/src/_platform/_lifecycle/atomic.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/history.ts +10 -5
- package/templates/api/src/_platform/_lifecycle/installer.ts +4 -4
- package/templates/api/src/_platform/_lifecycle/sha384.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/steps/download.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/steps/extract.ts +8 -8
- package/templates/api/src/_platform/_lifecycle/steps/register.ts +57 -29
- package/templates/api/src/_platform/_lifecycle/trust.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/uninstall.ts +29 -56
- package/templates/api/src/_platform/_lifecycle/verify-checks.ts +2 -2
- package/templates/api/src/_platform/_shared/service/{contacts.ts → domain/contacts.ts} +20 -13
- package/templates/api/src/_platform/_shared/service/{roles.ts → domain/roles.ts} +18 -19
- package/templates/api/src/_platform/_shared/service/{settings.ts → domain/settings.ts} +6 -6
- package/templates/api/src/_platform/_shared/service/{solution-perms.ts → domain/solution-perms.ts} +25 -22
- package/templates/api/src/_platform/_shared/service/{users.ts → domain/users.ts} +20 -26
- package/templates/api/src/_platform/_shared/service/{access.ts → infra/access.ts} +5 -8
- package/templates/api/src/_platform/_shared/service/{audit.ts → infra/audit.ts} +26 -26
- package/templates/api/src/_platform/_shared/service/infra/oplog.ts +40 -0
- package/templates/api/src/_platform/_shared/service/{query.ts → infra/query.ts} +18 -0
- package/templates/api/src/_platform/_shared/service/{rate.ts → infra/rate.ts} +16 -13
- package/templates/api/src/_platform/_shared/site-facade.ts +1 -1
- package/templates/api/src/_platform/contacts/api.ts +2 -2
- package/templates/api/src/_platform/contacts/detail/api.ts +2 -2
- package/templates/api/src/_platform/fixtures/l1-test-user.sql +13 -0
- package/templates/api/src/_platform/manifest.yaml +4 -1
- package/templates/api/src/_platform/market/api.ts +54 -16
- package/templates/api/src/_platform/migrations/0013__users_profile.sql +4 -0
- package/templates/api/src/_platform/schema.yaml +26 -0
- package/templates/api/src/_platform/solutions/api.ts +13 -12
- package/templates/api/src/auth/_shared/ldap-login.ts +11 -7
- package/templates/api/src/auth/_shared/login-throttle.ts +2 -2
- package/templates/api/src/auth/_shared/session.ts +26 -22
- package/templates/api/src/auth/api.ts +1 -1
- package/templates/api/src/demo/todos/api.ts +8 -8
- package/templates/api/src/home/line/api.ts +4 -4
- package/templates/api/src/home/pie/api.ts +4 -4
- package/templates/api/src/notification/_shared/notify.ts +97 -57
- package/templates/api/src/notification/api.ts +1 -1
- package/templates/api/src/notification/manifest.yaml +1 -0
- package/templates/api/src/notification/migrations/0004__notifications_broadcast_hidden.sql +12 -0
- package/templates/api/src/notification/schema.yaml +7 -0
- package/templates/api/src/notification/seed.sql +5 -4
- package/templates/api/src/personal-center/profile/api.ts +63 -0
- package/templates/api/src/personal-center/upload/api.ts +1 -1
- package/templates/api/src/system/login-methods/api.ts +1 -1
- package/templates/api/src/system/oplog/api.ts +3 -3
- package/templates/api/src/system/permissions/api.ts +2 -2
- package/templates/api/src/system/roles/api.ts +2 -2
- package/templates/api/src/system/roles/bindings/api.ts +2 -2
- package/templates/api/src/system/settings/api.ts +2 -2
- package/templates/api/src/system/users/api.ts +2 -2
- package/templates/api/src/system/users/forbidden/api.ts +2 -2
- package/templates/api/src/system/users/password/api.ts +2 -2
- package/templates/api/src/system/users/roles/api.ts +2 -2
- package/templates/api/src/web/user-info/api.ts +4 -5
- package/templates/api/tests/auth-session.spec.ts +1 -1
- package/templates/api/tests/auth-throttle.spec.ts +1 -1
- package/templates/api/tests/fixture.ts +8 -16
- package/templates/api/tests/guard.spec.ts +1 -1
- package/templates/api/tests/l1-builder.test.ts +91 -0
- package/templates/api/tests/lifecycle/installer.spec.ts +1 -4
- package/templates/api/tests/lifecycle/market-api.spec.ts +2 -4
- package/templates/api/tests/lifecycle/steps-fs.spec.ts +5 -8
- package/templates/api/tests/lifecycle/trust.spec.ts +1 -1
- package/templates/api/tests/lifecycle/uninstall.spec.ts +29 -89
- package/templates/api/tests/query-utils.spec.ts +85 -1
- package/templates/api/tests/rbac-audit.spec.ts +1 -1
- package/templates/api/tests/rbac-contacts.spec.ts +2 -2
- package/templates/api/tests/rbac-guard.spec.ts +1 -1
- package/templates/api/tests/rbac-roles.spec.ts +2 -2
- package/templates/api/tests/rbac-settings.spec.ts +66 -21
- package/templates/api/tests/rbac-solution-perms.spec.ts +1 -1
- package/templates/api/tests/rbac-users.spec.ts +1 -1
- package/templates/api/tsconfig.json +8 -0
- package/templates/cdn/README.md +48 -0
- package/templates/env.d.ts +2 -0
- package/templates/global.d.ts +850 -0
- package/templates/init.include.yaml +14 -2
- package/templates/logs/task-events.jsonl +0 -0
- package/templates/openapi.json +580 -23
- package/templates/package.json +2 -1
- package/templates/web/src/_platform/__client/api.schemas.ts +97 -0
- package/templates/web/src/_platform/__client/api.ts +152 -39
- package/templates/web/src/notification/entry.ts +46 -9
- package/templates/web/src/notification/locales/en-US.json +19 -0
- package/templates/web/src/notification/locales/zh-CN.json +19 -0
- package/templates/web/src/notification/pages/center/index.tsx +131 -0
- package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +19 -0
- package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +3 -0
- package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +1 -0
- package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +1 -0
- package/templates/web/src/oj-market/api/client.ts +5 -105
- package/templates/web/src/oj-market/api/defaultClient.ts +111 -5
- package/templates/web/src/oj-market/components/CardGrid.tsx +4 -1
- package/templates/web/src/oj-market/components/CardItem.tsx +9 -5
- package/templates/web/src/oj-market/components/DetailDrawer.tsx +7 -3
- package/templates/web/src/oj-market/components/InstallDialog.tsx +1 -1
- package/templates/web/src/oj-market/entry.ts +8 -2
- package/templates/web/src/oj-market/hooks/useMarket.ts +4 -1
- package/templates/web/src/oj-market/locales/en-US.json +2 -1
- package/templates/web/src/oj-market/locales/zh-CN.json +2 -1
- package/templates/web/src/oj-market/pages/MarketApp.tsx +16 -1
- package/templates/web/src/personal-center/__client/api.schemas.ts +9 -0
- package/templates/web/src/personal-center/__client/api.ts +25 -1
- package/templates/web/src/personal-center/entry.ts +5 -0
- package/templates/web/src/personal-center/pages/my-profile/index.tsx +25 -3
- package/templates/web/src/system/entry.ts +2 -1
- package/templates/web.config.ts +13 -10
- package/vendor/host-versions.json +3 -3
- package/templates/api/src/_platform/_shared/service/oplog.ts +0 -47
- package/templates/api/src/notifications/api.ts +0 -32
- package/templates/api/src/notifications/manifest.yaml +0 -5
- package/templates/web/src/notifications/__client/api.schemas.ts +0 -8
- package/templates/web/src/notifications/__client/api.ts +0 -80
- /package/templates/api/src/_platform/_shared/service/{guard.ts → infra/guard.ts} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# 上游 PR 提案:SQL 构造器缺失能力清单
|
|
2
|
+
|
|
3
|
+
> **闭环状态(2026-10-20):7 项诉求全部闭环**——6 项随 oj 0.1.81/0.1.82 发布落地,第 4 项
|
|
4
|
+
> (orIgnore + fromSelect)能力本身已发布但**组合在 sqlite 上被上游明文拒绝**,模板按上游
|
|
5
|
+
> 文档指引保留一处 sanctioned 裸 SQL 绕行(`notify.ts:216`,api-manual §"orIgnore + fromSelect
|
|
6
|
+
> 组合在 sqlite 不支持"原文即指向该绕行)。逐项映射见 §6;模板 `api/src` 内 `保留 db.query`
|
|
7
|
+
> 注释已归零(仅剩上述 1 处 `保留 db.exec`)。
|
|
8
|
+
>
|
|
9
|
+
> 目的:把模板 `db.query` 全面迁移到 `db.table(...)` 构造器以获得多库(sqlite / mysql / postgres)可移植性时,
|
|
10
|
+
> 识别出构造器**当时无法表达**、只能回退 `db.query` 的 7 类能力。本文档作为向上游 `oj` 运行时 SQL 构造器提 PR 的依据。
|
|
11
|
+
>
|
|
12
|
+
> 证据来源:`~/.local/share/oj/devkit/api-manual.md`(查询构造器章节,含 `like` 操作清单 §1142、子查询限制 §1343、
|
|
13
|
+
> `union` §1346、CTE §1378 等);框架 `README.md` v0.1.66(ESCAPE 纪律);模板代码 `packages/cli/templates/api/src/**`。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. 背景与现状
|
|
18
|
+
|
|
19
|
+
模板已完成 `db.query` → `db.table()` 的主体迁移:约 96 处查询/写入迁移到构造器,但仍有 **17 处**因构造器能力缺口被迫保留
|
|
20
|
+
`db.query`(含注释 `保留 db.query:…`)。这些缺口若不上游补齐,模板永远无法做到"零原生 SQL"。
|
|
21
|
+
|
|
22
|
+
构造器已具备:`select/where/orderBy/limit/offset/all`、`insert/update/delete/returning/run`、
|
|
23
|
+
`where` 树(`and/or/not`)、`in/like/isnull/eq…` 比较、`union`/`union all`、`case`/`window` 列、`with` CTE、嵌套子查询(深度≤4)。
|
|
24
|
+
|
|
25
|
+
缺口集中在**表达能力**而非基础 CRUD。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 缺失能力总览(按出现频率与阻塞面排序)
|
|
30
|
+
|
|
31
|
+
| # | 缺失能力 | 阻塞面 | 当前回退点(模板) | 优先级 |
|
|
32
|
+
|---|----------|--------|--------------------|--------|
|
|
33
|
+
| 1 | `like` 的 `ESCAPE` 转义子句 | 模糊搜索全场景(用户/方案/联系人/目录) | `contacts.ts:20`、`solution-perms.ts:100`、`uninstall.ts:76/82/116` | P0 |
|
|
34
|
+
| 2 | `join ... ON` 带**值条件**(`AND user_id = ?`) | 通知合并查询(broadcast LEFT JOIN 回执) | `notify.ts:135-167` | P0 |
|
|
35
|
+
| 3 | `select` 支持**字面量/计算列**(`'user' as scope`、`(x IS NOT NULL…) as y`) | 通知合并 + 角色作用域标记 | `notify.ts:144/148` | P1 |
|
|
36
|
+
| 4 | `INSERT OR IGNORE` / `INSERT … SELECT` | 通知已读回执幂等写入 | `notify.ts:167-169/186-188` | P1 |
|
|
37
|
+
| 5 | **全表 delete 的强制 where 豁免** | 登录限流重置等有意清空 | `rate.ts:78` | P2 |
|
|
38
|
+
| 6 | **动态 WHERE** 组合(运行时按可选参数拼接) | 多过滤项搜索、动态 IN | `demo/todos/api.ts:12`、`uninstall.ts:166` | P2 |
|
|
39
|
+
| 7 | `select *`(无列清单) | 刷新令牌等整行读取 | `session.ts:114-115` | P3 |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. 逐项详述
|
|
44
|
+
|
|
45
|
+
### 3.1 `like` 缺 `ESCAPE` 子句(P0)
|
|
46
|
+
|
|
47
|
+
> **订正(2026-10-20,上游复核结论)**:本节原判「`WhereCond` 的 `like` 不接受 `escape` 字段」
|
|
48
|
+
> 是**误报**——`like` 的 `escape` 逃生舱本就存在(白名单 `! / | ~ # @`、显式拒 `\`,见
|
|
49
|
+
> devkit `sql-builder-guide.md` §3.5)。真正缺的不是逃生舱,而是**业务零转义的语义化模糊匹配**:
|
|
50
|
+
> 每个调用点手写 `%!v!%` 式转义模式易错且三方言心智负担重。上游按此落地
|
|
51
|
+
> `contains` / `startswith` / `endswith`(v0.1.81),本节诉求以更彻底的形式闭环。
|
|
52
|
+
|
|
53
|
+
**为什么曾是硬约束**:框架对模糊搜索有强制纪律——`LIKE` 元字符必须转义,且**不能用 `ESCAPE '\'`**
|
|
54
|
+
(mysql 默认 `sql_mode` 下 `ESCAPE '\'` 直接 1064)。当时判定构造器无出口,各调用点只能回退
|
|
55
|
+
`db.query` 手写 `LIKE ? ESCAPE '!'`。
|
|
56
|
+
|
|
57
|
+
**阻塞的真实代码**(`contacts.ts:20`):
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// 保留 db.query:模糊搜索需 ESCAPE '!'(构造器 like 无 escape 子句;mysql 默认 sql_mode 下 ESCAPE '\' 即 1064)
|
|
61
|
+
const rows = await db.query(
|
|
62
|
+
`SELECT * FROM contacts WHERE name LIKE ? ESCAPE '!' OR phone LIKE ? ESCAPE '!'`,
|
|
63
|
+
[pat, pat],
|
|
64
|
+
) as unknown as ContactRow[];
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**提议 API**:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// 方案 A:WhereCond 增加 escape 字段
|
|
71
|
+
db.table("contacts").where({ field: "name", op: "like", value: pat, escape: "!" }).all();
|
|
72
|
+
// 方案 B:链式 like 三参
|
|
73
|
+
db.table("contacts").where(c => c.like("name", pat, "!").or.like("phone", pat, "!")).all();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
引擎侧:sqlite 原样 `ESCAPE '!'`;mysql/postgres 同语义转写。**务必锁定 `!` 为默认转义符**,杜绝 `'\'`。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 3.2 `join ... ON` 仅支持列=列,不支持值条件(P0)
|
|
81
|
+
|
|
82
|
+
**阻塞的真实代码**(`notify.ts:150`,广播通知合并读取):
|
|
83
|
+
|
|
84
|
+
```sql
|
|
85
|
+
SELECT b.id, …, (r.read_time IS NOT NULL AND r.read_time <> '') as is_read, …, 'broadcast' as scope
|
|
86
|
+
FROM notifications_broadcast b
|
|
87
|
+
LEFT JOIN notifications_broadcast_read r ON r.broadcast_id = b.id AND r.user_id = ?
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`api-manual.md` 的 join `on` 仅接受 `{ left, right }`(列=列)。`AND r.user_id = ?` 是**值条件**,构造器无法表达。
|
|
91
|
+
|
|
92
|
+
**提议 API**:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
db.table("notifications_broadcast as b")
|
|
96
|
+
.leftJoin("notifications_broadcast_read as r",
|
|
97
|
+
on => on.on("r.broadcast_id", "b.id").and("r.user_id", "=", uid))
|
|
98
|
+
.select([...])
|
|
99
|
+
.all();
|
|
100
|
+
// on() 第二形参支持 op + value:.and("r.user_id", "=", uid)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### 3.3 `select` 不支持字面量 / 计算列(P1)
|
|
106
|
+
|
|
107
|
+
**阻塞的真实代码**(`notify.ts:144/148`):
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
SELECT id, avatar, date, is_read, message, title, link, category, 'user' as scope
|
|
111
|
+
FROM notifications WHERE user_id = ?
|
|
112
|
+
UNION ALL
|
|
113
|
+
SELECT b.id, …, (r.read_time IS NOT NULL AND r.read_time <> '') as is_read, …, 'broadcast' as scope
|
|
114
|
+
FROM notifications_broadcast b LEFT JOIN …
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`'user' as scope`(字面量列)与 `(r.read_time IS NOT NULL AND r.read_time <> '') as is_read`(计算列)均无法用构造器 `select([...])`
|
|
118
|
+
表达(该 API 当前只收列名字符串或 `{ fn, field, as }` 聚合)。
|
|
119
|
+
|
|
120
|
+
**提议 API**:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
.select([
|
|
124
|
+
"id", "avatar", "date",
|
|
125
|
+
{ expr: "'user'", as: "scope" }, // 字面量
|
|
126
|
+
{ expr: "(r.read_time IS NOT NULL AND r.read_time <> '')", as: "is_read" }, // 计算列
|
|
127
|
+
])
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> 注:`case`/`window` 列(§1360)已支持,但**任意表达式 + as 别名**维度缺失,建议补齐 `expr` 入口。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 3.4 缺 `INSERT OR IGNORE` / `INSERT … SELECT`(P1)
|
|
135
|
+
|
|
136
|
+
**阻塞的真实代码**(`notify.ts:167-169` 与 `186-188`):
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// 保留 db.exec:INSERT OR IGNORE 构造器无等价(引擎按方言改写 IGNORE)
|
|
140
|
+
await d.exec("INSERT OR IGNORE INTO notifications_broadcast_read (broadcast_id, user_id, read_time) VALUES (?, ?, ?)", [...]);
|
|
141
|
+
// 保留 db.exec:INSERT OR IGNORE ... SELECT 构造器无法表达
|
|
142
|
+
await d.exec("INSERT OR IGNORE INTO notifications_broadcast_read (broadcast_id, user_id, read_time) SELECT id, ?, ? FROM notifications_broadcast", [...]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`INSERT OR IGNORE` 是 sqlite 方言;mysql 对应 `INSERT … ON DUPLICATE KEY IGNORE`/忽略,postgres 对应 `ON CONFLICT DO NOTHING`。
|
|
146
|
+
构造器当前 `insert()` 无 `orIgnore()` 修饰,也无 `fromSelect()`。
|
|
147
|
+
|
|
148
|
+
**提议 API**:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
db.table("notifications_broadcast_read")
|
|
152
|
+
.insert({ broadcast_id, user_id, read_time })
|
|
153
|
+
.orIgnore() // 引擎按方言转写:sqlite OR IGNORE / pg ON CONFLICT DO NOTHING / mysql 忽略重复
|
|
154
|
+
.run();
|
|
155
|
+
|
|
156
|
+
db.table("notifications_broadcast_read")
|
|
157
|
+
.insert(["broadcast_id", "user_id", "read_time"])
|
|
158
|
+
.fromSelect(db.table("notifications_broadcast").select(["id", lit(uid), lit(now)]))
|
|
159
|
+
.orIgnore()
|
|
160
|
+
.run();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### 3.5 全表 delete 强制 where 无豁免(P2)
|
|
166
|
+
|
|
167
|
+
**阻塞的真实代码**(`rate.ts:78`,登录限流重置——有意清空整张表):
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
await db.exec("delete from sys_login_throttle", []);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
构造器在运行时抛 `"update requires where"`(delete 同理强制 where)。清空整表是合法运维动作,需显式豁免。
|
|
174
|
+
|
|
175
|
+
**提议 API**:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
db.table("sys_login_throttle").delete().where({ __all: true }).run();
|
|
179
|
+
// 或显式逃逸:.delete().run({ force: true })
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
命名上建议用**显眼且不可误用**的写法规避"忘了加 where 误清表",例如要求 `where({ __all: true })` 或 `deleteAll()`。
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### 3.6 动态 WHERE 组合无安全出口(P2)
|
|
187
|
+
|
|
188
|
+
**阻塞的真实代码**(`demo/todos/api.ts:12`、`uninstall.ts:166`):
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// 保留 db.query:WHERE 由 JS 字符串拼接动态生成(规则5)
|
|
192
|
+
// 保留 db.query:IN 占位符经 JS 字符串拼接动态生成(动态 SQL),构造器 in 仅接受静态数组值
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
运行时按可选过滤参数拼接 WHERE(如"给了 status 才加 `status = ?`"、动态 IN 长度)超出了构造器静态 `where(...)` 树。
|
|
196
|
+
|
|
197
|
+
**提议 API**(安全逃逸舱,而非放开字符串拼接):
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
db.table("todos")
|
|
201
|
+
.where(sql`status = ? AND owner = ?`, [status, owner]) // 类型安全参数化片段
|
|
202
|
+
.all();
|
|
203
|
+
// 或 builder 拼接:.whereIf(cond, c => c.eq("status", v))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
> 反对直接暴露 `where(rawString, params)` 给业务层——保留给平台内部/高级场景,避免回到拼接 SQL 的老路。
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### 3.7 `select *` 无整行读取(P3)
|
|
211
|
+
|
|
212
|
+
**阻塞的真实代码**(`session.ts:114-115`):
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
// 保留 db.query:select * 无已知列清单(规则6)
|
|
216
|
+
const rows = await db.query("select * from sys_refresh_token where token_hash = ?", [hash]) …;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**提议 API**:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
db.table("sys_refresh_token").select("*").where({ field: "token_hash", op: "eq", value: hash }).all();
|
|
223
|
+
// 或省略 select,默认整行:.all()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 4. 不在本文范围
|
|
229
|
+
|
|
230
|
+
- **方言无关的表达式求值**:计算列目前建议以 `expr` 字符串透传(由引擎保证参数化安全),不要求构造器解析表达式语义。
|
|
231
|
+
- **存储过程 / 批处理 / 事务编排**:与可移植性目标无关。
|
|
232
|
+
- **`db.query` 下架**:补齐上述能力后模板侧可全量迁移,下架是后续动作,不在本 PR 提案内。
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 5. 验收建议(上游实现后模板侧可关闭的回退点)
|
|
237
|
+
|
|
238
|
+
1. `contacts.ts:20` 模糊搜索 → 改用 `like` + `escape`。
|
|
239
|
+
2. `solution-perms.ts:100` / `uninstall.ts:76/82/116` 目录与权限 LIKE → 同上。
|
|
240
|
+
3. `notify.ts:135-167` 通知合并 → `leftJoin` 值条件 + `expr` 列。
|
|
241
|
+
4. `notify.ts:167-188` 已读回执 → `orIgnore()` / `fromSelect()`。
|
|
242
|
+
5. `rate.ts:78` 限流重置 → `delete().where({ __all: true })`。
|
|
243
|
+
6. `demo/todos/api.ts:12` / `uninstall.ts:166` → 动态 WHERE 安全出口。
|
|
244
|
+
7. `session.ts:114-115` → `select("*")`。
|
|
245
|
+
|
|
246
|
+
全部关闭后,模板 `api/src` 内 `保留 db.query` / `保留 d.exec` 注释应归零,达成"零原生 SQL 可移植"目标。
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 6. 闭环状态(2026-10-20 实测)
|
|
251
|
+
|
|
252
|
+
| # | 诉求 | 上游落地(版本) | 模板现状 |
|
|
253
|
+
|---|------|------------------|----------|
|
|
254
|
+
| 1 | `like` 的 `ESCAPE` 转义子句 | `contains` / `startswith` / `endswith` 语义化模糊匹配(**v0.1.81**),引擎自动转义 `%`/`_`/`!` | 已闭环:`contacts.ts` 等模糊搜索全部走 `contains`。**订正**:原判「`like` 无 escape 出口」系误报——escape 逃生舱本就存在(白名单 `! / | ~ # @`);真正缺的是业务零转义语义 op,上游按此落地(L1 实测 `%` 元字符不泛化) |
|
|
255
|
+
| 2 | `join ... ON` 值条件 | join 支持表别名 + ON 值条件(**v0.1.81**):`{ left, op, value }` 混排列对列/列对常量 | 已闭环:`notify.ts:165-168` 广播 LEFT JOIN `r.user_id = ?` 直表达 |
|
|
256
|
+
| 3 | `select` 字面量/计算列 | 字面量列 `{ lit, as }`(**v0.1.81**,值走绑定参数);`expr` 任意表达式入口经上游四路评审**砍除**(计算列用现成 `case`) | 字面量部分已闭环:`notify.ts:163/177` 的 `'user'/'broadcast' as scope`。**计算列子项被设计化解掉**:广播分支第 4 列直接取 `r.read_time`,经 UNION 位置对齐落 `is_read` 列名,是否已读由 `mapItem` 按非空推导(`notify.ts:69-88` 注释同源说明)——模板侧无 `expr` 诉求残留 |
|
|
257
|
+
| 4 | `INSERT OR IGNORE` / `INSERT … SELECT` | `orIgnore()` 与 `fromSelect(q, cols)` 均落地(**v0.1.81**);但**组合在 sqlite 上被明文拒绝**(v0.1.82 文档:`orIgnore with fromSelect is not supported on sqlite`,报错原文即指引绕行) | 能力闭环 + 一处 sanctioned 绕行:VALUES 形态 upsert 已全量构造器化(`markRead` 等);仅 `markAllRead` 的 `INSERT OR IGNORE … SELECT`(`notify.ts:216-223`)保留裸 SQL——三方言通用的单语句,正是上游文档点名的绕行姿势 |
|
|
258
|
+
| 5 | 全表 delete 强制 where 豁免 | `truncate()`(**v0.1.82**):显式整表清空,渲染 `DELETE FROM t`,三方言通用 | 已闭环:`rate.ts` 限流重置改 `truncate()`;未采纳 `where({ __all: true })`(独立动词比魔法 where 值更不可误用) |
|
|
259
|
+
| 6 | 动态 WHERE 组合 | `whereIf(test, cond)`(**v0.1.81**):`undefined/null/false/[]` 整条跳过(注意 `""`/`0` 是合法值会保留——可选过滤的缺失值必须归一化为 `undefined`,模板 oplog 曾踩此坑并修复) | 已闭环:`demo/todos`、`uninstall.ts` 等全部改 `whereIf`;未采纳裸片段出口(whereIf 已覆盖诉求场景) |
|
|
260
|
+
| 7 | `select *` | `select(["*"])`(**v0.1.81**,必须是唯一元素) | 已闭环:`session.ts` refresh 查询改 `select(["*"])` |
|
|
261
|
+
|
|
262
|
+
**结论**:模板 `api/src` 内 `保留 db.query` 注释归零;`保留 d.exec` 仅剩 `notify.ts:216` 一处,
|
|
263
|
+
且属上游文档明文记载的 sqlite 组合限制绕行(非能力缺口)。"零原生 SQL 可移植"目标以
|
|
264
|
+
「一处上游 sanctioned 绕行」的形态达成。
|
|
265
|
+
|
|
266
|
+
> 上游侧复核记录(oj 0.1.81 release notes,devkit `oj-api-dev/README.md`):7 条诉求落地时
|
|
267
|
+
> 判定「1 条误报、1 条部分误报」——误报即 §3.1 的 escape 出口(本就存在),部分误报即 §3.3
|
|
268
|
+
> 的计算列(`expr` 被砍,`case` 已够用);另附赠表别名 `as()`(#8)。本表与该记录口径一致。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.14",
|
|
5
5
|
"description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@faker-js/faker": "^10.4.0",
|
|
52
|
-
"@oj-module/runtime": "0.2.
|
|
52
|
+
"@oj-module/runtime": "0.2.14",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|