

























1-import type {
2-FileSystem,
3-JsonlSessionMetadata,
4-LeafEntry,
5-SessionStorage,
6-SessionTreeEntry,
7-} from "../types.js";
1+import type { FileSystem, JsonlSessionMetadata, SessionTreeEntry } from "../types.js";
82import { SessionError, toError } from "../types.js";
93import { getFileSystemResultOrThrow } from "./repo-utils.js";
10-import { uuidv7 } from "./uuid.js";
4+import { BaseSessionStorage, leafIdAfterEntry } from "./storage-base.js";
115126type JsonlSessionStorageFileSystem = Pick<
137FileSystem,
@@ -23,36 +17,6 @@ interface SessionHeader {
2317parentSession?: string;
2418}
251926-function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
27-if (entry.type !== "label") {
28-return;
29-}
30-const label = entry.label?.trim();
31-if (label) {
32-labelsById.set(entry.targetId, label);
33-} else {
34-labelsById.delete(entry.targetId);
35-}
36-}
37-38-function buildLabelsById(entries: SessionTreeEntry[]): Map<string, string> {
39-const labelsById = new Map<string, string>();
40-for (const entry of entries) {
41-updateLabelCache(labelsById, entry);
42-}
43-return labelsById;
44-}
45-46-function generateEntryId(byId: { has(id: string): boolean }): string {
47-for (let i = 0; i < 100; i++) {
48-const id = uuidv7().slice(0, 8);
49-if (!byId.has(id)) {
50-return id;
51-}
52-}
53-return uuidv7();
54-}
55-5620function isRecord(value: unknown): value is Record<string, unknown> {
5721return typeof value === "object" && value !== null;
5822}
@@ -144,10 +108,6 @@ function parseEntryLine(line: string, filePath: string, lineNumber: number): Ses
144108return parsed as unknown as SessionTreeEntry;
145109}
146110147-function leafIdAfterEntry(entry: SessionTreeEntry): string | null {
148-return entry.type === "leaf" ? entry.targetId : entry.id;
149-}
150-151111function headerToSessionMetadata(header: SessionHeader, path: string): JsonlSessionMetadata {
152112return {
153113id: header.id,
@@ -201,14 +161,9 @@ async function loadJsonlStorage(
201161return { header, entries, leafId };
202162}
203163204-export class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {
164+export class JsonlSessionStorage extends BaseSessionStorage<JsonlSessionMetadata> {
205165private readonly fs: JsonlSessionStorageFileSystem;
206166private readonly filePath: string;
207-private readonly metadata: JsonlSessionMetadata;
208-private entries: SessionTreeEntry[];
209-private byId: Map<string, SessionTreeEntry>;
210-private labelsById: Map<string, string>;
211-private currentLeafId: string | null;
212167213168private constructor(
214169fs: JsonlSessionStorageFileSystem,
@@ -217,13 +172,9 @@ export class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata>
217172entries: SessionTreeEntry[],
218173leafId: string | null,
219174) {
175+super(headerToSessionMetadata(header, filePath), entries, leafId);
220176this.fs = fs;
221177this.filePath = filePath;
222-this.metadata = headerToSessionMetadata(header, this.filePath);
223-this.entries = entries;
224-this.byId = new Map(entries.map((entry) => [entry.id, entry]));
225-this.labelsById = buildLabelsById(entries);
226-this.currentLeafId = leafId;
227178}
228179229180static async open(
@@ -258,92 +209,20 @@ export class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata>
258209return new JsonlSessionStorage(fs, filePath, header, [], null);
259210}
260211261-async getMetadata(): Promise<JsonlSessionMetadata> {
262-return this.metadata;
263-}
264-265-async getLeafId(): Promise<string | null> {
266-if (this.currentLeafId !== null && !this.byId.has(this.currentLeafId)) {
267-throw new SessionError("invalid_session", `Entry ${this.currentLeafId} not found`);
268-}
269-return this.currentLeafId;
270-}
271-272-async setLeafId(leafId: string | null): Promise<void> {
273-if (leafId !== null && !this.byId.has(leafId)) {
274-throw new SessionError("not_found", `Entry ${leafId} not found`);
275-}
276-const entry: LeafEntry = {
277-type: "leaf",
278-id: generateEntryId(this.byId),
279-parentId: this.currentLeafId,
280-timestamp: new Date().toISOString(),
281-targetId: leafId,
282-};
212+override async setLeafId(leafId: string | null): Promise<void> {
213+const entry = this.createLeafEntry(leafId);
283214getFileSystemResultOrThrow(
284215await this.fs.appendFile(this.filePath, `${JSON.stringify(entry)}\n`),
285216`Failed to append session leaf ${entry.id}`,
286217);
287-this.entries.push(entry);
288-this.byId.set(entry.id, entry);
289-this.currentLeafId = leafId;
290-}
291-292-async createEntryId(): Promise<string> {
293-return generateEntryId(this.byId);
218+this.recordEntry(entry);
294219}
295220296-async appendEntry(entry: SessionTreeEntry): Promise<void> {
221+override async appendEntry(entry: SessionTreeEntry): Promise<void> {
297222getFileSystemResultOrThrow(
298223await this.fs.appendFile(this.filePath, `${JSON.stringify(entry)}\n`),
299224`Failed to append session entry ${entry.id}`,
300225);
301-this.entries.push(entry);
302-this.byId.set(entry.id, entry);
303-updateLabelCache(this.labelsById, entry);
304-this.currentLeafId = leafIdAfterEntry(entry);
305-}
306-307-async getEntry(id: string): Promise<SessionTreeEntry | undefined> {
308-return this.byId.get(id);
309-}
310-311-async findEntries<TType extends SessionTreeEntry["type"]>(
312-type: TType,
313-): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {
314-return this.entries.filter(
315-(entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type,
316-);
317-}
318-319-async getLabel(id: string): Promise<string | undefined> {
320-return this.labelsById.get(id);
321-}
322-323-async getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {
324-if (leafId === null) {
325-return [];
326-}
327-const path: SessionTreeEntry[] = [];
328-let current = this.byId.get(leafId);
329-if (!current) {
330-throw new SessionError("not_found", `Entry ${leafId} not found`);
331-}
332-while (current) {
333-path.unshift(current);
334-if (!current.parentId) {
335-break;
336-}
337-const parent = this.byId.get(current.parentId);
338-if (!parent) {
339-throw new SessionError("invalid_session", `Entry ${current.parentId} not found`);
340-}
341-current = parent;
342-}
343-return path;
344-}
345-346-async getEntries(): Promise<SessionTreeEntry[]> {
347-return [...this.entries];
226+this.recordEntry(entry);
348227}
349228}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。