Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
278eaa9
feat: Add StorageService for offloading large controller data
andrepimenta Nov 19, 2025
e844fdd
refactor(storage-service): Move getAllKeys/clear logic to adapters
andrepimenta Nov 19, 2025
19c7d6f
refactor: Adapters now build storage keys (not core)
andrepimenta Nov 25, 2025
5fedbc0
refactor(storage-service): delegate key building and serialization to…
andrepimenta Nov 25, 2025
5af0861
docs(storage-service): update CHANGELOG for initial release
andrepimenta Nov 25, 2025
de5388c
docs(storage-service): fix CHANGELOG format
andrepimenta Nov 25, 2025
e493d3e
docs(storage-service): update README with current API and precise met…
andrepimenta Nov 25, 2025
81100c2
build: add storage-service to tsconfig.build.json
andrepimenta Nov 26, 2025
f5c5aec
Merge branch 'main' into storage-service
andrepimenta Nov 26, 2025
04939f6
docs(storage-service): add JSDoc guidance for large value storage
andrepimenta Nov 26, 2025
3fe4314
Update packages/storage-service/src/StorageService.ts
andrepimenta Nov 26, 2025
84adfd5
Update packages/storage-service/src/StorageService.ts
andrepimenta Nov 26, 2025
7ac8bfc
refactor(storage-service): remove itemRemoved events, keep only itemSet
andrepimenta Nov 27, 2025
24b150e
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 27, 2025
65efd41
chore(storage-service): add dual MIT+Apache2 license
andrepimenta Nov 27, 2025
60efad1
refactor(storage-service): use unknown instead of generic types
andrepimenta Nov 27, 2025
2222e57
refactor(storage-service): use generate-method-action-types pattern
andrepimenta Nov 27, 2025
ce68a05
Update packages/storage-service/package.json
andrepimenta Nov 27, 2025
afde837
docs(storage-service): simplify README to focus on usage
andrepimenta Nov 27, 2025
ae501a3
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 27, 2025
81c9cf8
docs(storage-service): simplify CHANGELOG for initial release
andrepimenta Nov 27, 2025
625eee6
fix(storage-service): change event payload order to [key, value]
andrepimenta Nov 27, 2025
bda7243
Merge branch 'main' into storage-service
andrepimenta Nov 27, 2025
d3a7e4e
Fix prettier
andrepimenta Nov 27, 2025
389e50a
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 27, 2025
3aeafac
Fix keywords
andrepimenta Nov 27, 2025
c46e2e2
Update packages/storage-service/package.json
andrepimenta Nov 27, 2025
1b48670
test(storage-service): add tests for itemSet event
andrepimenta Nov 27, 2025
8050f20
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 27, 2025
9a65054
refactor(storage-service): use Json type instead of unknown
andrepimenta Nov 27, 2025
d6fe459
chore(storage-service): add CODEOWNERS and teams.json entries
andrepimenta Nov 27, 2025
5b4faeb
Update packages/storage-service/src/StorageService.ts
andrepimenta Nov 28, 2025
363533a
Update packages/storage-service/src/InMemoryStorageAdapter.ts
andrepimenta Nov 28, 2025
f5ccb18
Update packages/storage-service/src/InMemoryStorageAdapter.ts
andrepimenta Nov 28, 2025
b5c5d79
feat(storage-service): add StorageGetResult type for getItem responses
andrepimenta Nov 28, 2025
8557995
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 28, 2025
7ccd865
Update packages/storage-service/CHANGELOG.md
andrepimenta Nov 28, 2025
a139816
fix(storage-service): fix prettier formatting in test
andrepimenta Nov 28, 2025
5598df9
Merge branch 'storage-service' of https://github.com/MetaMask/core in…
andrepimenta Nov 28, 2025
bbd8e39
Merge branch 'main' into storage-service
andrepimenta Nov 28, 2025
cf634fb
Prettier fix
andrepimenta Nov 28, 2025
6bf384a
Fix return types on StorageServiceGetItemAction
andrepimenta Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(storage-service): Move getAllKeys/clear logic to adapters
- getAllKeys and clear now take namespace parameter
- Adapters handle filtering and clearing (allows platform-specific optimization)
- Removed internal key registry from core (simpler code)
- Renamed clearNamespace to clear (consistent with other methods)
- Use STORAGE_KEY_PREFIX constant ('storageService:') instead of hardcoded 'storage:'
- InMemoryAdapter implements filtering and clearing logic
- Mobile adapter can now be optimized per-platform

Benefits:
- Simpler core service (no registry maintenance)
- Platform-specific optimizations possible (IndexedDB can use IDBKeyRange)
- Clear adapter responsibilities (filtering, prefix handling)
- Consistent API (all methods take namespace first)

Tests: 100% coverage maintained, all tests passing
  • Loading branch information
andrepimenta committed Nov 19, 2025
commit e844fddf077ab50a8702e680a8c65ea5d60489b8
64 changes: 31 additions & 33 deletions packages/storage-service/src/InMemoryStorageAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,69 +65,72 @@
it('does not throw when removing non-existent key', async () => {
const adapter = new InMemoryStorageAdapter();

await expect(adapter.removeItem('nonExistent')).resolves.toBeUndefined();

Check failure on line 68 in packages/storage-service/src/InMemoryStorageAdapter.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Use `expect(await promise)` instead
});
});

