@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,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生成随机字符串
|
|
3
|
+
* @param size 长度,缺省 12
|
|
4
|
+
* @returns 随机 id
|
|
5
|
+
*/
|
|
6
|
+
export declare function randomId(size?: number): string;
|
|
7
|
+
/**
|
|
8
|
+
* 生成事件 id
|
|
9
|
+
*
|
|
10
|
+
* 形如 `"m9x2k1qa-7Kd3"`:前段是 36 进制毫秒时间戳,后段随机。
|
|
11
|
+
* @param prefix 可选前缀,如 `"evt"`
|
|
12
|
+
* @returns 事件 id
|
|
13
|
+
*/
|
|
14
|
+
export declare function createEventId(prefix?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* 生成 UUID v4
|
|
17
|
+
* @returns 标准 uuid 字符串
|
|
18
|
+
*/
|
|
19
|
+
export declare function uuid(): string;
|
|
20
|
+
/**
|
|
21
|
+
* 单调递增的自增 id 工厂
|
|
22
|
+
*
|
|
23
|
+
* 用于适配器的 `echo` 序号等只需进程内唯一的场景,比随机串更省。
|
|
24
|
+
* @param prefix 前缀
|
|
25
|
+
* @returns 每次调用返回下一个 id
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSeqFactory(prefix?: string): () => string;
|
|
28
|
+
/**
|
|
29
|
+
* 计算字符串的 sha256 十六进制摘要
|
|
30
|
+
* @param input 输入
|
|
31
|
+
* @param length 截断长度,缺省完整 64 位
|
|
32
|
+
* @returns 十六进制摘要
|
|
33
|
+
*/
|
|
34
|
+
export declare function sha256(input: string, length?: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* 计算字符串的 md5 十六进制摘要
|
|
37
|
+
*
|
|
38
|
+
* 仅用于兼容米游社等平台的签名算法,**不要用于任何安全用途**。
|
|
39
|
+
* @param input 输入
|
|
40
|
+
* @returns 十六进制摘要
|
|
41
|
+
*/
|
|
42
|
+
export declare function md5(input: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* 稳定的短哈希,用于给任意字符串生成缓存键
|
|
45
|
+
* @param input 输入
|
|
46
|
+
* @returns 12 位十六进制
|
|
47
|
+
*/
|
|
48
|
+
export declare function shortHash(input: string): string;
|
|
49
|
+
//# sourceMappingURL=id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/util/id.ts"],"names":[],"mappings":"AAcA;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,SAAK,GAAG,MAAM,CAQ1C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;GAGG;AACH,wBAAgB,IAAI,IAAI,MAAM,CAE7B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,SAAK,GAAG,MAAM,MAAM,CAK1D;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
package/dist/util/id.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:id 生成与哈希
|
|
3
|
+
* 依赖方向:仅依赖 node:crypto
|
|
4
|
+
* 生命周期:纯函数
|
|
5
|
+
* 注意事项:事件 id 采用"时间前缀 + 随机后缀"而非纯 uuid —— 排障时可直接
|
|
6
|
+
* 按时间排序,且在日志中可直接辨识所属批次。
|
|
7
|
+
* 刻意不引入 nanoid:一个 60 行的功能不值得增加一个依赖,在
|
|
8
|
+
* Termux 上安装依赖时尤为如此。
|
|
9
|
+
*/
|
|
10
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
11
|
+
/** id 字符表:去掉了易混淆的 0/O/1/l/I */
|
|
12
|
+
const ALPHABET = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
13
|
+
/**
|
|
14
|
+
* 生成随机字符串
|
|
15
|
+
* @param size 长度,缺省 12
|
|
16
|
+
* @returns 随机 id
|
|
17
|
+
*/
|
|
18
|
+
export function randomId(size = 12) {
|
|
19
|
+
const bytes = randomBytes(size);
|
|
20
|
+
let out = "";
|
|
21
|
+
for (let i = 0; i < size; i++) {
|
|
22
|
+
// 取模引入的偏差在 56/256 上可忽略;这里不用于密码学场景
|
|
23
|
+
out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 生成事件 id
|
|
29
|
+
*
|
|
30
|
+
* 形如 `"m9x2k1qa-7Kd3"`:前段是 36 进制毫秒时间戳,后段随机。
|
|
31
|
+
* @param prefix 可选前缀,如 `"evt"`
|
|
32
|
+
* @returns 事件 id
|
|
33
|
+
*/
|
|
34
|
+
export function createEventId(prefix) {
|
|
35
|
+
const stamp = Date.now().toString(36);
|
|
36
|
+
const tail = randomId(4);
|
|
37
|
+
return prefix ? `${prefix}-${stamp}-${tail}` : `${stamp}-${tail}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 生成 UUID v4
|
|
41
|
+
* @returns 标准 uuid 字符串
|
|
42
|
+
*/
|
|
43
|
+
export function uuid() {
|
|
44
|
+
return randomUUID();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 单调递增的自增 id 工厂
|
|
48
|
+
*
|
|
49
|
+
* 用于适配器的 `echo` 序号等只需进程内唯一的场景,比随机串更省。
|
|
50
|
+
* @param prefix 前缀
|
|
51
|
+
* @returns 每次调用返回下一个 id
|
|
52
|
+
*/
|
|
53
|
+
export function createSeqFactory(prefix = "") {
|
|
54
|
+
let seq = 0;
|
|
55
|
+
// 加一个进程内随机段,避免重启后与对端残留的旧 echo 撞号
|
|
56
|
+
const salt = randomId(4);
|
|
57
|
+
return () => `${prefix}${salt}${(++seq).toString(36)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 计算字符串的 sha256 十六进制摘要
|
|
61
|
+
* @param input 输入
|
|
62
|
+
* @param length 截断长度,缺省完整 64 位
|
|
63
|
+
* @returns 十六进制摘要
|
|
64
|
+
*/
|
|
65
|
+
export function sha256(input, length) {
|
|
66
|
+
const hex = createHash("sha256").update(input, "utf8").digest("hex");
|
|
67
|
+
return length ? hex.slice(0, length) : hex;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 计算字符串的 md5 十六进制摘要
|
|
71
|
+
*
|
|
72
|
+
* 仅用于兼容米游社等平台的签名算法,**不要用于任何安全用途**。
|
|
73
|
+
* @param input 输入
|
|
74
|
+
* @returns 十六进制摘要
|
|
75
|
+
*/
|
|
76
|
+
export function md5(input) {
|
|
77
|
+
return createHash("md5").update(input, "utf8").digest("hex");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 稳定的短哈希,用于给任意字符串生成缓存键
|
|
81
|
+
* @param input 输入
|
|
82
|
+
* @returns 12 位十六进制
|
|
83
|
+
*/
|
|
84
|
+
export function shortHash(input) {
|
|
85
|
+
return sha256(input, 12);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/util/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEjE,+BAA+B;AAC/B,MAAM,QAAQ,GAAG,0DAA0D,CAAA;AAE3E;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAI,GAAG,EAAE;IAChC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,kCAAkC;QAClC,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACxB,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,EAAE,CAAA;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI;IAClB,OAAO,UAAU,EAAE,CAAA;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAM,GAAG,EAAE;IAC1C,IAAI,GAAG,GAAG,CAAC,CAAA;IACX,iCAAiC;IACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACxB,OAAO,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAA;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,MAAe;IACnD,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACpE,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,CAAC,KAAa;IAC/B,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AAC1B,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:LRU + TTL 缓存,带并发去重
|
|
3
|
+
* 依赖方向:仅依赖类型包
|
|
4
|
+
* 生命周期:随创建者(插件 / 账号)一起被回收
|
|
5
|
+
* 注意事项:三项机制合起来使缓存的内存占用具备上界 —— 裸 Map 存群成员表且从不淘汰时,
|
|
6
|
+
* 数十个千人群即是数百 MB 常驻:
|
|
7
|
+
* 1) 硬性上限 + LRU 淘汰;
|
|
8
|
+
* 2) TTL 惰性过期;
|
|
9
|
+
* 3) single-flight 合并同 key 的并发请求,避免一条群消息中多处判定权限时
|
|
10
|
+
* 各发一次平台请求。
|
|
11
|
+
*
|
|
12
|
+
* **不采用定时器扫描**:定时器或造成泄漏,或需额外的生命周期管理。改为每次写入时
|
|
13
|
+
* 清理队首若干过期项,摊还成本 O(1)。
|
|
14
|
+
*/
|
|
15
|
+
import type { ContactCache, ContactCacheOptions } from "@yunzai-ng/types";
|
|
16
|
+
/**
|
|
17
|
+
* LRU + TTL 缓存
|
|
18
|
+
*
|
|
19
|
+
* 利用 `Map` 的插入顺序实现 LRU:读命中时把条目挪到队尾,
|
|
20
|
+
* 于是队首恒为最久未使用者,淘汰只需 `keys().next()`。
|
|
21
|
+
*/
|
|
22
|
+
export declare class LruCache<V> implements ContactCache<V> {
|
|
23
|
+
#private;
|
|
24
|
+
/**
|
|
25
|
+
* @param opts 缓存参数;`max` 至少为 1,`ttl` 为 0 表示不过期
|
|
26
|
+
*/
|
|
27
|
+
constructor(opts: ContactCacheOptions);
|
|
28
|
+
/** 当前条目数(含尚未被清理的过期项) */
|
|
29
|
+
get size(): number;
|
|
30
|
+
/** 命中率统计,供 `/api/diagnostics` 展示缓存是否配得合理 */
|
|
31
|
+
get stats(): {
|
|
32
|
+
hits: number;
|
|
33
|
+
misses: number;
|
|
34
|
+
size: number;
|
|
35
|
+
max: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 取缓存
|
|
39
|
+
* @param key 键
|
|
40
|
+
* @returns 值;未命中或已过期时 undefined
|
|
41
|
+
*/
|
|
42
|
+
get(key: string): V | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* 写缓存
|
|
45
|
+
* @param key 键
|
|
46
|
+
* @param value 值
|
|
47
|
+
* @param ttl 覆盖默认存活时长(毫秒)
|
|
48
|
+
*/
|
|
49
|
+
set(key: string, value: V, ttl?: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* 删除
|
|
52
|
+
* @param key 键
|
|
53
|
+
*/
|
|
54
|
+
delete(key: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* 取缓存,未命中时用 loader 拉取并回填
|
|
57
|
+
*
|
|
58
|
+
* 同一 key 的并发调用共享同一个 loader promise。loader 抛错时不缓存,
|
|
59
|
+
* 但会把错误传播给所有等待者——避免"一次网络抖动被缓存成永久失败"。
|
|
60
|
+
* @param key 键
|
|
61
|
+
* @param loader 拉取函数
|
|
62
|
+
* @returns 值;loader 返回 undefined 时不缓存
|
|
63
|
+
*/
|
|
64
|
+
fetch(key: string, loader: (key: string) => Promise<V | undefined>): Promise<V | undefined>;
|
|
65
|
+
/** 清空全部条目与进行中的 loader 记录 */
|
|
66
|
+
clear(): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 创建 LRU + TTL 缓存
|
|
70
|
+
* @param opts 缓存参数
|
|
71
|
+
* @returns 缓存实例
|
|
72
|
+
*/
|
|
73
|
+
export declare function createCache<V>(opts: ContactCacheOptions): LruCache<V>;
|
|
74
|
+
//# sourceMappingURL=lru.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lru.d.ts","sourceRoot":"","sources":["../../src/util/lru.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAczE;;;;;GAKG;AACH,qBAAa,QAAQ,CAAC,CAAC,CAAE,YAAW,YAAY,CAAC,CAAC,CAAC;;IAejD;;OAEG;gBACS,IAAI,EAAE,mBAAmB;IAMrC,wBAAwB;IACxB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,4CAA4C;IAC5C,IAAI,KAAK,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAEvE;IAED;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAkB/B;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAgB9C;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB;;;;;;;;OAQG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAqBjG,4BAA4B;IAC5B,KAAK,IAAI,IAAI;CAuBd;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,GAAG,QAAQ,CAAC,CAAC,CAAC,CAErE"}
|
package/dist/util/lru.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { parseDuration } from "./duration.js";
|
|
2
|
+
/** 每次写入时顺带检查的队首条目数 */
|
|
3
|
+
const SWEEP_PER_WRITE = 4;
|
|
4
|
+
/**
|
|
5
|
+
* LRU + TTL 缓存
|
|
6
|
+
*
|
|
7
|
+
* 利用 `Map` 的插入顺序实现 LRU:读命中时把条目挪到队尾,
|
|
8
|
+
* 于是队首恒为最久未使用者,淘汰只需 `keys().next()`。
|
|
9
|
+
*/
|
|
10
|
+
export class LruCache {
|
|
11
|
+
/** 主存储,键序即 LRU 序(队首最久未用) */
|
|
12
|
+
#map = new Map();
|
|
13
|
+
/** 进行中的 loader,用于并发去重 */
|
|
14
|
+
#pending = new Map();
|
|
15
|
+
/** 条目上限 */
|
|
16
|
+
#max;
|
|
17
|
+
/** 默认存活毫秒 */
|
|
18
|
+
#ttl;
|
|
19
|
+
/** 命中次数(诊断用) */
|
|
20
|
+
#hits = 0;
|
|
21
|
+
/** 未命中次数(诊断用) */
|
|
22
|
+
#misses = 0;
|
|
23
|
+
/**
|
|
24
|
+
* @param opts 缓存参数;`max` 至少为 1,`ttl` 为 0 表示不过期
|
|
25
|
+
*/
|
|
26
|
+
constructor(opts) {
|
|
27
|
+
this.#max = Math.max(1, Math.floor(opts.max));
|
|
28
|
+
const ttl = parseDuration(opts.ttl, 0);
|
|
29
|
+
this.#ttl = ttl > 0 ? ttl : Number.POSITIVE_INFINITY;
|
|
30
|
+
}
|
|
31
|
+
/** 当前条目数(含尚未被清理的过期项) */
|
|
32
|
+
get size() {
|
|
33
|
+
return this.#map.size;
|
|
34
|
+
}
|
|
35
|
+
/** 命中率统计,供 `/api/diagnostics` 展示缓存是否配得合理 */
|
|
36
|
+
get stats() {
|
|
37
|
+
return { hits: this.#hits, misses: this.#misses, size: this.#map.size, max: this.#max };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 取缓存
|
|
41
|
+
* @param key 键
|
|
42
|
+
* @returns 值;未命中或已过期时 undefined
|
|
43
|
+
*/
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = this.#map.get(key);
|
|
46
|
+
if (!entry) {
|
|
47
|
+
this.#misses++;
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (entry.expireAt <= Date.now()) {
|
|
51
|
+
this.#map.delete(key);
|
|
52
|
+
this.#misses++;
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
// 命中即挪到队尾:delete + set 是 Map 上唯一能改插入序的手段
|
|
56
|
+
this.#map.delete(key);
|
|
57
|
+
this.#map.set(key, entry);
|
|
58
|
+
this.#hits++;
|
|
59
|
+
return entry.value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 写缓存
|
|
63
|
+
* @param key 键
|
|
64
|
+
* @param value 值
|
|
65
|
+
* @param ttl 覆盖默认存活时长(毫秒)
|
|
66
|
+
*/
|
|
67
|
+
set(key, value, ttl) {
|
|
68
|
+
this.#sweep();
|
|
69
|
+
const expireAt = ttl === undefined ? this.#ttl : ttl;
|
|
70
|
+
this.#map.delete(key);
|
|
71
|
+
this.#map.set(key, {
|
|
72
|
+
value,
|
|
73
|
+
expireAt: Number.isFinite(expireAt) ? Date.now() + expireAt : Number.POSITIVE_INFINITY
|
|
74
|
+
});
|
|
75
|
+
// 超限时从队首淘汰,直到回到上限内
|
|
76
|
+
while (this.#map.size > this.#max) {
|
|
77
|
+
const oldest = this.#map.keys().next();
|
|
78
|
+
if (oldest.done)
|
|
79
|
+
break;
|
|
80
|
+
this.#map.delete(oldest.value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 删除
|
|
85
|
+
* @param key 键
|
|
86
|
+
*/
|
|
87
|
+
delete(key) {
|
|
88
|
+
this.#map.delete(key);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 取缓存,未命中时用 loader 拉取并回填
|
|
92
|
+
*
|
|
93
|
+
* 同一 key 的并发调用共享同一个 loader promise。loader 抛错时不缓存,
|
|
94
|
+
* 但会把错误传播给所有等待者——避免"一次网络抖动被缓存成永久失败"。
|
|
95
|
+
* @param key 键
|
|
96
|
+
* @param loader 拉取函数
|
|
97
|
+
* @returns 值;loader 返回 undefined 时不缓存
|
|
98
|
+
*/
|
|
99
|
+
async fetch(key, loader) {
|
|
100
|
+
const cached = this.get(key);
|
|
101
|
+
if (cached !== undefined)
|
|
102
|
+
return cached;
|
|
103
|
+
const inflight = this.#pending.get(key);
|
|
104
|
+
if (inflight)
|
|
105
|
+
return inflight;
|
|
106
|
+
const task = (async () => {
|
|
107
|
+
try {
|
|
108
|
+
const value = await loader(key);
|
|
109
|
+
if (value !== undefined)
|
|
110
|
+
this.set(key, value);
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
this.#pending.delete(key);
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
117
|
+
this.#pending.set(key, task);
|
|
118
|
+
return task;
|
|
119
|
+
}
|
|
120
|
+
/** 清空全部条目与进行中的 loader 记录 */
|
|
121
|
+
clear() {
|
|
122
|
+
this.#map.clear();
|
|
123
|
+
this.#pending.clear();
|
|
124
|
+
this.#hits = 0;
|
|
125
|
+
this.#misses = 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 清理队首若干个过期条目
|
|
129
|
+
*
|
|
130
|
+
* 只看队首是因为 LRU 序下队首最可能已经过期;全量扫描在千级条目上
|
|
131
|
+
* 会让每次写入都变成 O(n)。
|
|
132
|
+
*/
|
|
133
|
+
#sweep() {
|
|
134
|
+
if (this.#ttl === Number.POSITIVE_INFINITY)
|
|
135
|
+
return;
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
let checked = 0;
|
|
138
|
+
for (const [key, entry] of this.#map) {
|
|
139
|
+
if (checked++ >= SWEEP_PER_WRITE)
|
|
140
|
+
break;
|
|
141
|
+
if (entry.expireAt <= now)
|
|
142
|
+
this.#map.delete(key);
|
|
143
|
+
else
|
|
144
|
+
break; // 队首未过期时后续项更不可能过期(近似成立,满足本场景需要)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 创建 LRU + TTL 缓存
|
|
150
|
+
* @param opts 缓存参数
|
|
151
|
+
* @returns 缓存实例
|
|
152
|
+
*/
|
|
153
|
+
export function createCache(opts) {
|
|
154
|
+
return new LruCache(opts);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=lru.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lru.js","sourceRoot":"","sources":["../../src/util/lru.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按 key 串行的任务队列
|
|
3
|
+
*
|
|
4
|
+
* 同一 key 的任务严格按提交顺序逐个执行;不同 key 之间并行。
|
|
5
|
+
* key 上没有任务时会自动清理链表头,不会随 key 数量无限增长。
|
|
6
|
+
*/
|
|
7
|
+
export declare class KeyedQueue {
|
|
8
|
+
#private;
|
|
9
|
+
/** 当前有排队任务的 key 数量 */
|
|
10
|
+
get size(): number;
|
|
11
|
+
/**
|
|
12
|
+
* 排队执行
|
|
13
|
+
* @param key 串行域;同 key 串行,异 key 并行
|
|
14
|
+
* @param task 任务
|
|
15
|
+
* @returns 任务结果
|
|
16
|
+
*/
|
|
17
|
+
run<T>(key: string, task: () => Promise<T> | T): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* 等待某个 key 上的所有任务结束
|
|
20
|
+
* @param key 串行域
|
|
21
|
+
* @returns 全部结束后兑现
|
|
22
|
+
*/
|
|
23
|
+
drain(key: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* 等待全部 key 的任务结束
|
|
26
|
+
* @returns 全部结束后兑现
|
|
27
|
+
*/
|
|
28
|
+
drainAll(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 计数信号量
|
|
32
|
+
*
|
|
33
|
+
* 用于限制昂贵操作的并发上限。务必用 `use()` 而不是手工 acquire/release ——
|
|
34
|
+
* 手工写法一旦任务抛错忘了 release,闸门就永久关闭。
|
|
35
|
+
*/
|
|
36
|
+
export declare class Semaphore {
|
|
37
|
+
#private;
|
|
38
|
+
/**
|
|
39
|
+
* @param limit 并发上限,至少 1
|
|
40
|
+
*/
|
|
41
|
+
constructor(limit: number);
|
|
42
|
+
/** 当前正在执行的任务数 */
|
|
43
|
+
get active(): number;
|
|
44
|
+
/** 当前排队等待的任务数 */
|
|
45
|
+
get pending(): number;
|
|
46
|
+
/**
|
|
47
|
+
* 在信号量保护下执行任务
|
|
48
|
+
* @param task 任务
|
|
49
|
+
* @returns 任务结果
|
|
50
|
+
*/
|
|
51
|
+
use<T>(task: () => Promise<T> | T): Promise<T>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 并发受限地映射一组任务
|
|
55
|
+
*
|
|
56
|
+
* 与 `Promise.all` 的区别是**同时在跑的任务数有上限**;
|
|
57
|
+
* 结果顺序与输入一致。任一任务抛错则整体拒绝。
|
|
58
|
+
* @param items 输入项
|
|
59
|
+
* @param limit 并发上限
|
|
60
|
+
* @param mapper 映射函数
|
|
61
|
+
* @returns 结果数组,顺序与输入一致
|
|
62
|
+
*/
|
|
63
|
+
export declare function mapLimit<T, R>(items: readonly T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
64
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/util/queue.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,qBAAa,UAAU;;IAIrB,sBAAsB;IACtB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAsB3D;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvC;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAMhC;AAED;;;;;GAKG;AACH,qBAAa,SAAS;;IAQpB;;OAEG;gBACS,KAAK,EAAE,MAAM;IAIzB,iBAAiB;IACjB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,iBAAiB;IACjB,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;;OAIG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAiCrD;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EACjC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7C,OAAO,CAAC,CAAC,EAAE,CAAC,CAGd"}
|
|
@@ -0,0 +1,147 @@
|
|
|
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 } from "./defer.js";
|
|
13
|
+
/**
|
|
14
|
+
* 按 key 串行的任务队列
|
|
15
|
+
*
|
|
16
|
+
* 同一 key 的任务严格按提交顺序逐个执行;不同 key 之间并行。
|
|
17
|
+
* key 上没有任务时会自动清理链表头,不会随 key 数量无限增长。
|
|
18
|
+
*/
|
|
19
|
+
export class KeyedQueue {
|
|
20
|
+
/** key → 该 key 上最后一个任务的完成信号 */
|
|
21
|
+
#tails = new Map();
|
|
22
|
+
/** 当前有排队任务的 key 数量 */
|
|
23
|
+
get size() {
|
|
24
|
+
return this.#tails.size;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 排队执行
|
|
28
|
+
* @param key 串行域;同 key 串行,异 key 并行
|
|
29
|
+
* @param task 任务
|
|
30
|
+
* @returns 任务结果
|
|
31
|
+
*/
|
|
32
|
+
run(key, task) {
|
|
33
|
+
const prev = this.#tails.get(key) ?? Promise.resolve();
|
|
34
|
+
// 以 catch 捕获前一个任务的错误:前者失败不应阻断其后排队的任务,
|
|
35
|
+
// 但错误本身仍会经由各自的返回 Promise 抛给对应调用方。
|
|
36
|
+
const next = prev.then(() => task(), () => task());
|
|
37
|
+
this.#tails.set(key, next);
|
|
38
|
+
// 队尾未发生变化说明自身即最后一个 → 可摘除该 key
|
|
39
|
+
void next
|
|
40
|
+
.catch(() => undefined)
|
|
41
|
+
.finally(() => {
|
|
42
|
+
if (this.#tails.get(key) === next)
|
|
43
|
+
this.#tails.delete(key);
|
|
44
|
+
});
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 等待某个 key 上的所有任务结束
|
|
49
|
+
* @param key 串行域
|
|
50
|
+
* @returns 全部结束后兑现
|
|
51
|
+
*/
|
|
52
|
+
async drain(key) {
|
|
53
|
+
const tail = this.#tails.get(key);
|
|
54
|
+
if (tail)
|
|
55
|
+
await tail.catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 等待全部 key 的任务结束
|
|
59
|
+
* @returns 全部结束后兑现
|
|
60
|
+
*/
|
|
61
|
+
async drainAll() {
|
|
62
|
+
while (this.#tails.size > 0) {
|
|
63
|
+
const tails = [...this.#tails.values()];
|
|
64
|
+
await Promise.allSettled(tails);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 计数信号量
|
|
70
|
+
*
|
|
71
|
+
* 用于限制昂贵操作的并发上限。务必用 `use()` 而不是手工 acquire/release ——
|
|
72
|
+
* 手工写法一旦任务抛错忘了 release,闸门就永久关闭。
|
|
73
|
+
*/
|
|
74
|
+
export class Semaphore {
|
|
75
|
+
/** 并发上限 */
|
|
76
|
+
#limit;
|
|
77
|
+
/** 当前占用数 */
|
|
78
|
+
#active = 0;
|
|
79
|
+
/** 等待队列 */
|
|
80
|
+
#waiting = [];
|
|
81
|
+
/**
|
|
82
|
+
* @param limit 并发上限,至少 1
|
|
83
|
+
*/
|
|
84
|
+
constructor(limit) {
|
|
85
|
+
this.#limit = Math.max(1, Math.floor(limit));
|
|
86
|
+
}
|
|
87
|
+
/** 当前正在执行的任务数 */
|
|
88
|
+
get active() {
|
|
89
|
+
return this.#active;
|
|
90
|
+
}
|
|
91
|
+
/** 当前排队等待的任务数 */
|
|
92
|
+
get pending() {
|
|
93
|
+
return this.#waiting.length;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 在信号量保护下执行任务
|
|
97
|
+
* @param task 任务
|
|
98
|
+
* @returns 任务结果
|
|
99
|
+
*/
|
|
100
|
+
async use(task) {
|
|
101
|
+
await this.#acquire();
|
|
102
|
+
try {
|
|
103
|
+
return await task();
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
this.#release();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 获取一个许可
|
|
111
|
+
* @returns 拿到许可后兑现
|
|
112
|
+
*/
|
|
113
|
+
async #acquire() {
|
|
114
|
+
if (this.#active < this.#limit) {
|
|
115
|
+
this.#active++;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const waiter = defer();
|
|
119
|
+
this.#waiting.push(waiter);
|
|
120
|
+
await waiter.promise;
|
|
121
|
+
}
|
|
122
|
+
/** 释放许可并唤醒下一个等待者 */
|
|
123
|
+
#release() {
|
|
124
|
+
const next = this.#waiting.shift();
|
|
125
|
+
if (next) {
|
|
126
|
+
// 许可直接移交,不经过 active-- 再 ++,避免中间态被别人插队
|
|
127
|
+
next.resolve();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.#active--;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 并发受限地映射一组任务
|
|
135
|
+
*
|
|
136
|
+
* 与 `Promise.all` 的区别是**同时在跑的任务数有上限**;
|
|
137
|
+
* 结果顺序与输入一致。任一任务抛错则整体拒绝。
|
|
138
|
+
* @param items 输入项
|
|
139
|
+
* @param limit 并发上限
|
|
140
|
+
* @param mapper 映射函数
|
|
141
|
+
* @returns 结果数组,顺序与输入一致
|
|
142
|
+
*/
|
|
143
|
+
export async function mapLimit(items, limit, mapper) {
|
|
144
|
+
const sem = new Semaphore(limit);
|
|
145
|
+
return Promise.all(items.map((item, index) => sem.use(() => mapper(item, index))));
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/util/queue.ts"],"names":[],"mappings":"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"}
|