src/lib/storage/core.ts
File Relationships
Symbols by Kind
function
4
type
1
All Symbols
| Name | Kind | Visibility | Status | Lines | Signature |
|---|---|---|---|---|---|
| StorageKey | type | exported- | 24-24 | type StorageKey |
|
| getFromStorage | function | exported- | 38-44 | getFromStorage(
key: string,
defaultValue: T,
): : Promise<T> |
|
| setToStorage | function | exported- | 51-53 | setToStorage(key: string, value: T): : Promise<void> |
|
| withTimeout | function | - | 55-64 | withTimeout(promise: Promise<T>, ms: number): : Promise<T> |
|
| updateStorageAtomically | function | exported- | 77-104 | updateStorageAtomically(
key: StorageKey,
defaultValue: T,
updater: (current: T) => T,
): : Promise<T> |
Full Source
/**
* Core storage utilities — low-level wrappers over chrome.storage.local
* with atomic update support and write queue per key.
*/
import { createLogger } from "@/lib/logger";
const log = createLogger("Storage");
/**
* Canonical storage key constants used across all storage modules.
* Maps logical names to the actual `chrome.storage.local` keys.
*/
export const STORAGE_KEYS = {
RULES: "fill_all_rules",
SAVED_FORMS: "fill_all_saved_forms",
SETTINGS: "fill_all_settings",
IGNORED_FIELDS: "fill_all_ignored_fields",
FIELD_CACHE: "fill_all_field_cache",
DEMO_FLOWS: "fill_all_demo_flows",
} as const;
/** Union type of all valid storage key values. */
export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];
const MAX_FIELD_CACHE_ENTRIES = 100;
const WRITE_TIMEOUT_MS = 30_000;
const writeQueues = new Map<StorageKey, Promise<void>>();
export { MAX_FIELD_CACHE_ENTRIES };
/**
* Reads a value from `chrome.storage.local`.
* @param key - Storage key to retrieve
* @param defaultValue - Value returned when the key does not exist
* @returns The stored value or `defaultValue`
*/
export async function getFromStorage<T>(
key: string,
defaultValue: T,
): Promise<T> {
const result = await chrome.storage.local.get(key);
return (result[key] as T) ?? defaultValue;
}
/**
* Writes a value to `chrome.storage.local`.
* @param key - Storage key to write
* @param value - Value to persist
*/
export async function setToStorage<T>(key: string, value: T): Promise<void> {
await chrome.storage.local.set({ [key]: value });
}
async function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
let timer: ReturnType<typeof setTimeout>;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(
() => reject(new Error(`Storage write timeout (${ms}ms)`)),
ms,
);
});
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}
/**
* Atomically reads, transforms, and writes a storage key.
*
* Uses a per-key write queue to prevent race conditions when multiple
* async operations modify the same key concurrently.
*
* @param key - Storage key to update
* @param defaultValue - Default value if key does not exist yet
* @param updater - Pure function that receives current value and returns the next
* @returns The new value after the update
*/
export async function updateStorageAtomically<T>(
key: StorageKey,
defaultValue: T,
updater: (current: T) => T,
): Promise<T> {
const previous = writeQueues.get(key) ?? Promise.resolve();
let nextValue = defaultValue;
const currentWrite = previous.then(async () => {
const current = await getFromStorage<T>(key, defaultValue);
nextValue = updater(current);
await setToStorage(key, nextValue);
});
const guardedWrite = withTimeout(currentWrite, WRITE_TIMEOUT_MS).catch(
(err) => {
log.warn(`Atomic update for key "${key}" failed:`, err);
},
);
writeQueues.set(
key,
guardedWrite.then(() => {}),
);
await currentWrite;
return nextValue;
}