@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,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:串行队列与并发闸门
|
|
3
|
+
* 依赖方向:仅依赖 util/defer
|
|
4
|
+
* 生命周期:随持有者回收;`clear()` 会拒绝所有排队任务
|
|
5
|
+
* 注意事项:两个必须串行的场景:
|
|
6
|
+
* 1) **KV 自增**。内嵌 KV 没有原子 INCR,"读-改-写"必须按 key 串行,否则统计会掉数。
|
|
7
|
+
* 2) **同群发消息**。并发发送会乱序,且更容易触发平台风控。
|
|
8
|
+
*
|
|
9
|
+
* `Semaphore` 用于限制渲染、HTTP 拉取等昂贵操作的并发数 ——
|
|
10
|
+
* 在 1G 内存的安卓机上,同时开 8 个 puppeteer 页面就是 OOM。
|
|
11
|
+
*/
|
|
12
|
+
import { defer, type Deferred } from "./defer.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 按 key 串行的任务队列
|
|
16
|
+
*
|
|
17
|
+
* 同一 key 的任务严格按提交顺序逐个执行;不同 key 之间并行。
|
|
18
|
+
* key 上没有任务时会自动清理链表头,不会随 key 数量无限增长。
|
|
19
|
+
*/
|
|
20
|
+
export class KeyedQueue {
|
|
21
|
+
/** key → 该 key 上最后一个任务的完成信号 */
|
|
22
|
+
readonly #tails = new Map<string, Promise<unknown>>()
|
|
23
|
+
|
|
24
|
+
/** 当前有排队任务的 key 数量 */
|
|
25
|
+
get size(): number {
|
|
26
|
+
return this.#tails.size
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 排队执行
|
|
31
|
+
* @param key 串行域;同 key 串行,异 key 并行
|
|
32
|
+
* @param task 任务
|
|
33
|
+
* @returns 任务结果
|
|
34
|
+
*/
|
|
35
|
+
run<T>(key: string, task: () => Promise<T> | T): Promise<T> {
|
|
36
|
+
const prev = this.#tails.get(key) ?? Promise.resolve()
|
|
37
|
+
|
|
38
|
+
// 以 catch 捕获前一个任务的错误:前者失败不应阻断其后排队的任务,
|
|
39
|
+
// 但错误本身仍会经由各自的返回 Promise 抛给对应调用方。
|
|
40
|
+
const next = prev.then(
|
|
41
|
+
() => task(),
|
|
42
|
+
() => task()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
this.#tails.set(key, next)
|
|
46
|
+
|
|
47
|
+
// 队尾未发生变化说明自身即最后一个 → 可摘除该 key
|
|
48
|
+
void next
|
|
49
|
+
.catch(() => undefined)
|
|
50
|
+
.finally(() => {
|
|
51
|
+
if (this.#tails.get(key) === next) this.#tails.delete(key)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return next
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 等待某个 key 上的所有任务结束
|
|
59
|
+
* @param key 串行域
|
|
60
|
+
* @returns 全部结束后兑现
|
|
61
|
+
*/
|
|
62
|
+
async drain(key: string): Promise<void> {
|
|
63
|
+
const tail = this.#tails.get(key)
|
|
64
|
+
if (tail) await tail.catch(() => undefined)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 等待全部 key 的任务结束
|
|
69
|
+
* @returns 全部结束后兑现
|
|
70
|
+
*/
|
|
71
|
+
async drainAll(): Promise<void> {
|
|
72
|
+
while (this.#tails.size > 0) {
|
|
73
|
+
const tails = [...this.#tails.values()]
|
|
74
|
+
await Promise.allSettled(tails)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 计数信号量
|
|
81
|
+
*
|
|
82
|
+
* 用于限制昂贵操作的并发上限。务必用 `use()` 而不是手工 acquire/release ——
|
|
83
|
+
* 手工写法一旦任务抛错忘了 release,闸门就永久关闭。
|
|
84
|
+
*/
|
|
85
|
+
export class Semaphore {
|
|
86
|
+
/** 并发上限 */
|
|
87
|
+
readonly #limit: number
|
|
88
|
+
/** 当前占用数 */
|
|
89
|
+
#active = 0
|
|
90
|
+
/** 等待队列 */
|
|
91
|
+
readonly #waiting: Deferred<void>[] = []
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param limit 并发上限,至少 1
|
|
95
|
+
*/
|
|
96
|
+
constructor(limit: number) {
|
|
97
|
+
this.#limit = Math.max(1, Math.floor(limit))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 当前正在执行的任务数 */
|
|
101
|
+
get active(): number {
|
|
102
|
+
return this.#active
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 当前排队等待的任务数 */
|
|
106
|
+
get pending(): number {
|
|
107
|
+
return this.#waiting.length
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 在信号量保护下执行任务
|
|
112
|
+
* @param task 任务
|
|
113
|
+
* @returns 任务结果
|
|
114
|
+
*/
|
|
115
|
+
async use<T>(task: () => Promise<T> | T): Promise<T> {
|
|
116
|
+
await this.#acquire()
|
|
117
|
+
try {
|
|
118
|
+
return await task()
|
|
119
|
+
} finally {
|
|
120
|
+
this.#release()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 获取一个许可
|
|
126
|
+
* @returns 拿到许可后兑现
|
|
127
|
+
*/
|
|
128
|
+
async #acquire(): Promise<void> {
|
|
129
|
+
if (this.#active < this.#limit) {
|
|
130
|
+
this.#active++
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const waiter = defer<void>()
|
|
134
|
+
this.#waiting.push(waiter)
|
|
135
|
+
await waiter.promise
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 释放许可并唤醒下一个等待者 */
|
|
139
|
+
#release(): void {
|
|
140
|
+
const next = this.#waiting.shift()
|
|
141
|
+
if (next) {
|
|
142
|
+
// 许可直接移交,不经过 active-- 再 ++,避免中间态被别人插队
|
|
143
|
+
next.resolve()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
this.#active--
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 并发受限地映射一组任务
|
|
152
|
+
*
|
|
153
|
+
* 与 `Promise.all` 的区别是**同时在跑的任务数有上限**;
|
|
154
|
+
* 结果顺序与输入一致。任一任务抛错则整体拒绝。
|
|
155
|
+
* @param items 输入项
|
|
156
|
+
* @param limit 并发上限
|
|
157
|
+
* @param mapper 映射函数
|
|
158
|
+
* @returns 结果数组,顺序与输入一致
|
|
159
|
+
*/
|
|
160
|
+
export async function mapLimit<T, R>(
|
|
161
|
+
items: readonly T[],
|
|
162
|
+
limit: number,
|
|
163
|
+
mapper: (item: T, index: number) => Promise<R>
|
|
164
|
+
): Promise<R[]> {
|
|
165
|
+
const sem = new Semaphore(limit)
|
|
166
|
+
return Promise.all(items.map((item, index) => sem.use(() => mapper(item, index))))
|
|
167
|
+
}
|
package/src/util/text.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:文本处理(正则转义、宽度对齐、截断、脱敏、长文本切分)
|
|
3
|
+
* 依赖方向:无
|
|
4
|
+
* 生命周期:纯函数
|
|
5
|
+
* 注意事项:`visualWidth` 按东亚全角字符计 2 列宽 —— 日志表格中混排中英文时,
|
|
6
|
+
* 以 `String.length` 对齐将出现偏移。
|
|
7
|
+
*
|
|
8
|
+
* `maskSecret` 并非装饰:CK、token、Cookie 一旦写入日志文件,使用者发送
|
|
9
|
+
* 日志求助即等同于公开账号凭据。配置项标注 `secret` 者必须经由它处理。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ANSI 转义序列(用于清理日志文本)
|
|
14
|
+
*
|
|
15
|
+
* `no-control-regex` 是为"误将控制字符写入正则"而设,而此处需要匹配的目标本身
|
|
16
|
+
* 即为控制字符 —— 以 ESC(0x1b) 开头的 CSI 序列,不写入它则无法剥离颜色码。
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line no-control-regex
|
|
19
|
+
const ANSI_RE = /\u001b\[[0-9;]*[A-Za-z]/g
|
|
20
|
+
|
|
21
|
+
/** 需要在正则里转义的字符 */
|
|
22
|
+
const REGEX_SPECIAL_RE = /[.*+?^${}()|[\]\\]/g
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 转义字符串使其可安全嵌入正则
|
|
26
|
+
*
|
|
27
|
+
* 命令前缀可能含 `#`、`*`、`(` 等字符,直接拼进正则会改变语义甚至抛错。
|
|
28
|
+
* @param input 原始字符串
|
|
29
|
+
* @returns 转义后的字符串
|
|
30
|
+
*/
|
|
31
|
+
export function escapeRegExp(input: string): string {
|
|
32
|
+
return input.replace(REGEX_SPECIAL_RE, "\\$&")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 去掉 ANSI 颜色码
|
|
37
|
+
* @param input 原始字符串
|
|
38
|
+
* @returns 纯文本
|
|
39
|
+
*/
|
|
40
|
+
export function stripAnsi(input: string): string {
|
|
41
|
+
return input.replace(ANSI_RE, "")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 判断码点是否占两列宽(东亚全角、表意文字、部分符号)
|
|
46
|
+
* @param code 码点
|
|
47
|
+
* @returns 是否全角
|
|
48
|
+
*/
|
|
49
|
+
function isWide(code: number): boolean {
|
|
50
|
+
return (
|
|
51
|
+
(code >= 0x1100 && code <= 0x115f) || // 谚文字母
|
|
52
|
+
(code >= 0x2e80 && code <= 0x303e) || // CJK 部首、标点
|
|
53
|
+
(code >= 0x3041 && code <= 0x33ff) || // 假名、CJK 兼容
|
|
54
|
+
(code >= 0x3400 && code <= 0x4dbf) || // CJK 扩展 A
|
|
55
|
+
(code >= 0x4e00 && code <= 0x9fff) || // CJK 基本区
|
|
56
|
+
(code >= 0xa000 && code <= 0xa4cf) || // 彝文
|
|
57
|
+
(code >= 0xac00 && code <= 0xd7a3) || // 谚文音节
|
|
58
|
+
(code >= 0xf900 && code <= 0xfaff) || // CJK 兼容表意
|
|
59
|
+
(code >= 0xfe30 && code <= 0xfe6f) || // CJK 兼容形式
|
|
60
|
+
(code >= 0xff00 && code <= 0xff60) || // 全角 ASCII
|
|
61
|
+
(code >= 0xffe0 && code <= 0xffe6) || // 全角符号
|
|
62
|
+
(code >= 0x1f300 && code <= 0x1f64f) || // 绘文字
|
|
63
|
+
(code >= 0x1f900 && code <= 0x1f9ff) ||
|
|
64
|
+
(code >= 0x20000 && code <= 0x3fffd) // CJK 扩展 B+
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 计算字符串的终端显示列宽
|
|
70
|
+
* @param input 字符串
|
|
71
|
+
* @returns 列宽(全角字符计 2)
|
|
72
|
+
*/
|
|
73
|
+
export function visualWidth(input: string): number {
|
|
74
|
+
let width = 0
|
|
75
|
+
for (const ch of stripAnsi(input)) {
|
|
76
|
+
const code = ch.codePointAt(0)
|
|
77
|
+
if (code === undefined) continue
|
|
78
|
+
if (code < 0x20 || (code >= 0x7f && code < 0xa0)) continue // 控制字符不占宽
|
|
79
|
+
width += isWide(code) ? 2 : 1
|
|
80
|
+
}
|
|
81
|
+
return width
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 按显示宽度右侧补空格
|
|
86
|
+
* @param input 字符串
|
|
87
|
+
* @param width 目标列宽
|
|
88
|
+
* @returns 补齐后的字符串;已超宽时原样返回
|
|
89
|
+
*/
|
|
90
|
+
export function padVisualEnd(input: string, width: number): string {
|
|
91
|
+
const pad = width - visualWidth(input)
|
|
92
|
+
return pad > 0 ? input + " ".repeat(pad) : input
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 按显示宽度左侧补空格
|
|
97
|
+
* @param input 字符串
|
|
98
|
+
* @param width 目标列宽
|
|
99
|
+
* @returns 补齐后的字符串
|
|
100
|
+
*/
|
|
101
|
+
export function padVisualStart(input: string, width: number): string {
|
|
102
|
+
const pad = width - visualWidth(input)
|
|
103
|
+
return pad > 0 ? " ".repeat(pad) + input : input
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 截断字符串
|
|
108
|
+
*
|
|
109
|
+
* 按码点而非 UTF-16 单元切分,不会把 emoji 劈成两半(`slice` 会)。
|
|
110
|
+
* @param input 字符串
|
|
111
|
+
* @param max 最大字符数
|
|
112
|
+
* @param suffix 超长时的省略号,缺省 `"…"`
|
|
113
|
+
* @returns 截断后的字符串
|
|
114
|
+
*/
|
|
115
|
+
export function truncate(input: string, max: number, suffix = "…"): string {
|
|
116
|
+
const chars = [...input]
|
|
117
|
+
if (chars.length <= max) return input
|
|
118
|
+
return chars.slice(0, Math.max(0, max)).join("") + suffix
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 把连续空白折叠成单个空格并去首尾空白
|
|
123
|
+
* @param input 字符串
|
|
124
|
+
* @returns 规整后的字符串
|
|
125
|
+
*/
|
|
126
|
+
export function normalizeSpace(input: string): string {
|
|
127
|
+
return input.replace(/\s+/g, " ").trim()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 敏感信息脱敏
|
|
132
|
+
*
|
|
133
|
+
* 保留首尾各若干字符,中间部分打码;长度不足时整体打码,以避免脱敏后仍可推断原值。
|
|
134
|
+
* @param input 原始值
|
|
135
|
+
* @param keep 头尾各保留的字符数,缺省 4
|
|
136
|
+
* @returns 脱敏后的字符串
|
|
137
|
+
*/
|
|
138
|
+
export function maskSecret(input: string, keep = 4): string {
|
|
139
|
+
if (input === "") return ""
|
|
140
|
+
const chars = [...input]
|
|
141
|
+
if (chars.length <= keep * 2) return "*".repeat(chars.length)
|
|
142
|
+
const head = chars.slice(0, keep).join("")
|
|
143
|
+
const tail = chars.slice(-keep).join("")
|
|
144
|
+
return `${head}${"*".repeat(Math.min(8, chars.length - keep * 2))}${tail}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 将长文本按最大长度切分为多段
|
|
149
|
+
*
|
|
150
|
+
* 优先在换行处切分,其次在空白处切分,两者均不可行时才强制切分 —— 直接强制切分会使
|
|
151
|
+
* 长文本回复难以阅读。
|
|
152
|
+
* @param input 文本
|
|
153
|
+
* @param max 单段最大字符数
|
|
154
|
+
* @returns 分段数组;输入为空时返回空数组
|
|
155
|
+
*/
|
|
156
|
+
export function splitText(input: string, max: number): string[] {
|
|
157
|
+
if (max <= 0) return [input]
|
|
158
|
+
const chars = [...input]
|
|
159
|
+
if (chars.length === 0) return []
|
|
160
|
+
if (chars.length <= max) return [input]
|
|
161
|
+
|
|
162
|
+
const out: string[] = []
|
|
163
|
+
let rest = input
|
|
164
|
+
|
|
165
|
+
while ([...rest].length > max) {
|
|
166
|
+
const window = [...rest].slice(0, max).join("")
|
|
167
|
+
let cut = window.lastIndexOf("\n")
|
|
168
|
+
if (cut < max * 0.5) {
|
|
169
|
+
const space = window.search(/\s+\S*$/)
|
|
170
|
+
cut = space > max * 0.5 ? space : -1
|
|
171
|
+
}
|
|
172
|
+
if (cut <= 0) cut = window.length
|
|
173
|
+
|
|
174
|
+
out.push(rest.slice(0, cut).trimEnd())
|
|
175
|
+
rest = rest.slice(cut).replace(/^\s+/, "")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (rest !== "") out.push(rest)
|
|
179
|
+
return out
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 判断字符串是否可能是纯数字 id(QQ 号、群号)
|
|
184
|
+
* @param input 字符串
|
|
185
|
+
* @returns 是否为 1~20 位纯数字
|
|
186
|
+
*/
|
|
187
|
+
export function isNumericId(input: string): boolean {
|
|
188
|
+
return /^\d{1,20}$/.test(input)
|
|
189
|
+
}
|