describe('getAllKeys', () => {
it('returns all stored keys', async () => {
it('returns keys for a namespace', async () => {
const adapter = new InMemoryStorageAdapter();

await adapter.setItem('key1', 'value1');
await adapter.setItem('key2', 'value2');
await adapter.setItem('key3', 'value3');
await adapter.setItem('storageService:TestController:key1', 'value1');
await adapter.setItem('storageService:TestController:key2', 'value2');
await adapter.setItem('storageService:OtherController:key3', 'value3');

const keys = await adapter.getAllKeys();
const keys = await adapter.getAllKeys('TestController');

expect(keys).toStrictEqual(expect.arrayContaining(['key1', 'key2', 'key3']));
expect(keys).toHaveLength(3);
expect(keys).toStrictEqual(expect.arrayContaining(['key1', 'key2']));
expect(keys).toHaveLength(2);
});

it('returns empty array when no keys stored', async () => {
it('returns empty array when no keys for namespace', async () => {
const adapter = new InMemoryStorageAdapter();

const keys = await adapter.getAllKeys();
const keys = await adapter.getAllKeys('EmptyNamespace');

expect(keys).toStrictEqual([]);
});

it('reflects removed keys', async () => {
it('strips prefix from returned keys', async () => {
const adapter = new InMemoryStorageAdapter();

await adapter.setItem('key1', 'value1');
await adapter.setItem('key2', 'value2');
await adapter.removeItem('key1');
await adapter.setItem('storageService:TestController:my-key', 'value');

const keys = await adapter.getAllKeys();
const keys = await adapter.getAllKeys('TestController');

expect(keys).toStrictEqual(['key2']);
expect(keys).toStrictEqual(['my-key']);
expect(keys[0]).not.toContain('storage:');
expect(keys[0]).not.toContain('TestController:');
});
});

describe('clear', () => {
it('removes all items', async () => {
it('removes all items for a namespace', async () => {
const adapter = new InMemoryStorageAdapter();

await adapter.setItem('key1', 'value1');
await adapter.setItem('key2', 'value2');
await adapter.setItem('key3', 'value3');
await adapter.setItem('storageService:TestController:key1', 'value1');
await adapter.setItem('storageService:TestController:key2', 'value2');
await adapter.setItem('storageService:OtherController:key3', 'value3');

await adapter.clear();
await adapter.clear('TestController');

const keys = await adapter.getAllKeys();
const testKeys = await adapter.getAllKeys('TestController');
const otherKeys = await adapter.getAllKeys('OtherController');

expect(keys).toStrictEqual([]);
expect(testKeys).toStrictEqual([]);
expect(otherKeys).toStrictEqual(['key3']);
});

it('makes all previously stored items return null', async () => {
it('does not affect other namespaces', async () => {
const adapter = new InMemoryStorageAdapter();

await adapter.setItem('key1', 'value1');
await adapter.setItem('key2', 'value2');
await adapter.clear();
await adapter.setItem('storageService:Controller1:key', 'value1');
await adapter.setItem('storageService:Controller2:key', 'value2');

await adapter.clear('Controller1');

expect(await adapter.getItem('key1')).toBeNull();
expect(await adapter.getItem('key2')).toBeNull();
expect(await adapter.getItem('storageService:Controller1:key')).toBeNull();

Check failure on line 132 in packages/storage-service/src/InMemoryStorageAdapter.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Replace `await·adapter.getItem('storageService:Controller1:key')` with `⏎········await·adapter.getItem('storageService:Controller1:key'),⏎······`
expect(await adapter.getItem('storageService:Controller2:key')).toBe('value2');

Check failure on line 133 in packages/storage-service/src/InMemoryStorageAdapter.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Replace `'value2'` with `⏎········'value2',⏎······`
});
});

Expand All @@ -151,14 +154,9 @@
expect(typeof adapter.getItem).toBe('function');
expect(typeof adapter.setItem).toBe('function');
expect(typeof adapter.removeItem).toBe('function');
});

it('implements all optional methods', () => {
const adapter = new InMemoryStorageAdapter();

expect(typeof adapter.getAllKeys).toBe('function');
expect(typeof adapter.clear).toBe('function');
});
});
});

Check failure on line 162 in packages/storage-service/src/InMemoryStorageAdapter.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Delete `⏎`
26 changes: 19 additions & 7 deletions packages/storage-service/src/InMemoryStorageAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StorageAdapter } from './types';
import { STORAGE_KEY_PREFIX } from './types';

/**
* In-memory storage adapter (default fallback).
Expand Down Expand Up @@ -27,7 +28,7 @@
/**
* Internal storage map.
*/
#storage: Map<string, string>;

Check failure on line 31 in packages/storage-service/src/InMemoryStorageAdapter.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Member '#storage' is never reassigned; mark it as `readonly`

/**
* Constructs a new InMemoryStorageAdapter.
Expand Down Expand Up @@ -66,19 +67,30 @@
}

/**
* Get all keys from in-memory storage.
* Get all keys for a namespace.
* Returns keys without the 'storage:namespace:' prefix.
*
* @returns Array of all keys.
* @param namespace - The namespace to get keys for.
* @returns Array of keys (without prefix) for this namespace.
*/
async getAllKeys(): Promise<string[]> {
return Array.from(this.#storage.keys());
async getAllKeys(namespace: string): Promise<string[]> {
const prefix = `${STORAGE_KEY_PREFIX}${namespace}:`;
return Array.from(this.#storage.keys())
.filter((key) => key.startsWith(prefix))
.map((key) => key.slice(prefix.length));
}

/**
* Clear all items from in-memory storage.
* Clear all items for a namespace.
*
* @param namespace - The namespace to clear.
*/
async clear(): Promise<void> {
this.#storage.clear();
async clear(namespace: string): Promise<void> {
const prefix = `${STORAGE_KEY_PREFIX}${namespace}:`;
const keysToDelete = Array.from(this.#storage.keys()).filter((key) =>
key.startsWith(prefix),
);
keysToDelete.forEach((key) => this.#storage.delete(key));
}
}

Check failure on line 96 in packages/storage-service/src/InMemoryStorageAdapter.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Delete `⏎`
Loading
Loading