@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
|
+
* 模块职责:内嵌 LevelDB KV 驱动(默认驱动)
|
|
3
|
+
* 依赖方向:动态依赖可选原生模块 classic-level;依赖类型包
|
|
4
|
+
* 生命周期:`open()` 打开数据库目录,`close()` 关闭并 flush
|
|
5
|
+
* 注意事项:classic-level 是 **optionalDependency**(原生模块,Termux 上可能编不出来),
|
|
6
|
+
* 所以这里用动态 `import()` 且**不引用它的类型** —— 只声明自己真正用到的
|
|
7
|
+
* 那几个方法。这样在没装成功的机器上,整个内核依然能编译、能跑,
|
|
8
|
+
* 由 `openKv()` 自动降级到 json 驱动。
|
|
9
|
+
*
|
|
10
|
+
* 刻意不实现 `incr`:LevelDB 没有原子自增原语,硬写成 get+put 会在并发下
|
|
11
|
+
* 丢计数。留空让上层的按键串行队列接管,语义才是对的。
|
|
12
|
+
*/
|
|
13
|
+
import type { KvBatchOp, KvDriver, StoredEnvelope } from "@yunzai-ng/types"
|
|
14
|
+
import { ensureDir } from "../util/fs.js"
|
|
15
|
+
|
|
16
|
+
/** classic-level 中本驱动实际用到的最小面 */
|
|
17
|
+
interface LevelDb {
|
|
18
|
+
/** 打开数据库 */
|
|
19
|
+
open(): Promise<void>
|
|
20
|
+
/** 关闭数据库 */
|
|
21
|
+
close(): Promise<void>
|
|
22
|
+
/** 读取,缺失返回 undefined(abstract-level 2.x 语义) */
|
|
23
|
+
get(key: string): Promise<StoredEnvelope | undefined>
|
|
24
|
+
/** 写入 */
|
|
25
|
+
put(key: string, value: StoredEnvelope): Promise<void>
|
|
26
|
+
/** 删除 */
|
|
27
|
+
del(key: string): Promise<void>
|
|
28
|
+
/** 批量写入/删除 */
|
|
29
|
+
batch(ops: { type: "put" | "del"; key: string; value?: StoredEnvelope }[]): Promise<void>
|
|
30
|
+
/** 范围迭代器 */
|
|
31
|
+
iterator(opts: { gte?: string }): LevelIterator
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** classic-level 的迭代器最小面 */
|
|
35
|
+
interface LevelIterator extends AsyncIterable<[string, StoredEnvelope]> {
|
|
36
|
+
/** 关闭迭代器,释放底层快照 */
|
|
37
|
+
close(): Promise<void>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** classic-level 的构造签名 */
|
|
41
|
+
type LevelCtor = new (location: string, opts: { valueEncoding: "json" }) => LevelDb
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 尝试加载 classic-level
|
|
45
|
+
* @returns 构造函数;模块不可用时 undefined
|
|
46
|
+
*/
|
|
47
|
+
export async function loadLevel(): Promise<LevelCtor | undefined> {
|
|
48
|
+
try {
|
|
49
|
+
const mod = (await import("classic-level")) as unknown as { ClassicLevel?: LevelCtor }
|
|
50
|
+
return mod.ClassicLevel
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 内嵌 LevelDB KV 驱动 */
|
|
57
|
+
export class LevelKvDriver implements KvDriver {
|
|
58
|
+
/** 驱动 id */
|
|
59
|
+
readonly id = "level"
|
|
60
|
+
|
|
61
|
+
/** 数据库目录 */
|
|
62
|
+
readonly #dir: string
|
|
63
|
+
/** 构造函数 */
|
|
64
|
+
readonly #ctor: LevelCtor
|
|
65
|
+
/** 数据库实例 */
|
|
66
|
+
#db: LevelDb | undefined
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param dir 数据库目录
|
|
70
|
+
* @param ctor classic-level 构造函数,由 `loadLevel()` 提供
|
|
71
|
+
*/
|
|
72
|
+
constructor(dir: string, ctor: LevelCtor) {
|
|
73
|
+
this.#dir = dir
|
|
74
|
+
this.#ctor = ctor
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 数据库目录 */
|
|
78
|
+
get dir(): string {
|
|
79
|
+
return this.#dir
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 打开数据库
|
|
84
|
+
* @throws 目录被另一个进程锁住时(LevelDB 是单进程独占)
|
|
85
|
+
*/
|
|
86
|
+
async open(): Promise<void> {
|
|
87
|
+
await ensureDir(this.#dir)
|
|
88
|
+
const db = new this.#ctor(this.#dir, { valueEncoding: "json" })
|
|
89
|
+
await db.open()
|
|
90
|
+
this.#db = db
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 关闭数据库 */
|
|
94
|
+
async close(): Promise<void> {
|
|
95
|
+
const db = this.#db
|
|
96
|
+
this.#db = undefined
|
|
97
|
+
if (db) await db.close()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 读取
|
|
102
|
+
* @param key 完整键
|
|
103
|
+
* @returns 值信封;不存在时 undefined
|
|
104
|
+
*/
|
|
105
|
+
async get(key: string): Promise<StoredEnvelope | undefined> {
|
|
106
|
+
return this.#require().get(key)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 写入
|
|
111
|
+
* @param key 完整键
|
|
112
|
+
* @param value 值信封
|
|
113
|
+
*/
|
|
114
|
+
async set(key: string, value: StoredEnvelope): Promise<void> {
|
|
115
|
+
await this.#require().put(key, value)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 删除
|
|
120
|
+
* @param key 完整键
|
|
121
|
+
*/
|
|
122
|
+
async del(key: string): Promise<void> {
|
|
123
|
+
await this.#require().del(key)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 批量操作(LevelDB 的原子批写)
|
|
128
|
+
* @param ops 操作列表
|
|
129
|
+
*/
|
|
130
|
+
async batch(ops: KvBatchOp[]): Promise<void> {
|
|
131
|
+
if (ops.length === 0) return
|
|
132
|
+
await this.#require().batch(
|
|
133
|
+
ops.map(op => (op.op === "set" ? { type: "put" as const, key: op.key, value: op.value } : { type: "del" as const, key: op.key }))
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 按前缀扫描
|
|
139
|
+
*
|
|
140
|
+
* 只给出 `gte` 而不给上界,依靠"键按字节序排列 ⇒ 同前缀的键连续"提前结束。
|
|
141
|
+
* 较自行计算 `lt` 上界更可靠:手工计算上界一旦遇到 emoji 一类四字节字符即会漏键。
|
|
142
|
+
* @param prefix 键前缀
|
|
143
|
+
* @returns 键值对迭代器
|
|
144
|
+
*/
|
|
145
|
+
async *scan(prefix: string): AsyncIterableIterator<readonly [string, StoredEnvelope]> {
|
|
146
|
+
const it = this.#require().iterator({ gte: prefix })
|
|
147
|
+
try {
|
|
148
|
+
for await (const [key, value] of it) {
|
|
149
|
+
if (!key.startsWith(prefix)) break
|
|
150
|
+
yield [key, value]
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
// 迭代器持有底层快照,不关会一直占着 sst 文件不让压缩
|
|
154
|
+
await it.close().catch(() => undefined)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 取已打开的数据库
|
|
160
|
+
* @returns 数据库实例
|
|
161
|
+
* @throws 未 open 时
|
|
162
|
+
*/
|
|
163
|
+
#require(): LevelDb {
|
|
164
|
+
if (!this.#db) throw new Error("LevelDB 尚未打开,请先调用 open()")
|
|
165
|
+
return this.#db
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:内存 KV 驱动(测试与"仅内存"模式)
|
|
3
|
+
* 依赖方向:依赖类型包与 util/deep
|
|
4
|
+
* 生命周期:`close()` 后可再 `open()`
|
|
5
|
+
* 注意事项:**存取都做深拷贝**。若直接存引用,调用方在 set 之后修改对象会连带改掉
|
|
6
|
+
* "已落盘"的值;这样内存驱动下能跑通的代码换到 level/json 驱动就会出错,
|
|
7
|
+
* 属于最难查的一类问题。宁可多一次拷贝,也要让三种驱动语义完全一致。
|
|
8
|
+
*/
|
|
9
|
+
import type { KvBatchOp, KvDriver, StoredEnvelope } from "@yunzai-ng/types"
|
|
10
|
+
import { deepClone } from "../util/deep.js"
|
|
11
|
+
import { isExpired } from "./kv.js"
|
|
12
|
+
|
|
13
|
+
/** 内存 KV 驱动 */
|
|
14
|
+
export class MemoryKvDriver implements KvDriver {
|
|
15
|
+
/** 驱动 id */
|
|
16
|
+
readonly id = "memory"
|
|
17
|
+
|
|
18
|
+
/** 数据表 */
|
|
19
|
+
readonly #map = new Map<string, StoredEnvelope>()
|
|
20
|
+
|
|
21
|
+
/** 键数量(含未清理的过期键),仅用于调试与面板展示 */
|
|
22
|
+
get size(): number {
|
|
23
|
+
return this.#map.size
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 打开(内存驱动无需准备) */
|
|
27
|
+
async open(): Promise<void> {
|
|
28
|
+
// 无操作
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 关闭并清空 */
|
|
32
|
+
async close(): Promise<void> {
|
|
33
|
+
this.#map.clear()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 读取
|
|
38
|
+
* @param key 完整键
|
|
39
|
+
* @returns 值信封的副本
|
|
40
|
+
*/
|
|
41
|
+
async get(key: string): Promise<StoredEnvelope | undefined> {
|
|
42
|
+
const env = this.#map.get(key)
|
|
43
|
+
return env === undefined ? undefined : deepClone(env)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 写入
|
|
48
|
+
* @param key 完整键
|
|
49
|
+
* @param value 值信封
|
|
50
|
+
*/
|
|
51
|
+
async set(key: string, value: StoredEnvelope): Promise<void> {
|
|
52
|
+
this.#map.set(key, deepClone(value))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 删除
|
|
57
|
+
* @param key 完整键
|
|
58
|
+
*/
|
|
59
|
+
async del(key: string): Promise<void> {
|
|
60
|
+
this.#map.delete(key)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 批量操作
|
|
65
|
+
* @param ops 操作列表
|
|
66
|
+
*/
|
|
67
|
+
async batch(ops: KvBatchOp[]): Promise<void> {
|
|
68
|
+
for (const op of ops) {
|
|
69
|
+
if (op.op === "set") this.#map.set(op.key, deepClone(op.value))
|
|
70
|
+
else this.#map.delete(op.key)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 按前缀扫描
|
|
76
|
+
* @param prefix 键前缀
|
|
77
|
+
* @returns 键值对迭代器
|
|
78
|
+
*/
|
|
79
|
+
async *scan(prefix: string): AsyncIterableIterator<readonly [string, StoredEnvelope]> {
|
|
80
|
+
// 先快照键集合:调用方在遍历中删除过期键是正常用法,
|
|
81
|
+
// 直接迭代 Map 会撞上"遍历中修改"的未定义顺序
|
|
82
|
+
for (const key of [...this.#map.keys()]) {
|
|
83
|
+
if (!key.startsWith(prefix)) continue
|
|
84
|
+
const env = this.#map.get(key)
|
|
85
|
+
if (env === undefined) continue
|
|
86
|
+
yield [key, deepClone(env)]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 原生原子自增
|
|
92
|
+
*
|
|
93
|
+
* 单线程 JS 里同步的读改写天然原子,故内存驱动可以直接提供,
|
|
94
|
+
* 免掉上层的串行队列开销。
|
|
95
|
+
* @param key 完整键
|
|
96
|
+
* @param by 增量
|
|
97
|
+
* @param expireAt 首次创建时的过期时间点
|
|
98
|
+
* @returns 自增后的值
|
|
99
|
+
*/
|
|
100
|
+
async incr(key: string, by: number, expireAt?: number): Promise<number> {
|
|
101
|
+
const env = this.#map.get(key)
|
|
102
|
+
const alive = env !== undefined && !isExpired(env)
|
|
103
|
+
const current = alive && typeof env.v === "number" ? env.v : 0
|
|
104
|
+
const next = current + by
|
|
105
|
+
const keep = alive ? env.e : expireAt
|
|
106
|
+
this.#map.set(key, keep === undefined ? { v: next } : { v: next, e: keep })
|
|
107
|
+
return next
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:SQLite 存储的行为测试
|
|
3
|
+
* 依赖方向:测试文件
|
|
4
|
+
* 生命周期:每个用例一个临时数据库文件
|
|
5
|
+
* 注意事项:better-sqlite3 是可选原生模块,装不上时整组跳过而不是报红。
|
|
6
|
+
* 重点覆盖异步事务的正确性 —— 这是本模块唯一有真实并发风险的地方。
|
|
7
|
+
*/
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
9
|
+
import { tmpdir } from "node:os"
|
|
10
|
+
import { join } from "node:path"
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
|
12
|
+
import type { SqlHandle, SqlMigration } from "@yunzai-ng/types"
|
|
13
|
+
import { loadSqlite, openSql, type SqlStore } from "./sql.js"
|
|
14
|
+
|
|
15
|
+
const ctor = await loadSqlite()
|
|
16
|
+
|
|
17
|
+
describe.skipIf(!ctor)("SQLite 存储", () => {
|
|
18
|
+
let dir: string
|
|
19
|
+
let store: SqlStore
|
|
20
|
+
let db: SqlHandle
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
dir = await mkdtemp(join(tmpdir(), "yzng-sql-"))
|
|
24
|
+
const opened = await openSql({ file: join(dir, "data.db"), ctor })
|
|
25
|
+
store = opened!
|
|
26
|
+
db = store.handle
|
|
27
|
+
await db.run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, n INTEGER DEFAULT 0)")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
store.close()
|
|
32
|
+
await rm(dir, { recursive: true, force: true })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("run / all / get 的基本语义", async () => {
|
|
36
|
+
const inserted = await db.run("INSERT INTO t (name, n) VALUES (?, ?)", ["甲", 1])
|
|
37
|
+
expect(inserted.changes).toBe(1)
|
|
38
|
+
expect(Number(inserted.lastInsertRowid)).toBe(1)
|
|
39
|
+
|
|
40
|
+
await db.run("INSERT INTO t (name, n) VALUES (?, ?)", ["乙", 2])
|
|
41
|
+
expect(await db.all("SELECT name FROM t ORDER BY id")).toEqual([{ name: "甲" }, { name: "乙" }])
|
|
42
|
+
expect(await db.get("SELECT n FROM t WHERE name = ?", ["乙"])).toEqual({ n: 2 })
|
|
43
|
+
// 无结果统一返回 undefined,而不是 null
|
|
44
|
+
expect(await db.get("SELECT n FROM t WHERE name = ?", ["丙"])).toBeUndefined()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("不带参数的多语句脚本可以一次执行(迁移常用)", async () => {
|
|
48
|
+
await db.run(`
|
|
49
|
+
CREATE TABLE a (id INTEGER PRIMARY KEY);
|
|
50
|
+
CREATE INDEX idx_a ON a (id);
|
|
51
|
+
`)
|
|
52
|
+
const tables = await db.all<{ name: string }>("SELECT name FROM sqlite_master WHERE type = 'table'")
|
|
53
|
+
expect(tables.map(r => r.name)).toContain("a")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("语句里的分号字面量不会被误判成多语句", async () => {
|
|
57
|
+
await db.run("INSERT INTO t (name) VALUES (?)", ["带;分号"])
|
|
58
|
+
expect(await db.get("SELECT name FROM t WHERE name = ?", ["带;分号"])).toEqual({ name: "带;分号" })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("事务提交后数据可见", async () => {
|
|
62
|
+
await db.transaction(async tx => {
|
|
63
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["甲"])
|
|
64
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["乙"])
|
|
65
|
+
})
|
|
66
|
+
expect(await db.get("SELECT COUNT(*) AS c FROM t")).toEqual({ c: 2 })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("事务体抛错时整体回滚,错误照原样冒出", async () => {
|
|
70
|
+
await expect(
|
|
71
|
+
db.transaction(async tx => {
|
|
72
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["甲"])
|
|
73
|
+
throw new Error("业务失败")
|
|
74
|
+
})
|
|
75
|
+
).rejects.toThrow("业务失败")
|
|
76
|
+
|
|
77
|
+
expect(await db.get("SELECT COUNT(*) AS c FROM t")).toEqual({ c: 0 })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("事务里 await 别的异步操作也不会串台(互斥锁生效)", async () => {
|
|
81
|
+
// 两个事务同时开:若没有互斥锁,第二个 BEGIN 会撞上
|
|
82
|
+
// "cannot start a transaction within a transaction"
|
|
83
|
+
const one = db.transaction(async tx => {
|
|
84
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["甲"])
|
|
85
|
+
await new Promise(r => setTimeout(r, 30))
|
|
86
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["乙"])
|
|
87
|
+
})
|
|
88
|
+
const two = db.transaction(async tx => {
|
|
89
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["丙"])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await Promise.all([one, two])
|
|
93
|
+
expect(await db.get("SELECT COUNT(*) AS c FROM t")).toEqual({ c: 3 })
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("并发事务不会互相回滚对方的写入", async () => {
|
|
97
|
+
await db.run("INSERT INTO t (id, name, n) VALUES (1, '计数', 0)")
|
|
98
|
+
|
|
99
|
+
const bump = () =>
|
|
100
|
+
db.transaction(async tx => {
|
|
101
|
+
const row = await tx.get<{ n: number }>("SELECT n FROM t WHERE id = 1")
|
|
102
|
+
await tx.run("UPDATE t SET n = ? WHERE id = 1", [(row?.n ?? 0) + 1])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
await Promise.all(Array.from({ length: 20 }, bump))
|
|
106
|
+
expect(await db.get("SELECT n FROM t WHERE id = 1")).toEqual({ n: 20 })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("嵌套事务用 SAVEPOINT:内层回滚不牵连外层", async () => {
|
|
110
|
+
await db.transaction(async tx => {
|
|
111
|
+
await tx.run("INSERT INTO t (name) VALUES (?)", ["外层"])
|
|
112
|
+
await expect(
|
|
113
|
+
tx.transaction(async inner => {
|
|
114
|
+
await inner.run("INSERT INTO t (name) VALUES (?)", ["内层"])
|
|
115
|
+
throw new Error("内层失败")
|
|
116
|
+
})
|
|
117
|
+
).rejects.toThrow("内层失败")
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const names = await db.all<{ name: string }>("SELECT name FROM t")
|
|
121
|
+
expect(names.map(r => r.name)).toEqual(["外层"])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("迁移按版本号依次执行且幂等", async () => {
|
|
125
|
+
const log: string[] = []
|
|
126
|
+
const migrations: SqlMigration[] = [
|
|
127
|
+
{
|
|
128
|
+
version: 2,
|
|
129
|
+
name: "加索引",
|
|
130
|
+
up: async tx => {
|
|
131
|
+
log.push("v2")
|
|
132
|
+
await tx.run("CREATE INDEX idx_name ON t (name)")
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
version: 1,
|
|
137
|
+
name: "建表",
|
|
138
|
+
up: async tx => {
|
|
139
|
+
log.push("v1")
|
|
140
|
+
await tx.run("CREATE TABLE m (id INTEGER PRIMARY KEY)")
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
await db.migrate(migrations)
|
|
146
|
+
expect(log).toEqual(["v1", "v2"])
|
|
147
|
+
|
|
148
|
+
// 再跑一次什么都不该发生
|
|
149
|
+
await db.migrate(migrations)
|
|
150
|
+
expect(log).toEqual(["v1", "v2"])
|
|
151
|
+
|
|
152
|
+
// 新增一条只跑新的
|
|
153
|
+
migrations.push({
|
|
154
|
+
version: 3,
|
|
155
|
+
name: "再加一张表",
|
|
156
|
+
up: async tx => {
|
|
157
|
+
log.push("v3")
|
|
158
|
+
await tx.run("CREATE TABLE m2 (id INTEGER PRIMARY KEY)")
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
await db.migrate(migrations)
|
|
162
|
+
expect(log).toEqual(["v1", "v2", "v3"])
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("迁移失败时该条回滚,且不会被记为已应用", async () => {
|
|
166
|
+
const migrations: SqlMigration[] = [
|
|
167
|
+
{
|
|
168
|
+
version: 1,
|
|
169
|
+
name: "会失败的迁移",
|
|
170
|
+
up: async tx => {
|
|
171
|
+
await tx.run("CREATE TABLE ok (id INTEGER PRIMARY KEY)")
|
|
172
|
+
await tx.run("这不是 SQL")
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
await expect(db.migrate(migrations)).rejects.toThrow()
|
|
178
|
+
|
|
179
|
+
const tables = await db.all<{ name: string }>("SELECT name FROM sqlite_master WHERE type = 'table'")
|
|
180
|
+
expect(tables.map(r => r.name)).not.toContain("ok")
|
|
181
|
+
expect(await db.get("SELECT COUNT(*) AS c FROM _yzng_migrations")).toEqual({ c: 0 })
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("版本号重复直接报错,而不是随机执行一条", async () => {
|
|
185
|
+
await expect(
|
|
186
|
+
db.migrate([
|
|
187
|
+
{ version: 1, name: "甲", up: async () => undefined },
|
|
188
|
+
{ version: 1, name: "乙", up: async () => undefined }
|
|
189
|
+
])
|
|
190
|
+
).rejects.toThrow(/重复/)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("重开文件后数据仍在", async () => {
|
|
194
|
+
await db.run("INSERT INTO t (name) VALUES (?)", ["持久"])
|
|
195
|
+
const file = store.file
|
|
196
|
+
store.close()
|
|
197
|
+
|
|
198
|
+
const reopened = (await openSql({ file, ctor }))!
|
|
199
|
+
expect(await reopened.handle.get("SELECT name FROM t")).toEqual({ name: "持久" })
|
|
200
|
+
reopened.close()
|
|
201
|
+
// afterEach 会再 close 一次,close 必须可重复调用
|
|
202
|
+
store = reopened
|
|
203
|
+
})
|
|
204
|
+
})
|