Skip to main content

Storage API

Reference for the typed storage utilities defined in src/shared/lib/storage.ts.

Overview

createTypedStorage<StorageSchema>() returns a strongly typed interface to Chrome's storage areas:

  • local
  • sync
  • managed (read-only)
  • session

The default export in the project is:

import type { StorageSchema } from '@/shared/types';
import { createTypedStorage } from '@/shared/lib/storage';

export const kv = createTypedStorage<StorageSchema>();

All methods respect the schema declared in shared/types.d.ts.


API Surface

get

Fetch a single key.

const theme = await kv.get('sync', 'theme', 'light');

Signatures

get<Area extends Areas, Key extends keyof Schema[Area]>(
area: Area,
key: Key,
fallback: Schema[Area][Key]
): Promise<Schema[Area][Key]>;

get<Area extends Areas, Key extends keyof Schema[Area]>(
area: Area,
key: Key
): Promise<Schema[Area][Key] | undefined>;

Behavior

  • With fallback: always returns a value (fallback is injected if missing).
  • Without fallback: resolves to undefined when the key does not exist.

getAll

Fetch all keys for a storage area.

const allSync = await kv.getAll('sync', { settings: {}, version: '0.0.0' });

Signatures

getAll<Area extends Areas>(
area: Area,
defaults: Partial<Schema[Area]>
): Promise<Schema[Area]>;

getAll<Area extends Areas>(
area: Area
): Promise<Partial<Schema[Area]>>;

Behavior

  • With defaults: result is fully shaped.
  • Without defaults: only the stored keys are returned.

set

Persist a single key. Not allowed for managed storage.

await kv.set('local', 'darkMode', true);

Signature

set<Area extends Exclude<Areas, 'managed'>, Key extends keyof Schema[Area]>(
area: Area,
key: Key,
value: Schema[Area][Key]
): Promise<void>;

Errors

  • Throws at runtime if attempting to write to managed.
  • Propagates chrome.runtime.lastError messages (quota exceeded, etc.).

setAll

Write multiple keys in one call.

await kv.setAll('sync', {
version: '1.4.0',
settings: { theme: 'dark' }
});

Signature

setAll<Area extends Exclude<Areas, 'managed'>>(
area: Area,
data: Partial<Schema[Area]>
): Promise<void>;

remove

Delete a key (not available for managed).

await kv.remove('session', 'tempToken');

Signature

remove<Area extends Exclude<Areas, 'managed'>, Key extends keyof Schema[Area]>(
area: Area,
key: Key
): Promise<void>;

clear

Clear an entire storage area (not available for managed).

await kv.clear('local');

Signature

clear<Area extends Exclude<Areas, 'managed'>>(
area: Area
): Promise<void>;

Storage Areas

AreaReadWriteSyncsNotes
local~10MB quota
sync~100KB quota + per-item
sessionCleared with service worker
managedEnterprise policies only

Type Safety

All methods leverage the StorageSchema definition, preventing invalid keys or types at compile time.

interface StorageSchema {
local: {
darkMode: boolean;
};
}

// ✅ correct type
await kv.set('local', 'darkMode', true);

// ❌ compile error (key typo)
await kv.set('local', 'darkmode', true);

// ❌ compile error (wrong type)
await kv.set('local', 'darkMode', 'yes');

Examples

Settings Helper

const DEFAULT_SETTINGS = { theme: 'auto', language: 'en' } as const;

export const getSettings = async () => {
const stored = await kv.get('sync', 'settings');
return { ...DEFAULT_SETTINGS, ...stored };
};

export const updateSettings = async (updates: Partial<typeof DEFAULT_SETTINGS>) => {
const current = await getSettings();
await kv.set('sync', 'settings', { ...current, ...updates });
};

Session Token

export const getSessionToken = async () => kv.get('session', 'tempToken');

export const setSessionToken = (token: string | null) =>
token ? kv.set('session', 'tempToken', token) : kv.remove('session', 'tempToken');

Managed Policy Lookup

export const getOrgPolicy = async () => {
const enabled = await kv.get('managed', 'orgEnabled', false);
const hosts = await kv.get('managed', 'allowedHosts', []);
return { enabled, hosts };
};

Error Handling

Wrap calls in try/catch to capture quota and serialization errors:

try {
await kv.set('sync', 'settings', largeObject);
} catch (err) {
if (err instanceof Error && err.message.includes('QUOTA')) {
notifyUser('Sync storage quota exceeded');
}
}

Use chrome.runtime.lastError for detailed messages when mocking:

if (chrome.runtime.lastError) {
throw new Error(chrome.runtime.lastError.message);
}

Testing Helpers

Mock Chrome storage in Vitest:

const mockBucket = () => {
let store: Record<string, unknown> = {};
return {
get: vi.fn((keys, cb) => {
if (Array.isArray(keys)) {
const result = keys.reduce((acc, key) => ({ ...acc, [key]: store[key] }), {});
cb(result);
} else if (typeof keys === 'object') {
cb({ ...keys, ...store });
} else {
cb(store);
}
}),
set: vi.fn((items, cb) => {
store = { ...store, ...items };
cb?.();
}),
remove: vi.fn((keys, cb) => {
[].concat(keys as any).forEach((key) => delete store[key]);
cb?.();
}),
clear: vi.fn((cb) => {
store = {};
cb?.();
})
} as chrome.storage.StorageArea;
};