@metamask-previews/storage-service 1.0.0-preview-e493d3e8 → 1.0.0-preview-81c9cf83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +0 -8
  2. package/LICENSE +4 -18
  3. package/LICENSE.APACHE2 +201 -0
  4. package/LICENSE.MIT +21 -0
  5. package/README.md +41 -303
  6. package/dist/InMemoryStorageAdapter.cjs +129 -0
  7. package/dist/InMemoryStorageAdapter.cjs.map +1 -0
  8. package/dist/InMemoryStorageAdapter.d.cts +71 -0
  9. package/dist/InMemoryStorageAdapter.d.cts.map +1 -0
  10. package/dist/InMemoryStorageAdapter.d.mts +71 -0
  11. package/dist/InMemoryStorageAdapter.d.mts.map +1 -0
  12. package/dist/InMemoryStorageAdapter.mjs +125 -0
  13. package/dist/InMemoryStorageAdapter.mjs.map +1 -0
  14. package/dist/StorageService-method-action-types.cjs +7 -0
  15. package/dist/StorageService-method-action-types.cjs.map +1 -0
  16. package/dist/StorageService-method-action-types.d.cts +81 -0
  17. package/dist/StorageService-method-action-types.d.cts.map +1 -0
  18. package/dist/StorageService-method-action-types.d.mts +81 -0
  19. package/dist/StorageService-method-action-types.d.mts.map +1 -0
  20. package/dist/StorageService-method-action-types.mjs +6 -0
  21. package/dist/StorageService-method-action-types.mjs.map +1 -0
  22. package/dist/StorageService.cjs +203 -0
  23. package/dist/StorageService.cjs.map +1 -0
  24. package/dist/StorageService.d.cts +147 -0
  25. package/dist/StorageService.d.cts.map +1 -0
  26. package/dist/StorageService.d.mts +147 -0
  27. package/dist/StorageService.d.mts.map +1 -0
  28. package/dist/StorageService.mjs +199 -0
  29. package/dist/StorageService.mjs.map +1 -0
  30. package/dist/index.cjs +14 -0
  31. package/dist/index.cjs.map +1 -0
  32. package/dist/index.d.cts +6 -0
  33. package/dist/index.d.cts.map +1 -0
  34. package/dist/index.d.mts +6 -0
  35. package/dist/index.d.mts.map +1 -0
  36. package/dist/index.mjs +7 -0
  37. package/dist/index.mjs.map +1 -0
  38. package/dist/types.cjs +12 -0
  39. package/dist/types.cjs.map +1 -0
  40. package/dist/types.d.cts +148 -0
  41. package/dist/types.d.cts.map +1 -0
  42. package/dist/types.d.mts +148 -0
  43. package/dist/types.d.mts.map +1 -0
  44. package/dist/types.mjs +9 -0
  45. package/dist/types.mjs.map +1 -0
  46. package/package.json +2 -2
