@openfinclaw/openfinclaw-strategy 2026.3.25 → 2026.3.27
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/index.ts +17 -7
- package/openclaw.plugin.json +8 -1
- package/package.json +2 -2
- package/skills/price-check/SKILL.md +36 -31
- package/src/config.ts +8 -0
- package/src/datahub/tools.ts +19 -14
- package/src/db/db.ts +44 -0
- package/src/db/repositories.ts +315 -0
- package/src/db/schema.ts +110 -0
- package/src/http/routes.ts +107 -0
- package/src/http/server.ts +36 -0
- package/src/middleware/with-logging.ts +72 -0
- package/src/strategy/client.ts +1 -1
- package/src/strategy/fork.ts +9 -9
- package/src/strategy/storage.ts +1 -1
- package/src/strategy/tools.ts +211 -81
- package/src/strategy/validate.ts +1 -1
- package/src/types.ts +2 -0
package/index.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
1
2
|
/**
|
|
2
3
|
* OpenFinClaw — Unified financial tools plugin.
|
|
3
4
|
* Features:
|
|
4
5
|
* - Strategy tools: publish, validate, fork, leaderboard
|
|
5
6
|
* - Market data tools: price, K-line, crypto data, compare, search
|
|
7
|
+
* - SQLite persistence: all tool executions logged; domain tables for strategies and backtests
|
|
8
|
+
* - Dashboard: embedded HTTP server at http://127.0.0.1:<httpPort> (default 18792)
|
|
6
9
|
* Supports FEP v2.0 protocol for strategy packages.
|
|
7
10
|
*/
|
|
8
|
-
import type { OpenClawPluginApi } from "
|
|
9
|
-
import type { Command } from "commander";
|
|
10
|
-
import { registerDatahubTools } from "./src/datahub/tools.js";
|
|
11
|
-
import { registerStrategyTools } from "./src/strategy/tools.js";
|
|
11
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
12
12
|
import { registerStrategyCli } from "./src/cli.js";
|
|
13
13
|
import { resolvePluginConfig } from "./src/config.js";
|
|
14
|
+
import { registerDatahubTools } from "./src/datahub/tools.js";
|
|
15
|
+
import { getDb } from "./src/db/db.js";
|
|
16
|
+
import { startHttpServer } from "./src/http/server.js";
|
|
17
|
+
import { registerStrategyTools } from "./src/strategy/tools.js";
|
|
14
18
|
|
|
15
19
|
const openfinclawPlugin = {
|
|
16
20
|
id: "openfinclaw",
|
|
@@ -22,11 +26,14 @@ const openfinclawPlugin = {
|
|
|
22
26
|
register(api: OpenClawPluginApi) {
|
|
23
27
|
const config = resolvePluginConfig(api);
|
|
24
28
|
|
|
29
|
+
// Initialise SQLite database (creates tables on first run)
|
|
30
|
+
const db = getDb();
|
|
31
|
+
|
|
25
32
|
// Register DataHub market data tools (fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search)
|
|
26
|
-
registerDatahubTools(api, config);
|
|
33
|
+
registerDatahubTools(api, config, db);
|
|
27
34
|
|
|
28
35
|
// Register strategy tools (skill_publish, skill_validate, skill_fork, skill_leaderboard, etc.)
|
|
29
|
-
registerStrategyTools(api, config);
|
|
36
|
+
registerStrategyTools(api, config, db);
|
|
30
37
|
|
|
31
38
|
// Register CLI commands
|
|
32
39
|
api.registerCli(
|
|
@@ -38,7 +45,10 @@ const openfinclawPlugin = {
|
|
|
38
45
|
}),
|
|
39
46
|
{ commands: ["strategy"] },
|
|
40
47
|
);
|
|
48
|
+
|
|
49
|
+
// Start embedded dashboard HTTP server (loopback only, configurable port)
|
|
50
|
+
startHttpServer(db, config.httpPort, api.logger);
|
|
41
51
|
},
|
|
42
52
|
};
|
|
43
53
|
|
|
44
|
-
export default openfinclawPlugin;
|
|
54
|
+
export default openfinclawPlugin;
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "OpenFinClaw",
|
|
4
4
|
"description": "Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"kind": "financial",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "2026.3.25",
|
|
7
7
|
"skills": ["./skills"],
|
|
8
8
|
"configSchema": {
|
|
9
9
|
"type": "object",
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
"minimum": 5000,
|
|
31
31
|
"maximum": 300000,
|
|
32
32
|
"description": "HTTP request timeout in milliseconds"
|
|
33
|
+
},
|
|
34
|
+
"httpPort": {
|
|
35
|
+
"type": "number",
|
|
36
|
+
"default": 18792,
|
|
37
|
+
"minimum": 1024,
|
|
38
|
+
"maximum": 65535,
|
|
39
|
+
"description": "Dashboard HTTP server port (loopback only)"
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfinclaw/openfinclaw-strategy",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.27",
|
|
4
4
|
"description": "OpenFinClaw - Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backtest",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"registry": "https://registry.npmjs.org"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@sinclair/typebox": "
|
|
37
|
+
"@sinclair/typebox": "0.34.48",
|
|
38
38
|
"adm-zip": "^0.5.16"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -12,14 +12,15 @@ metadata: { "openclaw": { "emoji": "💰", "requires": { "extensions": ["openfin
|
|
|
12
12
|
|
|
13
13
|
### fin_price — 查当前价
|
|
14
14
|
|
|
15
|
-
| Parameter | Type | Required | Example
|
|
16
|
-
| --------- | ------ | -------- |
|
|
17
|
-
| symbol | string | Yes | BTC/USDT
|
|
18
|
-
| market | string | No | crypto
|
|
15
|
+
| Parameter | Type | Required | Example |
|
|
16
|
+
| --------- | ------ | -------- | -------- |
|
|
17
|
+
| symbol | string | Yes | BTC/USDT |
|
|
18
|
+
| market | string | No | crypto |
|
|
19
19
|
|
|
20
20
|
Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity。
|
|
21
21
|
|
|
22
22
|
**返回字段:**
|
|
23
|
+
|
|
23
24
|
- `price`: 最新价格
|
|
24
25
|
- `volume24h`: 24小时成交量(加密货币)
|
|
25
26
|
- `timestamp`: 数据时间
|
|
@@ -33,6 +34,7 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
|
|
|
33
34
|
| limit | number | No | 30 | 10 |
|
|
34
35
|
|
|
35
36
|
**返回字段:**
|
|
37
|
+
|
|
36
38
|
- `bars[]`: K线数组,包含 date, open, high, low, close, volume
|
|
37
39
|
|
|
38
40
|
### fin_compare — 多资产对比
|
|
@@ -42,50 +44,52 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
|
|
|
42
44
|
| symbols | string | Yes | BTC/USDT,ETH/USDT,600519.SH |
|
|
43
45
|
|
|
44
46
|
**返回字段:**
|
|
47
|
+
|
|
45
48
|
- `comparison[]`: 每个资产的 price, weekChange(周涨跌幅)
|
|
46
49
|
|
|
47
50
|
### fin_slim_search — 搜索代码
|
|
48
51
|
|
|
49
52
|
当用户只说公司/币种名称,不确定 symbol 时使用:
|
|
50
53
|
|
|
51
|
-
| Parameter | Type | Required | Example
|
|
52
|
-
| --------- | ------ | -------- |
|
|
53
|
-
| query | string | Yes | 茅台
|
|
54
|
-
| market | string | No | equity
|
|
54
|
+
| Parameter | Type | Required | Example |
|
|
55
|
+
| --------- | ------ | -------- | ------- |
|
|
56
|
+
| query | string | Yes | 茅台 |
|
|
57
|
+
| market | string | No | equity |
|
|
55
58
|
|
|
56
59
|
**触发场景:**
|
|
60
|
+
|
|
57
61
|
- 用户说 "茅台多少钱" → 先搜索 `query="茅台"` → 找到 `600519.SH` → 再查价格
|
|
58
62
|
- 用户说 "特斯拉股价" → 先搜索 `query="特斯拉"` → 找到 `TSLA` → 再查价格
|
|
59
63
|
|
|
60
64
|
### fin_crypto — 加密市场数据
|
|
61
65
|
|
|
62
|
-
| Parameter | Type | Required | Example
|
|
63
|
-
| --------- | ------ | -------- |
|
|
64
|
-
| endpoint | string | Yes | market/funding_rate
|
|
65
|
-
| symbol | string | No | BTC/USDT:USDT
|
|
66
|
-
| limit | number | No | 20
|
|
66
|
+
| Parameter | Type | Required | Example |
|
|
67
|
+
| --------- | ------ | -------- | ------------------- |
|
|
68
|
+
| endpoint | string | Yes | market/funding_rate |
|
|
69
|
+
| symbol | string | No | BTC/USDT:USDT |
|
|
70
|
+
| limit | number | No | 20 |
|
|
67
71
|
|
|
68
72
|
**常用端点:**
|
|
69
73
|
|
|
70
|
-
| 端点
|
|
71
|
-
|
|
|
72
|
-
| `market/ticker`
|
|
73
|
-
| `market/tickers`
|
|
74
|
-
| `market/funding_rate`
|
|
75
|
-
| `coin/market`
|
|
76
|
-
| `coin/trending`
|
|
77
|
-
| `defi/protocols`
|
|
78
|
-
| `defi/yields`
|
|
74
|
+
| 端点 | 用途 |
|
|
75
|
+
| --------------------- | ------------------ |
|
|
76
|
+
| `market/ticker` | 单币种行情 |
|
|
77
|
+
| `market/tickers` | 多币种行情 |
|
|
78
|
+
| `market/funding_rate` | 资金费率 |
|
|
79
|
+
| `coin/market` | CoinGecko 币种排行 |
|
|
80
|
+
| `coin/trending` | 热门币种 |
|
|
81
|
+
| `defi/protocols` | DeFi 协议 TVL 排行 |
|
|
82
|
+
| `defi/yields` | DeFi 收益率 |
|
|
79
83
|
|
|
80
84
|
## Symbol 格式速查
|
|
81
85
|
|
|
82
|
-
| 格式
|
|
83
|
-
|
|
|
84
|
-
| `XXX/YYY`
|
|
85
|
-
| `6位数.SZ/SH`
|
|
86
|
-
| `5位数.HK`
|
|
87
|
-
| `1-5大写字母`
|
|
88
|
-
| `000xxx.SH`
|
|
86
|
+
| 格式 | 市场 | 示例 |
|
|
87
|
+
| ------------- | ------ | ------------------------ |
|
|
88
|
+
| `XXX/YYY` | Crypto | `BTC/USDT`, `ETH/BTC` |
|
|
89
|
+
| `6位数.SZ/SH` | A股 | `000001.SZ`, `600519.SH` |
|
|
90
|
+
| `5位数.HK` | 港股 | `00700.HK`, `00941.HK` |
|
|
91
|
+
| `1-5大写字母` | 美股 | `AAPL`, `NVDA`, `TSLA` |
|
|
92
|
+
| `000xxx.SH` | 指数 | `000300.SH` (沪深300) |
|
|
89
93
|
|
|
90
94
|
## Response Guidelines
|
|
91
95
|
|
|
@@ -101,7 +105,8 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
|
|
|
101
105
|
**流程:** `fin_price(symbol="BTC/USDT")` → 返回 $69,552
|
|
102
106
|
|
|
103
107
|
**用户:** 茅台现在什么价?
|
|
104
|
-
**流程:**
|
|
108
|
+
**流程:**
|
|
109
|
+
|
|
105
110
|
1. `fin_slim_search(query="茅台")` → 找到 `600519.SH`
|
|
106
111
|
2. `fin_price(symbol="600519.SH")` → 返回 ¥1,856.00
|
|
107
112
|
|
|
@@ -115,4 +120,4 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
|
|
|
115
120
|
**流程:** `fin_crypto(endpoint="market/funding_rate", symbol="BTC/USDT:USDT")` → 费率数据
|
|
116
121
|
|
|
117
122
|
**用户:** 现在 DeFi 哪个协议 TVL 最高?
|
|
118
|
-
**流程:** `fin_crypto(endpoint="defi/protocols", limit=5)` → TVL 排行
|
|
123
|
+
**流程:** `fin_crypto(endpoint="defi/protocols", limit=5)` → TVL 排行
|
package/src/config.ts
CHANGED
|
@@ -48,10 +48,18 @@ export function resolvePluginConfig(api: OpenClawPluginApi): UnifiedPluginConfig
|
|
|
48
48
|
? Math.floor(Number(timeoutRaw))
|
|
49
49
|
: DEFAULT_TIMEOUT_MS;
|
|
50
50
|
|
|
51
|
+
const httpPortRaw = raw?.httpPort ?? readEnv(["OPENFINCLAW_HTTP_PORT"]);
|
|
52
|
+
const httpPortNum = Number(httpPortRaw);
|
|
53
|
+
const httpPort =
|
|
54
|
+
Number.isFinite(httpPortNum) && httpPortNum >= 1024 && httpPortNum <= 65535
|
|
55
|
+
? Math.floor(httpPortNum)
|
|
56
|
+
: 18792;
|
|
57
|
+
|
|
51
58
|
return {
|
|
52
59
|
apiKey: apiKey && apiKey.length > 0 ? apiKey : undefined,
|
|
53
60
|
hubApiUrl: hubApiUrl.replace(/\/$/, ""),
|
|
54
61
|
datahubGatewayUrl: datahubGatewayUrl.replace(/\/+$/, ""),
|
|
55
62
|
requestTimeoutMs,
|
|
63
|
+
httpPort,
|
|
56
64
|
};
|
|
57
65
|
}
|
package/src/datahub/tools.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
1
2
|
/**
|
|
2
3
|
* DataHub market data tools registration.
|
|
3
4
|
* Tools: fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search
|
|
4
5
|
*/
|
|
5
6
|
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import type { OpenClawPluginApi } from "
|
|
7
|
-
import {
|
|
7
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
8
|
+
import { withLogging } from "../middleware/with-logging.js";
|
|
8
9
|
import type { UnifiedPluginConfig, MarketType } from "../types.js";
|
|
10
|
+
import { DataHubClient, guessMarket } from "./client.js";
|
|
9
11
|
|
|
10
12
|
/** JSON tool result helper. */
|
|
11
13
|
function json(payload: unknown) {
|
|
@@ -24,14 +26,17 @@ function pick(params: Record<string, unknown>, ...keys: string[]): Record<string
|
|
|
24
26
|
return out;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
const NO_KEY =
|
|
29
|
+
const NO_KEY =
|
|
30
|
+
"API key not configured. Set apiKey in plugin config or OPENFINCLAW_API_KEY env var.";
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Register DataHub market data tools.
|
|
34
|
+
* @param db - SQLite database for activity logging.
|
|
31
35
|
*/
|
|
32
36
|
export function registerDatahubTools(
|
|
33
37
|
api: OpenClawPluginApi,
|
|
34
38
|
config: UnifiedPluginConfig,
|
|
39
|
+
db: DatabaseSync,
|
|
35
40
|
): void {
|
|
36
41
|
const datahubClient = config.apiKey
|
|
37
42
|
? new DataHubClient(config.datahubGatewayUrl, config.apiKey, config.requestTimeoutMs)
|
|
@@ -86,7 +91,7 @@ export function registerDatahubTools(
|
|
|
86
91
|
}),
|
|
87
92
|
),
|
|
88
93
|
}),
|
|
89
|
-
|
|
94
|
+
execute: withLogging(db, "fin_price", "market-data", async (_id, params) => {
|
|
90
95
|
try {
|
|
91
96
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
92
97
|
const symbol = String(params.symbol);
|
|
@@ -102,7 +107,7 @@ export function registerDatahubTools(
|
|
|
102
107
|
} catch (err) {
|
|
103
108
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
104
109
|
}
|
|
105
|
-
},
|
|
110
|
+
}),
|
|
106
111
|
},
|
|
107
112
|
{ names: ["fin_price"] },
|
|
108
113
|
);
|
|
@@ -130,7 +135,7 @@ export function registerDatahubTools(
|
|
|
130
135
|
Type.Number({ description: "Number of bars to return (default: 30)" }),
|
|
131
136
|
),
|
|
132
137
|
}),
|
|
133
|
-
|
|
138
|
+
execute: withLogging(db, "fin_kline", "market-data", async (_id, params) => {
|
|
134
139
|
try {
|
|
135
140
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
136
141
|
const symbol = String(params.symbol);
|
|
@@ -153,7 +158,7 @@ export function registerDatahubTools(
|
|
|
153
158
|
} catch (err) {
|
|
154
159
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
155
160
|
}
|
|
156
|
-
},
|
|
161
|
+
}),
|
|
157
162
|
},
|
|
158
163
|
{ names: ["fin_kline"] },
|
|
159
164
|
);
|
|
@@ -204,7 +209,7 @@ export function registerDatahubTools(
|
|
|
204
209
|
end_date: Type.Optional(Type.String({ description: "End date (YYYY-MM-DD)" })),
|
|
205
210
|
limit: Type.Optional(Type.Number({ description: "Max results (default: 20)" })),
|
|
206
211
|
}),
|
|
207
|
-
|
|
212
|
+
execute: withLogging(db, "fin_crypto", "market-data", async (_id, params) => {
|
|
208
213
|
try {
|
|
209
214
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
210
215
|
const endpoint = String(params.endpoint ?? "coin/market");
|
|
@@ -233,7 +238,7 @@ export function registerDatahubTools(
|
|
|
233
238
|
} catch (err) {
|
|
234
239
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
235
240
|
}
|
|
236
|
-
},
|
|
241
|
+
}),
|
|
237
242
|
},
|
|
238
243
|
{ names: ["fin_crypto"] },
|
|
239
244
|
);
|
|
@@ -251,7 +256,7 @@ export function registerDatahubTools(
|
|
|
251
256
|
description: "Comma-separated symbols (2-5). Example: BTC/USDT,ETH/USDT,600519.SH",
|
|
252
257
|
}),
|
|
253
258
|
}),
|
|
254
|
-
|
|
259
|
+
execute: withLogging(db, "fin_compare", "market-data", async (_id, params) => {
|
|
255
260
|
try {
|
|
256
261
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
257
262
|
const raw = String(params.symbols);
|
|
@@ -289,7 +294,7 @@ export function registerDatahubTools(
|
|
|
289
294
|
} catch (err) {
|
|
290
295
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
291
296
|
}
|
|
292
|
-
},
|
|
297
|
+
}),
|
|
293
298
|
},
|
|
294
299
|
{ names: ["fin_compare"] },
|
|
295
300
|
);
|
|
@@ -312,7 +317,7 @@ export function registerDatahubTools(
|
|
|
312
317
|
}),
|
|
313
318
|
),
|
|
314
319
|
}),
|
|
315
|
-
|
|
320
|
+
execute: withLogging(db, "fin_slim_search", "market-data", async (_id, params) => {
|
|
316
321
|
try {
|
|
317
322
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
318
323
|
const q = String(params.query);
|
|
@@ -342,8 +347,8 @@ export function registerDatahubTools(
|
|
|
342
347
|
} catch (err) {
|
|
343
348
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
344
349
|
}
|
|
345
|
-
},
|
|
350
|
+
}),
|
|
346
351
|
},
|
|
347
352
|
{ names: ["fin_slim_search"] },
|
|
348
353
|
);
|
|
349
|
-
}
|
|
354
|
+
}
|
package/src/db/db.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite database singleton for OpenFinClaw plugin.
|
|
4
|
+
* Database path: ~/.openfinclaw/workspace/openfinclaw-plugin.db
|
|
5
|
+
*/
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
10
|
+
import { ensureSchema } from "./schema.js";
|
|
11
|
+
|
|
12
|
+
// Use createRequire to load the built-in node:sqlite in an ESM context.
|
|
13
|
+
const _require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
let _db: DatabaseSync | null = null;
|
|
16
|
+
|
|
17
|
+
/** Resolve the database file path under ~/.openfinclaw/workspace/. */
|
|
18
|
+
function resolveDbPath(): string {
|
|
19
|
+
const base = join(homedir(), ".openfinclaw", "workspace");
|
|
20
|
+
mkdirSync(base, { recursive: true });
|
|
21
|
+
return join(base, "openfinclaw-plugin.db");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get (or lazily initialise) the SQLite database singleton.
|
|
26
|
+
* Calls ensureSchema on first access to create missing tables.
|
|
27
|
+
*/
|
|
28
|
+
export function getDb(): DatabaseSync {
|
|
29
|
+
if (_db) return _db;
|
|
30
|
+
// node:sqlite is available in Node 22+
|
|
31
|
+
const { DatabaseSync } = _require("node:sqlite") as typeof import("node:sqlite");
|
|
32
|
+
const dbPath = resolveDbPath();
|
|
33
|
+
_db = new DatabaseSync(dbPath);
|
|
34
|
+
ensureSchema(_db);
|
|
35
|
+
return _db;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Close the database (used in tests / graceful shutdown). */
|
|
39
|
+
export function closeDb(): void {
|
|
40
|
+
if (_db) {
|
|
41
|
+
_db.close();
|
|
42
|
+
_db = null;
|
|
43
|
+
}
|
|
44
|
+
}
|