Skip to main content
Glama
cache.ts4.41 kB
import type { ContentConverter } from "./converter"; import type { XiaomiNoteClient } from "./client"; import { buildNotesListItem } from "./note"; import type { FolderEntry, FullPageData, NoteEntry, NotesListItem, SyncFullResponse, } from "./types"; export interface NotesCacheOptions { syncInterval?: number; autoStart?: boolean; logger?: Pick<Console, "warn" | "error">; } export class NotesCache { private readonly notes = new Map<string, NoteEntry>(); private readonly folders = new Map<string, FolderEntry>(); private syncTag: string | null = null; private syncTimer: ReturnType<typeof setInterval> | null = null; private initialized = false; private lastSync = 0; constructor( private readonly client: XiaomiNoteClient, private readonly converter: ContentConverter, private readonly options: NotesCacheOptions = {}, ) {} async init(): Promise<void> { if (this.initialized) { return; } const snapshot = await this.client.fetchFullPage(); this.applySnapshot(snapshot.data); this.initialized = true; if (this.options.autoStart !== false) { this.startSync(); } } startSync(): void { const interval = this.options.syncInterval ?? 30_000; this.stopSync(); this.syncTimer = setInterval(() => { this.syncOnce().catch((error) => { this.logError("增量同步失败", error); }); }, interval); } stopSync(): void { if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = null; } } dispose(): void { this.stopSync(); } getNotes(): NoteEntry[] { return Array.from(this.notes.values()).sort((a, b) => b.modifyDate - a.modifyDate); } getFolders(): FolderEntry[] { return Array.from(this.folders.values()); } getNote(id: string): NoteEntry | undefined { return this.notes.get(id); } upsertNote(note: NoteEntry): void { this.notes.set(note.id, note); } upsertFolder(folder: FolderEntry): void { this.folders.set(folder.id, folder); } removeNote(id: string): void { this.notes.delete(id); } removeFolder(id: string): void { this.folders.delete(id); } getNotesListItems(): NotesListItem[] { return this.getNotes().map((note) => buildNotesListItem(note, this.converter)); } searchNotes(keyword: string): NotesListItem[] { const query = keyword.trim().toLowerCase(); if (!query) { return []; } return this.getNotesListItems().filter((item) => { return ( item.title.toLowerCase().includes(query) || item.snippet.toLowerCase().includes(query) ); }); } async syncOnce(): Promise<void> { if (!this.syncTag) { return; } let response: SyncFullResponse; try { response = await this.client.syncFull(this.syncTag); } catch (error) { this.logError("调用增量同步接口失败", error); throw error; } const envelope = response.data.note_view; const data = envelope.data; if (data.entries.length === 0 && data.folders.length === 0) { this.syncTag = data.syncTag; this.lastSync = Date.now(); return; } data.entries.forEach((entry) => { if (entry.status === "deleted") { this.notes.delete(entry.id); } else { this.notes.set(entry.id, entry); } }); data.folders.forEach((folder) => { if (folder.status === "deleted") { this.folders.delete(folder.id); } else { this.folders.set(folder.id, folder); } }); this.syncTag = data.syncTag; this.lastSync = Date.now(); } getSyncTag(): string | null { return this.syncTag; } getLastSyncTime(): number { return this.lastSync; } async refresh(): Promise<void> { const snapshot = await this.client.fetchFullPage(); this.applySnapshot(snapshot.data); } private applySnapshot(snapshot: FullPageData): void { this.notes.clear(); snapshot.entries.forEach((note) => this.notes.set(note.id, note)); this.folders.clear(); snapshot.folders.forEach((folder) => this.folders.set(folder.id, folder)); this.syncTag = snapshot.syncTag; this.lastSync = Date.now(); } private logError(message: string, error: unknown): void { const logger = this.options.logger ?? console; logger.error?.(`${message}: ${(error as Error).message}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LaelLuo/mi_note_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server