Skip to content

Instantly share code, notes, and snippets.

@AndrewPrifer
Created April 1, 2023 17:18
Show Gist options
  • Select an option

  • Save AndrewPrifer/40ca687a69a8691f12abb93dcbf4a889 to your computer and use it in GitHub Desktop.

Select an option

Save AndrewPrifer/40ca687a69a8691f12abb93dcbf4a889 to your computer and use it in GitHub Desktop.
Rolling storage wrapping a Storage object
/**
* A storage class that will remove the oldest items when the storage size is exceeded.
*/
export class RollingStorage implements Storage {
private baseKey: string;
private maxSize: number;
private storage: Storage;
/**
* @param baseKey Base key to use for storage. Will be prefixed to all keys.
* @param maxSize Maximum size of storage in bytes.
* @param storage Storage to use. Defaults to localStorage.
*/
constructor(
baseKey: string,
maxSize: number,
storage: Storage = localStorage
) {
this.baseKey = baseKey;
this.maxSize = maxSize;
this.storage = storage;
if (!this.storage.getItem(this.getIndexKey())) {
this.storage.setItem(
this.getIndexKey(),
JSON.stringify({ keys: [], size: 0 })
);
}
}
private getIndexKey(): string {
return `${this.baseKey}_index`;
}
private getIndex(): { keys: string[]; size: number } {
const indexString = this.storage.getItem(this.getIndexKey());
return indexString ? JSON.parse(indexString) : { keys: [], size: 0 };
}
private setIndex(index: { keys: string[]; size: number }): void {
this.storage.setItem(this.getIndexKey(), JSON.stringify(index));
}
private getStringByteSize(str: string): number {
return new Blob([str]).size;
}
private prefixedKey(key: string): string {
return `${this.baseKey}_${key}`;
}
public setItem(key: string, value: any): void {
const index = this.getIndex();
const currentIndex = index.keys.indexOf(key);
const newValueString = JSON.stringify(value);
const newSize = this.getStringByteSize(newValueString);
if (currentIndex === -1) {
index.keys.push(key);
while (index.size + newSize > this.maxSize) {
const removedKey = index.keys.shift();
if (removedKey) {
const removedValueString = this.storage.getItem(
this.prefixedKey(removedKey)
);
this.storage.removeItem(this.prefixedKey(removedKey));
if (removedValueString) {
index.size -= this.getStringByteSize(removedValueString);
}
}
}
} else {
const oldValueString = this.storage.getItem(this.prefixedKey(key));
if (oldValueString) {
index.size -= this.getStringByteSize(oldValueString);
}
}
this.storage.setItem(this.prefixedKey(key), newValueString);
index.size += newSize;
this.setIndex(index);
}
public getItem(key: string): any | null {
const itemString = this.storage.getItem(this.prefixedKey(key));
return itemString ? JSON.parse(itemString) : null;
}
public removeItem(key: string): void {
const index = this.getIndex();
const currentIndex = index.keys.indexOf(key);
if (currentIndex !== -1) {
index.keys.splice(currentIndex, 1);
const itemString = this.storage.getItem(this.prefixedKey(key));
if (itemString) {
index.size -= this.getStringByteSize(itemString);
}
this.storage.removeItem(this.prefixedKey(key));
this.setIndex(index);
}
}
public clear(): void {
const index = this.getIndex();
for (const key of index.keys) {
this.storage.removeItem(this.prefixedKey(key));
}
this.setIndex({ keys: [], size: 0 });
}
public get length(): number {
return this.getIndex().keys.length;
}
public key(index: number): string | null {
const keys = this.getIndex().keys;
return index >= 0 && index < keys.length ? keys[index] : null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment