@oj-module/cli 0.2.5 → 0.2.8

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.
Files changed (105) hide show
  1. package/README.md +1 -1
  2. package/devkit/manual.md +19 -17
  3. package/devkit/sql-builder-missing-capabilities-pr.md +234 -0
  4. package/package.json +2 -2
  5. package/shell-dist/assets/runtime.js +2 -2
  6. package/shell-dist/index.html +2 -2
  7. package/shell-dist/versions.json +3 -3
  8. package/src/contract/__tests__/handler-scan.test.ts +29 -0
  9. package/src/contract/__tests__/market-client.integration.test.ts +167 -0
  10. package/src/contract/handler-scan.ts +35 -4
  11. package/src/dev.ts +10 -2
  12. package/src/index.ts +3 -3
  13. package/src/init.ts +44 -0
  14. package/src/preview-config.ts +163 -0
  15. package/src/preview.ts +105 -56
  16. package/src/usage.ts +3 -2
  17. package/templates/api/config.yaml +4 -2
  18. package/templates/api/src/_platform/README.md +31 -0
  19. package/templates/api/src/_platform/_lifecycle/atomic.ts +1 -1
  20. package/templates/api/src/_platform/_lifecycle/history.ts +10 -5
  21. package/templates/api/src/_platform/_lifecycle/installer.ts +4 -4
  22. package/templates/api/src/_platform/_lifecycle/sha384.ts +1 -1
  23. package/templates/api/src/_platform/_lifecycle/steps/download.ts +1 -1
  24. package/templates/api/src/_platform/_lifecycle/steps/register.ts +57 -29
  25. package/templates/api/src/_platform/_lifecycle/trust.ts +1 -1
  26. package/templates/api/src/_platform/_lifecycle/uninstall.ts +13 -8
  27. package/templates/api/src/_platform/_lifecycle/verify-checks.ts +2 -2
  28. package/templates/api/src/_platform/_shared/service/{contacts.ts → domain/contacts.ts} +8 -6
  29. package/templates/api/src/_platform/_shared/service/{roles.ts → domain/roles.ts} +18 -19
  30. package/templates/api/src/_platform/_shared/service/{settings.ts → domain/settings.ts} +6 -6
  31. package/templates/api/src/_platform/_shared/service/{solution-perms.ts → domain/solution-perms.ts} +18 -17
  32. package/templates/api/src/_platform/_shared/service/{users.ts → domain/users.ts} +20 -26
  33. package/templates/api/src/_platform/_shared/service/{access.ts → infra/access.ts} +5 -8
  34. package/templates/api/src/_platform/_shared/service/{audit.ts → infra/audit.ts} +26 -26
  35. package/templates/api/src/_platform/_shared/service/{rate.ts → infra/rate.ts} +12 -12
  36. package/templates/api/src/_platform/_shared/site-facade.ts +1 -1
  37. package/templates/api/src/_platform/contacts/api.ts +2 -2
  38. package/templates/api/src/_platform/contacts/detail/api.ts +2 -2
  39. package/templates/api/src/_platform/manifest.yaml +3 -1
  40. package/templates/api/src/_platform/market/api.ts +54 -16
  41. package/templates/api/src/_platform/schema.yaml +14 -0
  42. package/templates/api/src/_platform/solutions/api.ts +13 -12
  43. package/templates/api/src/auth/_shared/ldap-login.ts +11 -7
  44. package/templates/api/src/auth/_shared/login-throttle.ts +2 -2
  45. package/templates/api/src/auth/_shared/session.ts +25 -21
  46. package/templates/api/src/auth/api.ts +1 -1
  47. package/templates/api/src/demo/todos/api.ts +1 -0
  48. package/templates/api/src/home/line/api.ts +4 -4
  49. package/templates/api/src/home/pie/api.ts +4 -4
  50. package/templates/api/src/notification/_shared/notify.ts +40 -38
  51. package/templates/api/src/notifications/api.ts +1 -4
  52. package/templates/api/src/personal-center/upload/api.ts +1 -1
  53. package/templates/api/src/system/login-methods/api.ts +1 -1
  54. package/templates/api/src/system/oplog/api.ts +3 -3
  55. package/templates/api/src/system/permissions/api.ts +2 -2
  56. package/templates/api/src/system/roles/api.ts +2 -2
  57. package/templates/api/src/system/roles/bindings/api.ts +2 -2
  58. package/templates/api/src/system/settings/api.ts +2 -2
  59. package/templates/api/src/system/users/api.ts +2 -2
  60. package/templates/api/src/system/users/forbidden/api.ts +2 -2
  61. package/templates/api/src/system/users/password/api.ts +2 -2
  62. package/templates/api/src/system/users/roles/api.ts +2 -2
  63. package/templates/api/src/web/user-info/api.ts +2 -5
  64. package/templates/api/tests/auth-session.spec.ts +1 -1
  65. package/templates/api/tests/auth-throttle.spec.ts +1 -1
  66. package/templates/api/tests/guard.spec.ts +1 -1
  67. package/templates/api/tests/lifecycle/market-api.spec.ts +1 -1
  68. package/templates/api/tests/lifecycle/trust.spec.ts +1 -1
  69. package/templates/api/tests/lifecycle/uninstall.spec.ts +1 -1
  70. package/templates/api/tests/query-utils.spec.ts +1 -1
  71. package/templates/api/tests/rbac-audit.spec.ts +1 -1
  72. package/templates/api/tests/rbac-contacts.spec.ts +2 -2
  73. package/templates/api/tests/rbac-guard.spec.ts +1 -1
  74. package/templates/api/tests/rbac-roles.spec.ts +2 -2
  75. package/templates/api/tests/rbac-settings.spec.ts +2 -2
  76. package/templates/api/tests/rbac-solution-perms.spec.ts +1 -1
  77. package/templates/api/tests/rbac-users.spec.ts +1 -1
  78. package/templates/api/tsconfig.json +8 -0
  79. package/templates/env.d.ts +2 -0
  80. package/templates/global.d.ts +816 -0
  81. package/templates/init.include.yaml +18 -1
  82. package/templates/logs/task-events.jsonl +0 -0
  83. package/templates/openapi.json +525 -13
  84. package/templates/package.json +2 -1
  85. package/templates/web/src/_platform/__client/api.schemas.ts +97 -0
  86. package/templates/web/src/_platform/__client/api.ts +152 -39
  87. package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +19 -0
  88. package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +3 -0
  89. package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +1 -0
  90. package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +1 -0
  91. package/templates/web/src/oj-market/api/client.ts +5 -105
  92. package/templates/web/src/oj-market/api/defaultClient.ts +111 -5
  93. package/templates/web/src/oj-market/components/CardGrid.tsx +4 -1
  94. package/templates/web/src/oj-market/components/CardItem.tsx +9 -5
  95. package/templates/web/src/oj-market/components/DetailDrawer.tsx +7 -3
  96. package/templates/web/src/oj-market/components/InstallDialog.tsx +1 -1
  97. package/templates/web/src/oj-market/hooks/useMarket.ts +4 -1
  98. package/templates/web/src/oj-market/locales/en-US.json +2 -1
  99. package/templates/web/src/oj-market/locales/zh-CN.json +2 -1
  100. package/templates/web/src/oj-market/pages/MarketApp.tsx +16 -1
  101. package/templates/web.config.ts +3 -3
  102. package/vendor/host-versions.json +3 -3
  103. /package/templates/api/src/_platform/_shared/service/{guard.ts → infra/guard.ts} +0 -0
  104. /package/templates/api/src/_platform/_shared/service/{oplog.ts → infra/oplog.ts} +0 -0
  105. /package/templates/api/src/_platform/_shared/service/{query.ts → infra/query.ts} +0 -0
