@ai-zen/agents-sdk 0.3.1 → 0.3.2
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/CHANGELOG.md +19 -0
- package/dist/capabilities/implements/skillTools.js +2 -2
- package/dist/config/ConfigManager.d.ts +8 -8
- package/dist/config/ConfigManager.js +66 -35
- package/dist/crud/DraftRepository.d.ts +3 -3
- package/dist/crud/DraftRepository.js +22 -11
- package/dist/plugin/AutoDraftPlugin.d.ts +1 -1
- package/dist/plugin/AutoDraftPlugin.js +10 -6
- package/dist/runtime/createAgent.d.ts +1 -1
- package/dist/runtime/createAgent.js +2 -2
- package/dist/shared/EntityRepository.d.ts +4 -4
- package/dist/shared/EntityRepository.js +31 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2] - 2026-07-26
|
|
4
|
+
|
|
5
|
+
### 💥 破坏性变更
|
|
6
|
+
|
|
7
|
+
- **`EntityRepository` 所有方法改为异步** — `list()`、`read()`、`write()`、`delete()` 现在返回 `Promise`,需 `await`
|
|
8
|
+
- **`DraftRepository` 所有方法改为异步** — `read()`、`write()`、`delete()` 需 `await`
|
|
9
|
+
- **`ConfigManager` 所有方法改为异步** — `read()`、`write()`、`ensureDirs()`、`ensureDefaultAgent()`、`ensureDefaultSubAgent()`、`ensureDefaultConfig()`、`bootstrap()` 均需 `await`
|
|
10
|
+
- **`createAgent()` 恢复为 `async`** — 因底层仓储改为异步,`createAgent(provider, agentId)` 需 `await`
|
|
11
|
+
- **`AutoDraftPlugin.checkDraftForRestore()` 改为异步** — 需 `await`
|
|
12
|
+
|
|
13
|
+
### 🎯 优化
|
|
14
|
+
|
|
15
|
+
- **全面消除同步文件 IO** — 所有生产代码中的 `existsSync`、`readFileSync`、`writeFileSync`、`readdirSync`、`mkdirSync`、`unlinkSync`、`renameSync` 全部替换为 `fs.promises` 异步 API,避免事件循环阻塞
|
|
16
|
+
- **`skillTools.ts` — 工具回调中的 `readdirSync` 替换为 `fs.promises.readdir`**
|
|
17
|
+
|
|
18
|
+
### ✅ 测试
|
|
19
|
+
|
|
20
|
+
- 7 个测试文件同步更新为 `async`/`await`,测试辅助函数也使用 `fs.promises`
|
|
21
|
+
|
|
3
22
|
## [0.3.1] - 2026-07-26
|
|
4
23
|
|
|
5
24
|
### 💥 破坏性变更
|
|
@@ -3,7 +3,7 @@ import { SdkAgent } from "../../runtime/SdkAgent.js";
|
|
|
3
3
|
import { createDisclosureParam } from "../disclosure.js";
|
|
4
4
|
import { readSkill } from "../discovery/skills.js";
|
|
5
5
|
import { createLogger } from "../../shared/logger.js";
|
|
6
|
-
import {
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
7
|
const log = createLogger();
|
|
8
8
|
const EMPTY_HINT = "(当前没有可用的 Skill,请联系用户添加)";
|
|
9
9
|
/**
|
|
@@ -44,7 +44,7 @@ export function createLoadSkillTool(skillDirs, filteredSkills) {
|
|
|
44
44
|
// 扫描 skill 目录下的文件列表
|
|
45
45
|
let fileList = "";
|
|
46
46
|
try {
|
|
47
|
-
const entries =
|
|
47
|
+
const entries = await fs.readdir(skill.dirPath, { withFileTypes: true });
|
|
48
48
|
const files = entries.map((e) => {
|
|
49
49
|
const suffix = e.isDirectory() ? "/" : "";
|
|
50
50
|
return ` - ${e.name}${suffix}`;
|
|
@@ -9,17 +9,17 @@ export declare class ConfigManager {
|
|
|
9
9
|
/**
|
|
10
10
|
* 读取配置。文件不存在时返回出厂默认配置。
|
|
11
11
|
*/
|
|
12
|
-
read(): AppConfig
|
|
13
|
-
write(config: AppConfig): void
|
|
12
|
+
read(): Promise<AppConfig>;
|
|
13
|
+
write(config: AppConfig): Promise<void>;
|
|
14
14
|
/**
|
|
15
15
|
* 确保基础目录结构存在。
|
|
16
16
|
* 创建 basePath 及所有标准共享子目录(agents/、sub-agents/、skills/ 等)。
|
|
17
17
|
*/
|
|
18
|
-
ensureDirs(): void
|
|
18
|
+
ensureDirs(): Promise<void>;
|
|
19
19
|
/**
|
|
20
20
|
* 确保 config.json 存在。不存在时写入出厂默认配置 DSL。
|
|
21
21
|
*/
|
|
22
|
-
ensureDefaultConfig(): AppConfig
|
|
22
|
+
ensureDefaultConfig(): Promise<AppConfig>;
|
|
23
23
|
/**
|
|
24
24
|
* 确保 basePath/agents/default.json 存在。
|
|
25
25
|
*
|
|
@@ -27,7 +27,7 @@ export declare class ConfigManager {
|
|
|
27
27
|
* - agents/ 为空 → 写入默认 Agent
|
|
28
28
|
* - 已有其他 Agent → 返回 null
|
|
29
29
|
*/
|
|
30
|
-
ensureDefaultAgent(): AgentDefinition | null
|
|
30
|
+
ensureDefaultAgent(): Promise<AgentDefinition | null>;
|
|
31
31
|
/**
|
|
32
32
|
* 确保 basePath/sub-agents/{DEFAULT_SUBAGENT_ID}.json 存在。
|
|
33
33
|
*
|
|
@@ -35,15 +35,15 @@ export declare class ConfigManager {
|
|
|
35
35
|
* - sub-agents/ 为空 → 写入默认通用助手 SubAgent
|
|
36
36
|
* - 已有其他 SubAgent → 返回 null
|
|
37
37
|
*/
|
|
38
|
-
ensureDefaultSubAgent(): AgentDefinition | null
|
|
38
|
+
ensureDefaultSubAgent(): Promise<AgentDefinition | null>;
|
|
39
39
|
/**
|
|
40
40
|
* 一键初始化:目录 + config.json + 默认 Agent + 默认 SubAgent。
|
|
41
41
|
* 已有文件不会被覆盖。
|
|
42
42
|
*/
|
|
43
|
-
bootstrap(): {
|
|
43
|
+
bootstrap(): Promise<{
|
|
44
44
|
config: AppConfig;
|
|
45
45
|
agent: AgentDefinition | null;
|
|
46
46
|
subAgent: AgentDefinition | null;
|
|
47
|
-
}
|
|
47
|
+
}>;
|
|
48
48
|
}
|
|
49
49
|
export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
|
|
4
4
|
/**
|
|
@@ -17,21 +17,27 @@ export class ConfigManager {
|
|
|
17
17
|
/**
|
|
18
18
|
* 读取配置。文件不存在时返回出厂默认配置。
|
|
19
19
|
*/
|
|
20
|
-
read() {
|
|
21
|
-
|
|
20
|
+
async read() {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(this.configPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
22
25
|
return { ...DEFAULT_APP_CONFIG };
|
|
23
26
|
}
|
|
24
|
-
const raw =
|
|
27
|
+
const raw = await fs.readFile(this.configPath, "utf-8");
|
|
25
28
|
return JSON.parse(raw);
|
|
26
29
|
}
|
|
27
|
-
write(config) {
|
|
30
|
+
async write(config) {
|
|
28
31
|
const dir = dirname(this.configPath);
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(dir);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
await fs.mkdir(dir, { recursive: true });
|
|
31
37
|
}
|
|
32
38
|
const tmpPath = this.configPath + ".tmp";
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
await fs.writeFile(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
40
|
+
await fs.rename(tmpPath, this.configPath);
|
|
35
41
|
}
|
|
36
42
|
// -----------------------------------------------------------------------
|
|
37
43
|
// 目录 & 默认实体
|
|
@@ -40,22 +46,25 @@ export class ConfigManager {
|
|
|
40
46
|
* 确保基础目录结构存在。
|
|
41
47
|
* 创建 basePath 及所有标准共享子目录(agents/、sub-agents/、skills/ 等)。
|
|
42
48
|
*/
|
|
43
|
-
ensureDirs() {
|
|
44
|
-
|
|
49
|
+
async ensureDirs() {
|
|
50
|
+
await fs.mkdir(this.basePath, { recursive: true });
|
|
45
51
|
for (const dir of CONFIG_SUB_DIRS) {
|
|
46
|
-
|
|
52
|
+
await fs.mkdir(join(this.basePath, dir), { recursive: true });
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
50
56
|
* 确保 config.json 存在。不存在时写入出厂默认配置 DSL。
|
|
51
57
|
*/
|
|
52
|
-
ensureDefaultConfig() {
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
async ensureDefaultConfig() {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(this.configPath);
|
|
61
|
+
return await this.read();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
await this.ensureDirs();
|
|
65
|
+
await this.write(DEFAULT_APP_CONFIG);
|
|
66
|
+
return { ...DEFAULT_APP_CONFIG };
|
|
55
67
|
}
|
|
56
|
-
this.ensureDirs();
|
|
57
|
-
this.write(DEFAULT_APP_CONFIG);
|
|
58
|
-
return { ...DEFAULT_APP_CONFIG };
|
|
59
68
|
}
|
|
60
69
|
/**
|
|
61
70
|
* 确保 basePath/agents/default.json 存在。
|
|
@@ -64,14 +73,25 @@ export class ConfigManager {
|
|
|
64
73
|
* - agents/ 为空 → 写入默认 Agent
|
|
65
74
|
* - 已有其他 Agent → 返回 null
|
|
66
75
|
*/
|
|
67
|
-
ensureDefaultAgent() {
|
|
76
|
+
async ensureDefaultAgent() {
|
|
68
77
|
const agentsDir = join(this.basePath, "agents");
|
|
69
78
|
const defaultPath = join(agentsDir, `${DEFAULT_AGENT_ID}.json`);
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
try {
|
|
80
|
+
await fs.access(defaultPath);
|
|
81
|
+
return JSON.parse(await fs.readFile(defaultPath, "utf-8"));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// default.json 不存在,继续
|
|
85
|
+
}
|
|
86
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
87
|
+
let existing;
|
|
88
|
+
try {
|
|
89
|
+
const allFiles = await fs.readdir(agentsDir);
|
|
90
|
+
existing = allFiles.filter((f) => f.endsWith(".json"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
existing = [];
|
|
72
94
|
}
|
|
73
|
-
mkdirSync(agentsDir, { recursive: true });
|
|
74
|
-
const existing = readdirSync(agentsDir).filter((f) => f.endsWith(".json"));
|
|
75
95
|
if (existing.length > 0) {
|
|
76
96
|
return null;
|
|
77
97
|
}
|
|
@@ -81,7 +101,7 @@ export class ConfigManager {
|
|
|
81
101
|
createdAt: now,
|
|
82
102
|
updatedAt: now,
|
|
83
103
|
};
|
|
84
|
-
|
|
104
|
+
await fs.writeFile(defaultPath, JSON.stringify(definition, null, 2), "utf-8");
|
|
85
105
|
return definition;
|
|
86
106
|
}
|
|
87
107
|
/**
|
|
@@ -91,14 +111,25 @@ export class ConfigManager {
|
|
|
91
111
|
* - sub-agents/ 为空 → 写入默认通用助手 SubAgent
|
|
92
112
|
* - 已有其他 SubAgent → 返回 null
|
|
93
113
|
*/
|
|
94
|
-
ensureDefaultSubAgent() {
|
|
114
|
+
async ensureDefaultSubAgent() {
|
|
95
115
|
const subDir = join(this.basePath, "sub-agents");
|
|
96
116
|
const defaultPath = join(subDir, `${DEFAULT_SUBAGENT_ID}.json`);
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
try {
|
|
118
|
+
await fs.access(defaultPath);
|
|
119
|
+
return JSON.parse(await fs.readFile(defaultPath, "utf-8"));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// 文件不存在,继续
|
|
123
|
+
}
|
|
124
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
125
|
+
let existing;
|
|
126
|
+
try {
|
|
127
|
+
const allFiles = await fs.readdir(subDir);
|
|
128
|
+
existing = allFiles.filter((f) => f.endsWith(".json"));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
existing = [];
|
|
99
132
|
}
|
|
100
|
-
mkdirSync(subDir, { recursive: true });
|
|
101
|
-
const existing = readdirSync(subDir).filter((f) => f.endsWith(".json"));
|
|
102
133
|
if (existing.length > 0) {
|
|
103
134
|
return null;
|
|
104
135
|
}
|
|
@@ -108,18 +139,18 @@ export class ConfigManager {
|
|
|
108
139
|
createdAt: now,
|
|
109
140
|
updatedAt: now,
|
|
110
141
|
};
|
|
111
|
-
|
|
142
|
+
await fs.writeFile(defaultPath, JSON.stringify(definition, null, 2), "utf-8");
|
|
112
143
|
return definition;
|
|
113
144
|
}
|
|
114
145
|
/**
|
|
115
146
|
* 一键初始化:目录 + config.json + 默认 Agent + 默认 SubAgent。
|
|
116
147
|
* 已有文件不会被覆盖。
|
|
117
148
|
*/
|
|
118
|
-
bootstrap() {
|
|
119
|
-
this.ensureDirs();
|
|
120
|
-
const config = this.ensureDefaultConfig();
|
|
121
|
-
const agent = this.ensureDefaultAgent();
|
|
122
|
-
const subAgent = this.ensureDefaultSubAgent();
|
|
149
|
+
async bootstrap() {
|
|
150
|
+
await this.ensureDirs();
|
|
151
|
+
const config = await this.ensureDefaultConfig();
|
|
152
|
+
const agent = await this.ensureDefaultAgent();
|
|
153
|
+
const subAgent = await this.ensureDefaultSubAgent();
|
|
123
154
|
return { config, agent, subAgent };
|
|
124
155
|
}
|
|
125
156
|
}
|
|
@@ -9,7 +9,7 @@ export declare class DraftRepository {
|
|
|
9
9
|
private dir;
|
|
10
10
|
constructor(dir: string);
|
|
11
11
|
private path;
|
|
12
|
-
read(conversationId?: string): Draft | null
|
|
13
|
-
write(draft: Draft): void
|
|
14
|
-
delete(conversationId?: string): void
|
|
12
|
+
read(conversationId?: string): Promise<Draft | null>;
|
|
13
|
+
write(draft: Draft): Promise<void>;
|
|
14
|
+
delete(conversationId?: string): Promise<void>;
|
|
15
15
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
const CURRENT_DRAFT = "_current.json";
|
|
4
4
|
/**
|
|
@@ -15,27 +15,38 @@ export class DraftRepository {
|
|
|
15
15
|
path(conversationId) {
|
|
16
16
|
return join(this.dir, conversationId ? `${conversationId}.json` : CURRENT_DRAFT);
|
|
17
17
|
}
|
|
18
|
-
read(conversationId) {
|
|
18
|
+
async read(conversationId) {
|
|
19
19
|
const p = this.path(conversationId);
|
|
20
|
-
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(p);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
21
24
|
return null;
|
|
25
|
+
}
|
|
22
26
|
try {
|
|
23
|
-
return JSON.parse(
|
|
27
|
+
return JSON.parse(await fs.readFile(p, "utf-8"));
|
|
24
28
|
}
|
|
25
29
|
catch {
|
|
26
30
|
return null;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
|
-
write(draft) {
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
async write(draft) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(this.dir);
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
catch {
|
|
38
|
+
await fs.mkdir(this.dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
await fs.writeFile(this.path(draft.conversationId), JSON.stringify(draft, null, 2), "utf-8");
|
|
34
41
|
}
|
|
35
|
-
delete(conversationId) {
|
|
42
|
+
async delete(conversationId) {
|
|
36
43
|
const p = this.path(conversationId);
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(p);
|
|
46
|
+
await fs.unlink(p);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// 文件不存在,忽略
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
52
|
}
|
|
@@ -22,5 +22,5 @@ export declare class AutoDraftPlugin implements AgentPlugin {
|
|
|
22
22
|
private options;
|
|
23
23
|
constructor(options: AutoDraftOptions);
|
|
24
24
|
onInnerLoopEnd(ctx: SendContext): Promise<void>;
|
|
25
|
-
static checkDraftForRestore(draftsDir: string): Draft | null
|
|
25
|
+
static checkDraftForRestore(draftsDir: string): Promise<Draft | null>;
|
|
26
26
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DraftRepository } from "../crud/DraftRepository.js";
|
|
2
2
|
import { createLogger } from "../shared/logger.js";
|
|
3
|
-
import {
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import dayjs from "dayjs";
|
|
6
6
|
const log = createLogger();
|
|
@@ -34,23 +34,27 @@ export class AutoDraftPlugin {
|
|
|
34
34
|
updatedAt: new Date().toISOString(),
|
|
35
35
|
};
|
|
36
36
|
const repo = new DraftRepository(draftsDir);
|
|
37
|
-
repo.write(draft);
|
|
37
|
+
await repo.write(draft);
|
|
38
38
|
}
|
|
39
39
|
catch (err) {
|
|
40
40
|
log.error(`[autoDraft] 保存失败: ${err?.message ?? err}`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
static checkDraftForRestore(draftsDir) {
|
|
43
|
+
static async checkDraftForRestore(draftsDir) {
|
|
44
44
|
const path = join(draftsDir, CURRENT_DRAFT);
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(path);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
46
49
|
return null;
|
|
50
|
+
}
|
|
47
51
|
const repo = new DraftRepository(draftsDir);
|
|
48
|
-
const draft = repo.read();
|
|
52
|
+
const draft = await repo.read();
|
|
49
53
|
if (!draft)
|
|
50
54
|
return null;
|
|
51
55
|
const age = dayjs().diff(dayjs(draft.updatedAt), "day");
|
|
52
56
|
if (age > EXPIRE_DAYS) {
|
|
53
|
-
repo.delete();
|
|
57
|
+
await repo.delete();
|
|
54
58
|
return null;
|
|
55
59
|
}
|
|
56
60
|
return draft;
|
|
@@ -2,7 +2,7 @@ import { AgentRepository } from "../crud/AgentRepository.js";
|
|
|
2
2
|
import { SdkAgent } from "./SdkAgent.js";
|
|
3
3
|
import { createModel } from "./createModel.js";
|
|
4
4
|
/**
|
|
5
|
-
* 从磁盘创建 Agent
|
|
5
|
+
* 从磁盘创建 Agent。
|
|
6
6
|
*
|
|
7
7
|
* 通过 Provider 获取已发现的能力,读取 Agent 定义,过滤并实例化工具,产出 SdkAgent。
|
|
8
8
|
*
|
|
@@ -12,7 +12,7 @@ import { createModel } from "./createModel.js";
|
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
export async function createAgent(provider, agentId) {
|
|
15
|
-
const definition = new AgentRepository(provider.agentsDir).read(agentId);
|
|
15
|
+
const definition = await new AgentRepository(provider.agentsDir).read(agentId);
|
|
16
16
|
if (!definition)
|
|
17
17
|
throw new Error(`Agent "${agentId}" 不存在`);
|
|
18
18
|
const modelId = definition.modelId ?? provider.config.defaultModel;
|
|
@@ -14,11 +14,11 @@ export declare class EntityRepository<T extends {
|
|
|
14
14
|
/** 获取实体的文件路径 */
|
|
15
15
|
protected path(id: string): string;
|
|
16
16
|
/** 列出目录下所有实体(跳过解析失败的文件) */
|
|
17
|
-
list(): T[]
|
|
17
|
+
list(): Promise<T[]>;
|
|
18
18
|
/** 读取单个实体,不存在返回 null */
|
|
19
|
-
read(id: string): T | null
|
|
19
|
+
read(id: string): Promise<T | null>;
|
|
20
20
|
/** 写入实体(创建或更新) */
|
|
21
|
-
write(entity: T): void
|
|
21
|
+
write(entity: T): Promise<void>;
|
|
22
22
|
/** 删除实体,不存在时不抛异常 */
|
|
23
|
-
delete(id: string): void
|
|
23
|
+
delete(id: string): Promise<void>;
|
|
24
24
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* 通用实体仓储 — 对磁盘 JSON 文件的 CRUD 操作。
|
|
@@ -18,27 +18,36 @@ export class EntityRepository {
|
|
|
18
18
|
return join(this.dir, `${id}.json`);
|
|
19
19
|
}
|
|
20
20
|
/** 列出目录下所有实体(跳过解析失败的文件) */
|
|
21
|
-
list() {
|
|
22
|
-
|
|
21
|
+
async list() {
|
|
22
|
+
try {
|
|
23
|
+
await fs.access(this.dir);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
23
26
|
return [];
|
|
24
|
-
|
|
27
|
+
}
|
|
28
|
+
const entries = await fs.readdir(this.dir);
|
|
29
|
+
const ids = entries
|
|
25
30
|
.filter((f) => f.endsWith(".json"))
|
|
26
31
|
.map((f) => f.replace(/\.json$/, ""));
|
|
27
32
|
const entities = [];
|
|
28
33
|
for (const id of ids) {
|
|
29
|
-
const entity = this.read(id);
|
|
34
|
+
const entity = await this.read(id);
|
|
30
35
|
if (entity)
|
|
31
36
|
entities.push(entity);
|
|
32
37
|
}
|
|
33
38
|
return entities;
|
|
34
39
|
}
|
|
35
40
|
/** 读取单个实体,不存在返回 null */
|
|
36
|
-
read(id) {
|
|
41
|
+
async read(id) {
|
|
37
42
|
const p = this.path(id);
|
|
38
|
-
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(p);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
39
47
|
return null;
|
|
48
|
+
}
|
|
40
49
|
try {
|
|
41
|
-
const raw =
|
|
50
|
+
const raw = await fs.readFile(p, "utf-8");
|
|
42
51
|
return JSON.parse(raw);
|
|
43
52
|
}
|
|
44
53
|
catch {
|
|
@@ -46,17 +55,24 @@ export class EntityRepository {
|
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
/** 写入实体(创建或更新) */
|
|
49
|
-
write(entity) {
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
async write(entity) {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(this.dir);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
await fs.mkdir(this.dir, { recursive: true });
|
|
52
64
|
}
|
|
53
|
-
|
|
65
|
+
await fs.writeFile(this.path(entity.id), JSON.stringify(entity, null, 2), "utf-8");
|
|
54
66
|
}
|
|
55
67
|
/** 删除实体,不存在时不抛异常 */
|
|
56
|
-
delete(id) {
|
|
68
|
+
async delete(id) {
|
|
57
69
|
const p = this.path(id);
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
try {
|
|
71
|
+
await fs.access(p);
|
|
72
|
+
await fs.unlink(p);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// 文件不存在,忽略
|
|
60
76
|
}
|
|
61
77
|
}
|
|
62
78
|
}
|