@yunzai-ng/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { deepClone, isPlainObject } from "../util/deep.js";
|
|
3
|
+
import { parseDuration } from "../util/duration.js";
|
|
4
|
+
/** 校验失败抛出的错误 */
|
|
5
|
+
export class SchemaError extends Error {
|
|
6
|
+
/** 错误名 */
|
|
7
|
+
name = "SchemaError";
|
|
8
|
+
/** 全部问题 */
|
|
9
|
+
issues;
|
|
10
|
+
/**
|
|
11
|
+
* @param issues 校验问题列表
|
|
12
|
+
*/
|
|
13
|
+
constructor(issues) {
|
|
14
|
+
const lines = issues
|
|
15
|
+
.filter(i => i.severity === "error")
|
|
16
|
+
.map(i => ` · ${i.path === "" ? "(根)" : i.path}:${i.message}`);
|
|
17
|
+
super(`配置校验失败:\n${lines.join("\n")}`);
|
|
18
|
+
this.issues = issues;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** 校验失败的哨兵值 */
|
|
22
|
+
const INVALID = Symbol("invalid");
|
|
23
|
+
/**
|
|
24
|
+
* 配置 schema 节点
|
|
25
|
+
*
|
|
26
|
+
* 不可变:全部 fluent 方法均返回新实例,因此可安全地抽取公共片段以供复用。
|
|
27
|
+
*/
|
|
28
|
+
export class Schema {
|
|
29
|
+
/** 内部定义 */
|
|
30
|
+
#def;
|
|
31
|
+
/**
|
|
32
|
+
* @param def 内部定义;请通过 `s.*` 工厂函数创建,不要直接 new
|
|
33
|
+
*/
|
|
34
|
+
constructor(def) {
|
|
35
|
+
this.#def = def;
|
|
36
|
+
}
|
|
37
|
+
/* ───────────────────────────── 元信息 ───────────────────────────── */
|
|
38
|
+
/**
|
|
39
|
+
* 设置表单标签
|
|
40
|
+
* @param text 标签文案
|
|
41
|
+
* @returns 新 schema
|
|
42
|
+
*/
|
|
43
|
+
title(text) {
|
|
44
|
+
return this.#patch({ meta: { title: text } });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 设置说明文案
|
|
48
|
+
* @param text 说明
|
|
49
|
+
* @returns 新 schema
|
|
50
|
+
*/
|
|
51
|
+
desc(text) {
|
|
52
|
+
return this.#patch({ meta: { description: text } });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 设置分组(WebUI 折叠区块)
|
|
56
|
+
* @param name 分组名
|
|
57
|
+
* @returns 新 schema
|
|
58
|
+
*/
|
|
59
|
+
group(name) {
|
|
60
|
+
return this.#patch({ meta: { group: name } });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 设置排序权重,小的在前
|
|
64
|
+
* @param weight 权重
|
|
65
|
+
* @returns 新 schema
|
|
66
|
+
*/
|
|
67
|
+
order(weight) {
|
|
68
|
+
return this.#patch({ meta: { order: weight } });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 指定控件类型
|
|
72
|
+
* @param widget 控件
|
|
73
|
+
* @returns 新 schema
|
|
74
|
+
*/
|
|
75
|
+
widget(widget) {
|
|
76
|
+
return this.#patch({ meta: { widget } });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 标记为敏感字段
|
|
80
|
+
*
|
|
81
|
+
* WebUI 读取时脱敏、日志中不打印。CK、token、密码必须标。
|
|
82
|
+
* @returns 新 schema
|
|
83
|
+
*/
|
|
84
|
+
secret() {
|
|
85
|
+
return this.#patch({ meta: { secret: true, widget: this.#def.meta.widget ?? "password" } });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 标记为只读
|
|
89
|
+
* @returns 新 schema
|
|
90
|
+
*/
|
|
91
|
+
readonly() {
|
|
92
|
+
return this.#patch({ meta: { readonly: true } });
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 设置输入框占位符
|
|
96
|
+
* @param text 占位文案
|
|
97
|
+
* @returns 新 schema
|
|
98
|
+
*/
|
|
99
|
+
placeholder(text) {
|
|
100
|
+
return this.#patch({ meta: { placeholder: text } });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 设置条件显隐
|
|
104
|
+
* @param cond 同级字段取值条件,如 `{ mode: "ws-reverse" }`;值写成数组表示"取其中之一"
|
|
105
|
+
* @returns 新 schema
|
|
106
|
+
*/
|
|
107
|
+
showWhen(cond) {
|
|
108
|
+
return this.#patch({ meta: { showWhen: cond } });
|
|
109
|
+
}
|
|
110
|
+
/* ───────────────────────────── 可选与默认 ───────────────────────────── */
|
|
111
|
+
/**
|
|
112
|
+
* 标记为可缺省
|
|
113
|
+
* @returns 新 schema,输出类型带上 undefined
|
|
114
|
+
*/
|
|
115
|
+
optional() {
|
|
116
|
+
return this.#patch({ optional: true });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 设置默认值
|
|
120
|
+
*
|
|
121
|
+
* 有默认值的字段在输出类型里仍是必填 —— 因为解析后一定有值。
|
|
122
|
+
* @param value 默认值
|
|
123
|
+
* @returns 新 schema
|
|
124
|
+
*/
|
|
125
|
+
default(value) {
|
|
126
|
+
return this.#patch({ hasDefault: true, defaultValue: value });
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 追加自定义校验
|
|
130
|
+
* @param message 不通过时的中文提示
|
|
131
|
+
* @param test 返回 true 表示通过
|
|
132
|
+
* @returns 新 schema
|
|
133
|
+
*/
|
|
134
|
+
check(message, test) {
|
|
135
|
+
const checks = [...(this.#def.checks ?? []), { message, test: test }];
|
|
136
|
+
return this.#patch({ checks });
|
|
137
|
+
}
|
|
138
|
+
/* ───────────────────────────── 约束 ───────────────────────────── */
|
|
139
|
+
/**
|
|
140
|
+
* 下界:数值最小值 / 字符串最短长度 / 数组最少元素
|
|
141
|
+
* @param value 下界
|
|
142
|
+
* @returns 新 schema
|
|
143
|
+
*/
|
|
144
|
+
min(value) {
|
|
145
|
+
return this.#patch({ min: value });
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 上界:数值最大值 / 字符串最长长度 / 数组最多元素
|
|
149
|
+
* @param value 上界
|
|
150
|
+
* @returns 新 schema
|
|
151
|
+
*/
|
|
152
|
+
max(value) {
|
|
153
|
+
return this.#patch({ max: value });
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 字符串正则约束
|
|
157
|
+
* @param regex 正则
|
|
158
|
+
* @param message 自定义提示
|
|
159
|
+
* @returns 新 schema
|
|
160
|
+
*/
|
|
161
|
+
pattern(regex, message) {
|
|
162
|
+
const next = this.#patch({ pattern: regex });
|
|
163
|
+
return message ? next.check(message, v => typeof v !== "string" || regex.test(v)) : next;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 要求整数
|
|
167
|
+
* @returns 新 schema
|
|
168
|
+
*/
|
|
169
|
+
int() {
|
|
170
|
+
return this.#patch({ int: true, step: this.#def.step ?? 1 });
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 数值步长(表单提示用)
|
|
174
|
+
* @param value 步长
|
|
175
|
+
* @returns 新 schema
|
|
176
|
+
*/
|
|
177
|
+
step(value) {
|
|
178
|
+
return this.#patch({ step: value });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 对象:拒绝未识别的键
|
|
182
|
+
*
|
|
183
|
+
* 缺省行为是"保留并 warn",适配器这类结构明确的配置可以开严格模式。
|
|
184
|
+
* @returns 新 schema
|
|
185
|
+
*/
|
|
186
|
+
strict() {
|
|
187
|
+
return this.#patch({ strict: true });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 数组:允许单值自动包成数组
|
|
191
|
+
*
|
|
192
|
+
* 让 `masterQQ: 123` 与 `masterQQ: [123]` 都能用 —— 旧配置最常见的困惑点。
|
|
193
|
+
* @returns 新 schema
|
|
194
|
+
*/
|
|
195
|
+
single() {
|
|
196
|
+
return this.#patch({ wrapSingle: true });
|
|
197
|
+
}
|
|
198
|
+
/* ───────────────────────────── 解析 ───────────────────────────── */
|
|
199
|
+
/**
|
|
200
|
+
* 解析并校验
|
|
201
|
+
* @param input 待校验值
|
|
202
|
+
* @returns 结果对象;失败时带全部问题,不抛错
|
|
203
|
+
*/
|
|
204
|
+
safeParse(input) {
|
|
205
|
+
const issues = [];
|
|
206
|
+
const value = this.#validate(input, "", issues);
|
|
207
|
+
const failed = value === INVALID || issues.some(i => i.severity === "error");
|
|
208
|
+
return failed ? { ok: false, issues } : { ok: true, value: value, issues };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 解析并校验,失败抛错
|
|
212
|
+
* @param input 待校验值
|
|
213
|
+
* @returns 校验后的值(已填默认、已强制转换)
|
|
214
|
+
* @throws SchemaError 校验失败
|
|
215
|
+
*/
|
|
216
|
+
parse(input) {
|
|
217
|
+
const result = this.safeParse(input);
|
|
218
|
+
if (!result.ok)
|
|
219
|
+
throw new SchemaError(result.issues);
|
|
220
|
+
return result.value;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 只取默认值,不需要输入
|
|
224
|
+
*
|
|
225
|
+
* 用于生成初始配置文件。
|
|
226
|
+
* @returns 全部默认值构成的对象
|
|
227
|
+
* @throws SchemaError 当 schema 里有无默认值的必填项
|
|
228
|
+
*/
|
|
229
|
+
defaults() {
|
|
230
|
+
return this.parse(undefined);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 降级为 WebUI 表单描述
|
|
234
|
+
* @returns 表单描述结构体
|
|
235
|
+
*/
|
|
236
|
+
describe() {
|
|
237
|
+
const def = this.#def;
|
|
238
|
+
const out = { type: this.#descriptorType() };
|
|
239
|
+
if (def.meta.title !== undefined)
|
|
240
|
+
out.title = def.meta.title;
|
|
241
|
+
if (def.meta.description !== undefined)
|
|
242
|
+
out.description = def.meta.description;
|
|
243
|
+
if (def.hasDefault)
|
|
244
|
+
out.default = deepClone(def.defaultValue);
|
|
245
|
+
if (!def.optional && !def.hasDefault)
|
|
246
|
+
out.required = true;
|
|
247
|
+
if (def.meta.secret)
|
|
248
|
+
out.secret = true;
|
|
249
|
+
if (def.meta.readonly)
|
|
250
|
+
out.readonly = true;
|
|
251
|
+
if (def.meta.widget !== undefined)
|
|
252
|
+
out.widget = def.meta.widget;
|
|
253
|
+
if (def.meta.group !== undefined)
|
|
254
|
+
out.group = def.meta.group;
|
|
255
|
+
if (def.meta.order !== undefined)
|
|
256
|
+
out.order = def.meta.order;
|
|
257
|
+
if (def.meta.placeholder !== undefined)
|
|
258
|
+
out.placeholder = def.meta.placeholder;
|
|
259
|
+
if (def.meta.showWhen !== undefined)
|
|
260
|
+
out.showWhen = def.meta.showWhen;
|
|
261
|
+
if (def.min !== undefined)
|
|
262
|
+
out.min = def.min;
|
|
263
|
+
if (def.max !== undefined)
|
|
264
|
+
out.max = def.max;
|
|
265
|
+
if (def.step !== undefined)
|
|
266
|
+
out.step = def.step;
|
|
267
|
+
if (def.pattern !== undefined)
|
|
268
|
+
out.pattern = def.pattern.source;
|
|
269
|
+
if (def.kind === "object" && def.shape) {
|
|
270
|
+
const properties = {};
|
|
271
|
+
for (const [key, child] of Object.entries(def.shape))
|
|
272
|
+
properties[key] = child.describe();
|
|
273
|
+
out.properties = properties;
|
|
274
|
+
}
|
|
275
|
+
if (def.kind === "array" && def.element)
|
|
276
|
+
out.items = def.element.describe();
|
|
277
|
+
if (def.kind === "record" && def.valueSchema)
|
|
278
|
+
out.values = def.valueSchema.describe();
|
|
279
|
+
if (def.kind === "enum" && def.enumItems)
|
|
280
|
+
out.enum = def.enumItems.map(item => ({ ...item }));
|
|
281
|
+
if (def.kind === "literal")
|
|
282
|
+
out.enum = [{ value: def.literalValue }];
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 对象 schema:取子字段 schema
|
|
287
|
+
* @param key 字段名
|
|
288
|
+
* @returns 子 schema;不是对象或字段不存在时 undefined
|
|
289
|
+
*/
|
|
290
|
+
/**
|
|
291
|
+
*
|
|
292
|
+
*/
|
|
293
|
+
field(key) {
|
|
294
|
+
return this.#def.shape?.[key];
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 收集全部标了 `secret()` 的字段路径
|
|
298
|
+
*
|
|
299
|
+
* 日志脱敏与 WebUI 读取脱敏都用它,保证"标一次、处处生效"。
|
|
300
|
+
* @returns 点分路径列表
|
|
301
|
+
*/
|
|
302
|
+
secretPaths() {
|
|
303
|
+
const out = [];
|
|
304
|
+
this.#collectSecrets("", out);
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
/* ───────────────────────────── 内部 ───────────────────────────── */
|
|
308
|
+
/**
|
|
309
|
+
* 克隆并打补丁
|
|
310
|
+
* @param patch 要覆盖的定义片段
|
|
311
|
+
* @returns 新 schema
|
|
312
|
+
*/
|
|
313
|
+
#patch(patch) {
|
|
314
|
+
return new _a({
|
|
315
|
+
...this.#def,
|
|
316
|
+
...patch,
|
|
317
|
+
meta: { ...this.#def.meta, ...(patch.meta ?? {}) }
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* 映射到表单描述的类型名
|
|
322
|
+
* @returns 描述类型
|
|
323
|
+
*/
|
|
324
|
+
#descriptorType() {
|
|
325
|
+
switch (this.#def.kind) {
|
|
326
|
+
case "literal":
|
|
327
|
+
return "enum";
|
|
328
|
+
case "string":
|
|
329
|
+
case "number":
|
|
330
|
+
case "boolean":
|
|
331
|
+
case "enum":
|
|
332
|
+
case "array":
|
|
333
|
+
case "object":
|
|
334
|
+
case "record":
|
|
335
|
+
return this.#def.kind;
|
|
336
|
+
default:
|
|
337
|
+
return "unknown";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 递归收集敏感字段路径
|
|
342
|
+
* @param prefix 当前路径前缀
|
|
343
|
+
* @param out 收集容器
|
|
344
|
+
*/
|
|
345
|
+
#collectSecrets(prefix, out) {
|
|
346
|
+
if (this.#def.meta.secret)
|
|
347
|
+
out.push(prefix);
|
|
348
|
+
if (this.#def.shape) {
|
|
349
|
+
for (const [key, child] of Object.entries(this.#def.shape)) {
|
|
350
|
+
child.#collectSecrets(prefix === "" ? key : `${prefix}.${key}`, out);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (this.#def.element)
|
|
354
|
+
this.#def.element.#collectSecrets(prefix === "" ? "*" : `${prefix}.*`, out);
|
|
355
|
+
if (this.#def.valueSchema)
|
|
356
|
+
this.#def.valueSchema.#collectSecrets(prefix === "" ? "*" : `${prefix}.*`, out);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 记录一条问题
|
|
360
|
+
* @param issues 收集容器
|
|
361
|
+
* @param path 路径
|
|
362
|
+
* @param message 说明
|
|
363
|
+
* @param severity 严重程度
|
|
364
|
+
*/
|
|
365
|
+
#fail(issues, path, message, severity = "error") {
|
|
366
|
+
issues.push({ path, message, severity });
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 核心校验
|
|
370
|
+
* @param input 输入值
|
|
371
|
+
* @param path 当前路径
|
|
372
|
+
* @param issues 问题收集容器
|
|
373
|
+
* @returns 校验后的值,或 INVALID
|
|
374
|
+
*/
|
|
375
|
+
#validate(input, path, issues) {
|
|
376
|
+
const def = this.#def;
|
|
377
|
+
// 缺失值处理:默认值 → 可选 → 对象自动物化 → 报缺失
|
|
378
|
+
if (input === undefined || input === null) {
|
|
379
|
+
if (def.hasDefault)
|
|
380
|
+
return this.#materializeDefault(path, issues);
|
|
381
|
+
if (def.optional)
|
|
382
|
+
return undefined;
|
|
383
|
+
if (def.kind === "object")
|
|
384
|
+
return this.#validateObject({}, path, issues);
|
|
385
|
+
if (def.kind === "unknown")
|
|
386
|
+
return undefined;
|
|
387
|
+
this.#fail(issues, path, "缺少必填项");
|
|
388
|
+
return INVALID;
|
|
389
|
+
}
|
|
390
|
+
let value;
|
|
391
|
+
switch (def.kind) {
|
|
392
|
+
case "string":
|
|
393
|
+
value = this.#validateString(input, path, issues);
|
|
394
|
+
break;
|
|
395
|
+
case "number":
|
|
396
|
+
value = this.#validateNumber(input, path, issues);
|
|
397
|
+
break;
|
|
398
|
+
case "boolean":
|
|
399
|
+
value = this.#validateBoolean(input, path, issues);
|
|
400
|
+
break;
|
|
401
|
+
case "enum":
|
|
402
|
+
value = this.#validateEnum(input, path, issues);
|
|
403
|
+
break;
|
|
404
|
+
case "literal":
|
|
405
|
+
value = input === def.literalValue ? input : this.#reject(issues, path, `必须为 ${String(def.literalValue)}`);
|
|
406
|
+
break;
|
|
407
|
+
case "array":
|
|
408
|
+
value = this.#validateArray(input, path, issues);
|
|
409
|
+
break;
|
|
410
|
+
case "object":
|
|
411
|
+
value = this.#validateObject(input, path, issues);
|
|
412
|
+
break;
|
|
413
|
+
case "record":
|
|
414
|
+
value = this.#validateRecord(input, path, issues);
|
|
415
|
+
break;
|
|
416
|
+
default:
|
|
417
|
+
value = input;
|
|
418
|
+
}
|
|
419
|
+
if (value === INVALID)
|
|
420
|
+
return INVALID;
|
|
421
|
+
for (const rule of def.checks ?? []) {
|
|
422
|
+
if (!rule.test(value)) {
|
|
423
|
+
this.#fail(issues, path, rule.message);
|
|
424
|
+
return INVALID;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return value;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 记录错误并返回 INVALID
|
|
431
|
+
* @param issues 问题收集容器
|
|
432
|
+
* @param path 路径
|
|
433
|
+
* @param message 说明
|
|
434
|
+
* @returns INVALID
|
|
435
|
+
*/
|
|
436
|
+
#reject(issues, path, message) {
|
|
437
|
+
this.#fail(issues, path, message);
|
|
438
|
+
return INVALID;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 把声明的默认值也过一遍校验
|
|
442
|
+
*
|
|
443
|
+
* 默认值必须走同一条校验/转换链,否则 `defaults()` 与 `parse(用户文件)`
|
|
444
|
+
* 会给出**形态不同的同一份配置**(例如 duration 的默认 `0` 与文件里读回的
|
|
445
|
+
* `"0"`),下游按路径比对时就会在每次启动时误报"配置变了"。
|
|
446
|
+
*
|
|
447
|
+
* 默认值不合法属于插件作者的 bug,不是用户的错:这里只记 warn 并退回原始
|
|
448
|
+
* 默认值,让机器人照常启动,而不是让用户面对一个自己无法修复的启动失败。
|
|
449
|
+
* @param path 当前路径
|
|
450
|
+
* @param issues 问题收集容器
|
|
451
|
+
* @returns 规范化后的默认值
|
|
452
|
+
*/
|
|
453
|
+
#materializeDefault(path, issues) {
|
|
454
|
+
const raw = deepClone(this.#def.defaultValue);
|
|
455
|
+
if (raw === undefined || raw === null)
|
|
456
|
+
return raw;
|
|
457
|
+
const probe = [];
|
|
458
|
+
const value = this.#validate(raw, path, probe);
|
|
459
|
+
if (value === INVALID || probe.some(i => i.severity === "error")) {
|
|
460
|
+
const detail = probe.map(i => i.message).join(";");
|
|
461
|
+
issues.push({
|
|
462
|
+
path,
|
|
463
|
+
message: `schema 声明的默认值不符合自身规则(${detail}),请联系插件作者`,
|
|
464
|
+
severity: "warn"
|
|
465
|
+
});
|
|
466
|
+
return raw;
|
|
467
|
+
}
|
|
468
|
+
issues.push(...probe);
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* 校验字符串
|
|
473
|
+
* @param input 输入
|
|
474
|
+
* @param path 路径
|
|
475
|
+
* @param issues 问题容器
|
|
476
|
+
* @returns 字符串或 INVALID
|
|
477
|
+
*/
|
|
478
|
+
#validateString(input, path, issues) {
|
|
479
|
+
const def = this.#def;
|
|
480
|
+
// 数字/布尔宽容转字符串:YAML 里 `token: 12345` 会被解析成数字
|
|
481
|
+
let text;
|
|
482
|
+
if (typeof input === "string")
|
|
483
|
+
text = input;
|
|
484
|
+
else if (typeof input === "number" && def.allowNumber)
|
|
485
|
+
return input;
|
|
486
|
+
else if (typeof input === "number" || typeof input === "boolean")
|
|
487
|
+
text = String(input);
|
|
488
|
+
else
|
|
489
|
+
return this.#reject(issues, path, "应为字符串");
|
|
490
|
+
if (def.trim !== false)
|
|
491
|
+
text = text.trim();
|
|
492
|
+
if (def.min !== undefined && [...text].length < def.min) {
|
|
493
|
+
return this.#reject(issues, path, `长度不能少于 ${def.min}`);
|
|
494
|
+
}
|
|
495
|
+
if (def.max !== undefined && [...text].length > def.max) {
|
|
496
|
+
return this.#reject(issues, path, `长度不能超过 ${def.max}`);
|
|
497
|
+
}
|
|
498
|
+
if (def.pattern && !def.pattern.test(text)) {
|
|
499
|
+
return this.#reject(issues, path, `格式不正确(需匹配 ${def.pattern.source})`);
|
|
500
|
+
}
|
|
501
|
+
return text;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 校验数值
|
|
505
|
+
* @param input 输入
|
|
506
|
+
* @param path 路径
|
|
507
|
+
* @param issues 问题容器
|
|
508
|
+
* @returns 数值或 INVALID
|
|
509
|
+
*/
|
|
510
|
+
#validateNumber(input, path, issues) {
|
|
511
|
+
const def = this.#def;
|
|
512
|
+
let num;
|
|
513
|
+
if (typeof input === "number")
|
|
514
|
+
num = input;
|
|
515
|
+
else if (typeof input === "string" && input.trim() !== "" && Number.isFinite(Number(input))) {
|
|
516
|
+
num = Number(input);
|
|
517
|
+
}
|
|
518
|
+
else
|
|
519
|
+
return this.#reject(issues, path, "应为数字");
|
|
520
|
+
if (!Number.isFinite(num))
|
|
521
|
+
return this.#reject(issues, path, "应为有限数字");
|
|
522
|
+
if (def.int && !Number.isInteger(num))
|
|
523
|
+
return this.#reject(issues, path, "应为整数");
|
|
524
|
+
if (def.min !== undefined && num < def.min)
|
|
525
|
+
return this.#reject(issues, path, `不能小于 ${def.min}`);
|
|
526
|
+
if (def.max !== undefined && num > def.max)
|
|
527
|
+
return this.#reject(issues, path, `不能大于 ${def.max}`);
|
|
528
|
+
return num;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* 校验布尔
|
|
532
|
+
* @param input 输入
|
|
533
|
+
* @param path 路径
|
|
534
|
+
* @param issues 问题容器
|
|
535
|
+
* @returns 布尔或 INVALID
|
|
536
|
+
*/
|
|
537
|
+
#validateBoolean(input, path, issues) {
|
|
538
|
+
if (typeof input === "boolean")
|
|
539
|
+
return input;
|
|
540
|
+
if (typeof input === "string") {
|
|
541
|
+
const text = input.trim().toLowerCase();
|
|
542
|
+
if (["true", "1", "yes", "on", "是"].includes(text))
|
|
543
|
+
return true;
|
|
544
|
+
if (["false", "0", "no", "off", "否"].includes(text))
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
if (input === 1)
|
|
548
|
+
return true;
|
|
549
|
+
if (input === 0)
|
|
550
|
+
return false;
|
|
551
|
+
return this.#reject(issues, path, "应为布尔值");
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 校验枚举
|
|
555
|
+
* @param input 输入
|
|
556
|
+
* @param path 路径
|
|
557
|
+
* @param issues 问题容器
|
|
558
|
+
* @returns 枚举值或 INVALID
|
|
559
|
+
*/
|
|
560
|
+
#validateEnum(input, path, issues) {
|
|
561
|
+
const items = this.#def.enumItems ?? [];
|
|
562
|
+
for (const item of items) {
|
|
563
|
+
if (item.value === input)
|
|
564
|
+
return input;
|
|
565
|
+
// WebUI 的 select 只会回传字符串,数字枚举需要按文本比一次
|
|
566
|
+
if (String(item.value) === String(input))
|
|
567
|
+
return item.value;
|
|
568
|
+
}
|
|
569
|
+
const allowed = items.map(i => String(i.value)).join(" / ");
|
|
570
|
+
return this.#reject(issues, path, `只能是 ${allowed}`);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* 校验数组
|
|
574
|
+
* @param input 输入
|
|
575
|
+
* @param path 路径
|
|
576
|
+
* @param issues 问题容器
|
|
577
|
+
* @returns 数组或 INVALID
|
|
578
|
+
*/
|
|
579
|
+
#validateArray(input, path, issues) {
|
|
580
|
+
const def = this.#def;
|
|
581
|
+
let list;
|
|
582
|
+
if (Array.isArray(input))
|
|
583
|
+
list = input;
|
|
584
|
+
else if (def.wrapSingle)
|
|
585
|
+
list = [input];
|
|
586
|
+
else
|
|
587
|
+
return this.#reject(issues, path, "应为数组");
|
|
588
|
+
if (def.min !== undefined && list.length < def.min) {
|
|
589
|
+
return this.#reject(issues, path, `至少需要 ${def.min} 项`);
|
|
590
|
+
}
|
|
591
|
+
if (def.max !== undefined && list.length > def.max) {
|
|
592
|
+
return this.#reject(issues, path, `最多 ${def.max} 项`);
|
|
593
|
+
}
|
|
594
|
+
const element = def.element;
|
|
595
|
+
if (!element)
|
|
596
|
+
return deepClone(list);
|
|
597
|
+
const out = [];
|
|
598
|
+
let bad = false;
|
|
599
|
+
for (let i = 0; i < list.length; i++) {
|
|
600
|
+
const item = element.#validate(list[i], `${path}[${i}]`, issues);
|
|
601
|
+
if (item === INVALID)
|
|
602
|
+
bad = true;
|
|
603
|
+
else
|
|
604
|
+
out.push(item);
|
|
605
|
+
}
|
|
606
|
+
return bad ? INVALID : out;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* 校验对象
|
|
610
|
+
* @param input 输入
|
|
611
|
+
* @param path 路径
|
|
612
|
+
* @param issues 问题容器
|
|
613
|
+
* @returns 对象或 INVALID
|
|
614
|
+
*/
|
|
615
|
+
#validateObject(input, path, issues) {
|
|
616
|
+
const def = this.#def;
|
|
617
|
+
if (!isPlainObject(input))
|
|
618
|
+
return this.#reject(issues, path, "应为对象");
|
|
619
|
+
const shape = def.shape ?? {};
|
|
620
|
+
const out = {};
|
|
621
|
+
let bad = false;
|
|
622
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
623
|
+
const childPath = path === "" ? key : `${path}.${key}`;
|
|
624
|
+
const value = child.#validate(input[key], childPath, issues);
|
|
625
|
+
if (value === INVALID)
|
|
626
|
+
bad = true;
|
|
627
|
+
else if (value !== undefined)
|
|
628
|
+
out[key] = value;
|
|
629
|
+
}
|
|
630
|
+
for (const key of Object.keys(input)) {
|
|
631
|
+
if (Object.prototype.hasOwnProperty.call(shape, key))
|
|
632
|
+
continue;
|
|
633
|
+
const childPath = path === "" ? key : `${path}.${key}`;
|
|
634
|
+
if (def.strict) {
|
|
635
|
+
this.#fail(issues, childPath, "无法识别的配置项");
|
|
636
|
+
bad = true;
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// 保留但提示:可能是拼写错误,也可能来自暂时卸载的插件
|
|
640
|
+
this.#fail(issues, childPath, "无法识别的配置项,已原样保留", "warn");
|
|
641
|
+
out[key] = deepClone(input[key]);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return bad ? INVALID : out;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* 校验字典
|
|
648
|
+
* @param input 输入
|
|
649
|
+
* @param path 路径
|
|
650
|
+
* @param issues 问题容器
|
|
651
|
+
* @returns 字典或 INVALID
|
|
652
|
+
*/
|
|
653
|
+
#validateRecord(input, path, issues) {
|
|
654
|
+
if (!isPlainObject(input))
|
|
655
|
+
return this.#reject(issues, path, "应为键值对象");
|
|
656
|
+
const valueSchema = this.#def.valueSchema;
|
|
657
|
+
if (!valueSchema)
|
|
658
|
+
return deepClone(input);
|
|
659
|
+
const out = {};
|
|
660
|
+
let bad = false;
|
|
661
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
662
|
+
const childPath = path === "" ? key : `${path}.${key}`;
|
|
663
|
+
const value = valueSchema.#validate(raw, childPath, issues);
|
|
664
|
+
if (value === INVALID)
|
|
665
|
+
bad = true;
|
|
666
|
+
else
|
|
667
|
+
out[key] = value;
|
|
668
|
+
}
|
|
669
|
+
return bad ? INVALID : out;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
_a = Schema;
|
|
673
|
+
/**
|
|
674
|
+
* 创建基础节点
|
|
675
|
+
* @param def 定义片段
|
|
676
|
+
* @returns schema
|
|
677
|
+
*/
|
|
678
|
+
function make(def) {
|
|
679
|
+
return new Schema({ ...def, meta: def.meta ?? {} });
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* 字符串
|
|
683
|
+
* @returns 字符串 schema
|
|
684
|
+
*/
|
|
685
|
+
function string() {
|
|
686
|
+
return make({ kind: "string" });
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* 数字
|
|
690
|
+
* @returns 数字 schema
|
|
691
|
+
*/
|
|
692
|
+
function number() {
|
|
693
|
+
return make({ kind: "number" });
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* 布尔
|
|
697
|
+
* @returns 布尔 schema
|
|
698
|
+
*/
|
|
699
|
+
function boolean() {
|
|
700
|
+
return make({ kind: "boolean" });
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* 字面量
|
|
704
|
+
* @param value 唯一允许的值
|
|
705
|
+
* @returns 字面量 schema
|
|
706
|
+
*/
|
|
707
|
+
function literal(value) {
|
|
708
|
+
return make({ kind: "literal", literalValue: value });
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 枚举
|
|
712
|
+
* @param values 候选值
|
|
713
|
+
* @param labels 值到展示文案的映射
|
|
714
|
+
* @returns 枚举 schema
|
|
715
|
+
*/
|
|
716
|
+
function enumOf(values, labels) {
|
|
717
|
+
const items = values.map(value => {
|
|
718
|
+
const label = labels?.[String(value)];
|
|
719
|
+
return label === undefined ? { value } : { value, label };
|
|
720
|
+
});
|
|
721
|
+
return make({ kind: "enum", enumItems: items, meta: { widget: "select" } });
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* 带说明的枚举(WebUI 下拉框里显示 label 与 description)
|
|
725
|
+
* @param items 候选项
|
|
726
|
+
* @returns 枚举 schema
|
|
727
|
+
*/
|
|
728
|
+
function select(items) {
|
|
729
|
+
return make({
|
|
730
|
+
kind: "enum",
|
|
731
|
+
enumItems: items.map(item => ({ ...item })),
|
|
732
|
+
meta: { widget: "select" }
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* 数组
|
|
737
|
+
* @param element 元素 schema
|
|
738
|
+
* @returns 数组 schema
|
|
739
|
+
*/
|
|
740
|
+
function array(element) {
|
|
741
|
+
return make({ kind: "array", element });
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 对象
|
|
745
|
+
* @param shape 字段表
|
|
746
|
+
* @returns 对象 schema
|
|
747
|
+
*/
|
|
748
|
+
function object(shape) {
|
|
749
|
+
return make({ kind: "object", shape });
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* 字典(键任意、值同构)
|
|
753
|
+
* @param valueSchema 值 schema
|
|
754
|
+
* @returns 字典 schema
|
|
755
|
+
*/
|
|
756
|
+
function record(valueSchema) {
|
|
757
|
+
return make({ kind: "record", valueSchema, meta: { widget: "keyValue" } });
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* 任意值(不校验)
|
|
761
|
+
*
|
|
762
|
+
* 兼容出口:插件存在确实无法描述的结构时使用,但 WebUI 仅能提供 JSON 编辑框。
|
|
763
|
+
* @returns 任意值 schema
|
|
764
|
+
*/
|
|
765
|
+
function unknownValue() {
|
|
766
|
+
return make({ kind: "unknown", meta: { widget: "code" } });
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* 时长(`"30s"` / `"7d"` / 毫秒数)
|
|
770
|
+
*
|
|
771
|
+
* 数值写法与字符串写法都原样保留,不互相转换 —— 见 `SchemaDef.allowNumber`。
|
|
772
|
+
* @returns 时长 schema
|
|
773
|
+
*/
|
|
774
|
+
function duration() {
|
|
775
|
+
return make({ kind: "string", meta: { widget: "duration" }, allowNumber: true }).check("时长格式应为毫秒数或形如 30s / 5m / 1h / 7d", value => typeof value === "number" || parseDuration(value, Number.NaN) === parseDuration(value, 0));
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* cron 表达式
|
|
779
|
+
* @returns cron schema
|
|
780
|
+
*/
|
|
781
|
+
function cron() {
|
|
782
|
+
return make({ kind: "string", meta: { widget: "cron" } }).check("cron 表达式应为 5 或 6 段", value => {
|
|
783
|
+
const parts = value.trim().split(/\s+/);
|
|
784
|
+
return parts.length === 5 || parts.length === 6;
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* 端口号
|
|
789
|
+
* @returns 端口 schema
|
|
790
|
+
*/
|
|
791
|
+
function port() {
|
|
792
|
+
return make({ kind: "number", meta: { widget: "number" }, int: true, min: 1, max: 65535 });
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 密码 / 令牌(自动脱敏)
|
|
796
|
+
* @returns 敏感字符串 schema
|
|
797
|
+
*/
|
|
798
|
+
function password() {
|
|
799
|
+
return string().secret();
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* 多行文本
|
|
803
|
+
* @returns 文本域 schema
|
|
804
|
+
*/
|
|
805
|
+
function text() {
|
|
806
|
+
return string().widget("textarea");
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* 字符串标签数组(WebUI 用 tag 输入框)
|
|
810
|
+
* @returns 标签数组 schema
|
|
811
|
+
*/
|
|
812
|
+
function tags() {
|
|
813
|
+
return array(string()).widget("tags").single();
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* id 列表(QQ 号 / 群号,允许写单个值)
|
|
817
|
+
* @returns id 数组 schema
|
|
818
|
+
*/
|
|
819
|
+
function ids() {
|
|
820
|
+
return array(string().pattern(/^\d{1,20}$/, "应为纯数字 id"))
|
|
821
|
+
.widget("tags")
|
|
822
|
+
.single();
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* 目录路径
|
|
826
|
+
* @returns 目录 schema
|
|
827
|
+
*/
|
|
828
|
+
function dir() {
|
|
829
|
+
return string().widget("dir");
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* 文件路径
|
|
833
|
+
* @returns 文件 schema
|
|
834
|
+
*/
|
|
835
|
+
function file() {
|
|
836
|
+
return string().widget("file");
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* schema 构造器集合
|
|
840
|
+
*
|
|
841
|
+
* 用法:
|
|
842
|
+
* ```ts
|
|
843
|
+
* const conf = s.object({
|
|
844
|
+
* port: s.port().default(3000).title("监听端口"),
|
|
845
|
+
* mode: s.enum(["ws", "ws-reverse"]).default("ws-reverse").title("连接方式")
|
|
846
|
+
* })
|
|
847
|
+
* type Conf = Infer<typeof conf>
|
|
848
|
+
* ```
|
|
849
|
+
*/
|
|
850
|
+
export const s = {
|
|
851
|
+
string,
|
|
852
|
+
number,
|
|
853
|
+
boolean,
|
|
854
|
+
literal,
|
|
855
|
+
enum: enumOf,
|
|
856
|
+
select,
|
|
857
|
+
array,
|
|
858
|
+
object,
|
|
859
|
+
record,
|
|
860
|
+
unknown: unknownValue,
|
|
861
|
+
duration,
|
|
862
|
+
cron,
|
|
863
|
+
port,
|
|
864
|
+
password,
|
|
865
|
+
text,
|
|
866
|
+
tags,
|
|
867
|
+
ids,
|
|
868
|
+
dir,
|
|
869
|
+
file
|
|
870
|
+
};
|
|
871
|
+
//# sourceMappingURL=schema.js.map
|