@@ -0,0 +1,816 @@
1
+ // .route 声明的 TS 支持(编辑器不报错;dev server 不依赖此文件运行)。
2
+ //
3
+ // 下面为 src/bridge/bootstrap.js 在运行时注入的全局对象提供类型声明,
4
+ // 使 sample/src 中的业务代码在编辑器内不报 TS 错误。
5
+ //
6
+ // ext_boot.js(config.yaml 同目录,可选)在运行时补充的全局**不在本文件声明** ——
7
+ // 它们是项目自定义的,框架无法预知。用 ext_boot.js 增补了全局(如 `json.page()`)后,
8
+ // 在业务项目里另建一个 .d.ts 自行声明(并加进 tsconfig 的 include),否则编辑器报
9
+ // TS2339 "Property 'page' does not exist"。运行时不受影响——类型只影响编辑器与 tsc。
10
+
11
+ // JSON 反序列化后可能出现的值(SQL 行 / KV / fetch body 等)。
12
+ type Json = string | number | boolean | null | Json[] | { [k: string]: Json };
13
+
14
+ // 数据库查询返回的一行(列名 -> 值)。
15
+ type Row = Record<string, Json>;
16
+
17
+ // db.table(...).where(cond) 的单个条件。叶子用 field/op/value;组合用 and/or/not
18
+ // (也可经 db.leaf/db.and/db.or/db.not 构造)——故 field 可缺省(纯组合节点)。
19
+ interface WhereCond {
20
+ field?: string
21
+ op?: string // eq/neq/gt/gte/lt/lte/like/in/nin/null/notnull/... 依服务端支持
22
+ value?: unknown
23
+ and?: WhereCond[]
24
+ or?: WhereCond[]
25
+ not?: WhereCond
26
+ }
27
+
28
+ // db.table(...).orderBy(items) 的单个排序项。
29
+ interface OrderByItem {
30
+ field: string
31
+ dir?: "asc" | "desc" | null
32
+ }
33
+
34
+ // db.table(name) 返回的安全查询构造器(流式、结构化)。
35
+ interface QueryBuilder {
36
+ select: (cols?: (string | object)[]) => QueryBuilder
37
+ where: (cond: WhereCond) => QueryBuilder
38
+ orderBy: (items?: OrderByItem[]) => QueryBuilder
39
+ // LIMIT/OFFSET(v0.1.20 起可配,见 config `db_query` 段——不能写进 `db:` 段):
40
+ // 未给 limit 的**顶层** select 隐式补 `db_query.default_limit`(默认 100);显式 limit
41
+ // 被 clamp 到 `db_query.max_limit`(默认 1000,硬顶 100000)。嵌套/子查询不隐式截断。
42
+ // 结果行数 ≥ 生效上限时,响应带 `X-OJ-Row-Limit: <上限>` 头(仅 json.* 路径)——
43
+ // 判「可能被截断」看该头,别猜。
44
+ limit: (n: number) => QueryBuilder
45
+ offset: (n: number) => QueryBuilder
46
+ // 行数组(每行是列名 → 值;与 db.query 的返回同形)。
47
+ all: () => Promise<Row[]>
48
+ // DML:动词由 insert/update/delete 声明,run() 终执行(返回受影响行数)。
49
+ // insert + returning(["id"]) 时 run() 改返回行数组 [{ id: n }]——
50
+ // pg/sqlite 是单条 RETURNING 语句;mysql 同连接两步取 LAST_INSERT_ID()(建议放 db.tx)。
51
+ insert: (rows: object | object[]) => QueryBuilder
52
+ update: (sets: object) => QueryBuilder
53
+ delete: () => QueryBuilder
54
+ returning: (cols?: string[]) => QueryBuilder
55
+ // JOIN:on = 左右列对;kind 缺省 "inner"(left/right/... 依服务端支持)。
56
+ join: (
57
+ table: string,
58
+ on: { left: string, right: string }[],
59
+ kind?: string,
60
+ ) => QueryBuilder
61
+ // DISTINCT / GROUP BY / HAVING / UNION / CTE(WITH)。
62
+ distinct: () => QueryBuilder
63
+ groupBy: (cols?: string[]) => QueryBuilder
64
+ having: (cond: WhereCond) => QueryBuilder
65
+ union: (other: QueryBuilder, kind?: string) => QueryBuilder
66
+ with: (name: string, columns: string[], query: QueryBuilder) => QueryBuilder
67
+ run: () => Promise<number | Row[]>
68
+ // 只构造不执行(与 .all()/.run() 完全相同的校验管线)。
69
+ toSQL: () => { sql: string, params: unknown[] }
70
+ // 序列化当前构造成员(可经 db.fromJSON 复原继续链式调用)。
71
+ toJSON: () => object
72
+ }
73
+
74
+ // 事务内的库实例:与 DBInstance 同面,但没有 tx(嵌套事务不支持)。
75
+ // isTx 标记事务域句柄——DDL(如 search.ensureIndex)必须拒绝在此类句柄上执行
76
+ // (MySQL 的 DDL 会隐式提交调用方事务)。
77
+ type TxInstance = Omit<DBInstance, "tx"> & { isTx?: true };
78
+
79
+ // json.* :统一响应信封 + 响应头。
80
+ interface JsonApi {
81
+ ok: (data?: unknown) => void
82
+ fail: (code: number, msg: string, data?: unknown) => void
83
+ header: (name: string, value: string) => void
84
+ // 裸 JSON 200(不套信封):标准协议端点(如 OIDC)用;错误仍走 fail()。
85
+ raw: (data?: unknown) => void
86
+ // 3xx 重定向原语(v0.1.26):Location + RFC 9110 §15.4 短超文本注记(HEAD 为空 body)。
87
+ // code 非 3xx 一律回落 302;具名封装对应五个标准 3xx,语义见名。
88
+ redirect: {
89
+ (url: string, code?: number): void
90
+ movedPermanently: (url: string) => void // 301 永久迁移
91
+ found: (url: string) => void // 302 临时(默认)
92
+ seeOther: (url: string) => void // 303 跟随后改 GET
93
+ temporaryRedirect: (url: string) => void // 307 方法/体保持
94
+ permanentRedirect: (url: string) => void // 308 永久 + 方法保持
95
+ }
96
+ // 流式响应(v0.1.35):开流后逐块 write,绕过 {code,msg,data} 信封。
97
+ // opts:{ status?, contentType? };返回 writer 必须显式 end()(或 handler 返回即自动关流)。
98
+ stream: (opts?: { status?: number, contentType?: string }) => StreamWriter
99
+ // SSE 便捷封装:自动 text/event-stream + 数据帧 `data: <c>\n\n`;
100
+ // opts:{ status?, heartbeatSecs? }(心跳默认 15s,保活穿透代理)。
101
+ sse: (opts?: { status?: number, heartbeatSecs?: number }) => StreamWriter
102
+ }
103
+
104
+ // 流式写入器(json.stream/json.sse 返回)。
105
+ interface StreamWriter {
106
+ write: (chunk: string) => void
107
+ end: () => void
108
+ }
109
+
110
+ // http.* :当前请求上下文(只读,懒加载,per-request 最新)。
111
+ interface HttpApi {
112
+ method: string
113
+ params: Record<string, string>
114
+ query: Record<string, string>
115
+ headers: Record<string, string>
116
+ body: any
117
+ // WS Binary 帧时 body 为 null;原始帧字节走 bodyBytes()(v0.1.16,文本帧亦可用)。
118
+ bodyBytes: () => Promise<Uint8Array>
119
+ // 取路由参数或 query 参数:路径参数优先,query 兜底,均缺失返回 def 原值。
120
+ param: (name: string, def?: unknown) => any
121
+ // 租户 id(tenant 启用时从租户头提取;未启用为 null)。`db.asTenant(id)` 声明成功后
122
+ // 即为该 id(v0.1.20)。
123
+ tenantId: string | null
124
+ // 已验签用户(auth 启用且通过 Bearer 守卫;否则 null)。
125
+ user: AuthUser | null
126
+ // 本请求命中的模块身份(v0.1.76:路由分发时注入;无模块上下文——任务桥 /
127
+ // WS 帧 / run_with / 启动期内省——为 null)。**只读**:对象已冻结,strict 下
128
+ // 写属性抛 TypeError(CJS require 路径静默无效)。version 勿回显给外部客户端
129
+ // (指纹面)。
130
+ module: { name: string, version: string } | null
131
+ // multipart 上传文件元信息(非 multipart 为空数组)。
132
+ files: UploadedFileMeta[]
133
+ // 取第 i 个上传文件的字节(越界报错 no such file)。
134
+ // 仅小文件(≤ max_upload)有字节;流式大文件报错并指路 files[i].key/.url(v0.1.38)。
135
+ file: (i: number) => Promise<Uint8Array>
136
+ }
137
+
138
+ // 已验签用户(JWT claims)。
139
+ interface AuthUser {
140
+ id: string | number
141
+ roles: string[]
142
+ claims: Record<string, Json>
143
+ }
144
+
145
+ // multipart 上传文件元信息。
146
+ // 大文件(> max_upload)由服务端流式落 blob(v0.1.38):bytes 拿不到(http.file 报错),
147
+ // 用 key/url 取——blob.get(key) 读全文、url 直接下载/外链。
148
+ interface UploadedFileMeta {
149
+ field: string
150
+ filename: string
151
+ content_type: string
152
+ size: number
153
+ // 流式大文件的 blob 对象 key(小文件为 null)。
154
+ key: string | null
155
+ // 流式大文件的下载地址(小文件为 null)。
156
+ url: string | null
157
+ }
158
+
159
+ // log.* :结构化日志(zap SugaredLogger 风格:msg + 交替键值对)。
160
+ interface Logger {
161
+ debug: (msg: string, ...kv: unknown[]) => void
162
+ info: (msg: string, ...kv: unknown[]) => void
163
+ warn: (msg: string, ...kv: unknown[]) => void
164
+ error: (msg: string, ...kv: unknown[]) => void
165
+ }
166
+
167
+ // redis.* / kv.* :KV 存储(redis.default 配置即真 Redis,否则进程内存 KV)。
168
+ interface KVApi {
169
+ get: (key: string) => Promise<string | null>
170
+ set: (key: string, value: string) => Promise<boolean>
171
+ del: (key: string) => Promise<boolean>
172
+ // 设过期(秒)。真 Redis 走 EXPIRE;内存 KV 惰性过期。
173
+ expire: (key: string, ttlSec: number) => Promise<boolean>
174
+ // 自增返回新值(键不存在从 0 起)。
175
+ incr: (key: string) => Promise<number>
176
+ }
177
+
178
+ // ws.* :WebSocket 生命周期钩子内的主动发送/关闭控制(HTTP 路径 no-op)。
179
+ // send 帧型由参数类型决定:string → Text 帧(0x1);Uint8Array → Binary 帧(0x2)(v0.1.16)。
180
+ interface WSApi {
181
+ send: (data: string | Uint8Array) => void
182
+ close: () => void
183
+ // 房间原语(v0.1.30,进程内单例 hub;跨实例扇出仍走 bus.publish):
184
+ // join/leave 仅在 ws.ts 生命周期钩子内可用(连接身份自动取,断连自动摘除);
185
+ // broadcast 为 socket.io 除己语义(发送者要回声自己在 JS 里发),HTTP handler 可调
186
+ // (无连接身份 = 不排除任何人);roomSize 任意上下文可调。
187
+ join: (room: string) => void
188
+ leave: (room: string) => void
189
+ broadcast: (room: string, data: string) => number
190
+ roomSize: (room: string) => number
191
+ }
192
+
193
+ // sess.* :WS 帧池的会话上下文(v0.1.10 帧池模型;**只在 ws.ts 的生命周期钩子内存在**——
194
+ // HTTP / 任务桥路径没有这个全局)。
195
+ // 执行模型:路由级 W 个无状态 Worker 共享执行,连接态必须外置到 sess.state(Rust 会话表
196
+ // 持久、按连接隔离);模块作用域只是 Worker 本地只读缓存,**不要**用来放连接状态。
197
+ interface WsSess {
198
+ // 连接标识(进程内自增;同一连接的各帧相同)。
199
+ readonly id: number
200
+ // 跨帧持久的会话状态。**必须可 JSON 序列化**:不可序列化时该帧的状态回传被丢弃
201
+ // (Rust 侧保留旧快照),连接不中断——所以别往里塞函数 / class 实例 / Uint8Array。
202
+ state: Record<string, Json>
203
+ }
204
+
205
+ // blob.* :对象存储(可调用取命名实例:blob("media").put(...);裸调用 blob.put(...) 等价 default)。
206
+ interface BlobApi {
207
+ (name?: string): BlobApi
208
+ put: (key: string, bytes: Uint8Array, contentType?: string) => Promise<boolean>
209
+ get: (key: string) => Promise<Uint8Array>
210
+ // 幂等:不存在视为成功。
211
+ del: (key: string) => Promise<boolean>
212
+ // local = {base}/blob/{key};s3 = presigned URL(15min)。
213
+ url: (key: string) => Promise<string>
214
+ // 上传直传预签名(v0.1.30):s3 返回 15min 预签名 PUT URL(客户端直传不经 handler,
215
+ // 绕开 max_upload_bytes/30s);opts 缺省 {"kind":"put"}。local 后端无预签名——抛错,
216
+ // 改用直传路由 PUT {base}/blob/{key}(上限 server.blob_upload_max_bytes,默认 1 GiB)。
217
+ uploadUrl: (key: string, opts?: { kind: string }) => Promise<{ url: string }>
218
+ // local 缺失 sidecar 且无法按扩展名推断时返回空串;s3 无 Content-Type 时返回 null。
219
+ contentType: (key: string) => Promise<string | null>
220
+ // 服务端复制(v0.1.47,ABI 11):src 保留。字节不进 JS——大文件搬运别用 get + put。
221
+ copy: (src: string, dst: string) => Promise<boolean>
222
+ // 服务端搬移(v0.1.47,ABI 11):src 不再存在(local 同目录 rename / s3 CopyObject+Delete)。
223
+ move: (src: string, dst: string) => Promise<boolean>
224
+ // 区间读(v0.1.47,ABI 11):短读截断——越尾只给实际剩余字节,offset 过尾给空数组;
225
+ // offset / len 为负、小数、NaN 即报错。
226
+ readRange: (key: string, offset: number, len: number) => Promise<Uint8Array>
227
+ }
228
+
229
+ // fs.* :本地文件系统(v0.1.53;config `fs:` 段启用)。一次性 API(无 fd 句柄,
230
+ // op 内自开自关);相对路径解析到 jail 根 fs.root 之下(不是进程 cwd)。
231
+ // 未配置 fs: / 越出 root(含 root 内 symlink 指向外部)/ readonly 下写 → 抛 NotCapable。
232
+ interface FsApi {
233
+ readFile: (path: string) => Promise<Uint8Array>
234
+ readTextFile: (path: string) => Promise<string>
235
+ writeFile: (path: string, data: Uint8Array) => Promise<void>
236
+ writeTextFile: (path: string, data: string) => Promise<void>
237
+ mkdir: (path: string, opts?: { recursive?: boolean }) => Promise<void>
238
+ remove: (path: string, opts?: { recursive?: boolean, force?: boolean }) => Promise<void>
239
+ rename: (oldPath: string, newPath: string) => Promise<void>
240
+ stat: (path: string) => Promise<FsFileInfo>
241
+ // 列目录:返回异步迭代器(不是数组),迭代项为 DirEntry;迭代完自动关闭句柄。
242
+ readDir: (path: string) => Promise<AsyncIterable<FsDirEntry>>
243
+ }
244
+
245
+ interface FsFileInfo {
246
+ isFile: boolean
247
+ isDirectory: boolean
248
+ isSymlink: boolean
249
+ size: number
250
+ mtime: Date | null
251
+ atime: Date | null
252
+ birthtime: Date | null
253
+ ctime: Date | null
254
+ dev: number
255
+ mode: number | null
256
+ ino: number | null
257
+ nlink: number | null
258
+ uid: number | null
259
+ gid: number | null
260
+ }
261
+
262
+ interface FsDirEntry {
263
+ name: string
264
+ isFile: boolean
265
+ isDirectory: boolean
266
+ isSymlink: boolean
267
+ }
268
+
269
+ // bus.* :主题广播。publish 广播给订阅 topic 的全部 WS 会话,返回接收方数;
270
+ // subscribe 仅 WS 会话内可用(HTTP 路径报错);kind 报告活跃 broker 类型。
271
+ interface BusApi {
272
+ // data 为 Uint8Array/ArrayBuffer → Binary 帧原字节(不包信封,v0.1.16);其余 → JSON 信封 Text 帧。
273
+ publish: (topic: string, data?: unknown) => Promise<number>
274
+ subscribe: (topic: string) => Promise<void>
275
+ kind: () => Promise<string>
276
+ }
277
+
278
+ // cert.* :JWS 证书生成/重签(RSA + RS256 在 Rust 侧;纯内存,不落盘)。
279
+ interface CertApi {
280
+ // 生成密钥对并签发 JWS。bits >= 2048;nbf/exp 为 Unix 秒,exp 必须 > nbf。
281
+ generate: (
282
+ bits: number,
283
+ nbf: number,
284
+ exp: number,
285
+ ) => Promise<{ private_pem: string, public_pem: string, cert_jws: string }>
286
+ // 用现有 PKCS#8 私钥重签续期(公钥不变),返回新 cert.jws 串。
287
+ renew: (privatePem: string, nbf: number, exp: number) => Promise<string>
288
+ }
289
+
290
+ // es.* :Elasticsearch 薄客户端(直通 ES 响应体;未配置调用报 es not configured)。
291
+ interface EsApi {
292
+ search: (index: string, dsl?: unknown) => Promise<Json>
293
+ index: (index: string, id: string, doc?: unknown) => Promise<Json>
294
+ del: (index: string, id: string) => Promise<Json>
295
+ }
296
+
297
+ // jwt.* :JWT 签发/验签(secret/alg/durations 由 config auth: 注入;未配置调用报
298
+ // jwt not configured)。iat/exp 由 Rust 补(JS 不可控有效期),exp 取 access 时长。
299
+ interface JwtApi {
300
+ // payload 至少含 sub;roles 可选(缺省空数组)。返回签名 token。
301
+ sign: (payload: { sub: string, roles?: string[] }) => string
302
+ // 验签 + exp(leeway 0);篡改/过期/算法不符直接抛错。
303
+ verify: (token: string) => { sub: string, roles: string[], iat: number, exp: number }
304
+ // 配置的有效期(秒;getter 惰性求值)。
305
+ readonly accessDuration: number
306
+ readonly refreshDuration: number
307
+ }
308
+
309
+ // oidc.* :内置 OP/RP(RS256 私钥只在 Rust 侧;issuer/rp/clients 由 config oidc: 注入;
310
+ // 未配置段调用报 oidc not configured)。verify 传 jwks 时按其 RS256 kid 匹配验签,
311
+ // 不传(或 null)回落本地私钥;篡改/过期/无匹配钥直接抛错。
312
+ interface OidcApi {
313
+ // claims 至少含 iss/sub/aud/iat/exp。返回签名 token。
314
+ sign: (claims: Record<string, unknown>) => string
315
+ // 验签 + exp;jwks 可选(缺省用本地钥)。
316
+ verify: (token: string, jwks?: unknown) => Record<string, unknown>
317
+ // 本地公钥 JWKS(keys[0].kid/kty/alg/n/e)。
318
+ jwks: () => unknown
319
+ // config oidc.issuer(getter 惰性求值)。
320
+ readonly issuer: string
321
+ // RP 侧 tenant → IdP 配置。
322
+ readonly rp: Record<string, { issuer: string, client_id: string, client_secret: string, scope: string }>
323
+ // OP 侧客户端白名单。
324
+ readonly clients: Record<string, { secret: string, redirect_uris: string[], tenant: string }>
325
+ }
326
+
327
+ // sso.* :HTTP 表单 / CAS SSO 提供者注册表(v0.1.61,config `http_sso:` 段注入;
328
+ // 未配置调用报 http sso not configured)。字段语义与校验规则见 api-manual §sso。
329
+ interface SsoProviderCfg {
330
+ kind: "form" | "cas2"
331
+ login_url: string
332
+ cas_validate_url?: string
333
+ callback_url?: string
334
+ user_field?: string
335
+ pass_field?: string
336
+ body_encoding?: "form" | "json"
337
+ extra_fields?: Record<string, unknown>
338
+ success_when?: Record<string, unknown>
339
+ profile?: Record<string, string>
340
+ allow_http?: boolean
341
+ }
342
+ interface SsoApi {
343
+ readonly providers: Record<string, SsoProviderCfg>
344
+ }
345
+
346
+ // mail / Mail(key) :邮件投递(config smtp: 段 + oj-mail 插件启用;未配置调用报
347
+ // mail not configured)。宿主做校验/附件解析,插件做投递;所有方法**都 resolve 信封**
348
+ // (校验失败 = {code:5},不抛),只有「未配置」抛异常。
349
+ // code:0 成功(data.messageId 为投递凭据、data.jobId 为作业号)/ 1 网络/超时(**以及
350
+ // 一切投递期失败**,含 SMTP 5xx 与鉴权失败)/ 4 队列满 / 5 入参校验(地址、白名单、正文、
351
+ // 附件、profile)。**2(SMTP 5xx)/ 3(鉴权)为保留未启用**:两类都归 1,判失败请用
352
+ // `code !== 0`(见 docs/mail-smtp.md §3)。
353
+ interface MailEnvelope {
354
+ code: number
355
+ msg: string
356
+ data: { jobId?: string, messageId?: string } & Record<string, Json>
357
+ }
358
+
359
+ // 附件(引用式:字节不进 JS、不走 base64):filename 必填,blobKey / path 二选一。
360
+ interface MailAttachment {
361
+ filename: string
362
+ // blob 后端里的键(经 blob 字段选后端,缺省 "default")。
363
+ blobKey?: string
364
+ blob?: string
365
+ // 项目根内的本地文件路径(../ 越界即拒)。
366
+ path?: string
367
+ // 显式 MIME(缺省按扩展名 → 字节嗅探 → application/octet-stream)。
368
+ mime?: string
369
+ }
370
+
371
+ interface MailSendRequest {
372
+ from: string
373
+ to: string[]
374
+ cc?: string[]
375
+ bcc?: string[]
376
+ subject?: string
377
+ text?: string
378
+ html?: string
379
+ headers?: Record<string, string>
380
+ attachments?: MailAttachment[]
381
+ }
382
+
383
+ interface MailRawRequest extends MailSendRequest {
384
+ // RFC5322 原文(与 attachments 互斥)。
385
+ raw: string
386
+ }
387
+
388
+ // ldap 泛型轴载荷形状(v0.1.54 起 `ldap`/`LDAP` 全局已下线,调用面为
389
+ // `axis("ldap").<op>`,op 为 snake_case:bind/search/search_paged/whoami/compare;
390
+ // axis 返回 any,cast 到下列形状)。错误模型同 db:**除「未配置」外,校验/协议/
391
+ // 网络错一律 reject**——只有 bind 的「凭据被 LDAP 拒绝」是正常返回 false(含 rc 49)。
392
+ interface LdapSearchOpts {
393
+ // 默认 "sub"(整棵子树);"one" = 仅 base 下一层;"base" = 只查 base 自身。
394
+ scope?: "base" | "one" | "sub"
395
+ // RFC 4515 过滤器,默认 "(objectClass=*)"——用户输入须先转义(无参数绑定)。
396
+ filter?: string
397
+ // 要读的属性名;缺省/空数组 = 服务端默认属性集。
398
+ attrs?: string[]
399
+ // 可选:覆盖本次查询的绑定凭据(与 config ldap:<inst> 的 bind_dn/bind_pw 合并——
400
+ // 二者取一即可,另一个回落 config)。用于把密码作为运行时参数传入,避免落配置。
401
+ // 仅 search / search_paged 支持;whoami/compare/bind 仍用 config 服务账号或各自入参。
402
+ bindDn?: string
403
+ bindPw?: string
404
+ }
405
+
406
+ interface LdapEntry {
407
+ dn: string
408
+ // 文本属性(多值即多元素)。
409
+ attrs: Record<string, string[]>
410
+ // 二进制属性(jpegPhoto 等):base64 编码字符串。
411
+ bin: Record<string, string[]>
412
+ }
413
+
414
+ // ldap / LDAP 全局已随 oj-ldap 迁移泛型轴下线(v0.1.54)——调用面为
415
+ // `axis("ldap").<op>`(返回 any),op 为 snake_case:bind / search / search_paged /
416
+ // whoami / compare(载荷形状见上 LdapSearchOpts / LdapEntry)。
417
+
418
+ interface MailApi {
419
+ // 异步 transport;resolve 投递结果信封。
420
+ send: (m: MailSendRequest) => Promise<MailEnvelope>
421
+ // 同步 transport(插件 worker 内阻塞投递)。
422
+ sendSync: (m: MailSendRequest) => Promise<MailEnvelope>
423
+ // 入队即回 {code:0,data:{jobId}};真实完成经 bus topic "mail.result" 上送。
424
+ // jobId 由宿主生成(不可猜;调用方传入的 jobId 被剥离)。
425
+ enqueue: (m: MailSendRequest) => Promise<MailEnvelope>
426
+ // 查宿主侧结果(未命中/已过期/**非本归属** → null;归属 = 本实例的 profile + 模块 + 租户)。
427
+ result: (jobId: string) => Promise<Json | null>
428
+ // 原始 MIME 投递。
429
+ sendRaw: (o: MailRawRequest) => Promise<MailEnvelope>
430
+ }
431
+
432
+ interface Mail {
433
+ new (key?: string): MailApi
434
+ // 已配置 profile 名清单(非密钥面:连接信息/凭据不进 JS)。
435
+ profiles: () => Promise<string[]>
436
+ }
437
+
438
+ // bcrypt.* :密码哈希(Rust 侧 spawn_blocking,CPU 密集不卡 isolate)。
439
+ interface BcryptApi {
440
+ hash: (password: string, cost?: number) => Promise<string>
441
+ // 非法 hash 返回 false(不抛错)。
442
+ verify: (password: string, hash: string) => Promise<boolean>
443
+ }
444
+
445
+ // vars.* :部署期常量读口(config `vars:` 段,v0.1.25)。
446
+ // **同步**(装配期冻结的表,无 IO);**fail-closed**:只有 config 里声明的键可读,
447
+ // 其余恒 null(没有「读任意 OS env / 任意 config 键」的通道)。值按 YAML 标量文本成串
448
+ // (`PORT: 3000` → `"3000"`;嵌套 map/list 是配置解析错误)。
449
+ // const web = vars.get("WEB_URL") ?? "http://localhost:3000";
450
+ interface VarsApi {
451
+ get: (name: string) => string | null
452
+ }
453
+
454
+ // exec.*:外部命令执行(v0.1.75,config `platform.exec` 段授权)。
455
+ // 授权模型 = argv 模板白名单(literal token 逐字相等 + ${path:NAME}/${raw:NAME} 占位符
456
+ // 恰吸收一个 token)+ 无 shell:argv 必须恰命中一条授权模板(歧义命中报错,不静默择一)。
457
+ // ${path:} 值按路径校验(fs.root ∪ trusted_paths);${raw:} 拒 `-` 开头与绝对形态。
458
+ // exec 授权与 platform.modules 相互独立;缺省全 deny(fail-closed)。
459
+ interface ExecRunOpts {
460
+ /** 缺省 30_000,硬上限 600_000(超出钳制)。大 DDL / 大 stdin 显式调大。 */
461
+ timeoutMs?: number
462
+ /** 一次性写入子进程 stdin 后关闭写端;写入也在超时窗口内。 */
463
+ stdin?: string
464
+ }
465
+ interface ExecResult {
466
+ /** 进程退出码:非 0 也正常返回(不抛错);信号终止 = 128 + signal。 */
467
+ code: number
468
+ stdout: string
469
+ stderr: string
470
+ }
471
+ interface ExecApi {
472
+ /**
473
+ * 执行一条授权模板匹配后的外部命令(无 shell;cwd 钉在 fs.root,env 默认空表仅
474
+ * 白名单透传、PATH 不继承;argv[0] 为宿主解析后的绝对路径——"oj" 即服务进程自身)。
475
+ * 抛错边界:未授权 / 模板失配 / 歧义 / 路径越界 → NotCapable;超时(整组击杀)/
476
+ * 输出超限(stdout+stderr 合计 10MB) / spawn 失败 / 非 UTF-8 输出 → 普通 Error。
477
+ * 未配 `fs:` 段或 tasks/WS 上下文(无模块归属)恒拒。
478
+ */
479
+ run: (argv: string[], opts?: ExecRunOpts) => Promise<ExecResult>
480
+ }
481
+
482
+ // yaml.*:YAML 读写原语(v0.1.75,无特权,始终可用)。
483
+ // parse: 三预算防炸弹(1 MiB 输入 / 10 万节点含 alias 展开 / 深度 128),Rust 阻塞池
484
+ // 执行 + 独立 5s deadline;重复键后键覆盖;多文档 / 非字符串键 / NaN·Inf 拒绝;
485
+ // 非法 YAML 抛带行列错误;超 2^53-1 的整数为十进制字符串(json.ok 安全)。
486
+ // stringify: 仅承诺机器产物(manifests.yaml)往返——serde_yaml 丢注释,
487
+ // 禁用于 config.yaml 回写(走文本手术)。
488
+ interface YamlApi {
489
+ parse: (text: string) => Promise<unknown>
490
+ /**
491
+ * 仅承诺机器产物(manifests.yaml)往返——serde_yaml 丢注释,禁用于 config.yaml
492
+ * 回写(走文本手术)。对象键输出保留 JS 对象键插入序(整数型键先行),不排序;
493
+ * 需确定序请在 stringify 前自行归序。
494
+ */
495
+ stringify: (value: object) => Promise<string>
496
+ }
497
+
498
+ // server.*:平台级控制(v0.1.75,platform 特权)。
499
+ // reloadConfig:进程内触发热更,与 `POST {base}/_system/config` 同一 executor 与串行锁。
500
+ // 返回恒定形状五键:`unchanged=true` 时三清单为空数组;tasks/WS 上下文无归属 → NotCapable;
501
+ // `oj test` / 嵌入式 → `reload not available`;并发撞锁 → `server.reloadConfig: reload in progress (busy)`。
502
+ interface ServerReloadResult {
503
+ /** 代际 id(每次成功 swap +1;unchanged 时不变)。 */
504
+ generation: number
505
+ /** 配置未变(归一后与原代等价):true 时 applied/restart_pending/kept_listener 均为空数组。 */
506
+ unchanged: boolean
507
+ /** 本轮实际生效的变更项(段/键清单)。 */
508
+ applied: string[]
509
+ /** 需重启进程才生效的项(如 db/redis/plugins 后端段、server 段)——提示重启,本进程不重建。 */
510
+ restart_pending: string[]
511
+ /** 本进程永久持有、不随热更变更的项(如 listener:server.port/host/证书路径)。 */
512
+ kept_listener: string[]
513
+ }
514
+ interface ServerApi {
515
+ reloadConfig: () => Promise<ServerReloadResult>
516
+ }
517
+
518
+ declare global {
519
+ interface Function {
520
+ route?: string
521
+ /** handler 入参契约(v0.1.44 `.schema`;嵌套组动词同样可挂,键 = (文件, handler 链)) */
522
+ schema?: Record<string, unknown>
523
+ /**
524
+ * handler 出参契约(v0.1.72):与 `.schema` 平级声明,语义同 `.schema.response`
525
+ * (`json.ok(data)` 的 data payload;运行期不校验,只喂 `oj openapi` / client 生成)。
526
+ * 优先级:动词 `.response` > 组 `.response` > 动词 `.schema.response` > 组 `.schema.response`
527
+ */
528
+ response?: Record<string, unknown>
529
+ }
530
+
531
+ // 嵌套组(v0.1.64):以实体对象为根的组同样可挂组级 `.route`(整组默认前缀)、
532
+ // 组级 `.schema`(整组默认入参契约)与组级 `.response`(整组默认出参,v0.1.72),
533
+ // 与 handler 级同语义。组是普通对象字面量,
534
+ // 故在 `Object` 上声明,使 `anchor.route = "/x/{anchor}/upload"` 之类不报 TS2339。
535
+ // 运行时:组级 `.schema` 作继承默认,动词自身 `.schema` 在其上合并(通道级
536
+ // required 并集 / properties 逐键覆盖);组级 `.response` 同样沿嵌套层层继承。
537
+ interface Object {
538
+ route?: string
539
+ /** 组级入参契约(v0.1.64 嵌套组):整组默认,动词 `.schema` 在其上合并 */
540
+ schema?: Record<string, unknown>
541
+ /**
542
+ * 组级出参契约(v0.1.72):整组默认,沿嵌套继承,动词 `.response` 整体覆盖。
543
+ * ⚠️ 保留键:组对象上的 `response` 被当作声明读走,**不能**用来命名嵌套子组
544
+ * (子组命名为 response 会静默不产生路由,与 `schema`/`route` 相同)
545
+ */
546
+ response?: Record<string, unknown>
547
+ }
548
+
549
+ // DB(name) 返回的命名数据库实例。(在 declare global 内:业务代码要能**点名**
550
+ // 这个类型(如 `function f(dbi: DBInstance)`);放模块作用域会 TS2304。)
551
+ interface DBInstance {
552
+ // 原始 SQL + 绑定参数(params 可选)。返回行数组。
553
+ query(sql: string, params?: unknown[]): Promise<Row[]>
554
+ // 执行写操作,返回受影响行数。
555
+ exec(sql: string, params?: unknown[]): Promise<number>
556
+ // 安全查询构造器(标识符白名单 + 参数化值)。
557
+ table(name: string): QueryBuilder
558
+ // 条件构造器:leaf(field, op, value) / and / or / not,供 where/having 组合。
559
+ leaf(field: string, op: string, value?: unknown): WhereCond
560
+ and(...conds: WhereCond[]): WhereCond
561
+ or(...conds: WhereCond[]): WhereCond
562
+ not(cond: WhereCond): WhereCond
563
+ // 从 toJSON() 快照复原构造器(继续链式调用)。
564
+ fromJSON(snap: object): QueryBuilder
565
+ // 系统逃生通道(tenant sql_guard):本请求绕过租户注入/校验。显式且被审计,
566
+ // 业务 handler 不得使用。
567
+ asSystem(): DBInstance
568
+ // 匿名请求声明租户(v0.1.20)。公开页 handler 用**服务端派生**的租户 id 声明本次请求
569
+ // 的身份(典型派生链:分享 token → 查共享表 → 租户 id)。**与 asSystem 不同——租户条件
570
+ // 仍然强制注入**,只是身份由 handler 给。
571
+ // 三道门禁全过才生效,缺一即抛错(不是静默忽略):
572
+ // ① config `tenant.allow_as_tenant: true`(默认关);
573
+ // ② 请求是匿名请求(命中 `tenant.anonymous_paths` 且未带租户头;`oj test --anonymous`);
574
+ // ③ id 非空。
575
+ // 另:请求级且**只能调用一次**(已带租户头的请求、或同一请求内二次调用 → 抛错,防运行期
576
+ // 切换身份);生效后 `http.tenantId` 即该值,并记一条审计日志。
577
+ // 红线:id 必须服务端派生,**绝不可**直接取请求参数(否则等于把租户交给调用方)。
578
+ /**
579
+ * 平台序列分配(v0.1.24):单语句原子取号,`max+1` 竞态的终态。
580
+ * 首次使用自动建平台表 `_oj_sequences`;返回值 ≤2^53-1 是 number,超出是十进制字符串。
581
+ */
582
+ nextSeq(name: string): Promise<number | string>
583
+ /**
584
+ * 连接引擎名(v0.1.61):`"sqlite" | "mysql" | "postgres"`。供 `_shared` 共享模块与
585
+ * 诊断分派方言 SQL;业务 handler 分支方言 = 放弃跨引擎可移植承诺,慎用。
586
+ */
587
+ dialect(): "sqlite" | "mysql" | "postgres"
588
+ asTenant(id: string): DBInstance
589
+ // 事务:回调 resolve 提交 / throw 回滚再抛;tx.query/exec/table 同签名走同一连接。
590
+ // 每请求至多一个活跃事务(嵌套报错);请求结束未完结自动回滚。
591
+ // 回调参数**不含 tx 自身**(嵌套事务直接被拒,故类型上也拿不到——见 TxInstance)。
592
+ tx(fn: (tx: TxInstance) => unknown): Promise<unknown>
593
+ }
594
+
595
+ const json: JsonApi;
596
+ const http: HttpApi;
597
+ const log: Logger;
598
+ const kv: KVApi;
599
+ const redis: KVApi;
600
+ const ws: WSApi;
601
+ // WS 帧池会话上下文(仅 ws.ts 生命周期钩子内可用,见 WsSess)。
602
+ const sess: WsSess;
603
+ const blob: BlobApi;
604
+ const fs: FsApi;
605
+ // 外部命令执行(v0.1.75,platform.exec 段授权;见 ExecApi JSDoc 与 api-manual §6「exec」)。
606
+ const exec: ExecApi;
607
+ // YAML 读写原语(v0.1.75,无特权;见 YamlApi JSDoc 与 api-manual §6「yaml」)。
608
+ const yaml: YamlApi;
609
+ // server.*:平台级控制(v0.1.75,platform 特权;见 ServerApi JSDoc 与 api-manual §6「server」)。
610
+ const server: ServerApi;
611
+ const bus: BusApi;
612
+ const es: EsApi;
613
+ // 默认 profile("default")的 mail 实例;其它 profile 用 new Mail("name")。
614
+ const mail: MailApi;
615
+ // ldap 全局已下线(v0.1.54):调用面 axis("ldap").<op>(op 为 snake_case)。
616
+ const Mail: Mail;
617
+
618
+ // 命名数据库实例;未配置的名字返回 undefined。
619
+ function DB(name: string): DBInstance | undefined;
620
+ // 默认("default")数据库实例。
621
+ const db: DBInstance;
622
+ const cert: CertApi;
623
+ const jwt: JwtApi;
624
+ const oidc: OidcApi;
625
+ // sso.*:HTTP 表单 / CAS SSO 提供者注册表(config `http_sso:` 段注入;未配置调用报
626
+ // http sso not configured)。登录端点(业务模块)读它分派上游;详见 api-manual §sso。
627
+ const sso: SsoApi;
628
+ const bcrypt: BcryptApi;
629
+ // 部署期常量(config `vars:` 段;未声明的键恒 null)。
630
+ const vars: VarsApi;
631
+
632
+ // ---- 大整数(v0.1.22;u64 见 v0.1.24;详见 docs/numeric-limits.md)----
633
+ // DB 的 i64 超出安全整数范围(|v| > 2^53-1,雪花 id 常态)时,读出来是**十进制字符串**;
634
+ // 写回去必须用 toBigInt()(字符串是文本意图,PG 上写 bigint 列会直接报错)。
635
+ // 两者都 fail-loud:非规范十进制 / 已坍缩的 number 一律抛错,不静默取近似。
636
+ /** 十进制整数字符串 | 安全范围内的 number | bigint → bigint(精确 64 位,范围 [-2^63, 2^63-1])。 */
637
+ function toBigInt(v: string | number | bigint): bigint;
638
+ /**
639
+ * 同上但为**无符号** 64 位 `[0, 2^64-1]`:只有 MySQL `BIGINT UNSIGNED` 列能承载,
640
+ * PG / SQLite 传本值会**明确报错**(它们的 bigint 就是 i64)。
641
+ */
642
+ function toUBigInt(v: string | number | bigint): bigint;
643
+ /** number | 数字串(含科学计数法)| bigint → number(f64,**显式接受精度丢失**)。 */
644
+ function toDouble(v: string | number | bigint): number;
645
+
646
+ // crypto 增补(bootstrap 对 crypto 做 Object.assign 合并):
647
+ // sha256Hex = 十六进制摘要;randomHex = nBytes 字节随机数的 hex(默认 32 字节);
648
+ // getRandomValues(v0.1.30)= oj 实现(原生不存在):任意 TypedArray view 填充后
649
+ // 返回原 view,非 view 抛 TypeError,单次 ≤65536 字节(对齐 WebCrypto;lib.dom 的
650
+ // 同名声明只是兜底,运行时是 oj 实现)。
651
+ interface Crypto {
652
+ /** string 走 UTF-8 摘要;Uint8Array / ArrayBuffer 走原始字节摘要(非 UTF-8 字节安全)。 */
653
+ sha256Hex(data: string | Uint8Array | ArrayBuffer): string
654
+ /**
655
+ * MD5 十六进制摘要。**MD5 已密码学破损,仅限非安全场景(缓存键/etag/旧系统兼容),
656
+ * 不得用于凭据/签名/完整性校验。** 入参分流同 sha256Hex:string 走 UTF-8;
657
+ * Uint8Array / ArrayBuffer 走原始字节摘要(非 UTF-8 字节安全);其余抛 TypeError。
658
+ */
659
+ md5Hex(data: string | Uint8Array | ArrayBuffer): string
660
+ randomHex(nBytes?: number): string
661
+ getRandomValues<T extends ArrayBufferView>(view: T): T
662
+ // AES-GCM 应用级加密(v0.1.35):key 为 hex/base64 编码的原始 16/32 字节
663
+ // (按长度选 Aes128/256;aes-gcm 0.10 不含 AES-192),来自 vars.get(...);明文 utf8 进,
664
+ // base64(nonce12 ‖ ct ‖ tag16) 出。密钥缺失/非法由调用方 json.fail。
665
+ aesGcmEncrypt(plaintext: string, key: string): string
666
+ aesGcmDecrypt(ciphertext: string, key: string): string
667
+ /**
668
+ * Ed25519 验签:publicKey 为 hex/base64 编码的 **32 字节原始公钥**(不支持 PEM/SPKI);
669
+ * msg 为 UTF-8 字符串;sigBase64 仅 base64(STANDARD/NO_PAD,不支持 base64url)。
670
+ * 验签通过返回 true,签名/密钥不匹配返回 false;格式错(公钥 ≠ 32 字节、
671
+ * sig 解码后 ≠ 64 字节、base64 非法)抛错。
672
+ */
673
+ verifyEd25519(publicKey: string, msg: string, sigBase64: string): boolean
674
+ }
675
+
676
+ // ---- oj test L1 测试 SDK(oj test 运行时注入;仅测试文件使用) ----
677
+ // 进程内 HTTP 派发助手,对标 Go Fiber app.Test:client.get/post/... 触发真实
678
+ // 路由 + 真实运行时 + 真实后端(零 TCP)。path 为相对 base 的路径(如 "/user/account")。
679
+ interface ClientResp {
680
+ status: number
681
+ headers: Record<string, string>
682
+ body: string
683
+ upgrade: boolean
684
+ }
685
+ interface ClientOptions {
686
+ headers?: Record<string, string>
687
+ body?: string
688
+ }
689
+ // WS 帧测试面(v0.1.16):path 形如 "/echo-bin/ws"(相对 base)。
690
+ // 帧型判别:binary = true 为二进制帧(data 为 Uint8Array),false 为文本帧(data 为 string)。
691
+ interface TestWsFrame {
692
+ binary: boolean
693
+ data: string | Uint8Array
694
+ // 判别位(可选):收到帧时为 false/缺失,便于 `if (f.closed) …` 收窄联合类型。
695
+ closed?: false
696
+ }
697
+ // 对端已关闭(此后不会再收到帧)。
698
+ interface TestWsClosed {
699
+ closed: true
700
+ }
701
+ interface TestWs {
702
+ send(data: string | Uint8Array): Promise<void>
703
+ // 下一帧:{binary, data}(收到帧)/ {closed: true}(对端关闭)/ null(超时无帧,默认 1000ms)。
704
+ // 取 binary/data 前先收窄:`if (!f || f.closed) …`(否则 TS 报「binary 不存在于 closed 分支」)。
705
+ next(ms?: number): Promise<TestWsFrame | TestWsClosed | null>
706
+ close(): Promise<void>
707
+ }
708
+ interface Client {
709
+ ws(path: string): TestWs
710
+ get(path: string, opts?: ClientOptions): Promise<ClientResp>
711
+ post(path: string, opts?: ClientOptions): Promise<ClientResp>
712
+ put(path: string, opts?: ClientOptions): Promise<ClientResp>
713
+ del(path: string, opts?: ClientOptions): Promise<ClientResp>
714
+ patch(path: string, opts?: ClientOptions): Promise<ClientResp>
715
+ head(path: string, opts?: ClientOptions): Promise<ClientResp>
716
+ options(path: string, opts?: ClientOptions): Promise<ClientResp>
717
+ // 登录助手:POST /auth/login → 返回 access_token(失败抛错)。headers 透传(如租户头)。
718
+ login(username: string, password: string, headers?: Record<string, string>): Promise<string>
719
+ }
720
+ const client: Client;
721
+
722
+ // 轻量测试框架(vitest 风格子集):describe/it/expect/beforeEach。
723
+ function describe(name: string, fn: () => void): void;
724
+ function it(name: string, fn: () => void | Promise<void>): void;
725
+ function beforeEach(fn: () => void | Promise<void>): void;
726
+ function expect(actual: unknown): {
727
+ toBe(e: unknown): void
728
+ toEqual(e: unknown): void
729
+ toBeTruthy(): void
730
+ toBeFalsy(): void
731
+ toContain(sub: unknown): void
732
+ };
733
+
734
+ // 标记会话结束。
735
+ function finish(): void;
736
+ // CJS 同步 require(eval + 进程级缓存)。
737
+ function __ojRequire(name: string, referrerPath?: string): any;
738
+ // fetch 返回的 Response(浏览器风格子集)。在 declare global 内:业务代码要能
739
+ // **点名**它(如 `let r: OjFetchResponse`,try/catch 两段赋值场景);放模块作用域
740
+ // 会 TS2304,误标 DOM 的 Response 则 TS2740(缺 redirected/type/url 等)。
741
+ interface OjFetchResponse {
742
+ ok: boolean
743
+ status: number
744
+ statusText: string
745
+ headers: Record<string, string>
746
+ json(): Promise<Json | null>
747
+ text(): Promise<string>
748
+ arrayBuffer(): Promise<Uint8Array>
749
+ clone(): OjFetchResponse
750
+ }
751
+ // 浏览器兼容的 fetch(url 必填,options 同 RequestInit 的常用子集)。
752
+ function fetch(url: string, options?: {
753
+ method?: string
754
+ headers?: Record<string, string>
755
+ body?: string | null
756
+ }): Promise<OjFetchResponse>;
757
+ // 已加载插件自省:[{name, semver, abi_version, fingerprint, host_abi_version}]。
758
+ function plugins(): any[];
759
+ // 泛型插件轴调用面(v0.1.54):axis("cache").get("k")——任意方法名 = 该轴的一个 op,
760
+ // 参数位置传、末位对象 = opts(轴自定义)。未知轴抛错并列出可用轴名。
761
+ function axis(name: string): any;
762
+ // 装配期固化诊断(v0.1.54;与 `oj info` CLI 同源,同步无 IO):build/abi/plugins/
763
+ // backends/config 五段;config 只出键名不出值;无公共 HTTP 端点。
764
+ function ojInfo(): any;
765
+
766
+ // 命名 MQ 客户端(config kafkas:/rabbits: 段;未配置的名 → undefined)。
767
+ // 消费方法(poll/commit/ack/nack)仅长任务上下文可用(HTTP/WS 内调用报错)。
768
+ function Kafka(name: string): OjKafkaClient | undefined;
769
+ function RabbitMQ(name: string): OjRabbitClient | undefined;
770
+ // 长任务上下文(对象全局):stopping = 停机信号(HTTP/WS 上下文恒 false);
771
+ // sleep = 等待原语(本运行时无 timer 全局,setTimeout 不可用)。
772
+ const tasks: {
773
+ stopping(): boolean
774
+ sleep(ms: number): Promise<void>
775
+ };
776
+ }
777
+
778
+ interface OjMqMessage {
779
+ topic: string
780
+ partition?: number
781
+ offset?: number
782
+ key?: string
783
+ value: any
784
+ // 非 UTF-8 载荷时 value 为 null,value_b64 为 base64 字符串(v0.1.16);生产侧传
785
+ // Uint8Array 亦编码进 value_b64(record 载荷 = 原始字节)。
786
+ value_b64?: string
787
+ headers?: Record<string, string>
788
+ ts?: number
789
+ delivery_tag?: number // rabbit 专属:ack/nack 载荷原样回传
790
+ }
791
+
792
+ interface OjKafkaClient {
793
+ kind: () => Promise<string>
794
+ send: (topic: string, o: { key?: string, partition?: number, headers?: Record<string, string>, value: any }) => Promise<{ sent: number }>
795
+ poll: (topics: string[], o?: { max?: number, timeoutMs?: number }) => Promise<{ messages: OjMqMessage[] }>
796
+ commit: (m: OjMqMessage) => Promise<Record<string, never>>
797
+ metadata: () => Promise<any>
798
+ }
799
+
800
+ interface OjRabbitClient {
801
+ kind: () => Promise<string>
802
+ publish: (exchange: string, routingKey: string, value: any, o?: { headers?: Record<string, string> }) => Promise<{ sent: number }>
803
+ poll: (queues: string[], o?: { max?: number, timeoutMs?: number }) => Promise<{ messages: OjMqMessage[] }>
804
+ ack: (m: OjMqMessage) => Promise<Record<string, never>>
805
+ nack: (m: OjMqMessage, requeue?: boolean) => Promise<Record<string, never>>
806
+ metadata: () => Promise<any>
807
+ }
808
+
809
+ // oj 的导入别名 `#x`(本模块根)/ `#/m/x`(src 根)是「引用方所在模块」相对的,
810
+ // tsconfig 的 `paths` 无法表达模块相对解析,只能枚举模块目录(见 tsconfig.json),
811
+ // 因此仅能命中**已存在**的文件。无法静态定位的别名(如尚未创建的 `#_shared/view`)
812
+ // 由 `types/oj-modules.d.ts`(非模块,真 ambient `declare module "#*"`)兜底为 `any`
813
+ // 而非报 TS2307;已存在、能被 `paths` 命中的别名仍走真实文件类型。运行时由 oj 的
814
+ // loader 解析,本文件与该兜底只影响编辑器 / tsc。
815
+
816
+ export {};