Add autocomplete history store

This commit is contained in:
MareStare 2025-03-04 04:03:14 +00:00
parent a77b8dd81c
commit 99e597d94d

View file

@ -0,0 +1,95 @@
import store from '../../utils/store';
/**
* The root JSON object that contains the history records and is persisted to disk.
*/
interface History {
/**
* Used to track the version of the schema layout just in case we do any
* breaking changes to this schema so that we can properly migrate old
* search history data. It's also used to prevent older versions of
* the frontend code from trying to use the newer incompatible schema they
* know nothing about (extremely improbable, but just in case).
*/
schemaVersion: 1;
/**
* The list of history records sorted from the last recently used to the oldest unused.
*/
records: string[];
}
/**
* History store backend is responsible for parsing and serializing the data
* to/from `localStorage`. It handles versioning of the schema, and transparently
* disables writing to the storage if the schema version is unknown to prevent
* data loss (extremely improbable, but just in case).
*/
export class HistoryStore {
private writable: boolean = true;
private readonly key: string;
constructor(key: string) {
this.key = key;
}
read(): string[] {
return this.extractRecords(store.get<History>(this.key));
}
write(records: string[]): void {
if (!this.writable) {
return;
}
const history: History = {
schemaVersion: 1,
records,
};
const start = performance.now();
store.set(this.key, history);
const end = performance.now();
console.debug(
`Writing ${records.length} history records to the localStorage took ${end - start}ms. ` +
`Records: ${records.length}`,
);
}
/**
* Extracts the records from the history. To do this, we first need to migrate
* the history object to the latest schema version if necessary.
*/
private extractRecords(history: History | null): string[] {
// `null` here means we are starting from the initial state (empty list of records).
if (history === null) {
return [];
}
// We have only one version at the time of this writing, so we don't need
// to do any migration yet. Hopefully we never need to do a breaking change
// and this stays at version `1` forever.
const latestSchemaVersion = 1;
switch (history.schemaVersion) {
case latestSchemaVersion:
return history.records;
default:
// It's very unlikely that we ever hit this branch.
console.warn(
`Unknown search history schema version: '${history.schemaVersion}'. ` +
`This frontend code was built with the maximum supported schema version ` +
`'${latestSchemaVersion}'. The search history will be disabled for this ` +
`session to prevent potential history data loss. The cause of the version ` +
`mismatch may be that a newer version of the frontend code is running in a ` +
`separate tab, or you were mistakenly served with an older version of the ` +
`frontend code.`,
);
// Disallow writing to the storage to prevent data loss.
this.writable = false;
return [];
}
}
}