@dsh-libing/knowledge-base 0.1.0 → 0.1.3
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 +16 -0
- package/README.md +8 -2
- package/lib/client-types/client/JobDetails.d.ts +16 -0
- package/lib/client-types/client/JobsPanel.d.ts +6 -1
- package/lib/client-types/client/QualityReport.d.ts +3 -2
- package/lib/client-types/client/api.d.ts +83 -52
- package/lib/client-types/client/locales.d.ts +2 -0
- package/lib/client-types/client/useKnowledge.d.ts +30 -15
- package/lib/client-types/contract.d.ts +83 -52
- package/lib/client-types/data.d.ts +11 -0
- package/lib/client-types/settings.d.ts +4 -0
- package/lib/client-types/shared.d.ts +17 -0
- package/lib/client.js +40 -40
- package/lib/contract.d.ts +33 -2
- package/lib/contract.js +2 -1
- package/lib/data.d.ts +11 -0
- package/lib/data.js +2 -0
- package/lib/figures/image-client.d.ts +5 -0
- package/lib/figures/image-client.js +58 -0
- package/lib/figures/image-gate.d.ts +12 -0
- package/lib/figures/image-gate.js +39 -0
- package/lib/figures/image-protocol.d.ts +19 -0
- package/lib/figures/image-protocol.js +9 -0
- package/lib/figures/image-worker.d.ts +1 -0
- package/lib/figures/image-worker.js +60 -0
- package/lib/figures/process.d.ts +1 -1
- package/lib/figures/process.js +6 -35
- package/lib/indexes.js +9 -2
- package/lib/job-storage.d.ts +264 -0
- package/lib/job-storage.js +115 -0
- package/lib/jobs.d.ts +782 -1489
- package/lib/jobs.js +47 -23
- package/lib/models.d.ts +1 -1
- package/lib/models.js +8 -7
- package/lib/retrieval.js +8 -4
- package/lib/revision-cache.d.ts +24 -0
- package/lib/revision-cache.js +100 -0
- package/lib/routes.js +10 -7
- package/lib/runtime.d.ts +4 -0
- package/lib/runtime.js +6 -1
- package/lib/settings.d.ts +4 -0
- package/lib/settings.js +5 -2
- package/lib/shared.d.ts +17 -0
- package/lib/shared.js +3 -0
- package/lib/store.d.ts +101 -5
- package/lib/store.js +54 -20
- package/package.json +1 -1
package/lib/store.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { RevisionCache } from './revision-cache.ts';
|
|
1
2
|
import type { Settings } from './config.ts';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { registrySchema } from './data.ts';
|
|
4
5
|
import type { Library, Manifest, Snapshot, Chunk, PublishedDocument, BuildDocument, Overlay } from './data.ts';
|
|
5
6
|
export declare const DEFAULT_SCHEMA = "# \u77E5\u8BC6\u5E93\u6CBB\u7406\u7EA6\u5B9A\n\n\u4FDD\u7559\u539F\u6587\u4E2D\u7684\u578B\u53F7\u3001\u7BA1\u811A\u3001\u5355\u4F4D\u3001\u6570\u503C\u3001\u6781\u6027\u548C\u9002\u7528\u6761\u4EF6\u3002\u56FE\u7247\u63CF\u8FF0\u4E0E\u539F\u6587\u5206\u522B\u6807\u6CE8\uFF1B\u4E0D\u786E\u5B9A\u7684\u4FE1\u606F\u4FDD\u7559\u5F85\u590D\u6838\u3002\u6458\u8981\u7528\u4E8E\u5BFC\u822A\uFF0C\u56DE\u7B54\u4F9D\u636E\u539F\u6587\u548C\u53EF\u6838\u9A8C\u56FE\u50CF\u3002\u5F15\u7528\u5305\u542B\u6587\u6863\u7248\u672C\u3001\u7AE0\u8282\u3001\u7247\u6BB5\u548C\u56FE\u8868\u7F16\u53F7\u3002\n";
|
|
6
7
|
export declare class Store {
|
|
8
|
+
readonly revisionCache: RevisionCache;
|
|
7
9
|
readonly config: Settings;
|
|
8
10
|
readonly root: string;
|
|
9
11
|
private registryTail;
|
|
@@ -103,16 +105,88 @@ export declare class Store {
|
|
|
103
105
|
coverageIncomplete: boolean;
|
|
104
106
|
};
|
|
105
107
|
}>;
|
|
106
|
-
|
|
108
|
+
private inspectKnown;
|
|
109
|
+
overview(): Promise<{
|
|
110
|
+
kbs: ({
|
|
111
|
+
location: {
|
|
112
|
+
kbPath: string;
|
|
113
|
+
revision: null;
|
|
114
|
+
wikiPath: null;
|
|
115
|
+
indexPath: null;
|
|
116
|
+
};
|
|
117
|
+
summary: {
|
|
118
|
+
revision: null;
|
|
119
|
+
documentCount: number;
|
|
120
|
+
chunkCount: number;
|
|
121
|
+
coverageIncomplete: boolean;
|
|
122
|
+
};
|
|
123
|
+
kbId: string;
|
|
124
|
+
name: string;
|
|
125
|
+
createdAt: string;
|
|
126
|
+
deletedAt?: string | undefined;
|
|
127
|
+
purge?: {
|
|
128
|
+
startedAt: string;
|
|
129
|
+
jobIds: string[];
|
|
130
|
+
error?: string | undefined;
|
|
131
|
+
} | undefined;
|
|
132
|
+
} | {
|
|
133
|
+
location: {
|
|
134
|
+
kbPath: string;
|
|
135
|
+
revision: string;
|
|
136
|
+
wikiPath: string;
|
|
137
|
+
indexPath: string;
|
|
138
|
+
};
|
|
139
|
+
summary: {
|
|
140
|
+
revision: string;
|
|
141
|
+
documentCount: number;
|
|
142
|
+
chunkCount: number;
|
|
143
|
+
coverageIncomplete: boolean;
|
|
144
|
+
};
|
|
145
|
+
kbId: string;
|
|
146
|
+
name: string;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
deletedAt?: string | undefined;
|
|
149
|
+
purge?: {
|
|
150
|
+
startedAt: string;
|
|
151
|
+
jobIds: string[];
|
|
152
|
+
error?: string | undefined;
|
|
153
|
+
} | undefined;
|
|
154
|
+
})[];
|
|
155
|
+
deletedKbs: {
|
|
156
|
+
kbId: string;
|
|
157
|
+
name: string;
|
|
158
|
+
createdAt: string;
|
|
159
|
+
deletedAt?: string | undefined;
|
|
160
|
+
purge?: {
|
|
161
|
+
startedAt: string;
|
|
162
|
+
jobIds: string[];
|
|
163
|
+
error?: string | undefined;
|
|
164
|
+
} | undefined;
|
|
165
|
+
}[];
|
|
166
|
+
activities: {
|
|
167
|
+
eventId: string;
|
|
168
|
+
at: string;
|
|
169
|
+
action: "purge" | "create" | "trash" | "restore";
|
|
170
|
+
kbId: string;
|
|
171
|
+
name: string;
|
|
172
|
+
}[];
|
|
173
|
+
}>;
|
|
174
|
+
snapshotLocation(kbId: string, snapshot: Snapshot): Promise<{
|
|
107
175
|
kbPath: string;
|
|
108
|
-
revision:
|
|
109
|
-
wikiPath:
|
|
110
|
-
indexPath:
|
|
111
|
-
}
|
|
176
|
+
revision: string;
|
|
177
|
+
wikiPath: string;
|
|
178
|
+
indexPath: string;
|
|
179
|
+
}>;
|
|
180
|
+
location(kbId: string, revision?: string): Promise<{
|
|
112
181
|
kbPath: string;
|
|
113
182
|
revision: string;
|
|
114
183
|
wikiPath: string;
|
|
115
184
|
indexPath: string;
|
|
185
|
+
} | {
|
|
186
|
+
kbPath: string;
|
|
187
|
+
revision: null;
|
|
188
|
+
wikiPath: null;
|
|
189
|
+
indexPath: null;
|
|
116
190
|
}>;
|
|
117
191
|
schema(kbId: string): Promise<string>;
|
|
118
192
|
setSchema(kbId: string, content: string, expectedHash: string): Promise<{
|
|
@@ -138,7 +212,22 @@ export declare class Store {
|
|
|
138
212
|
buildKey: string;
|
|
139
213
|
coverageIncomplete: boolean;
|
|
140
214
|
} | null>;
|
|
215
|
+
open(selector: string, requested?: string): Promise<{
|
|
216
|
+
kb: {
|
|
217
|
+
kbId: string;
|
|
218
|
+
name: string;
|
|
219
|
+
createdAt: string;
|
|
220
|
+
deletedAt?: string | undefined;
|
|
221
|
+
purge?: {
|
|
222
|
+
startedAt: string;
|
|
223
|
+
jobIds: string[];
|
|
224
|
+
error?: string | undefined;
|
|
225
|
+
} | undefined;
|
|
226
|
+
};
|
|
227
|
+
snapshot: Snapshot;
|
|
228
|
+
}>;
|
|
141
229
|
revision(kbId: string, requested?: string): Promise<Snapshot>;
|
|
230
|
+
private revisionKnown;
|
|
142
231
|
readChunk(kbId: string, revision: string | undefined, chunkId: string): Promise<{
|
|
143
232
|
chunk: {
|
|
144
233
|
chunkId: string;
|
|
@@ -187,6 +276,10 @@ export declare class Store {
|
|
|
187
276
|
manifest: Manifest;
|
|
188
277
|
}>;
|
|
189
278
|
chunkContent(snapshot: Snapshot, chunk: Chunk): Promise<string>;
|
|
279
|
+
chunkView(snapshot: Snapshot, chunk: Chunk, fresh?: boolean): Promise<{
|
|
280
|
+
content: string;
|
|
281
|
+
lower: string;
|
|
282
|
+
}>;
|
|
190
283
|
asset(kbId: string, revision: string | undefined, docId: string, assetId: string): Promise<{
|
|
191
284
|
asset: {
|
|
192
285
|
assetId: string;
|
|
@@ -197,6 +290,7 @@ export declare class Store {
|
|
|
197
290
|
source?: import("./data.ts").Source | undefined;
|
|
198
291
|
};
|
|
199
292
|
file: string;
|
|
293
|
+
data: Buffer<ArrayBufferLike>;
|
|
200
294
|
doc: {
|
|
201
295
|
docId: string;
|
|
202
296
|
title: string;
|
|
@@ -404,6 +498,8 @@ export declare class Store {
|
|
|
404
498
|
}): Promise<string>;
|
|
405
499
|
overlays(kbId: string): Promise<Record<string, Overlay>>;
|
|
406
500
|
setOverlay(kbId: string, key: string, value: Overlay | null, expectedHash: string): Promise<Overlay | undefined>;
|
|
501
|
+
/** Cleanup runs with the sole owner before accepting uploads or starting jobs. Inputs are independent copies. */
|
|
502
|
+
cleanUploads(at?: number): Promise<void>;
|
|
407
503
|
jobDirectories(): Promise<string[]>;
|
|
408
504
|
acquireOwner(): Promise<() => Promise<void>>;
|
|
409
505
|
}
|
package/lib/store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RevisionCache } from "./revision-cache.js";
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { registrySchema, currentSchema, manifestSchema, uploadSchema, overlaySchema } from "./data.js";
|
|
3
4
|
import { code, message, required } from "./util.js";
|
|
@@ -7,6 +8,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
7
8
|
import { atomicWrite, fileBytes, hash, id, now, readJson, safePath, text } from "./util.js";
|
|
8
9
|
export const DEFAULT_SCHEMA = `# 知识库治理约定\n\n保留原文中的型号、管脚、单位、数值、极性和适用条件。图片描述与原文分别标注;不确定的信息保留待复核。摘要用于导航,回答依据原文和可核验图像。引用包含文档版本、章节、片段和图表编号。\n`;
|
|
9
10
|
export class Store {
|
|
11
|
+
revisionCache = new RevisionCache();
|
|
10
12
|
config;
|
|
11
13
|
root;
|
|
12
14
|
registryTail;
|
|
@@ -35,6 +37,7 @@ export class Store {
|
|
|
35
37
|
throw new Error('知识库不存在,请刷新');
|
|
36
38
|
if (kb.name !== expectedName)
|
|
37
39
|
throw new Error('知识库名称已变化,请刷新后确认删除');
|
|
40
|
+
this.revisionCache.clear();
|
|
38
41
|
kb.deletedAt = now();
|
|
39
42
|
this.record(registry, 'trash', kb);
|
|
40
43
|
await atomicWrite(join(this.root, 'registry.json'), registry);
|
|
@@ -50,6 +53,7 @@ export class Store {
|
|
|
50
53
|
throw new Error('知识库已开始彻底删除,不能恢复;请继续彻底删除');
|
|
51
54
|
if (registry.kbs.some(k => !k.deletedAt && k.name === kb.name))
|
|
52
55
|
throw new Error('同名知识库已存在,不能恢复;请先删除同名库');
|
|
56
|
+
this.revisionCache.clear();
|
|
53
57
|
delete kb.deletedAt;
|
|
54
58
|
this.record(registry, 'restore', kb);
|
|
55
59
|
await atomicWrite(join(this.root, 'registry.json'), registry);
|
|
@@ -72,6 +76,7 @@ export class Store {
|
|
|
72
76
|
}
|
|
73
77
|
async removePurgeTree(path) { await rm(path, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); }
|
|
74
78
|
async purge(kbId, expectedName, confirmed, jobIds = []) {
|
|
79
|
+
this.revisionCache.clear();
|
|
75
80
|
if (confirmed !== true)
|
|
76
81
|
throw new Error('必须明确确认彻底删除不可恢复');
|
|
77
82
|
return this.serialized(async () => {
|
|
@@ -129,13 +134,20 @@ export class Store {
|
|
|
129
134
|
});
|
|
130
135
|
}
|
|
131
136
|
kbRoot(kbId) { return join(this.root, 'kbs', id(kbId, 'kbId')); }
|
|
132
|
-
async inspect(kbId, revision) {
|
|
133
|
-
|
|
134
|
-
const current = await this.current(kb.kbId);
|
|
137
|
+
async inspect(kbId, revision) { return this.inspectKnown(await this.kb(kbId), revision); }
|
|
138
|
+
async inspectKnown(kb, revision) {
|
|
139
|
+
const kbPath = await safePath(this.root, join('kbs', kb.kbId)), current = await this.current(kb.kbId);
|
|
135
140
|
if (!revision && !current)
|
|
136
141
|
return { location: { kbPath, revision: null, wikiPath: null, indexPath: null }, summary: { revision: null, documentCount: 0, chunkCount: 0, coverageIncomplete: false } };
|
|
137
|
-
const snapshot = await this.
|
|
138
|
-
return { location:
|
|
142
|
+
const snapshot = await this.revisionKnown(kb, revision || required(current).revision);
|
|
143
|
+
return { location: await this.snapshotLocation(kb.kbId, snapshot), summary: { revision: snapshot.revision, documentCount: snapshot.manifest.documents.length, chunkCount: snapshot.manifest.documents.reduce((sum, d) => sum + d.chunks.length, 0), coverageIncomplete: Boolean(snapshot.manifest.coverageIncomplete) } };
|
|
144
|
+
}
|
|
145
|
+
async overview() {
|
|
146
|
+
const registry = await readJson(join(this.root, 'registry.json'), registrySchema);
|
|
147
|
+
return { kbs: await Promise.all(registry.kbs.filter(k => !k.deletedAt).map(async (kb) => ({ ...kb, ...await this.inspectKnown(kb) }))), deletedKbs: registry.kbs.filter(k => k.deletedAt), activities: registry.activities || [] };
|
|
148
|
+
}
|
|
149
|
+
async snapshotLocation(kbId, snapshot) {
|
|
150
|
+
return { kbPath: await safePath(this.root, join('kbs', id(kbId))), revision: snapshot.revision, wikiPath: await safePath(snapshot.root, 'wiki'), indexPath: await safePath(snapshot.root, 'index.md') };
|
|
139
151
|
}
|
|
140
152
|
async location(kbId, revision) { return (await this.inspect(kbId, revision)).location; }
|
|
141
153
|
async schema(kbId) { return readFile(await safePath(this.kbRoot(kbId), 'schema.md'), 'utf8'); }
|
|
@@ -167,15 +179,16 @@ export class Store {
|
|
|
167
179
|
throw new Error('上传内容已变化');
|
|
168
180
|
return { ...receipt, data };
|
|
169
181
|
}
|
|
170
|
-
async current(kbId) { return readJson(
|
|
171
|
-
async
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
async current(kbId) { return readJson(await safePath(this.root, join('kbs', id(kbId), 'current.json'), false), currentSchema, null); }
|
|
183
|
+
async open(selector, requested) { const kb = await this.kb(selector); return { kb, snapshot: await this.revisionKnown(kb, requested) }; }
|
|
184
|
+
async revision(kbId, requested) { return (await this.open(kbId, requested)).snapshot; }
|
|
185
|
+
async revisionKnown(kb, requested) {
|
|
186
|
+
const current = requested ? null : await this.current(kb.kbId), revision = requested || current?.revision;
|
|
174
187
|
if (!revision)
|
|
175
188
|
throw new Error('知识库尚无已发布版本');
|
|
176
189
|
id(revision, 'revision');
|
|
177
|
-
const root = await safePath(this.
|
|
178
|
-
const manifest = await
|
|
190
|
+
const root = await safePath(this.root, join('kbs', kb.kbId, 'revisions', revision));
|
|
191
|
+
const manifest = await this.revisionCache.manifest(await safePath(root, 'manifest.json'));
|
|
179
192
|
if (manifest.revision !== revision)
|
|
180
193
|
throw new Error('版本清单不一致');
|
|
181
194
|
return { root, manifest, revision };
|
|
@@ -188,11 +201,9 @@ export class Store {
|
|
|
188
201
|
const content = await this.chunkContent(snapshot, chunk);
|
|
189
202
|
return { ...snapshot, chunk, content };
|
|
190
203
|
}
|
|
191
|
-
async chunkContent(snapshot, chunk) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
throw new Error('片段内容与清单不一致,请重建');
|
|
195
|
-
return content;
|
|
204
|
+
async chunkContent(snapshot, chunk) { return (await this.chunkView(snapshot, chunk, true)).content; }
|
|
205
|
+
async chunkView(snapshot, chunk, fresh = false) {
|
|
206
|
+
return this.revisionCache.chunk(await safePath(snapshot.root, chunk.path), chunk.contentHash, fresh);
|
|
196
207
|
}
|
|
197
208
|
async asset(kbId, revision, docId, assetId) {
|
|
198
209
|
const snapshot = await this.revision(kbId, revision), doc = snapshot.manifest.documents.find(d => d.docId === docId);
|
|
@@ -200,9 +211,10 @@ export class Store {
|
|
|
200
211
|
if (!asset)
|
|
201
212
|
throw new Error('图片不在所选文档版本中');
|
|
202
213
|
const file = await safePath(snapshot.root, join('sources', id(docId), asset.path));
|
|
203
|
-
|
|
214
|
+
const data = await fileBytes(file, this.config.maxUncompressedBytes);
|
|
215
|
+
if (`a-${hash(data).slice(0, 20)}` !== asset.assetId)
|
|
204
216
|
throw new Error('图片内容与清单不一致');
|
|
205
|
-
return { ...snapshot, asset, file, doc: required(doc) };
|
|
217
|
+
return { ...snapshot, asset, file, data, doc: required(doc) };
|
|
206
218
|
}
|
|
207
219
|
async rawSnapshot(kbId, docId, sourceHash, data, info) {
|
|
208
220
|
const root = join(this.kbRoot(kbId), 'raw', id(docId), id(sourceHash));
|
|
@@ -233,7 +245,8 @@ export class Store {
|
|
|
233
245
|
return [];
|
|
234
246
|
const { root, manifest } = await this.revision(kbId, current.revision);
|
|
235
247
|
const docs = [];
|
|
236
|
-
for (const
|
|
248
|
+
for (const saved of manifest.documents.filter(d => d.docId !== replaceDocId)) {
|
|
249
|
+
const document = structuredClone(saved);
|
|
237
250
|
const chunks = [];
|
|
238
251
|
for (const chunk of document.chunks) {
|
|
239
252
|
const content = await this.chunkContent({ root, manifest, revision: manifest.revision }, chunk);
|
|
@@ -267,6 +280,7 @@ export class Store {
|
|
|
267
280
|
if (existing.buildKey !== manifest.buildKey)
|
|
268
281
|
throw new Error('版本发布冲突');
|
|
269
282
|
}
|
|
283
|
+
this.revisionCache.clear();
|
|
270
284
|
// The pointer owns visibility. Replaying the same job after a crash is idempotent.
|
|
271
285
|
await atomicWrite(join(kbRoot, 'current.json'), { revision, publishedAt: now(), buildKey: manifest.buildKey, coverageIncomplete: manifest.coverageIncomplete });
|
|
272
286
|
await this.serialized(async () => {
|
|
@@ -290,7 +304,27 @@ export class Store {
|
|
|
290
304
|
return overlays[key];
|
|
291
305
|
});
|
|
292
306
|
}
|
|
293
|
-
|
|
307
|
+
/** Cleanup runs with the sole owner before accepting uploads or starting jobs. Inputs are independent copies. */
|
|
308
|
+
async cleanUploads(at = Date.now()) {
|
|
309
|
+
const root = await safePath(this.root, 'uploads'), cutoff = at - this.config.uploadRetentionDays * 86400000;
|
|
310
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
311
|
+
if (!entry.isFile() || !/^u-[a-f0-9-]{36}\.json$/u.test(entry.name))
|
|
312
|
+
continue;
|
|
313
|
+
const file = await safePath(root, entry.name), receipt = await readJson(file, uploadSchema);
|
|
314
|
+
if (entry.name !== receipt.receiptId + '.json')
|
|
315
|
+
throw new Error('上传收据身份不一致');
|
|
316
|
+
const time = Date.parse(receipt.createdAt);
|
|
317
|
+
if (!Number.isFinite(time))
|
|
318
|
+
throw new Error('上传收据日期无效');
|
|
319
|
+
if (time >= cutoff)
|
|
320
|
+
continue;
|
|
321
|
+
const binary = await safePath(root, receipt.receiptId + '.bin', false);
|
|
322
|
+
// Remove the binary first. If interrupted, the validated receipt makes cleanup retryable.
|
|
323
|
+
await rm(binary, { force: true });
|
|
324
|
+
await rm(file);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async jobDirectories() { return (await readdir(await safePath(this.root, 'jobs'), { withFileTypes: true })).filter(d => d.isDirectory()).map(d => join(this.root, 'jobs', d.name)); }
|
|
294
328
|
async acquireOwner() {
|
|
295
329
|
const dir = join(this.root, '.owner.lock'), nonce = randomUUID();
|
|
296
330
|
try {
|