@actiondock/core 2.10.0 → 2.11.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/README.md +1 -2
- package/dist/catalog/action-catalog.js +3 -33
- package/dist/catalog/discovery.d.ts +0 -3
- package/dist/catalog/discovery.js +4 -4
- package/dist/catalog/graph.d.ts +0 -4
- package/dist/catalog/graph.js +8 -14
- package/dist/{profile/client-actions.d.ts → client/actions.d.ts} +1 -1
- package/dist/{profile/client-actions.js → client/actions.js} +2 -8
- package/dist/{profile/client-config.d.ts → client/config.d.ts} +1 -1
- package/dist/{profile/client-config.js → client/config.js} +1 -1
- package/dist/client/dispatcher.d.ts +4 -0
- package/dist/client/dispatcher.js +21 -0
- package/dist/client/health.d.ts +38 -0
- package/dist/{profile/client-health.js → client/health.js} +9 -6
- package/dist/client/index.d.ts +16 -0
- package/dist/client/index.js +20 -0
- package/dist/{profile/client-playbooks.d.ts → client/playbooks.d.ts} +1 -1
- package/dist/{profile/client-playbooks.js → client/playbooks.js} +1 -1
- package/dist/{profile/client-runs.d.ts → client/runs.d.ts} +4 -2
- package/dist/{profile/client-runs.js → client/runs.js} +7 -1
- package/dist/{profile/client-state.d.ts → client/state.d.ts} +1 -1
- package/dist/{profile/client-state.js → client/state.js} +1 -1
- package/dist/{profile/client-transport.d.ts → client/transport.d.ts} +29 -10
- package/dist/{profile/client-transport.js → client/transport.js} +125 -35
- package/dist/doctor/checks/index.d.ts +1 -1
- package/dist/doctor/checks/index.js +1 -1
- package/dist/doctor/checks/project.js +20 -88
- package/dist/doctor/checks/runtime.d.ts +0 -4
- package/dist/doctor/checks/runtime.js +0 -28
- package/dist/doctor/doctor.js +1 -2
- package/dist/errors.d.ts +53 -3
- package/dist/errors.js +53 -3
- package/dist/execution/service.d.ts +55 -37
- package/dist/execution/service.js +601 -212
- package/dist/execution/types.d.ts +24 -5
- package/dist/host/host.d.ts +42 -22
- package/dist/host/host.js +562 -149
- package/dist/host/routing.js +6 -6
- package/dist/host/types.d.ts +25 -13
- package/dist/index.d.ts +9 -4
- package/dist/index.js +5 -2
- package/dist/input/advice.d.ts +0 -44
- package/dist/input/advice.js +7 -141
- package/dist/input/flat-materializer.js +1 -1
- package/dist/input/flat-parser.js +1 -1
- package/dist/input/index.d.ts +0 -1
- package/dist/input/index.js +0 -1
- package/dist/input/input-resolver.js +1 -1
- package/dist/input/validation-mapper.d.ts +1 -1
- package/dist/ipc/diagnostic.d.ts +8 -21
- package/dist/ipc/diagnostic.js +24 -57
- package/dist/ipc/host.js +21 -7
- package/dist/ipc/service.d.ts +14 -0
- package/dist/ipc/service.js +132 -26
- package/dist/ipc/types.d.ts +0 -4
- package/dist/package/index.d.ts +4 -5
- package/dist/package/index.js +1 -2
- package/dist/package/runtime.d.ts +94 -23
- package/dist/package/runtime.js +628 -274
- package/dist/package/static-index.d.ts +3 -6
- package/dist/package/static-index.js +11 -40
- package/dist/package/types.d.ts +19 -4
- package/dist/platform/index.d.ts +2 -1
- package/dist/platform/index.js +2 -1
- package/dist/platform/loader-hook.d.ts +32 -0
- package/dist/platform/loader-hook.js +90 -0
- package/dist/{node → platform}/module-loader.d.ts +13 -0
- package/dist/{node → platform}/module-loader.js +51 -4
- package/dist/platform/node.d.ts +19 -8
- package/dist/platform/node.js +50 -20
- package/dist/platform/types.d.ts +7 -47
- package/dist/process/context-process.d.ts +3 -18
- package/dist/process/context-process.js +3 -44
- package/dist/process/control.d.ts +21 -0
- package/dist/process/control.js +34 -0
- package/dist/process/converters.d.ts +23 -0
- package/dist/process/converters.js +92 -0
- package/dist/process/cursor.js +2 -22
- package/dist/process/{diagnostics-sink.d.ts → diagnostics.d.ts} +0 -7
- package/dist/process/{diagnostics-sink.js → diagnostics.js} +6 -8
- package/dist/process/index.d.ts +1 -3
- package/dist/process/index.js +1 -3
- package/dist/process/input-dispatcher.d.ts +4 -13
- package/dist/process/input-dispatcher.js +48 -69
- package/dist/process/managed-record.d.ts +3 -31
- package/dist/process/metadata-store.d.ts +0 -41
- package/dist/process/metadata-store.js +2 -341
- package/dist/process/process-driver.js +2 -2
- package/dist/process/process-manager.d.ts +8 -50
- package/dist/process/process-manager.js +28 -417
- package/dist/process/quotas.d.ts +14 -0
- package/dist/process/quotas.js +29 -0
- package/dist/process/run-executor.js +0 -1
- package/dist/process/terminal-output-cache.d.ts +20 -34
- package/dist/process/terminal-output-cache.js +43 -64
- package/dist/profile/index.d.ts +1 -2
- package/dist/profile/index.js +1 -2
- package/dist/profile/manager.d.ts +2 -4
- package/dist/profile/manager.js +10 -40
- package/dist/profile/types.d.ts +1 -17
- package/dist/project/digest.d.ts +5 -1
- package/dist/project/digest.js +81 -219
- package/dist/project/index.d.ts +4 -5
- package/dist/project/index.js +4 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +3 -3
- package/dist/project/loader.js +72 -50
- package/dist/project/lockfile.js +6 -5
- package/dist/project/manifest.js +5 -4
- package/dist/project/transactions.d.ts +4 -42
- package/dist/project/transactions.js +83 -138
- package/dist/project/types-generator.js +2 -1
- package/dist/project/types.d.ts +2 -0
- package/dist/registry/index.d.ts +0 -1
- package/dist/registry/index.js +0 -1
- package/dist/registry/lock.d.ts +2 -11
- package/dist/registry/lock.js +14 -183
- package/dist/registry/registry.d.ts +1 -6
- package/dist/registry/registry.js +5 -17
- package/dist/runtime/action-registry.d.ts +1 -5
- package/dist/runtime/action-registry.js +0 -12
- package/dist/runtime/context.js +2 -2
- package/dist/runtime/events.d.ts +11 -50
- package/dist/runtime/events.js +88 -368
- package/dist/runtime/index.d.ts +0 -4
- package/dist/runtime/index.js +0 -4
- package/dist/runtime/run-persistence.d.ts +2 -0
- package/dist/runtime/run-persistence.js +2 -1
- package/dist/runtime/standalone-commands.d.ts +18 -0
- package/dist/runtime/standalone-commands.js +466 -0
- package/dist/runtime/standalone-help.d.ts +20 -0
- package/dist/runtime/standalone-help.js +72 -0
- package/dist/runtime/standalone.d.ts +1 -44
- package/dist/runtime/standalone.js +32 -573
- package/dist/schema/validator.d.ts +2 -2
- package/dist/schema/validator.js +2 -9
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -2
- package/dist/server/routes/actions.js +1 -1
- package/dist/server/routes/info.js +1 -1
- package/dist/server/routes/playbooks.js +1 -1
- package/dist/server/routes/runs.js +123 -16
- package/dist/server/routes/state.js +43 -18
- package/dist/server/security.d.ts +1 -1
- package/dist/server/security.js +2 -2
- package/dist/server/server.js +3 -7
- package/dist/service/factory.js +4 -3
- package/dist/service/index.d.ts +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/remote-errors.d.ts +1 -1
- package/dist/service/remote-errors.js +6 -6
- package/dist/service/remote-polling.d.ts +3 -0
- package/dist/service/remote-polling.js +10 -5
- package/dist/service/remote.d.ts +5 -1
- package/dist/service/remote.js +108 -17
- package/dist/service/sse-stream.js +2 -2
- package/dist/service/types.d.ts +22 -19
- package/dist/service/types.js +1 -24
- package/dist/storage/clock.d.ts +1 -1
- package/dist/storage/clock.js +1 -1
- package/dist/storage/data-dir-lock.d.ts +10 -22
- package/dist/storage/data-dir-lock.js +28 -56
- package/dist/storage/file-lock.d.ts +64 -0
- package/dist/storage/file-lock.js +447 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +5 -4
- package/dist/storage/run-liveness.d.ts +48 -0
- package/dist/storage/run-liveness.js +44 -0
- package/dist/storage/sqlite-config.d.ts +16 -0
- package/dist/storage/sqlite-config.js +59 -0
- package/dist/storage/sqlite-driver.d.ts +3 -1
- package/dist/storage/sqlite-driver.js +14 -4
- package/dist/storage/sqlite-events.d.ts +13 -0
- package/dist/storage/sqlite-events.js +48 -0
- package/dist/storage/sqlite-runs.d.ts +107 -0
- package/dist/storage/sqlite-runs.js +622 -0
- package/dist/storage/sqlite-schema.d.ts +5 -0
- package/dist/storage/sqlite-schema.js +143 -0
- package/dist/storage/sqlite-state.d.ts +33 -0
- package/dist/storage/sqlite-state.js +270 -0
- package/dist/storage/sqlite.d.ts +30 -57
- package/dist/storage/sqlite.js +84 -741
- package/dist/storage/types.d.ts +35 -2
- package/dist/utils/index.d.ts +18 -14
- package/dist/utils/index.js +37 -29
- package/dist/utils/semver.d.ts +18 -0
- package/dist/utils/semver.js +19 -0
- package/dist/utils/warning.d.ts +1 -0
- package/dist/utils/warning.js +52 -0
- package/dist/{json/value-validator.d.ts → value-validator.d.ts} +9 -0
- package/dist/value-validator.js +330 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +10 -12
- package/dist/execution/index.d.ts +0 -2
- package/dist/execution/index.js +0 -2
- package/dist/filter/index.d.ts +0 -1
- package/dist/filter/index.js +0 -1
- package/dist/graph/index.d.ts +0 -4
- package/dist/graph/index.js +0 -4
- package/dist/input/metadata.d.ts +0 -99
- package/dist/input/metadata.js +0 -99
- package/dist/invocation/index.d.ts +0 -2
- package/dist/invocation/index.js +0 -2
- package/dist/ipc/index.d.ts +0 -4
- package/dist/ipc/index.js +0 -4
- package/dist/json/index.d.ts +0 -1
- package/dist/json/index.js +0 -1
- package/dist/json/value-validator.js +0 -330
- package/dist/node/index.d.ts +0 -1
- package/dist/node/index.js +0 -1
- package/dist/platform/node-fs.d.ts +0 -37
- package/dist/platform/node-fs.js +0 -81
- package/dist/process/control-arbiter.d.ts +0 -108
- package/dist/process/control-arbiter.js +0 -250
- package/dist/process/process-executor.d.ts +0 -47
- package/dist/process/process-executor.js +0 -296
- package/dist/profile/client-health.d.ts +0 -14
- package/dist/profile/client-query.d.ts +0 -30
- package/dist/profile/client-query.js +0 -69
- package/dist/profile/client.d.ts +0 -26
- package/dist/profile/client.js +0 -31
- package/dist/project/closure.d.ts +0 -25
- package/dist/project/closure.js +0 -74
- package/dist/registry/store.d.ts +0 -42
- package/dist/registry/store.js +0 -43
- package/dist/runtime/clock.d.ts +0 -7
- package/dist/runtime/clock.js +0 -7
- package/dist/runtime/module-loader.d.ts +0 -2
- package/dist/runtime/module-loader.js +0 -1
- package/dist/runtime/process.d.ts +0 -8
- package/dist/runtime/process.js +0 -1
- package/dist/runtime/runner.d.ts +0 -234
- package/dist/runtime/runner.js +0 -609
- package/dist/schema/index.d.ts +0 -1
- package/dist/schema/index.js +0 -1
- package/dist/server/dispatcher.d.ts +0 -25
- package/dist/server/dispatcher.js +0 -54
- package/dist/service/local.d.ts +0 -27
- package/dist/service/local.js +0 -141
- package/dist/storage/driver.d.ts +0 -10
- package/dist/storage/driver.js +0 -10
- package/dist/storage/lazy.d.ts +0 -8
- package/dist/storage/lazy.js +0 -45
- package/dist/storage/lock-core.d.ts +0 -158
- package/dist/storage/lock-core.js +0 -872
- package/dist/utils/net.d.ts +0 -10
- package/dist/utils/net.js +0 -16
- package/dist/utils/process.d.ts +0 -10
- package/dist/utils/process.js +0 -21
- /package/dist/{filter → utils}/intent.d.ts +0 -0
- /package/dist/{filter → utils}/intent.js +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, chmodSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { UNSUPPORTED_STORAGE_SCHEMA } from "../errors.js";
|
|
4
|
+
import { STORAGE_SCHEMA_VERSION } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* 数据库 Schema 定义、初始化与版本校验。
|
|
7
|
+
*/
|
|
8
|
+
export function initSchema(driver, dbPath, recoverOrphans, recoverDeadSessionFn, cleanExpiredRunsFn) {
|
|
9
|
+
if (dbPath !== ":memory:") {
|
|
10
|
+
const dir = dirname(dbPath);
|
|
11
|
+
if (!existsSync(dir)) {
|
|
12
|
+
try {
|
|
13
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
14
|
+
chmodSync(dir, 0o700);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// 忽略系统权限设置失败
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// 跨进程写并发基线:优先设置 busy_timeout,确保后续 PRAGMA(如 journal_mode = WAL)与表结构初始化在锁争用时能够重试等待
|
|
22
|
+
driver.exec("PRAGMA busy_timeout = 5000;");
|
|
23
|
+
driver.exec("PRAGMA journal_mode = WAL;");
|
|
24
|
+
driver.exec("PRAGMA synchronous = NORMAL;");
|
|
25
|
+
const versionRes = driver.prepare("PRAGMA user_version;").get();
|
|
26
|
+
const version = Number(versionRes?.user_version ?? 0);
|
|
27
|
+
if (version === 0) {
|
|
28
|
+
driver.transaction(() => {
|
|
29
|
+
driver.exec(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
31
|
+
package_id TEXT NOT NULL,
|
|
32
|
+
key TEXT NOT NULL,
|
|
33
|
+
value_json TEXT,
|
|
34
|
+
updated_at TEXT NOT NULL,
|
|
35
|
+
PRIMARY KEY (package_id, key)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS state (
|
|
39
|
+
package_id TEXT NOT NULL,
|
|
40
|
+
namespace TEXT NOT NULL,
|
|
41
|
+
key TEXT NOT NULL,
|
|
42
|
+
value_json TEXT,
|
|
43
|
+
updated_at TEXT NOT NULL,
|
|
44
|
+
expires_at TEXT,
|
|
45
|
+
PRIMARY KEY (package_id, namespace, key)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
root_run_id TEXT NOT NULL,
|
|
51
|
+
parent_run_id TEXT,
|
|
52
|
+
package_id TEXT NOT NULL,
|
|
53
|
+
package_instance_id TEXT NOT NULL,
|
|
54
|
+
action_id TEXT NOT NULL,
|
|
55
|
+
generation_id TEXT NOT NULL,
|
|
56
|
+
owner_id TEXT NOT NULL,
|
|
57
|
+
host_session_id TEXT,
|
|
58
|
+
host_pid INTEGER,
|
|
59
|
+
heartbeat_at TEXT,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
input_json TEXT,
|
|
62
|
+
output_json TEXT,
|
|
63
|
+
error_json TEXT,
|
|
64
|
+
started_at TEXT NOT NULL,
|
|
65
|
+
finished_at TEXT,
|
|
66
|
+
duration_ms INTEGER
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_runs_action ON runs(package_id, action_id);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_runs_root ON runs(root_run_id);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at DESC);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_runs_host_session ON runs(host_session_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_state_expires ON state(expires_at);
|
|
74
|
+
|
|
75
|
+
CREATE TABLE IF NOT EXISTS idempotency_keys (
|
|
76
|
+
owner_id TEXT NOT NULL,
|
|
77
|
+
action_ref TEXT NOT NULL,
|
|
78
|
+
request_id TEXT NOT NULL,
|
|
79
|
+
input_digest TEXT NOT NULL,
|
|
80
|
+
run_id TEXT NOT NULL,
|
|
81
|
+
created_at TEXT NOT NULL,
|
|
82
|
+
PRIMARY KEY (owner_id, action_ref, request_id)
|
|
83
|
+
);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_idemp_run ON idempotency_keys(run_id);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
package_id TEXT NOT NULL,
|
|
89
|
+
event_type TEXT NOT NULL,
|
|
90
|
+
payload_json TEXT,
|
|
91
|
+
created_at TEXT NOT NULL
|
|
92
|
+
);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_events_type ON events(package_id, event_type);
|
|
94
|
+
|
|
95
|
+
PRAGMA user_version = ${STORAGE_SCHEMA_VERSION};
|
|
96
|
+
`);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else if (version !== STORAGE_SCHEMA_VERSION) {
|
|
100
|
+
const err = new Error(`UNSUPPORTED_STORAGE_SCHEMA: Database schema version ${version} is incompatible (expected ${STORAGE_SCHEMA_VERSION}). Silent upgrade and database overwriting are strictly prohibited.`);
|
|
101
|
+
err.code = UNSUPPORTED_STORAGE_SCHEMA;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
// 确保存量旧版本数据库补充新增的列或表
|
|
105
|
+
if (version !== 0) {
|
|
106
|
+
try {
|
|
107
|
+
const columns = driver.prepare("PRAGMA table_info(runs);").all();
|
|
108
|
+
if (Array.isArray(columns) && !columns.some((c) => c.name === "host_session_id")) {
|
|
109
|
+
driver.exec("ALTER TABLE runs ADD COLUMN host_session_id TEXT;");
|
|
110
|
+
}
|
|
111
|
+
// 存活判定依据列:记录所属宿主进程标识与最后一次活跃心跳,旧库以可空列兼容补充
|
|
112
|
+
if (Array.isArray(columns) && !columns.some((c) => c.name === "host_pid")) {
|
|
113
|
+
driver.exec("ALTER TABLE runs ADD COLUMN host_pid INTEGER;");
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(columns) && !columns.some((c) => c.name === "heartbeat_at")) {
|
|
116
|
+
driver.exec("ALTER TABLE runs ADD COLUMN heartbeat_at TEXT;");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// 忽略非结构化表或兼容驱动异常
|
|
121
|
+
}
|
|
122
|
+
// 确保 events 表存在以支持新增的审计事件仓储
|
|
123
|
+
try {
|
|
124
|
+
driver.exec(`
|
|
125
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
126
|
+
id TEXT PRIMARY KEY,
|
|
127
|
+
package_id TEXT NOT NULL,
|
|
128
|
+
event_type TEXT NOT NULL,
|
|
129
|
+
payload_json TEXT,
|
|
130
|
+
created_at TEXT NOT NULL
|
|
131
|
+
);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_events_type ON events(package_id, event_type);
|
|
133
|
+
`);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// 忽略
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (recoverOrphans) {
|
|
140
|
+
recoverDeadSessionFn();
|
|
141
|
+
cleanExpiredRunsFn();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Logger } from "@actiondock/sdk";
|
|
2
|
+
import type { SqliteDriver, StateEntry } from "./types.js";
|
|
3
|
+
import type { Clock } from "./clock.js";
|
|
4
|
+
export declare class SqliteStateStore {
|
|
5
|
+
private statementCache;
|
|
6
|
+
private driver;
|
|
7
|
+
private packageId;
|
|
8
|
+
private dbPath;
|
|
9
|
+
private clock;
|
|
10
|
+
private logger?;
|
|
11
|
+
private logWarnFn?;
|
|
12
|
+
constructor(driver: SqliteDriver, packageId: string, dbPath: string, clock: Clock, logger?: Logger, logWarnFn?: (msg: string) => void);
|
|
13
|
+
private logWarn;
|
|
14
|
+
private getStatement;
|
|
15
|
+
getState<T = unknown>(namespace: string, key: string): Promise<T | undefined>;
|
|
16
|
+
private findMatchingStateRows;
|
|
17
|
+
findState<T = unknown>(targetKey: string, namespace?: string): Promise<StateEntry | undefined>;
|
|
18
|
+
setState<T = unknown>(namespace: string, key: string, value: T, ttl?: number): Promise<void>;
|
|
19
|
+
deleteState(namespace: string, key: string): Promise<boolean>;
|
|
20
|
+
deleteStateSmart(targetKey: string, namespace?: string): Promise<boolean>;
|
|
21
|
+
cleanExpiredState(isClosed: boolean): Promise<number>;
|
|
22
|
+
clearState(options?: {
|
|
23
|
+
namespace?: string;
|
|
24
|
+
all?: boolean;
|
|
25
|
+
prefix?: string;
|
|
26
|
+
}): Promise<number>;
|
|
27
|
+
listStateKeys(namespace?: string | null, prefix?: string, isClosed?: boolean): Promise<string[]>;
|
|
28
|
+
listStateEntries(options?: {
|
|
29
|
+
namespace?: string;
|
|
30
|
+
prefix?: string;
|
|
31
|
+
}, isClosed?: boolean): Promise<StateEntry[]>;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { decodeStateKey, encodeStateKey } from "@actiondock/sdk";
|
|
2
|
+
import { safeParseStoredJson } from "./utils.js";
|
|
3
|
+
import { ActionDockError, AMBIGUOUS_STATE_KEY } from "../errors.js";
|
|
4
|
+
import { IDEMPOTENCY_RETENTION_MS } from "./types.js";
|
|
5
|
+
export class SqliteStateStore {
|
|
6
|
+
statementCache = new Map();
|
|
7
|
+
driver;
|
|
8
|
+
packageId;
|
|
9
|
+
dbPath;
|
|
10
|
+
clock;
|
|
11
|
+
logger;
|
|
12
|
+
logWarnFn;
|
|
13
|
+
constructor(driver, packageId, dbPath, clock, logger, logWarnFn) {
|
|
14
|
+
this.driver = driver;
|
|
15
|
+
this.packageId = packageId;
|
|
16
|
+
this.dbPath = dbPath;
|
|
17
|
+
this.clock = clock;
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.logWarnFn = logWarnFn;
|
|
20
|
+
}
|
|
21
|
+
logWarn(message) {
|
|
22
|
+
if (this.logWarnFn) {
|
|
23
|
+
this.logWarnFn(message);
|
|
24
|
+
}
|
|
25
|
+
else if (this.logger) {
|
|
26
|
+
this.logger.warn(message);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.warn(message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
getStatement(sql) {
|
|
33
|
+
let stmt = this.statementCache.get(sql);
|
|
34
|
+
if (!stmt) {
|
|
35
|
+
stmt = this.driver.prepare(sql);
|
|
36
|
+
this.statementCache.set(sql, stmt);
|
|
37
|
+
}
|
|
38
|
+
return stmt;
|
|
39
|
+
}
|
|
40
|
+
async getState(namespace, key) {
|
|
41
|
+
const stmt = this.getStatement("SELECT value_json, expires_at FROM state WHERE package_id = ? AND namespace = ? AND key = ?");
|
|
42
|
+
const row = stmt.get(this.packageId, namespace, key);
|
|
43
|
+
if (!row || row.value_json === undefined || row.value_json === null) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (row.expires_at) {
|
|
47
|
+
const expires = new Date(row.expires_at).getTime();
|
|
48
|
+
if (this.clock.now().getTime() >= expires) {
|
|
49
|
+
this.deleteState(namespace, key).catch((err) => {
|
|
50
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
51
|
+
this.logWarn(`[actiondock] expired state lazy delete failed (namespace='${namespace}' key='${key}'): ${reason}`);
|
|
52
|
+
});
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return safeParseStoredJson(row.value_json, `state db=${this.dbPath} package=${this.packageId} namespace=${namespace} key=${key}`);
|
|
57
|
+
}
|
|
58
|
+
async findMatchingStateRows(targetKey) {
|
|
59
|
+
let decoded;
|
|
60
|
+
try {
|
|
61
|
+
decoded = decodeStateKey(targetKey);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// ignore
|
|
65
|
+
}
|
|
66
|
+
const candidateStmt = this.getStatement(`
|
|
67
|
+
SELECT namespace, key, value_json, updated_at, expires_at FROM state
|
|
68
|
+
WHERE package_id = ? AND (
|
|
69
|
+
(CASE WHEN ? IS NOT NULL THEN (namespace = ? AND key = ?) ELSE 0 END)
|
|
70
|
+
OR (CASE WHEN namespace = '' THEN key ELSE namespace || ':' || key END) = ?
|
|
71
|
+
OR key = ?
|
|
72
|
+
)
|
|
73
|
+
`);
|
|
74
|
+
const rawResult = candidateStmt.all(this.packageId, decoded ? 1 : null, decoded?.namespace ?? "", decoded?.key ?? "", targetKey, targetKey);
|
|
75
|
+
const rawRows = Array.isArray(rawResult) ? rawResult : [];
|
|
76
|
+
const now = this.clock.now().getTime();
|
|
77
|
+
const uniqueMap = new Map();
|
|
78
|
+
for (const r of rawRows) {
|
|
79
|
+
if (r.expires_at && now >= new Date(r.expires_at).getTime()) {
|
|
80
|
+
const ns = r.namespace;
|
|
81
|
+
const k = r.key;
|
|
82
|
+
this.deleteState(ns, k).catch((err) => {
|
|
83
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
84
|
+
this.logWarn(`[actiondock] expired state lazy delete failed (namespace='${ns}' key='${k}'): ${reason}`);
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
uniqueMap.set(`${r.namespace}\0${r.key}`, r);
|
|
89
|
+
}
|
|
90
|
+
return Array.from(uniqueMap.values());
|
|
91
|
+
}
|
|
92
|
+
async findState(targetKey, namespace) {
|
|
93
|
+
if (namespace !== undefined) {
|
|
94
|
+
const val = await this.getState(namespace, targetKey);
|
|
95
|
+
if (val === undefined)
|
|
96
|
+
return undefined;
|
|
97
|
+
const stmt = this.getStatement("SELECT updated_at, expires_at FROM state WHERE package_id = ? AND namespace = ? AND key = ?");
|
|
98
|
+
const row = stmt.get(this.packageId, namespace, targetKey);
|
|
99
|
+
return {
|
|
100
|
+
packageId: this.packageId,
|
|
101
|
+
namespace,
|
|
102
|
+
key: targetKey,
|
|
103
|
+
fullKey: encodeStateKey(namespace, targetKey),
|
|
104
|
+
value: val,
|
|
105
|
+
updatedAt: row?.updated_at || this.clock.now().toISOString(),
|
|
106
|
+
expiresAt: row?.expires_at,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const matchingRows = await this.findMatchingStateRows(targetKey);
|
|
110
|
+
if (matchingRows.length > 1) {
|
|
111
|
+
throw new ActionDockError(AMBIGUOUS_STATE_KEY, `Ambiguous state key '${targetKey}': matches ${matchingRows.length} entries (${matchingRows.map((r) => `${r.namespace}:${r.key}`).join(", ")})`);
|
|
112
|
+
}
|
|
113
|
+
if (matchingRows.length === 0) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const row = matchingRows[0];
|
|
117
|
+
const parsedVal = safeParseStoredJson(row.value_json, `state db=${this.dbPath} package=${this.packageId} namespace=${row.namespace} key=${row.key}`);
|
|
118
|
+
return {
|
|
119
|
+
packageId: this.packageId,
|
|
120
|
+
namespace: row.namespace,
|
|
121
|
+
key: row.key,
|
|
122
|
+
fullKey: encodeStateKey(row.namespace, row.key),
|
|
123
|
+
value: parsedVal,
|
|
124
|
+
updatedAt: row.updated_at,
|
|
125
|
+
expiresAt: row.expires_at,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async setState(namespace, key, value, ttl) {
|
|
129
|
+
const stmt = this.getStatement(`
|
|
130
|
+
INSERT INTO state (package_id, namespace, key, value_json, updated_at, expires_at)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
132
|
+
ON CONFLICT(package_id, namespace, key) DO UPDATE SET
|
|
133
|
+
value_json = excluded.value_json,
|
|
134
|
+
updated_at = excluded.updated_at,
|
|
135
|
+
expires_at = excluded.expires_at
|
|
136
|
+
`);
|
|
137
|
+
const valJson = JSON.stringify(value);
|
|
138
|
+
const now = this.clock.now();
|
|
139
|
+
const updatedAt = now.toISOString();
|
|
140
|
+
let expiresAt = null;
|
|
141
|
+
if (typeof ttl === "number" && ttl > 0) {
|
|
142
|
+
expiresAt = new Date(now.getTime() + ttl * 1000).toISOString();
|
|
143
|
+
}
|
|
144
|
+
stmt.run(this.packageId, namespace, key, valJson, updatedAt, expiresAt);
|
|
145
|
+
}
|
|
146
|
+
async deleteState(namespace, key) {
|
|
147
|
+
const stmt = this.getStatement("DELETE FROM state WHERE package_id = ? AND namespace = ? AND key = ?");
|
|
148
|
+
const res = stmt.run(this.packageId, namespace, key);
|
|
149
|
+
return res.changes > 0;
|
|
150
|
+
}
|
|
151
|
+
async deleteStateSmart(targetKey, namespace) {
|
|
152
|
+
if (namespace !== undefined) {
|
|
153
|
+
return this.deleteState(namespace, targetKey);
|
|
154
|
+
}
|
|
155
|
+
const matchingRows = await this.findMatchingStateRows(targetKey);
|
|
156
|
+
if (matchingRows.length > 1) {
|
|
157
|
+
throw new ActionDockError(AMBIGUOUS_STATE_KEY, `Ambiguous state key '${targetKey}': matches ${matchingRows.length} entries for deletion (${matchingRows.map((r) => `${r.namespace}:${r.key}`).join(", ")})`);
|
|
158
|
+
}
|
|
159
|
+
if (matchingRows.length === 1) {
|
|
160
|
+
return this.deleteState(matchingRows[0].namespace, matchingRows[0].key);
|
|
161
|
+
}
|
|
162
|
+
return this.deleteState("", targetKey);
|
|
163
|
+
}
|
|
164
|
+
async cleanExpiredState(isClosed) {
|
|
165
|
+
if (isClosed)
|
|
166
|
+
return 0;
|
|
167
|
+
try {
|
|
168
|
+
const now = this.clock.now();
|
|
169
|
+
const stmt = this.getStatement("DELETE FROM state WHERE package_id = ? AND expires_at IS NOT NULL AND expires_at <= ?");
|
|
170
|
+
const res = stmt.run(this.packageId, now.toISOString());
|
|
171
|
+
const retentionCutoff = new Date(now.getTime() - IDEMPOTENCY_RETENTION_MS).toISOString();
|
|
172
|
+
try {
|
|
173
|
+
this.getStatement("DELETE FROM idempotency_keys WHERE created_at < ?").run(retentionCutoff);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
this.logWarn(`[actiondock] idempotency retention cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
177
|
+
}
|
|
178
|
+
return res.changes;
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
this.logWarn(`[actiondock] cleanExpiredState failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async clearState(options = {}) {
|
|
186
|
+
let sql = "DELETE FROM state WHERE package_id = ?";
|
|
187
|
+
const params = [this.packageId];
|
|
188
|
+
if (options.namespace !== undefined) {
|
|
189
|
+
sql += " AND namespace = ?";
|
|
190
|
+
params.push(options.namespace);
|
|
191
|
+
}
|
|
192
|
+
if (options.prefix) {
|
|
193
|
+
sql += " AND key LIKE ? ESCAPE '\\'";
|
|
194
|
+
const escapedPrefix = options.prefix.replace(/([%_\\])/g, "\\$1");
|
|
195
|
+
params.push(`${escapedPrefix}%`);
|
|
196
|
+
}
|
|
197
|
+
const stmt = this.getStatement(sql);
|
|
198
|
+
const res = stmt.run(...params);
|
|
199
|
+
return res.changes;
|
|
200
|
+
}
|
|
201
|
+
async listStateKeys(namespace, prefix, isClosed) {
|
|
202
|
+
await this.cleanExpiredState(isClosed || false);
|
|
203
|
+
let sql = "SELECT namespace, key, expires_at FROM state WHERE package_id = ?";
|
|
204
|
+
const params = [this.packageId];
|
|
205
|
+
if (namespace !== null && namespace !== undefined) {
|
|
206
|
+
sql += " AND namespace = ?";
|
|
207
|
+
params.push(namespace);
|
|
208
|
+
}
|
|
209
|
+
if (prefix) {
|
|
210
|
+
sql += " AND key LIKE ? ESCAPE '\\'";
|
|
211
|
+
const escapedPrefix = prefix.replace(/([%_\\])/g, "\\$1");
|
|
212
|
+
params.push(`${escapedPrefix}%`);
|
|
213
|
+
}
|
|
214
|
+
const stmt = this.getStatement(sql);
|
|
215
|
+
const rawResult = stmt.all(...params);
|
|
216
|
+
const rows = Array.isArray(rawResult) ? rawResult : [];
|
|
217
|
+
const now = this.clock.now().getTime();
|
|
218
|
+
const result = [];
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
if (row.expires_at && now >= new Date(row.expires_at).getTime()) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (namespace !== null && namespace !== undefined) {
|
|
224
|
+
result.push(row.key);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
result.push(encodeStateKey(row.namespace, row.key));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
async listStateEntries(options = {}, isClosed) {
|
|
233
|
+
await this.cleanExpiredState(isClosed || false);
|
|
234
|
+
let sql = "SELECT namespace, key, value_json, updated_at, expires_at FROM state WHERE package_id = ?";
|
|
235
|
+
const params = [this.packageId];
|
|
236
|
+
if (options.namespace !== undefined) {
|
|
237
|
+
sql += " AND namespace = ?";
|
|
238
|
+
params.push(options.namespace);
|
|
239
|
+
}
|
|
240
|
+
if (options.prefix) {
|
|
241
|
+
sql += " AND key LIKE ? ESCAPE '\\'";
|
|
242
|
+
const escapedPrefix = options.prefix.replace(/([%_\\])/g, "\\$1");
|
|
243
|
+
params.push(`${escapedPrefix}%`);
|
|
244
|
+
}
|
|
245
|
+
const stmt = this.getStatement(sql);
|
|
246
|
+
const rawResult = stmt.all(...params);
|
|
247
|
+
const rows = Array.isArray(rawResult) ? rawResult : [];
|
|
248
|
+
const now = this.clock.now().getTime();
|
|
249
|
+
const results = [];
|
|
250
|
+
for (const row of rows) {
|
|
251
|
+
if (row.expires_at && now >= new Date(row.expires_at).getTime()) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const parsedVal = safeParseStoredJson(row.value_json, `state db=${this.dbPath} package=${this.packageId} namespace=${row.namespace} key=${row.key}`);
|
|
255
|
+
results.push({
|
|
256
|
+
packageId: this.packageId,
|
|
257
|
+
namespace: row.namespace,
|
|
258
|
+
key: row.key,
|
|
259
|
+
fullKey: encodeStateKey(row.namespace, row.key),
|
|
260
|
+
value: parsedVal,
|
|
261
|
+
updatedAt: row.updated_at,
|
|
262
|
+
expiresAt: row.expires_at,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return results;
|
|
266
|
+
}
|
|
267
|
+
close() {
|
|
268
|
+
this.statementCache.clear();
|
|
269
|
+
}
|
|
270
|
+
}
|
package/dist/storage/sqlite.d.ts
CHANGED
|
@@ -1,72 +1,39 @@
|
|
|
1
|
-
import { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, type RuntimeError, type RunRecord } from "@actiondock/sdk";
|
|
1
|
+
import { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, type Logger, type RuntimeError, type RunRecord } from "@actiondock/sdk";
|
|
2
2
|
import { type IdempotencyCheckResult, type IdempotencyRecord, type RunsRetentionPolicy, type RuntimeStorage, type StateEntry, type StorageOptions, type TerminalRunStatus } from "./types.js";
|
|
3
3
|
export { decodeStateKey, encodeStateKey, escapeStateSegment, unescapeStateSegment, };
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* 跨进程并发模型:
|
|
9
|
-
* - 同一数据目录下的 runtime.db 允许持有者进程(serve、mcp、ad run 等执行宿主)
|
|
10
|
-
* 与旁观查询进程(ad state、ad runs、ad config 等只读命令)并发打开。
|
|
11
|
-
* - 死亡会话遗留非终态运行记录的收割(recoverDeadSessionRuns)仅由显式声明
|
|
12
|
-
* 持有者身份的打开方执行:StorageOptions.recoverOrphans 为 true 时构造阶段收割,
|
|
13
|
-
* 宿主接管路径亦可显式调用 recoverDeadSessionRuns 收敛死亡会话记录。
|
|
14
|
-
* - 旁观打开(默认 recoverOrphans 为 false)保持只读语义,绝不收敛其他进程的在途
|
|
15
|
-
* running 记录,防止查询进程把执行宿主的在途运行误收割为 interrupted,导致持有者
|
|
16
|
-
* 侧终态结算静默丢失。
|
|
17
|
-
* - updateRun 以 WHERE id = ? AND status = 'running' 条件更新;若命中零行且新状态
|
|
18
|
-
* 为终态,说明记录已被其他持有者或恢复流程改写,此处输出告警保证状态漂移可观测。
|
|
19
|
-
*/
|
|
4
|
+
export interface SqliteStorageOptions extends StorageOptions {
|
|
5
|
+
logger?: Logger;
|
|
6
|
+
}
|
|
20
7
|
export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
21
8
|
private driver;
|
|
22
9
|
private packageId;
|
|
23
10
|
private clock;
|
|
11
|
+
private logger?;
|
|
24
12
|
private isClosed;
|
|
25
|
-
private statementCache;
|
|
26
13
|
private dbPath;
|
|
27
14
|
private recoverOrphans;
|
|
28
15
|
private retentionPolicy?;
|
|
29
|
-
private
|
|
30
|
-
private
|
|
31
|
-
private
|
|
32
|
-
private
|
|
16
|
+
private runsStore;
|
|
17
|
+
private stateStore;
|
|
18
|
+
private configStore;
|
|
19
|
+
private eventsStore;
|
|
33
20
|
get isOpen(): boolean;
|
|
34
21
|
get closed(): boolean;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* 初始化数据库结构并执行严格版本校验。
|
|
38
|
-
*
|
|
39
|
-
* 规则:
|
|
40
|
-
* - 空数据目录首次初始化在单个事务中完成完整 Schema 构建。
|
|
41
|
-
* - 若创建事务失败则拒绝启动且不留下残缺表。
|
|
42
|
-
* - 打开旧版本或不兼容版本的数据库时,在写事务前直接抛出 UNSUPPORTED_STORAGE_SCHEMA 异常并拒绝启动。
|
|
43
|
-
*/
|
|
22
|
+
private logWarn;
|
|
23
|
+
constructor(options: SqliteStorageOptions);
|
|
44
24
|
private init;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
recoverDeadSessionRuns(currentHostSessionId?: string): number;
|
|
49
|
-
/**
|
|
50
|
-
* 获取或复用预编译 SQL 语句缓存。
|
|
51
|
-
*/
|
|
52
|
-
private getStatement;
|
|
25
|
+
recoverDeadSessionRuns(currentHostSessionId?: string, options?: {
|
|
26
|
+
probe?: import("./run-liveness.js").ProcessLivenessProbe;
|
|
27
|
+
}): number;
|
|
53
28
|
getConfig<T = unknown>(key: string): T | undefined;
|
|
54
29
|
listConfig(): Record<string, unknown>;
|
|
55
30
|
setConfig(key: string, value: unknown): void;
|
|
56
31
|
deleteConfig(key: string): boolean;
|
|
57
32
|
getState<T = unknown>(namespace: string, key: string): Promise<T | undefined>;
|
|
58
|
-
private findMatchingStateRows;
|
|
59
33
|
findState<T = unknown>(targetKey: string, namespace?: string): Promise<StateEntry | undefined>;
|
|
60
34
|
setState<T = unknown>(namespace: string, key: string, value: T, ttl?: number): Promise<void>;
|
|
61
35
|
deleteState(namespace: string, key: string): Promise<boolean>;
|
|
62
36
|
deleteStateSmart(targetKey: string, namespace?: string): Promise<boolean>;
|
|
63
|
-
/**
|
|
64
|
-
* 清理已过期的状态记录。
|
|
65
|
-
*
|
|
66
|
-
* 同时按 TargetInfo.idempotencyPolicy 声明的 24 小时保留窗口,
|
|
67
|
-
* 清理 idempotency_keys 表中超过保留期的幂等去重记录,
|
|
68
|
-
* 保证承诺的 retentionMs 策略与实际清理逻辑一致。
|
|
69
|
-
*/
|
|
70
37
|
cleanExpiredState(): Promise<number>;
|
|
71
38
|
clearState(options?: {
|
|
72
39
|
namespace?: string;
|
|
@@ -85,25 +52,31 @@ export declare class SqliteRuntimeStorage implements RuntimeStorage {
|
|
|
85
52
|
actionId?: string;
|
|
86
53
|
status?: string;
|
|
87
54
|
limit?: number;
|
|
55
|
+
offset?: number;
|
|
88
56
|
}): RunRecord[];
|
|
57
|
+
countRuns(options?: {
|
|
58
|
+
actionId?: string;
|
|
59
|
+
status?: string;
|
|
60
|
+
requestIds?: string[];
|
|
61
|
+
}): number;
|
|
89
62
|
clearRuns(options?: {
|
|
90
63
|
actionId?: string;
|
|
91
64
|
status?: string;
|
|
92
65
|
olderThanMs?: number;
|
|
93
66
|
keep?: number;
|
|
94
67
|
}): number;
|
|
95
|
-
/**
|
|
96
|
-
* 按保留策略清理过期的终态运行记录与超出容量限制的最旧运行记录。
|
|
97
|
-
*
|
|
98
|
-
* 包含:
|
|
99
|
-
* - 基于时间:删除早于 maxAgeMs 的终态记录(保留 minRetainRuns 保底数量)。
|
|
100
|
-
* - 基于数量:若终态记录总数超过 maxRuns,按时间由旧至新淘汰超出部分。
|
|
101
|
-
* - 级联清理孤立的幂等去重记录。
|
|
102
|
-
*/
|
|
103
68
|
cleanExpiredRuns(policy?: RunsRetentionPolicy): number;
|
|
104
|
-
|
|
69
|
+
touchRunHeartbeat(runIds: string[]): number;
|
|
105
70
|
checkAndRecordIdempotency(record: IdempotencyRecord): IdempotencyCheckResult;
|
|
106
71
|
getIdempotencyRecord(ownerId: string, actionRef: string, requestId: string): IdempotencyRecord | undefined;
|
|
107
|
-
|
|
72
|
+
listRunsByRequestIds(requestIds: string[]): RunRecord[];
|
|
73
|
+
countRunsByRequestIds(requestIds: string[], options?: {
|
|
74
|
+
actionId?: string;
|
|
75
|
+
status?: string;
|
|
76
|
+
}): number;
|
|
77
|
+
getRunRequestIds(runIds: string[]): Record<string, string>;
|
|
78
|
+
getRunWithRequestId(id: string): RunRecord | null;
|
|
79
|
+
appendEvent(eventType: string, payload: unknown): void;
|
|
80
|
+
queryEvents(eventType?: string): any[];
|
|
108
81
|
close(): Promise<void>;
|
|
109
82
|
}
|