@@ -0,0 +1,199 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _StorageService_messenger, _StorageService_storage;
13
+ import { InMemoryStorageAdapter } from "./InMemoryStorageAdapter.mjs";
14
+ import { SERVICE_NAME } from "./types.mjs";
15
+ // === MESSENGER ===
16
+ const MESSENGER_EXPOSED_METHODS = [
17
+ 'setItem',
18
+ 'getItem',
19
+ 'removeItem',
20
+ 'getAllKeys',
21
+ 'clear',
22
+ ];
23
+ /**
24
+ * StorageService provides a platform-agnostic way for controllers to store
25
+ * large, infrequently accessed data outside of memory/Redux state.
26
+ *
27
+ * **Use cases:**
28
+ * - Snap source code (6+ MB that's rarely accessed)
29
+ * - Token metadata caches (4+ MB of cached data)
30
+ * - Large cached responses from APIs
31
+ * - Any data > 100 KB that's not frequently accessed
32
+ *
33
+ * **Benefits:**
34
+ * - Reduces memory usage (data stays on disk)
35
+ * - Faster Redux persist (less data to serialize)
36
+ * - Faster app startup (less data to parse)
37
+ * - Lazy loading (data loaded only when needed)
38
+ *
39
+ * **Platform Support:**
40
+ * - Mobile: FilesystemStorage adapter
41
+ * - Extension: IndexedDB adapter
42
+ * - Tests/Dev: InMemoryStorageAdapter (default)
43
+ *
44
+ * @example Using the service via messenger
45
+ *
46
+ * ```typescript
47
+ * // In a controller
48
+ * type AllowedActions =
49
+ * | StorageServiceSetItemAction
50
+ * | StorageServiceGetItemAction;
51
+ *
52
+ * class SnapController extends BaseController {
53
+ * async storeSnapSourceCode(snapId: string, sourceCode: string) {
54
+ * await this.messenger.call(
55
+ * 'StorageService:setItem',
56
+ * 'SnapController',
57
+ * `${snapId}:sourceCode`,
58
+ * sourceCode,
59
+ * );
60
+ * }
61
+ *
62
+ * async getSnapSourceCode(snapId: string): Promise<string | null> {
63
+ * const result = await this.messenger.call(
64
+ * 'StorageService:getItem',
65
+ * 'SnapController',
66
+ * `${snapId}:sourceCode`,
67
+ * );
68
+ * return result as string | null; // Caller must validate/cast
69
+ * }
70
+ * }
71
+ * ```
72
+ *
73
+ * @example Initializing in a client
74
+ *
75
+ * ```typescript
76
+ * // Mobile
77
+ * const service = new StorageService({
78
+ * messenger: storageServiceMessenger,
79
+ * storage: filesystemStorageAdapter, // Platform-specific
80
+ * });
81
+ *
82
+ * // Extension
83
+ * const service = new StorageService({
84
+ * messenger: storageServiceMessenger,
85
+ * storage: indexedDBAdapter, // Platform-specific
86
+ * });
87
+ *
88
+ * // Tests (uses in-memory by default)
89
+ * const service = new StorageService({
90
+ * messenger: storageServiceMessenger,
91
+ * // No storage - uses InMemoryStorageAdapter
92
+ * });
93
+ * ```
94
+ */
95
+ export class StorageService {
96
+ /**
97
+ * Constructs a new StorageService.
98
+ *
99
+ * @param options - The options.
100
+ * @param options.messenger - The messenger suited for this service.
101
+ * @param options.storage - Storage adapter for persisting data.
102
+ * If not provided, uses InMemoryStorageAdapter (data lost on restart).
103
+ */
104
+ constructor({ messenger, storage }) {
105
+ /**
106
+ * The messenger suited for this service.
107
+ */
108
+ _StorageService_messenger.set(this, void 0);
109
+ /**
110
+ * The storage adapter for persisting data.
111
+ */
112
+ _StorageService_storage.set(this, void 0);
113
+ this.name = SERVICE_NAME;
114
+ __classPrivateFieldSet(this, _StorageService_messenger, messenger, "f");
115
+ __classPrivateFieldSet(this, _StorageService_storage, storage ?? new InMemoryStorageAdapter(), "f");
116
+ // Warn if using in-memory storage (data won't persist)
117
+ if (!storage) {
118
+ console.warn(`${SERVICE_NAME}: No storage adapter provided. Using in-memory storage. ` +
119
+ 'Data will be lost on restart. Provide a storage adapter for persistence.');
120
+ }
121
+ // Register messenger actions
122
+ __classPrivateFieldGet(this, _StorageService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
123
+ }
124
+ /**
125
+ * Store large data in storage.
126
+ *
127
+ * ⚠️ **Designed for large values (100KB+), not many small ones.**
128
+ * Each storage operation has I/O overhead. For best performance,
129
+ * store one large object rather than many small key-value pairs.
130
+ *
131
+ * @example Good: Store entire cache as one value
132
+ * ```typescript
133
+ * await service.setItem('TokenList', 'cache', { '0x1': [...], '0x38': [...] });
134
+ * ```
135
+ *
136
+ * @example Avoid: Many small values
137
+ * ```typescript
138
+ * // ❌ Don't do this - too many small writes
139
+ * await service.setItem('TokenList', 'cache:0x1', [...]);
140
+ * await service.setItem('TokenList', 'cache:0x38', [...]);
141
+ * ```
142
+ *
143
+ * @param namespace - Controller namespace (e.g., 'SnapController').
144
+ * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').
145
+ * @param value - Data to store (should be 100KB+ for optimal use).
146
+ */
147
+ async setItem(namespace, key, value) {
148
+ // Adapter handles serialization and wrapping with metadata
149
+ await __classPrivateFieldGet(this, _StorageService_storage, "f").setItem(namespace, key, value);
150
+ // Publish event so other controllers can react to changes
151
+ // Event type: StorageService:itemSet:namespace
152
+ // Payload: [value, key]
153
+ __classPrivateFieldGet(this, _StorageService_messenger, "f").publish(`${SERVICE_NAME}:itemSet:${namespace}`, value, key);
154
+ }
155
+ /**
156
+ * Retrieve data from storage.
157
+ *
158
+ * Returns `unknown` since there's no schema validation.
159
+ * Callers should validate or cast the result to the expected type.
160
+ *
161
+ * @param namespace - Controller namespace (e.g., 'SnapController').
162
+ * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').
163
+ * @returns Parsed data or null if not found. Type is `unknown` - caller must validate.
164
+ */
165
+ async getItem(namespace, key) {
166
+ // Adapter handles deserialization and unwrapping
167
+ return await __classPrivateFieldGet(this, _StorageService_storage, "f").getItem(namespace, key);
168
+ }
169
+ /**
170
+ * Remove data from storage.
171
+ *
172
+ * @param namespace - Controller namespace (e.g., 'SnapController').
173
+ * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').
174
+ */
175
+ async removeItem(namespace, key) {
176
+ // Adapter builds full storage key (e.g., mobile: 'storageService:namespace:key')
177
+ await __classPrivateFieldGet(this, _StorageService_storage, "f").removeItem(namespace, key);
178
+ }
179
+ /**
180
+ * Get all keys for a namespace.
181
+ * Delegates to storage adapter which handles filtering.
182
+ *
183
+ * @param namespace - Controller namespace (e.g., 'SnapController').
184
+ * @returns Array of keys (without prefix) for this namespace.
185
+ */
186
+ async getAllKeys(namespace) {
187
+ return await __classPrivateFieldGet(this, _StorageService_storage, "f").getAllKeys(namespace);
188
+ }
189
+ /**
190
+ * Clear all data for a namespace.
191
+ *
192
+ * @param namespace - Controller namespace (e.g., 'SnapController').
193
+ */
194
+ async clear(namespace) {
195
+ await __classPrivateFieldGet(this, _StorageService_storage, "f").clear(namespace);
196
+ }
197
+ }
198
+ _StorageService_messenger = new WeakMap(), _StorageService_storage = new WeakMap();
199
+ //# sourceMappingURL=StorageService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageService.mjs","sourceRoot":"","sources":["../src/StorageService.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,sBAAsB,EAAE,qCAAiC;AAMlE,OAAO,EAAE,YAAY,EAAE,oBAAgB;AAEvC,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG;IAChC,SAAS;IACT,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,OAAO;CACC,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,MAAM,OAAO,cAAc;IAgBzB;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,EAAE,OAAO,EAAyB;QAlBzD;;WAEG;QACM,4CAAoC;QAE7C;;WAEG;QACM,0CAAyB;QAWhC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,uBAAA,IAAI,6BAAc,SAAS,MAAA,CAAC;QAC5B,uBAAA,IAAI,2BAAY,OAAO,IAAI,IAAI,sBAAsB,EAAE,MAAA,CAAC;QAExD,uDAAuD;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CACV,GAAG,YAAY,0DAA0D;gBACvE,0EAA0E,CAC7E,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,uBAAA,IAAI,iCAAW,CAAC,4BAA4B,CAC1C,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,GAAW,EAAE,KAAc;QAC1D,2DAA2D;QAC3D,MAAM,uBAAA,IAAI,+BAAS,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAEnD,0DAA0D;QAC1D,+CAA+C;QAC/C,wBAAwB;QACxB,uBAAA,IAAI,iCAAW,CAAC,OAAO,CACrB,GAAG,YAAY,YAAY,SAAS,EAAW,EAC/C,KAAK,EACL,GAAG,CACJ,CAAC;IACJ,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,GAAW;QAC1C,iDAAiD;QACjD,OAAO,MAAM,uBAAA,IAAI,+BAAS,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,GAAW;QAC7C,iFAAiF;QACjF,MAAM,uBAAA,IAAI,+BAAS,CAAC,UAAU,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CAAC,SAAiB;QAChC,OAAO,MAAM,uBAAA,IAAI,+BAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,MAAM,uBAAA,IAAI,+BAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["import { InMemoryStorageAdapter } from './InMemoryStorageAdapter';\nimport type {\n StorageAdapter,\n StorageServiceMessenger,\n StorageServiceOptions,\n} from './types';\nimport { SERVICE_NAME } from './types';\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'setItem',\n 'getItem',\n 'removeItem',\n 'getAllKeys',\n 'clear',\n] as const;\n\n/**\n * StorageService provides a platform-agnostic way for controllers to store\n * large, infrequently accessed data outside of memory/Redux state.\n *\n * **Use cases:**\n * - Snap source code (6+ MB that's rarely accessed)\n * - Token metadata caches (4+ MB of cached data)\n * - Large cached responses from APIs\n * - Any data > 100 KB that's not frequently accessed\n *\n * **Benefits:**\n * - Reduces memory usage (data stays on disk)\n * - Faster Redux persist (less data to serialize)\n * - Faster app startup (less data to parse)\n * - Lazy loading (data loaded only when needed)\n *\n * **Platform Support:**\n * - Mobile: FilesystemStorage adapter\n * - Extension: IndexedDB adapter\n * - Tests/Dev: InMemoryStorageAdapter (default)\n *\n * @example Using the service via messenger\n *\n * ```typescript\n * // In a controller\n * type AllowedActions =\n * | StorageServiceSetItemAction\n * | StorageServiceGetItemAction;\n *\n * class SnapController extends BaseController {\n * async storeSnapSourceCode(snapId: string, sourceCode: string) {\n * await this.messenger.call(\n * 'StorageService:setItem',\n * 'SnapController',\n * `${snapId}:sourceCode`,\n * sourceCode,\n * );\n * }\n *\n * async getSnapSourceCode(snapId: string): Promise<string | null> {\n * const result = await this.messenger.call(\n * 'StorageService:getItem',\n * 'SnapController',\n * `${snapId}:sourceCode`,\n * );\n * return result as string | null; // Caller must validate/cast\n * }\n * }\n * ```\n *\n * @example Initializing in a client\n *\n * ```typescript\n * // Mobile\n * const service = new StorageService({\n * messenger: storageServiceMessenger,\n * storage: filesystemStorageAdapter, // Platform-specific\n * });\n *\n * // Extension\n * const service = new StorageService({\n * messenger: storageServiceMessenger,\n * storage: indexedDBAdapter, // Platform-specific\n * });\n *\n * // Tests (uses in-memory by default)\n * const service = new StorageService({\n * messenger: storageServiceMessenger,\n * // No storage - uses InMemoryStorageAdapter\n * });\n * ```\n */\nexport class StorageService {\n /**\n * The name of the service.\n */\n readonly name: typeof SERVICE_NAME;\n\n /**\n * The messenger suited for this service.\n */\n readonly #messenger: StorageServiceMessenger;\n\n /**\n * The storage adapter for persisting data.\n */\n readonly #storage: StorageAdapter;\n\n /**\n * Constructs a new StorageService.\n *\n * @param options - The options.\n * @param options.messenger - The messenger suited for this service.\n * @param options.storage - Storage adapter for persisting data.\n * If not provided, uses InMemoryStorageAdapter (data lost on restart).\n */\n constructor({ messenger, storage }: StorageServiceOptions) {\n this.name = SERVICE_NAME;\n this.#messenger = messenger;\n this.#storage = storage ?? new InMemoryStorageAdapter();\n\n // Warn if using in-memory storage (data won't persist)\n if (!storage) {\n console.warn(\n `${SERVICE_NAME}: No storage adapter provided. Using in-memory storage. ` +\n 'Data will be lost on restart. Provide a storage adapter for persistence.',\n );\n }\n\n // Register messenger actions\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Store large data in storage.\n *\n * ⚠️ **Designed for large values (100KB+), not many small ones.**\n * Each storage operation has I/O overhead. For best performance,\n * store one large object rather than many small key-value pairs.\n *\n * @example Good: Store entire cache as one value\n * ```typescript\n * await service.setItem('TokenList', 'cache', { '0x1': [...], '0x38': [...] });\n * ```\n *\n * @example Avoid: Many small values\n * ```typescript\n * // ❌ Don't do this - too many small writes\n * await service.setItem('TokenList', 'cache:0x1', [...]);\n * await service.setItem('TokenList', 'cache:0x38', [...]);\n * ```\n *\n * @param namespace - Controller namespace (e.g., 'SnapController').\n * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').\n * @param value - Data to store (should be 100KB+ for optimal use).\n */\n async setItem(namespace: string, key: string, value: unknown): Promise<void> {\n // Adapter handles serialization and wrapping with metadata\n await this.#storage.setItem(namespace, key, value);\n\n // Publish event so other controllers can react to changes\n // Event type: StorageService:itemSet:namespace\n // Payload: [value, key]\n this.#messenger.publish(\n `${SERVICE_NAME}:itemSet:${namespace}` as const,\n value,\n key,\n );\n }\n\n /**\n * Retrieve data from storage.\n *\n * Returns `unknown` since there's no schema validation.\n * Callers should validate or cast the result to the expected type.\n *\n * @param namespace - Controller namespace (e.g., 'SnapController').\n * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').\n * @returns Parsed data or null if not found. Type is `unknown` - caller must validate.\n */\n async getItem(namespace: string, key: string): Promise<unknown> {\n // Adapter handles deserialization and unwrapping\n return await this.#storage.getItem(namespace, key);\n }\n\n /**\n * Remove data from storage.\n *\n * @param namespace - Controller namespace (e.g., 'SnapController').\n * @param key - Storage key (e.g., 'npm:@metamask/example-snap:sourceCode').\n */\n async removeItem(namespace: string, key: string): Promise<void> {\n // Adapter builds full storage key (e.g., mobile: 'storageService:namespace:key')\n await this.#storage.removeItem(namespace, key);\n }\n\n /**\n * Get all keys for a namespace.\n * Delegates to storage adapter which handles filtering.\n *\n * @param namespace - Controller namespace (e.g., 'SnapController').\n * @returns Array of keys (without prefix) for this namespace.\n */\n async getAllKeys(namespace: string): Promise<string[]> {\n return await this.#storage.getAllKeys(namespace);\n }\n\n /**\n * Clear all data for a namespace.\n *\n * @param namespace - Controller namespace (e.g., 'SnapController').\n */\n async clear(namespace: string): Promise<void> {\n await this.#storage.clear(namespace);\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STORAGE_KEY_PREFIX = exports.SERVICE_NAME = exports.InMemoryStorageAdapter = exports.StorageService = void 0;
4
+ // Export service class
5
+ var StorageService_1 = require("./StorageService.cjs");
6
+ Object.defineProperty(exports, "StorageService", { enumerable: true, get: function () { return StorageService_1.StorageService; } });
7
+ // Export adapters
8
+ var InMemoryStorageAdapter_1 = require("./InMemoryStorageAdapter.cjs");
9
+ Object.defineProperty(exports, "InMemoryStorageAdapter", { enumerable: true, get: function () { return InMemoryStorageAdapter_1.InMemoryStorageAdapter; } });
10
+ // Export service name and storage key prefix constants
11
+ var types_1 = require("./types.cjs");
12
+ Object.defineProperty(exports, "SERVICE_NAME", { enumerable: true, get: function () { return types_1.SERVICE_NAME; } });
13
+ Object.defineProperty(exports, "STORAGE_KEY_PREFIX", { enumerable: true, get: function () { return types_1.STORAGE_KEY_PREFIX; } });
14
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uBAAuB;AACvB,uDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB,kBAAkB;AAClB,uEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAqB/B,uDAAuD;AACvD,qCAA2D;AAAlD,qGAAA,YAAY,OAAA;AAAE,2GAAA,kBAAkB,OAAA","sourcesContent":["// Export service class\nexport { StorageService } from './StorageService';\n\n// Export adapters\nexport { InMemoryStorageAdapter } from './InMemoryStorageAdapter';\n\n// Export types from types.ts\nexport type {\n StorageAdapter,\n StorageServiceOptions,\n StorageServiceActions,\n StorageServiceEvents,\n StorageServiceMessenger,\n StorageServiceItemSetEvent,\n} from './types';\n\n// Export individual action types from generated file\nexport type {\n StorageServiceSetItemAction,\n StorageServiceGetItemAction,\n StorageServiceRemoveItemAction,\n StorageServiceGetAllKeysAction,\n StorageServiceClearAction,\n} from './StorageService-method-action-types';\n\n// Export service name and storage key prefix constants\nexport { SERVICE_NAME, STORAGE_KEY_PREFIX } from './types';\n"]}
@@ -0,0 +1,6 @@
1
+ export { StorageService } from "./StorageService.cjs";
2
+ export { InMemoryStorageAdapter } from "./InMemoryStorageAdapter.cjs";
3
+ export type { StorageAdapter, StorageServiceOptions, StorageServiceActions, StorageServiceEvents, StorageServiceMessenger, StorageServiceItemSetEvent, } from "./types.cjs";
4
+ export type { StorageServiceSetItemAction, StorageServiceGetItemAction, StorageServiceRemoveItemAction, StorageServiceGetAllKeysAction, StorageServiceClearAction, } from "./StorageService-method-action-types.cjs";
5
+ export { SERVICE_NAME, STORAGE_KEY_PREFIX } from "./types.cjs";
6
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,6BAAyB;AAGlD,OAAO,EAAE,sBAAsB,EAAE,qCAAiC;AAGlE,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,GAC3B,oBAAgB;AAGjB,YAAY,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,8BAA8B,EAC9B,8BAA8B,EAC9B,yBAAyB,GAC1B,iDAA6C;AAG9C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAgB"}
@@ -0,0 +1,6 @@
1
+ export { StorageService } from "./StorageService.mjs";
2
+ export { InMemoryStorageAdapter } from "./InMemoryStorageAdapter.mjs";
3
+ export type { StorageAdapter, StorageServiceOptions, StorageServiceActions, StorageServiceEvents, StorageServiceMessenger, StorageServiceItemSetEvent, } from "./types.mjs";
4
+ export type { StorageServiceSetItemAction, StorageServiceGetItemAction, StorageServiceRemoveItemAction, StorageServiceGetAllKeysAction, StorageServiceClearAction, } from "./StorageService-method-action-types.mjs";
5
+ export { SERVICE_NAME, STORAGE_KEY_PREFIX } from "./types.mjs";
6
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,6BAAyB;AAGlD,OAAO,EAAE,sBAAsB,EAAE,qCAAiC;AAGlE,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,GAC3B,oBAAgB;AAGjB,YAAY,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,8BAA8B,EAC9B,8BAA8B,EAC9B,yBAAyB,GAC1B,iDAA6C;AAG9C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAgB"}
package/dist/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ // Export service class
2
+ export { StorageService } from "./StorageService.mjs";
3
+ // Export adapters
4
+ export { InMemoryStorageAdapter } from "./InMemoryStorageAdapter.mjs";
5
+ // Export service name and storage key prefix constants
6
+ export { SERVICE_NAME, STORAGE_KEY_PREFIX } from "./types.mjs";
7
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAE,cAAc,EAAE,6BAAyB;AAElD,kBAAkB;AAClB,OAAO,EAAE,sBAAsB,EAAE,qCAAiC;AAqBlE,uDAAuD;AACvD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAgB","sourcesContent":["// Export service class\nexport { StorageService } from './StorageService';\n\n// Export adapters\nexport { InMemoryStorageAdapter } from './InMemoryStorageAdapter';\n\n// Export types from types.ts\nexport type {\n StorageAdapter,\n StorageServiceOptions,\n StorageServiceActions,\n StorageServiceEvents,\n StorageServiceMessenger,\n StorageServiceItemSetEvent,\n} from './types';\n\n// Export individual action types from generated file\nexport type {\n StorageServiceSetItemAction,\n StorageServiceGetItemAction,\n StorageServiceRemoveItemAction,\n StorageServiceGetAllKeysAction,\n StorageServiceClearAction,\n} from './StorageService-method-action-types';\n\n// Export service name and storage key prefix constants\nexport { SERVICE_NAME, STORAGE_KEY_PREFIX } from './types';\n"]}
package/dist/types.cjs ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STORAGE_KEY_PREFIX = exports.SERVICE_NAME = void 0;
4
+ // Service name constant
5
+ exports.SERVICE_NAME = 'StorageService';
6
+ /**
7
+ * Storage key prefix for all keys managed by StorageService.
8
+ * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}
9
+ * Example: 'storageService:SnapController:snap-id:sourceCode'
10
+ */
11
+ exports.STORAGE_KEY_PREFIX = 'storageService:';
12
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA2GA,wBAAwB;AACX,QAAA,YAAY,GAAG,gBAAgB,CAAC;AAE7C;;;;GAIG;AACU,QAAA,kBAAkB,GAAG,iBAAiB,CAAC","sourcesContent":["import type { Messenger } from '@metamask/messenger';\n\nimport type { StorageServiceMethodActions } from './StorageService-method-action-types';\n\n/**\n * Platform-agnostic storage adapter interface.\n * Each client (mobile, extension) implements this interface\n * with their preferred storage mechanism.\n *\n * ⚠️ **Designed for large, infrequently accessed data (100KB+)**\n *\n * ✅ **Use for:**\n * - Snap source code (~6 MB per snap)\n * - Token metadata caches (~4 MB)\n * - Large API response caches\n *\n * ❌ **Avoid for:**\n * - Small values (< 10 KB) - use controller state instead\n * - Frequently accessed data - use controller state instead\n * - Many small key-value pairs - use a single large object instead\n *\n * @example Mobile implementation using FilesystemStorage\n * @example Extension implementation using IndexedDB\n * @example Tests using InMemoryStorageAdapter\n */\nexport type StorageAdapter = {\n /**\n * Retrieve an item from storage.\n * Adapter is responsible for building the full storage key.\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n * @returns The value as a string, or null if not found.\n */\n getItem(namespace: string, key: string): Promise<unknown>;\n\n /**\n * Store a large value in storage.\n *\n * ⚠️ **Store large values, not many small ones.**\n * Each storage operation has I/O overhead. For best performance:\n * - Store one large object rather than many small key-value pairs\n * - Minimum recommended size: 100 KB per value\n *\n * Adapter is responsible for:\n * - Building the full storage key\n * - Wrapping value with metadata (timestamp, etc.)\n * - Serializing to string (JSON.stringify)\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n * @param value - The value to store (will be wrapped and serialized by adapter).\n */\n setItem(namespace: string, key: string, value: unknown): Promise<void>;\n\n /**\n * Remove an item from storage.\n * Adapter is responsible for building the full storage key.\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n */\n removeItem(namespace: string, key: string): Promise<void>;\n\n /**\n * Get all keys for a specific namespace.\n * Should return keys without the 'storage:namespace:' prefix.\n *\n * Adapter is responsible for:\n * - Filtering keys by prefix: 'storage:{namespace}:'\n * - Stripping the prefix from returned keys\n * - Returning only the key portion after the prefix\n *\n * @param namespace - The namespace to get keys for (e.g., 'SnapController').\n * @returns Array of keys without prefix (e.g., ['snap1:sourceCode', 'snap2:sourceCode']).\n */\n getAllKeys(namespace: string): Promise<string[]>;\n\n /**\n * Clear all items for a specific namespace.\n *\n * Adapter is responsible for:\n * - Finding all keys with prefix: 'storageService:{namespace}:'\n * - Removing all matching keys\n *\n * @param namespace - The namespace to clear (e.g., 'SnapController').\n */\n clear(namespace: string): Promise<void>;\n};\n\n/**\n * Options for constructing a {@link StorageService}.\n */\nexport type StorageServiceOptions = {\n /**\n * The messenger suited for this service.\n */\n messenger: StorageServiceMessenger;\n\n /**\n * Storage adapter for persisting data.\n * If not provided, uses in-memory storage (data lost on restart).\n * Production clients MUST provide a persistent storage adapter.\n */\n storage?: StorageAdapter;\n};\n\n// Service name constant\nexport const SERVICE_NAME = 'StorageService';\n\n/**\n * Storage key prefix for all keys managed by StorageService.\n * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}\n * Example: 'storageService:SnapController:snap-id:sourceCode'\n */\nexport const STORAGE_KEY_PREFIX = 'storageService:';\n\n/**\n * All actions that {@link StorageService} exposes to other consumers.\n * Action types are auto-generated from the service methods.\n */\nexport type StorageServiceActions = StorageServiceMethodActions;\n\n/**\n * Event published when a storage item is set.\n * Event type includes namespace only, key passed in payload.\n *\n * @example\n * Subscribe to all changes in TokenListController:\n * messenger.subscribe('StorageService:itemSet:TokenListController', (value, key) => {\n * // value = the data that was set\n * // key = 'cache:0x1', 'cache:0x38', etc.\n * if (key.startsWith('cache:')) {\n * const chainId = key.replace('cache:', '');\n * // React to cache change for specific chain\n * }\n * });\n */\nexport type StorageServiceItemSetEvent = {\n type: `${typeof SERVICE_NAME}:itemSet:${string}`;\n payload: [value: unknown, key: string];\n};\n\n/**\n * All events that {@link StorageService} publishes.\n */\nexport type StorageServiceEvents = StorageServiceItemSetEvent;\n\n/**\n * Actions from other messengers that {@link StorageService} calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events from other messengers that {@link StorageService} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events that\n * {@link StorageService} needs to access.\n */\nexport type StorageServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n StorageServiceActions | AllowedActions,\n StorageServiceEvents | AllowedEvents\n>;\n"]}
@@ -0,0 +1,148 @@
1
+ import type { Messenger } from "@metamask/messenger";
2
+ import type { StorageServiceMethodActions } from "./StorageService-method-action-types.cjs";
3
+ /**
4
+ * Platform-agnostic storage adapter interface.
5
+ * Each client (mobile, extension) implements this interface
6
+ * with their preferred storage mechanism.
7
+ *
8
+ * ⚠️ **Designed for large, infrequently accessed data (100KB+)**
9
+ *
10
+ * ✅ **Use for:**
11
+ * - Snap source code (~6 MB per snap)
12
+ * - Token metadata caches (~4 MB)
13
+ * - Large API response caches
14
+ *
15
+ * ❌ **Avoid for:**
16
+ * - Small values (< 10 KB) - use controller state instead
17
+ * - Frequently accessed data - use controller state instead
18
+ * - Many small key-value pairs - use a single large object instead
19
+ *
20
+ * @example Mobile implementation using FilesystemStorage
21
+ * @example Extension implementation using IndexedDB
22
+ * @example Tests using InMemoryStorageAdapter
23
+ */
24
+ export type StorageAdapter = {
25
+ /**
26
+ * Retrieve an item from storage.
27
+ * Adapter is responsible for building the full storage key.
28
+ *
29
+ * @param namespace - The controller namespace (e.g., 'SnapController').
30
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
31
+ * @returns The value as a string, or null if not found.
32
+ */
33
+ getItem(namespace: string, key: string): Promise<unknown>;
34
+ /**
35
+ * Store a large value in storage.
36
+ *
37
+ * ⚠️ **Store large values, not many small ones.**
38
+ * Each storage operation has I/O overhead. For best performance:
39
+ * - Store one large object rather than many small key-value pairs
40
+ * - Minimum recommended size: 100 KB per value
41
+ *
42
+ * Adapter is responsible for:
43
+ * - Building the full storage key
44
+ * - Wrapping value with metadata (timestamp, etc.)
45
+ * - Serializing to string (JSON.stringify)
46
+ *
47
+ * @param namespace - The controller namespace (e.g., 'SnapController').
48
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
49
+ * @param value - The value to store (will be wrapped and serialized by adapter).
50
+ */
51
+ setItem(namespace: string, key: string, value: unknown): Promise<void>;
52
+ /**
53
+ * Remove an item from storage.
54
+ * Adapter is responsible for building the full storage key.
55
+ *
56
+ * @param namespace - The controller namespace (e.g., 'SnapController').
57
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
58
+ */
59
+ removeItem(namespace: string, key: string): Promise<void>;
60
+ /**
61
+ * Get all keys for a specific namespace.
62
+ * Should return keys without the 'storage:namespace:' prefix.
63
+ *
64
+ * Adapter is responsible for:
65
+ * - Filtering keys by prefix: 'storage:{namespace}:'
66
+ * - Stripping the prefix from returned keys
67
+ * - Returning only the key portion after the prefix
68
+ *
69
+ * @param namespace - The namespace to get keys for (e.g., 'SnapController').
70
+ * @returns Array of keys without prefix (e.g., ['snap1:sourceCode', 'snap2:sourceCode']).
71
+ */
72
+ getAllKeys(namespace: string): Promise<string[]>;
73
+ /**
74
+ * Clear all items for a specific namespace.
75
+ *
76
+ * Adapter is responsible for:
77
+ * - Finding all keys with prefix: 'storageService:{namespace}:'
78
+ * - Removing all matching keys
79
+ *
80
+ * @param namespace - The namespace to clear (e.g., 'SnapController').
81
+ */
82
+ clear(namespace: string): Promise<void>;
83
+ };
84
+ /**
85
+ * Options for constructing a {@link StorageService}.
86
+ */
87
+ export type StorageServiceOptions = {
88
+ /**
89
+ * The messenger suited for this service.
90
+ */
91
+ messenger: StorageServiceMessenger;
92
+ /**
93
+ * Storage adapter for persisting data.
94
+ * If not provided, uses in-memory storage (data lost on restart).
95
+ * Production clients MUST provide a persistent storage adapter.
96
+ */
97
+ storage?: StorageAdapter;
98
+ };
99
+ export declare const SERVICE_NAME = "StorageService";
100
+ /**
101
+ * Storage key prefix for all keys managed by StorageService.
102
+ * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}
103
+ * Example: 'storageService:SnapController:snap-id:sourceCode'
104
+ */
105
+ export declare const STORAGE_KEY_PREFIX = "storageService:";
106
+ /**
107
+ * All actions that {@link StorageService} exposes to other consumers.
108
+ * Action types are auto-generated from the service methods.
109
+ */
110
+ export type StorageServiceActions = StorageServiceMethodActions;
111
+ /**
112
+ * Event published when a storage item is set.
113
+ * Event type includes namespace only, key passed in payload.
114
+ *
115
+ * @example
116
+ * Subscribe to all changes in TokenListController:
117
+ * messenger.subscribe('StorageService:itemSet:TokenListController', (value, key) => {
118
+ * // value = the data that was set
119
+ * // key = 'cache:0x1', 'cache:0x38', etc.
120
+ * if (key.startsWith('cache:')) {
121
+ * const chainId = key.replace('cache:', '');
122
+ * // React to cache change for specific chain
123
+ * }
124
+ * });
125
+ */
126
+ export type StorageServiceItemSetEvent = {
127
+ type: `${typeof SERVICE_NAME}:itemSet:${string}`;
128
+ payload: [value: unknown, key: string];
129
+ };
130
+ /**
131
+ * All events that {@link StorageService} publishes.
132
+ */
133
+ export type StorageServiceEvents = StorageServiceItemSetEvent;
134
+ /**
135
+ * Actions from other messengers that {@link StorageService} calls.
136
+ */
137
+ type AllowedActions = never;
138
+ /**
139
+ * Events from other messengers that {@link StorageService} subscribes to.
140
+ */
141
+ type AllowedEvents = never;
142
+ /**
143
+ * The messenger restricted to actions and events that
144
+ * {@link StorageService} needs to access.
145
+ */
146
+ export type StorageServiceMessenger = Messenger<typeof SERVICE_NAME, StorageServiceActions | AllowedActions, StorageServiceEvents | AllowedEvents>;
147
+ export {};
148
+ //# sourceMappingURL=types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,2BAA2B,EAAE,iDAA6C;AAExF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1D;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjD;;;;;;;;OAQG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,SAAS,EAAE,uBAAuB,CAAC;IAEnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAGF,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AAEhE;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,GAAG,OAAO,YAAY,YAAY,MAAM,EAAE,CAAC;IACjD,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE9D;;GAEG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,CAC7C,OAAO,YAAY,EACnB,qBAAqB,GAAG,cAAc,EACtC,oBAAoB,GAAG,aAAa,CACrC,CAAC"}
@@ -0,0 +1,148 @@
1
+ import type { Messenger } from "@metamask/messenger";
2
+ import type { StorageServiceMethodActions } from "./StorageService-method-action-types.mjs";
3
+ /**
4
+ * Platform-agnostic storage adapter interface.
5
+ * Each client (mobile, extension) implements this interface
6
+ * with their preferred storage mechanism.
7
+ *
8
+ * ⚠️ **Designed for large, infrequently accessed data (100KB+)**
9
+ *
10
+ * ✅ **Use for:**
11
+ * - Snap source code (~6 MB per snap)
12
+ * - Token metadata caches (~4 MB)
13
+ * - Large API response caches
14
+ *
15
+ * ❌ **Avoid for:**
16
+ * - Small values (< 10 KB) - use controller state instead
17
+ * - Frequently accessed data - use controller state instead
18
+ * - Many small key-value pairs - use a single large object instead
19
+ *
20
+ * @example Mobile implementation using FilesystemStorage
21
+ * @example Extension implementation using IndexedDB
22
+ * @example Tests using InMemoryStorageAdapter
23
+ */
24
+ export type StorageAdapter = {
25
+ /**
26
+ * Retrieve an item from storage.
27
+ * Adapter is responsible for building the full storage key.
28
+ *
29
+ * @param namespace - The controller namespace (e.g., 'SnapController').
30
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
31
+ * @returns The value as a string, or null if not found.
32
+ */
33
+ getItem(namespace: string, key: string): Promise<unknown>;
34
+ /**
35
+ * Store a large value in storage.
36
+ *
37
+ * ⚠️ **Store large values, not many small ones.**
38
+ * Each storage operation has I/O overhead. For best performance:
39
+ * - Store one large object rather than many small key-value pairs
40
+ * - Minimum recommended size: 100 KB per value
41
+ *
42
+ * Adapter is responsible for:
43
+ * - Building the full storage key
44
+ * - Wrapping value with metadata (timestamp, etc.)
45
+ * - Serializing to string (JSON.stringify)
46
+ *
47
+ * @param namespace - The controller namespace (e.g., 'SnapController').
48
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
49
+ * @param value - The value to store (will be wrapped and serialized by adapter).
50
+ */
51
+ setItem(namespace: string, key: string, value: unknown): Promise<void>;
52
+ /**
53
+ * Remove an item from storage.
54
+ * Adapter is responsible for building the full storage key.
55
+ *
56
+ * @param namespace - The controller namespace (e.g., 'SnapController').
57
+ * @param key - The data key (e.g., 'snap-id:sourceCode').
58
+ */
59
+ removeItem(namespace: string, key: string): Promise<void>;
60
+ /**
61
+ * Get all keys for a specific namespace.
62
+ * Should return keys without the 'storage:namespace:' prefix.
63
+ *
64
+ * Adapter is responsible for:
65
+ * - Filtering keys by prefix: 'storage:{namespace}:'
66
+ * - Stripping the prefix from returned keys
67
+ * - Returning only the key portion after the prefix
68
+ *
69
+ * @param namespace - The namespace to get keys for (e.g., 'SnapController').
70
+ * @returns Array of keys without prefix (e.g., ['snap1:sourceCode', 'snap2:sourceCode']).
71
+ */
72
+ getAllKeys(namespace: string): Promise<string[]>;
73
+ /**
74
+ * Clear all items for a specific namespace.
75
+ *
76
+ * Adapter is responsible for:
77
+ * - Finding all keys with prefix: 'storageService:{namespace}:'
78
+ * - Removing all matching keys
79
+ *
80
+ * @param namespace - The namespace to clear (e.g., 'SnapController').
81
+ */
82
+ clear(namespace: string): Promise<void>;
83
+ };
84
+ /**
85
+ * Options for constructing a {@link StorageService}.
86
+ */
87
+ export type StorageServiceOptions = {
88
+ /**
89
+ * The messenger suited for this service.
90
+ */
91
+ messenger: StorageServiceMessenger;
92
+ /**
93
+ * Storage adapter for persisting data.
94
+ * If not provided, uses in-memory storage (data lost on restart).
95
+ * Production clients MUST provide a persistent storage adapter.
96
+ */
97
+ storage?: StorageAdapter;
98
+ };
99
+ export declare const SERVICE_NAME = "StorageService";
100
+ /**
101
+ * Storage key prefix for all keys managed by StorageService.
102
+ * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}
103
+ * Example: 'storageService:SnapController:snap-id:sourceCode'
104
+ */
105
+ export declare const STORAGE_KEY_PREFIX = "storageService:";
106
+ /**
107
+ * All actions that {@link StorageService} exposes to other consumers.
108
+ * Action types are auto-generated from the service methods.
109
+ */
110
+ export type StorageServiceActions = StorageServiceMethodActions;
111
+ /**
112
+ * Event published when a storage item is set.
113
+ * Event type includes namespace only, key passed in payload.
114
+ *
115
+ * @example
116
+ * Subscribe to all changes in TokenListController:
117
+ * messenger.subscribe('StorageService:itemSet:TokenListController', (value, key) => {
118
+ * // value = the data that was set
119
+ * // key = 'cache:0x1', 'cache:0x38', etc.
120
+ * if (key.startsWith('cache:')) {
121
+ * const chainId = key.replace('cache:', '');
122
+ * // React to cache change for specific chain
123
+ * }
124
+ * });
125
+ */
126
+ export type StorageServiceItemSetEvent = {
127
+ type: `${typeof SERVICE_NAME}:itemSet:${string}`;
128
+ payload: [value: unknown, key: string];
129
+ };
130
+ /**
131
+ * All events that {@link StorageService} publishes.
132
+ */
133
+ export type StorageServiceEvents = StorageServiceItemSetEvent;
134
+ /**
135
+ * Actions from other messengers that {@link StorageService} calls.
136
+ */
137
+ type AllowedActions = never;
138
+ /**
139
+ * Events from other messengers that {@link StorageService} subscribes to.
140
+ */
141
+ type AllowedEvents = never;
142
+ /**
143
+ * The messenger restricted to actions and events that
144
+ * {@link StorageService} needs to access.
145
+ */
146
+ export type StorageServiceMessenger = Messenger<typeof SERVICE_NAME, StorageServiceActions | AllowedActions, StorageServiceEvents | AllowedEvents>;
147
+ export {};
148
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAErD,OAAO,KAAK,EAAE,2BAA2B,EAAE,iDAA6C;AAExF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1D;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;;;;;OAMG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjD;;;;;;;;OAQG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,SAAS,EAAE,uBAAuB,CAAC;IAEnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAGF,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AAEhE;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,GAAG,OAAO,YAAY,YAAY,MAAM,EAAE,CAAC;IACjD,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE9D;;GAEG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,CAC7C,OAAO,YAAY,EACnB,qBAAqB,GAAG,cAAc,EACtC,oBAAoB,GAAG,aAAa,CACrC,CAAC"}
package/dist/types.mjs ADDED
@@ -0,0 +1,9 @@
1
+ // Service name constant
2
+ export const SERVICE_NAME = 'StorageService';
3
+ /**
4
+ * Storage key prefix for all keys managed by StorageService.
5
+ * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}
6
+ * Example: 'storageService:SnapController:snap-id:sourceCode'
7
+ */
8
+ export const STORAGE_KEY_PREFIX = 'storageService:';
9
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2GA,wBAAwB;AACxB,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAE7C;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC","sourcesContent":["import type { Messenger } from '@metamask/messenger';\n\nimport type { StorageServiceMethodActions } from './StorageService-method-action-types';\n\n/**\n * Platform-agnostic storage adapter interface.\n * Each client (mobile, extension) implements this interface\n * with their preferred storage mechanism.\n *\n * ⚠️ **Designed for large, infrequently accessed data (100KB+)**\n *\n * ✅ **Use for:**\n * - Snap source code (~6 MB per snap)\n * - Token metadata caches (~4 MB)\n * - Large API response caches\n *\n * ❌ **Avoid for:**\n * - Small values (< 10 KB) - use controller state instead\n * - Frequently accessed data - use controller state instead\n * - Many small key-value pairs - use a single large object instead\n *\n * @example Mobile implementation using FilesystemStorage\n * @example Extension implementation using IndexedDB\n * @example Tests using InMemoryStorageAdapter\n */\nexport type StorageAdapter = {\n /**\n * Retrieve an item from storage.\n * Adapter is responsible for building the full storage key.\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n * @returns The value as a string, or null if not found.\n */\n getItem(namespace: string, key: string): Promise<unknown>;\n\n /**\n * Store a large value in storage.\n *\n * ⚠️ **Store large values, not many small ones.**\n * Each storage operation has I/O overhead. For best performance:\n * - Store one large object rather than many small key-value pairs\n * - Minimum recommended size: 100 KB per value\n *\n * Adapter is responsible for:\n * - Building the full storage key\n * - Wrapping value with metadata (timestamp, etc.)\n * - Serializing to string (JSON.stringify)\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n * @param value - The value to store (will be wrapped and serialized by adapter).\n */\n setItem(namespace: string, key: string, value: unknown): Promise<void>;\n\n /**\n * Remove an item from storage.\n * Adapter is responsible for building the full storage key.\n *\n * @param namespace - The controller namespace (e.g., 'SnapController').\n * @param key - The data key (e.g., 'snap-id:sourceCode').\n */\n removeItem(namespace: string, key: string): Promise<void>;\n\n /**\n * Get all keys for a specific namespace.\n * Should return keys without the 'storage:namespace:' prefix.\n *\n * Adapter is responsible for:\n * - Filtering keys by prefix: 'storage:{namespace}:'\n * - Stripping the prefix from returned keys\n * - Returning only the key portion after the prefix\n *\n * @param namespace - The namespace to get keys for (e.g., 'SnapController').\n * @returns Array of keys without prefix (e.g., ['snap1:sourceCode', 'snap2:sourceCode']).\n */\n getAllKeys(namespace: string): Promise<string[]>;\n\n /**\n * Clear all items for a specific namespace.\n *\n * Adapter is responsible for:\n * - Finding all keys with prefix: 'storageService:{namespace}:'\n * - Removing all matching keys\n *\n * @param namespace - The namespace to clear (e.g., 'SnapController').\n */\n clear(namespace: string): Promise<void>;\n};\n\n/**\n * Options for constructing a {@link StorageService}.\n */\nexport type StorageServiceOptions = {\n /**\n * The messenger suited for this service.\n */\n messenger: StorageServiceMessenger;\n\n /**\n * Storage adapter for persisting data.\n * If not provided, uses in-memory storage (data lost on restart).\n * Production clients MUST provide a persistent storage adapter.\n */\n storage?: StorageAdapter;\n};\n\n// Service name constant\nexport const SERVICE_NAME = 'StorageService';\n\n/**\n * Storage key prefix for all keys managed by StorageService.\n * Keys are formatted as: {STORAGE_KEY_PREFIX}{namespace}:{key}\n * Example: 'storageService:SnapController:snap-id:sourceCode'\n */\nexport const STORAGE_KEY_PREFIX = 'storageService:';\n\n/**\n * All actions that {@link StorageService} exposes to other consumers.\n * Action types are auto-generated from the service methods.\n */\nexport type StorageServiceActions = StorageServiceMethodActions;\n\n/**\n * Event published when a storage item is set.\n * Event type includes namespace only, key passed in payload.\n *\n * @example\n * Subscribe to all changes in TokenListController:\n * messenger.subscribe('StorageService:itemSet:TokenListController', (value, key) => {\n * // value = the data that was set\n * // key = 'cache:0x1', 'cache:0x38', etc.\n * if (key.startsWith('cache:')) {\n * const chainId = key.replace('cache:', '');\n * // React to cache change for specific chain\n * }\n * });\n */\nexport type StorageServiceItemSetEvent = {\n type: `${typeof SERVICE_NAME}:itemSet:${string}`;\n payload: [value: unknown, key: string];\n};\n\n/**\n * All events that {@link StorageService} publishes.\n */\nexport type StorageServiceEvents = StorageServiceItemSetEvent;\n\n/**\n * Actions from other messengers that {@link StorageService} calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events from other messengers that {@link StorageService} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events that\n * {@link StorageService} needs to access.\n */\nexport type StorageServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n StorageServiceActions | AllowedActions,\n StorageServiceEvents | AllowedEvents\n>;\n"]}