@yunzai-ng/core 0.1.0 → 0.2.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.
@@ -1,156 +1,156 @@
1
- /**
2
- * 模块职责:KV 存储的装配(驱动选择、自动降级、根命名空间)
3
- * 依赖方向:依赖 store/{kv,memory,json,level}、util/fs、类型包
4
- * 生命周期:应用级单例,`close()` 时落盘
5
- * 注意事项:**存储不可用不等于机器人不可用。** `auto` 模式按 level → json → memory 逐级降级,
6
- * 每次降级记一条 warn 说明原因;最坏退到纯内存(重启丢缓存),但机器人始终能收发消息。
7
- *
8
- * 外部注册的驱动(如 store-redis 插件)通过 `registerKvDriver()` 挂进来,
9
- * 与内置驱动一视同仁 —— 这正是"一切皆可为插件"在存储层的落点。
10
- */
11
- import { join } from "node:path"
12
- import type { KvDriver, KvNamespace, Logger } from "@yunzai-ng/types"
13
- import { Kv } from "./kv.js"
14
- import { MemoryKvDriver } from "./memory.js"
15
- import { JsonKvDriver } from "./json.js"
16
- import { LevelKvDriver, loadLevel } from "./level.js"
17
-
18
- /** 内置驱动 id */
19
- export type BuiltinDriverId = "auto" | "level" | "json" | "memory"
20
-
21
- /** 驱动工厂:拿到数据目录,产出一个尚未 open 的驱动 */
22
- export type KvDriverFactory = (dir: string) => Promise<KvDriver> | KvDriver
23
-
24
- /** 外部注册的驱动表(插件注册的驱动进这里) */
25
- const externalDrivers = new Map<string, KvDriverFactory>()
26
-
27
- /**
28
- * 注册一个 KV 驱动
29
- *
30
- * 供 `store-redis` 这类插件调用;配置里把 `store.driver` 写成同一个 id 即可启用。
31
- * @param id 驱动 id
32
- * @param factory 驱动工厂
33
- * @returns 取消注册
34
- * @throws id 与内置驱动冲突时
35
- */
36
- export function registerKvDriver(id: string, factory: KvDriverFactory): () => void {
37
- if (id === "auto" || id === "level" || id === "json" || id === "memory") {
38
- throw new Error(`驱动 id ${id} 与内置驱动冲突`)
39
- }
40
- externalDrivers.set(id, factory)
41
- return () => void externalDrivers.delete(id)
42
- }
43
-
44
- /** 打开 KV 存储的参数 */
45
- export interface OpenKvOptions {
46
- /** 数据目录(KV 数据会放在其子目录里) */
47
- dir: string
48
- /** 驱动 id,缺省 `auto` */
49
- driver?: string
50
- /** 日志器 */
51
- logger: Logger
52
- }
53
-
54
- /** 已打开的 KV 存储 */
55
- export interface KvStore {
56
- /** 实际生效的驱动 id(`auto` 会被解析为具体驱动) */
57
- readonly driver: string
58
- /** 根命名空间 */
59
- readonly root: KvNamespace
60
- /**
61
- * 取一个子命名空间
62
- * @param name 命名空间名
63
- * @returns KV 视图
64
- */
65
- namespace(name: string): KvNamespace
66
- /** 关闭并落盘 */
67
- close(): Promise<void>
68
- }
69
-
70
- /**
71
- * 打开 KV 存储
72
- *
73
- * `auto` 的降级顺序:level(性能最优)→ json(纯 JS,Termux 兜底)→ memory。
74
- * 显式指定驱动时同样会降级,但会将"所指定的驱动为何不可用"记为 warn,
75
- * 而非静默替换 —— 使用者配置了 redis 却运行于内存驱动之上而不知情是最糟的情形。
76
- * @param opts 参数
77
- * @returns 已打开的 KV 存储
78
- */
79
- export async function openKv(opts: OpenKvOptions): Promise<KvStore> {
80
- const logger = opts.logger.child({ scope: "store" })
81
- const requested = opts.driver ?? "auto"
82
- const chain = resolveChain(requested)
83
-
84
- let driver: KvDriver | undefined
85
- for (const id of chain) {
86
- try {
87
- const candidate = await createDriver(id, opts.dir)
88
- if (!candidate) {
89
- logger.warn(`KV 驱动 ${id} 不可用(未安装或未注册),尝试下一个`)
90
- continue
91
- }
92
- await candidate.open()
93
- driver = candidate
94
- if (id !== requested && requested !== "auto") {
95
- logger.warn(`配置指定的 KV 驱动 ${requested} 不可用,已降级为 ${id}`)
96
- }
97
- break
98
- } catch (err) {
99
- logger.warn(`KV 驱动 ${id} 打开失败,尝试下一个`, err)
100
- }
101
- }
102
-
103
- if (!driver) {
104
- // chain 末尾恒为 memory,理论上到不了这里;留着是为了让失败可诊断而不是空指针
105
- throw new Error("所有 KV 驱动均不可用")
106
- }
107
-
108
- logger.info(`KV 存储就绪:驱动 ${driver.id}`)
109
- const root = new Kv(driver)
110
-
111
- return {
112
- driver: driver.id,
113
- root,
114
- namespace: (name: string) => root.sub(name),
115
- close: async () => {
116
- await driver.close()
117
- }
118
- }
119
- }
120
-
121
- /**
122
- * 把请求的驱动 id 展开成降级链
123
- * @param requested 请求的驱动 id
124
- * @returns 依次尝试的驱动 id 列表,末尾恒为 memory
125
- */
126
- function resolveChain(requested: string): string[] {
127
- if (requested === "auto") return ["level", "json", "memory"]
128
- if (requested === "memory") return ["memory"]
129
- if (requested === "json") return ["json", "memory"]
130
- if (requested === "level") return ["level", "json", "memory"]
131
- // 外部驱动(redis 等)失败后退回内置默认链
132
- return [requested, "level", "json", "memory"]
133
- }
134
-
135
- /**
136
- * 按 id 造驱动
137
- * @param id 驱动 id
138
- * @param dir 数据目录
139
- * @returns 驱动实例;不可用时 undefined
140
- */
141
- async function createDriver(id: string, dir: string): Promise<KvDriver | undefined> {
142
- switch (id) {
143
- case "memory":
144
- return new MemoryKvDriver()
145
- case "json":
146
- return new JsonKvDriver(join(dir, "kv.json"))
147
- case "level": {
148
- const ctor = await loadLevel()
149
- return ctor ? new LevelKvDriver(join(dir, "kv"), ctor) : undefined
150
- }
151
- default: {
152
- const factory = externalDrivers.get(id)
153
- return factory ? await factory(dir) : undefined
154
- }
155
- }
156
- }
1
+ /**
2
+ * 模块职责:KV 存储的装配(驱动选择、自动降级、根命名空间)
3
+ * 依赖方向:依赖 store/{kv,memory,json,level}、util/fs、类型包
4
+ * 生命周期:应用级单例,`close()` 时落盘
5
+ * 注意事项:**存储不可用不等于机器人不可用。** `auto` 模式按 level → json → memory 逐级降级,
6
+ * 每次降级记一条 warn 说明原因;最坏退到纯内存(重启丢缓存),但机器人始终能收发消息。
7
+ *
8
+ * 外部注册的驱动(如 store-redis 插件)通过 `registerKvDriver()` 挂进来,
9
+ * 与内置驱动一视同仁 —— 这正是"一切皆可为插件"在存储层的落点。
10
+ */
11
+ import { join } from "node:path"
12
+ import type { KvDriver, KvNamespace, Logger } from "@yunzai-ng/types"
13
+ import { Kv } from "./kv.js"
14
+ import { MemoryKvDriver } from "./memory.js"
15
+ import { JsonKvDriver } from "./json.js"
16
+ import { LevelKvDriver, loadLevel } from "./level.js"
17
+
18
+ /** 内置驱动 id */
19
+ export type BuiltinDriverId = "auto" | "level" | "json" | "memory"
20
+
21
+ /** 驱动工厂:拿到数据目录,产出一个尚未 open 的驱动 */
22
+ export type KvDriverFactory = (dir: string) => Promise<KvDriver> | KvDriver
23
+
24
+ /** 外部注册的驱动表(插件注册的驱动进这里) */
25
+ const externalDrivers = new Map<string, KvDriverFactory>()
26
+
27
+ /**
28
+ * 注册一个 KV 驱动
29
+ *
30
+ * 供 `store-redis` 这类插件调用;配置里把 `store.driver` 写成同一个 id 即可启用。
31
+ * @param id 驱动 id
32
+ * @param factory 驱动工厂
33
+ * @returns 取消注册
34
+ * @throws id 与内置驱动冲突时
35
+ */
36
+ export function registerKvDriver(id: string, factory: KvDriverFactory): () => void {
37
+ if (id === "auto" || id === "level" || id === "json" || id === "memory") {
38
+ throw new Error(`驱动 id ${id} 与内置驱动冲突`)
39
+ }
40
+ externalDrivers.set(id, factory)
41
+ return () => void externalDrivers.delete(id)
42
+ }
43
+
44
+ /** 打开 KV 存储的参数 */
45
+ export interface OpenKvOptions {
46
+ /** 数据目录(KV 数据会放在其子目录里) */
47
+ dir: string
48
+ /** 驱动 id,缺省 `auto` */
49
+ driver?: string
50
+ /** 日志器 */
51
+ logger: Logger
52
+ }
53
+
54
+ /** 已打开的 KV 存储 */
55
+ export interface KvStore {
56
+ /** 实际生效的驱动 id(`auto` 会被解析为具体驱动) */
57
+ readonly driver: string
58
+ /** 根命名空间 */
59
+ readonly root: KvNamespace
60
+ /**
61
+ * 取一个子命名空间
62
+ * @param name 命名空间名
63
+ * @returns KV 视图
64
+ */
65
+ namespace(name: string): KvNamespace
66
+ /** 关闭并落盘 */
67
+ close(): Promise<void>
68
+ }
69
+
70
+ /**
71
+ * 打开 KV 存储
72
+ *
73
+ * `auto` 的降级顺序:level(性能最优)→ json(纯 JS,Termux 兜底)→ memory。
74
+ * 显式指定驱动时同样会降级,但会将"所指定的驱动为何不可用"记为 warn,
75
+ * 而非静默替换 —— 使用者配置了 redis 却运行于内存驱动之上而不知情是最糟的情形。
76
+ * @param opts 参数
77
+ * @returns 已打开的 KV 存储
78
+ */
79
+ export async function openKv(opts: OpenKvOptions): Promise<KvStore> {
80
+ const logger = opts.logger.child({ scope: "store" })
81
+ const requested = opts.driver ?? "auto"
82
+ const chain = resolveChain(requested)
83
+
84
+ let driver: KvDriver | undefined
85
+ for (const id of chain) {
86
+ try {
87
+ const candidate = await createDriver(id, opts.dir)
88
+ if (!candidate) {
89
+ logger.warn(`KV 驱动 ${id} 不可用(未安装或未注册),尝试下一个`)
90
+ continue
91
+ }
92
+ await candidate.open()
93
+ driver = candidate
94
+ if (id !== requested && requested !== "auto") {
95
+ logger.warn(`配置指定的 KV 驱动 ${requested} 不可用,已降级为 ${id}`)
96
+ }
97
+ break
98
+ } catch (err) {
99
+ logger.warn(`KV 驱动 ${id} 打开失败,尝试下一个`, err)
100
+ }
101
+ }
102
+
103
+ if (!driver) {
104
+ // chain 末尾恒为 memory,理论上到不了这里;留着是为了让失败可诊断而不是空指针
105
+ throw new Error("所有 KV 驱动均不可用")
106
+ }
107
+
108
+ logger.info(`KV 存储就绪:驱动 ${driver.id}`)
109
+ const root = new Kv(driver)
110
+
111
+ return {
112
+ driver: driver.id,
113
+ root,
114
+ namespace: (name: string) => root.sub(name),
115
+ close: async () => {
116
+ await driver.close()
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 把请求的驱动 id 展开成降级链
123
+ * @param requested 请求的驱动 id
124
+ * @returns 依次尝试的驱动 id 列表,末尾恒为 memory
125
+ */
126
+ function resolveChain(requested: string): string[] {
127
+ if (requested === "auto") return ["level", "json", "memory"]
128
+ if (requested === "memory") return ["memory"]
129
+ if (requested === "json") return ["json", "memory"]
130
+ if (requested === "level") return ["level", "json", "memory"]
131
+ // 外部驱动(redis 等)失败后退回内置默认链
132
+ return [requested, "level", "json", "memory"]
133
+ }
134
+
135
+ /**
136
+ * 按 id 造驱动
137
+ * @param id 驱动 id
138
+ * @param dir 数据目录
139
+ * @returns 驱动实例;不可用时 undefined
140
+ */
141
+ async function createDriver(id: string, dir: string): Promise<KvDriver | undefined> {
142
+ switch (id) {
143
+ case "memory":
144
+ return new MemoryKvDriver()
145
+ case "json":
146
+ return new JsonKvDriver(join(dir, "kv.json"))
147
+ case "level": {
148
+ const ctor = await loadLevel()
149
+ return ctor ? new LevelKvDriver(join(dir, "kv"), ctor) : undefined
150
+ }
151
+ default: {
152
+ const factory = externalDrivers.get(id)
153
+ return factory ? await factory(dir) : undefined
154
+ }
155
+ }
156
+ }