-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathdatabase.ts
139 lines (125 loc) · 5.21 KB
/
database.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import fs from 'fs-extra';
import path from 'path';
import { NamespaceDatabase } from './namespace-database.js';
import { NamespaceName, type RepoInfo, type Sha1Value, type RepoData, TagType } from './interfaces/ehtag.js';
import type { TagRecord } from './tag-record.js';
import type { RawTag } from './raw-tag.js';
import type { DatabaseView } from './interfaces/database.js';
import { Logger } from './markdown/index.js';
import { GitRepoInfoProvider, type RepoInfoProvider } from './repo-info-provider.js';
const SUPPORTED_REPO_VERSION = 6;
export class Database implements DatabaseView {
static async create(repoPath: string, repoInfoProvider?: RepoInfoProvider, logger?: Logger): Promise<Database> {
const resolvedPath = path.resolve(repoPath);
const versionPath = path.join(resolvedPath, 'version');
const versionData = await fs.readFile(versionPath, 'utf-8').catch((err: unknown) => {
throw new Error(
`无法访问 "${path.join(repoPath, 'version')}","${repoPath}" 可能不是一个有效的数据库\n${String(err)}`,
);
});
const version = Number.parseFloat(versionData.trim());
if (Number.isNaN(version) || version < SUPPORTED_REPO_VERSION || version >= SUPPORTED_REPO_VERSION + 1) {
throw new Error(`不支持的数据库版本 ${versionData},当前支持版本 ${SUPPORTED_REPO_VERSION}`);
}
await fs.access(path.join(resolvedPath, 'database')).catch((err: unknown) => {
throw new Error(
`无法访问 "${path.join(repoPath, 'database')}","${repoPath}" 可能不是一个有效的数据库\n${String(err)}`,
);
});
const files = NamespaceName.map((ns) => [ns, path.join(resolvedPath, 'database', `${ns}.md`)] as const);
await Promise.all(
files.map(async ([ns, file]) => {
try {
await fs.access(file);
} catch (e) {
const p = path.join(repoPath, 'database', `${ns}.md`);
throw new Error(`无法访问 "${p}","${repoPath}" 可能不是一个有效的数据库\n${String(e)}`);
}
}),
);
const info =
repoInfoProvider ??
((await fs.pathExists(path.join(repoPath, '.git'))) ? new GitRepoInfoProvider(repoPath) : undefined);
const db = new Database(repoPath, version, files, info);
if (logger) db.logger = logger;
await db.load();
return db;
}
private constructor(
readonly repoPath: string,
readonly version: number,
files: ReadonlyArray<readonly [NamespaceName, string]>,
private readonly repoInfoProvider?: RepoInfoProvider,
) {
const data = {} as { [key in NamespaceName]: NamespaceDatabase };
for (const [ns, file] of files) {
data[ns] = new NamespaceDatabase(ns, file, this);
}
this.data = data;
}
readonly data: { readonly [key in NamespaceName]: NamespaceDatabase };
async load(): Promise<void> {
await Promise.all(Object.values(this.data).map((n) => n.load()));
}
async save(): Promise<void> {
await Promise.all(Object.values(this.data).map((n) => n.save()));
}
async sha(): Promise<Sha1Value> {
if (!this.repoInfoProvider) throw new Error('This is not a git repo');
return (await this.repoInfoProvider.head()).sha;
}
private async repoInfo(): Promise<Omit<RepoInfo, 'data'>> {
if (!this.repoInfoProvider) throw new Error('This is not a git repo');
const head = this.repoInfoProvider.head();
let repo = await this.repoInfoProvider.repo();
if (/^https?:\/\//.test(repo)) {
const url = new URL(repo);
url.username = '';
url.password = '';
repo = url.href;
} else if (/^git@/.test(repo)) {
const match = /^git@([^:]+):(.+)$/.exec(repo);
if (match) {
const [_, host, path] = match;
repo = `https://${host}/${path}`;
}
}
return {
repo,
head: await head,
version: this.version,
};
}
async info(): Promise<RepoInfo> {
return {
...(await this.repoInfo()),
data: Object.values(this.data).map((ns) => ns.info()),
};
}
async render<T extends TagType>(type: T): Promise<RepoData<T>> {
return {
...(await this.repoInfo()),
data: Object.values(this.data).map((ns) => ns.render(type)),
};
}
async renderAll(): Promise<Map<TagType, RepoData<TagType>>> {
const data = new Map<TagType, RepoData<TagType>>();
const info = await this.repoInfo();
for (const type of TagType) {
data.set(type, {
...info,
data: Object.values(this.data).map((ns) => ns.render(type)),
});
}
return data;
}
get(raw: RawTag): TagRecord | undefined {
for (const ns of NamespaceName) {
const record = this.data[ns].get(raw);
if (record) return record;
}
return undefined;
}
revision = 1;
logger = Logger.default;
}