@coveord/plasma-mantine
Version:
A Plasma flavoured Mantine theme
152 lines (138 loc) • 4.67 kB
text/typescript
/**
* Versioned localStorage utility for Plasma.
*
* All Plasma data is stored under a single `"plasma"` key with the shape:
* ```json
* {
* "storage-version": 1,
* "storage": {
* "table": {
* "my-table": { "columnVisibility": { ... } }
* }
* }
* }
* ```
*/
export const STORAGE_KEY = 'plasma';
export const CURRENT_STORAGE_VERSION = 1;
type JsonObject = Record<string, unknown>;
const createStorageObject = (): JsonObject => Object.create(null) as JsonObject;
const isUnsafeKey = (key: string): boolean => key === '__proto__' || key === 'constructor' || key === 'prototype';
const isSafePath = (path: string[]): boolean => path.every((key) => !isUnsafeKey(key));
interface PlasmaStorageSchema {
'storage-version': number;
storage: JsonObject;
}
const readStorage = (): PlasmaStorageSchema | null => {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw === null) {
return null;
}
const parsed = JSON.parse(raw);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
localStorage.removeItem(STORAGE_KEY);
return null;
}
return parsed as PlasmaStorageSchema;
} catch {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {
console.warn(`Unable to clean up corrupted localStorage key "${STORAGE_KEY}".`);
}
return null;
}
};
const writeStorage = (data: PlasmaStorageSchema): void => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch {
console.warn(`Unable to write to localStorage key "${STORAGE_KEY}".`);
}
};
const getNestedValue = (obj: JsonObject, path: string[]): unknown => {
let current: unknown = obj;
for (const key of path) {
if (typeof current !== 'object' || current === null) {
return undefined;
}
current = (current as JsonObject)[key];
}
return current;
};
const setNestedValue = (obj: JsonObject, path: string[], value: unknown): void => {
let current = obj;
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (isUnsafeKey(key)) {
return;
}
if (typeof current[key] !== 'object' || current[key] === null) {
current[key] = createStorageObject();
}
current = current[key] as JsonObject;
}
const lastKey = path[path.length - 1];
if (!isUnsafeKey(lastKey)) {
current[lastKey] = value;
}
};
/**
* Read a value from the versioned Plasma storage at the given path.
*
* @param path - Path segments within `storage`, e.g. `['table', 'my-table', 'columnVisibility']`.
* @returns The stored value, or `null` if it doesn't exist or the storage is corrupted.
*/
export const getStorageItem = <T = unknown>(path: string[]): T | null => {
if (!isSafePath(path)) {
return null;
}
const data = readStorage();
if (!data || data['storage-version'] !== CURRENT_STORAGE_VERSION) {
return null;
}
const value = getNestedValue(data.storage ?? createStorageObject(), path);
return value !== undefined ? (value as T) : null;
};
/**
* Write a value to the versioned Plasma storage at the given path.
*
* @param path - Path segments within `storage`, e.g. `['table', 'my-table', 'columnVisibility']`.
* @param value - The value to store (must be JSON-serializable).
*/
export const setStorageItem = <T = unknown>(path: string[], value: T): void => {
if (!isSafePath(path)) {
return;
}
let data = readStorage();
if (!data || data['storage-version'] !== CURRENT_STORAGE_VERSION) {
data = {'storage-version': CURRENT_STORAGE_VERSION, storage: createStorageObject()};
}
if (!data.storage) {
data.storage = createStorageObject();
}
setNestedValue(data.storage, path, value);
writeStorage(data);
};
/**
* Remove a value from the versioned Plasma storage at the given path.
*
* @param path - Path segments within `storage`, e.g. `['table', 'my-table', 'columnVisibility']`.
*/
export const removeStorageItem = (path: string[]): void => {
if (!isSafePath(path)) {
return;
}
const data = readStorage();
if (!data || data['storage-version'] !== CURRENT_STORAGE_VERSION) {
return;
}
const parentPath = path.slice(0, -1);
const key = path[path.length - 1];
const parent = getNestedValue(data.storage ?? {}, parentPath);
if (typeof parent === 'object' && parent !== null) {
delete (parent as JsonObject)[key];
writeStorage(data);
}
};