import { Doc, Path, ShareAddress } from "../util/doc-types.ts";import { ReplicaIsClosedError } from "../util/errors.ts";import { ReplicaDriverMemory } from "./replica-driver-memory.ts";
import { Logger } from "../util/log.ts";import { checkShareIsValid } from "../core-validators/addresses.ts";let logger = new Logger("storage driver localStorage", "gold");
type SerializedDriverDocs = { byPathAndAuthor: Record<string, Doc>; byPathNewestFirst: Record<Path, Doc[]>;};
function isSerializedDriverDocs(value: any): value is SerializedDriverDocs { if (typeof value !== "object") { return false; } return ("byPathAndAuthor" in value && "byPathNewestFirst" in value);}
export class ReplicaDriverLocalStorage extends ReplicaDriverMemory { _localStorageKeyConfig: string; _localStorageKeyDocs: string;
constructor(share: ShareAddress) { super(share); logger.debug("constructor");
this._localStorageKeyConfig = `stonesoup:config:${share}`; this._localStorageKeyDocs = `stonesoup:documents:pathandauthor:${share}`;
const existingData = localStorage.getItem(this._localStorageKeyDocs); if (existingData !== null) { logger.debug("...constructor: loading data from localStorage"); const parsed = JSON.parse(existingData);
if (!isSerializedDriverDocs(parsed)) { console.warn( `localStorage data could not be parsed for share ${share}`, ); return; }
this.docByPathAndAuthor = new Map( Object.entries(parsed.byPathAndAuthor), ); this.docsByPathNewestFirst = new Map( Object.entries(parsed.byPathNewestFirst), );
const localIndexes = Array.from(this.docByPathAndAuthor.values()).map(( doc, ) => doc._localIndex as number); this._maxLocalIndex = Math.max(...localIndexes); } else { logger.debug( "...constructor: there was no existing data in localStorage", ); }
logger.debug("...constructor is done."); }
close(erase: boolean) { logger.debug("close"); if (this._isClosed) throw new ReplicaIsClosedError(); if (erase) { logger.debug("...close: and erase"); this._configKv = {}; this._maxLocalIndex = -1; this.docsByPathNewestFirst.clear(); this.docByPathAndAuthor.clear();
logger.debug("...close: erasing localStorage"); localStorage.removeItem(this._localStorageKeyDocs); for (let key of this._listConfigKeysSync()) { this._deleteConfigSync(key); } logger.debug("...close: erasing is done"); } this._isClosed = true; logger.debug("...close is done.");
return Promise.resolve(); }
_getConfigSync(key: string): string | undefined { if (this._isClosed) throw new ReplicaIsClosedError(); key = `${this._localStorageKeyConfig}:${key}`; let result = localStorage.getItem(key); return result === null ? undefined : result; }
_setConfigSync(key: string, value: string): void { if (this._isClosed) throw new ReplicaIsClosedError(); key = `${this._localStorageKeyConfig}:${key}`; localStorage.setItem(key, value); }
_listConfigKeysSync(): string[] { if (this._isClosed) throw new ReplicaIsClosedError(); let keys = Object.keys(localStorage) .filter((key) => key.startsWith(this._localStorageKeyConfig + ":")) .map((key) => key.slice(this._localStorageKeyConfig.length + 1)); keys.sort(); return keys; }
_deleteConfigSync(key: string): boolean { if (this._isClosed) throw new ReplicaIsClosedError(); let hadIt = this._getConfigSync(key); key = `${this._localStorageKeyConfig}:${key}`; localStorage.removeItem(key); return hadIt !== undefined; }
async getConfig(key: string): Promise<string | undefined> { return this._getConfigSync(key); } async setConfig(key: string, value: string): Promise<void> { return this._setConfigSync(key, value); } async listConfigKeys(): Promise<string[]> { return this._listConfigKeysSync(); } async deleteConfig(key: string): Promise<boolean> { return this._deleteConfigSync(key); }
async upsert(doc: Doc): Promise<Doc> { if (this._isClosed) throw new ReplicaIsClosedError(); const upsertedDoc = await super.upsert(doc);
const docsToBeSerialised: SerializedDriverDocs = { byPathAndAuthor: Object.fromEntries(this.docByPathAndAuthor), byPathNewestFirst: Object.fromEntries(this.docsByPathNewestFirst), };
localStorage.setItem( this._localStorageKeyDocs, JSON.stringify(docsToBeSerialised), );
return upsertedDoc; }}