Skip to content

Commit 2a06610

Browse files
committed
feat: 🎸 Added scope to the storage interface
1 parent e8a2af6 commit 2a06610

File tree

3 files changed

+218
-12
lines changed

3 files changed

+218
-12
lines changed

‎src/storage/abstract.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export abstract class Storage {
1515
>;
1616
}
1717

18-
export type GenericStorageOptions = Record<string, unknown>;
18+
export interface GenericStorageOptions {
19+
scope?: string;
20+
}
1921

2022
/**
2123
* Storage initializer type

‎src/storage/local.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { stringify } from 'viem';
2-
import { Storage, StorageInitializerFunction } from './abstract.js';
1+
import { stringify, parse } from 'superjson';
2+
import {
3+
GenericStorageOptions,
4+
Storage,
5+
StorageInitializerFunction,
6+
} from './abstract.js';
37
import { createLogger } from '../utils/logger.js';
48

59
const logger = createLogger('LocalStorage');
@@ -18,7 +22,7 @@ export interface WindowStorage {
1822
/**
1923
* Local storage options type
2024
*/
21-
export interface LocalStorageOptions {
25+
export interface LocalStorageOptions extends GenericStorageOptions {
2226
session?: boolean;
2327
}
2428

@@ -30,6 +34,7 @@ export interface LocalStorageOptions {
3034
*/
3135
export class LocalStorage extends Storage {
3236
db: WindowStorage;
37+
scopeIdsKey?: string;
3338

3439
/**
3540
* Creates an instance of LocalStorage.
@@ -44,6 +49,11 @@ export class LocalStorage extends Storage {
4449
// @todo Validate LocalStorageOptions
4550

4651
this.db = options.session ? sessionStorage : localStorage;
52+
53+
if (options.scope) {
54+
this.scopeIdsKey = `local_storage_scope_${options.scope}_ids`;
55+
}
56+
4757
logger.trace('Local storage initialized');
4858
}
4959

@@ -55,7 +65,7 @@ export class LocalStorage extends Storage {
5565
* @returns {string}
5666
* @memberof LocalStorage
5767
*/
58-
serialize<ValueType>(value: ValueType): string {
68+
private serialize<ValueType>(value: ValueType): string {
5969
return stringify(value);
6070
}
6171
/**
@@ -66,8 +76,54 @@ export class LocalStorage extends Storage {
6676
* @returns {ValueType}
6777
* @memberof LocalStorage
6878
*/
69-
deserialize<ValueType>(value: string): ValueType {
70-
return JSON.parse(value) as ValueType;
79+
private deserialize<ValueType>(value: string): ValueType {
80+
return parse<ValueType>(value);
81+
}
82+
83+
private getScopeIds(): Set<string> {
84+
if (!this.scopeIdsKey) {
85+
return new Set();
86+
}
87+
88+
return new Set(
89+
this.deserialize<string[]>(this.db.getItem(this.scopeIdsKey) ?? '[]'),
90+
);
91+
}
92+
93+
private saveScopeIds(ids: Set<string>) {
94+
if (!this.scopeIdsKey) {
95+
return;
96+
}
97+
98+
this.db.setItem(this.scopeIdsKey, this.serialize(Array.from(ids)));
99+
}
100+
101+
private addScopeId(id: string) {
102+
try {
103+
if (!this.scopeIdsKey) {
104+
return;
105+
}
106+
107+
const ids = this.getScopeIds();
108+
ids.add(id);
109+
this.saveScopeIds(ids);
110+
} catch (error) {
111+
logger.error('addScopeId', error);
112+
}
113+
}
114+
115+
private deleteScopeId(id: string) {
116+
try {
117+
if (!this.scopeIdsKey) {
118+
return;
119+
}
120+
121+
const ids = this.getScopeIds();
122+
ids.delete(id);
123+
this.saveScopeIds(ids);
124+
} catch (error) {
125+
logger.error('addScopeId', error);
126+
}
71127
}
72128

73129
/**
@@ -81,6 +137,7 @@ export class LocalStorage extends Storage {
81137
// eslint-disable-next-line @typescript-eslint/require-await
82138
async set<ValueType>(key: string, value: ValueType) {
83139
this.db.setItem(key, this.serialize(value));
140+
this.addScopeId(key);
84141
}
85142

86143
/**
@@ -94,9 +151,11 @@ export class LocalStorage extends Storage {
94151
// eslint-disable-next-line @typescript-eslint/require-await
95152
async get<ValueType>(key: string): Promise<ValueType | undefined> {
96153
const value = this.db.getItem(key);
154+
97155
if (value !== null) {
98156
return this.deserialize<ValueType>(value);
99157
}
158+
100159
return;
101160
}
102161

@@ -110,7 +169,13 @@ export class LocalStorage extends Storage {
110169
// eslint-disable-next-line @typescript-eslint/require-await
111170
async delete(key: string): Promise<boolean> {
112171
this.db.removeItem(key);
113-
return this.db.getItem(key) === null;
172+
const isDeleted = this.db.getItem(key) === null;
173+
174+
if (isDeleted) {
175+
this.deleteScopeId(key);
176+
}
177+
178+
return isDeleted;
114179
}
115180

116181
/**
@@ -123,13 +188,19 @@ export class LocalStorage extends Storage {
123188
entries<ValueType>(): IterableIterator<[string, ValueType]> {
124189
// eslint-disable-next-line @typescript-eslint/no-this-alias
125190
const source = this;
191+
const scopeEnabled = Boolean(this.scopeIdsKey);
192+
const ids = this.getScopeIds();
126193

127194
function* entriesIterator(): IterableIterator<[string, ValueType]> {
128195
let index = 0;
129196

130197
while (index < source.db.length) {
131198
const key = source.db.key(index++);
132199

200+
if (scopeEnabled && key && !ids.has(key)) {
201+
continue;
202+
}
203+
133204
if (key === null) {
134205
return;
135206
}

‎src/storage/memory.ts

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { Storage, StorageInitializerFunction } from './abstract.js';
1+
import {
2+
GenericStorageOptions,
3+
Storage,
4+
StorageInitializerFunction,
5+
} from './abstract.js';
26
import { createLogger } from '../utils/logger.js';
37

48
const logger = createLogger('MemoryStorage');
59

610
/**
711
* Memory storage options type
812
*/
9-
export interface MemoryStorageOptions {
13+
export interface MemoryStorageOptions extends GenericStorageOptions {
1014
entries?: Array<[string, unknown]>;
1115
}
1216

@@ -19,6 +23,8 @@ export interface MemoryStorageOptions {
1923
export class MemoryStorage extends Storage {
2024
/** Map as in-memory key-value storage */
2125
private db: Map<string, unknown>;
26+
/** Key for storing ids included in the scope */
27+
scopeIdsKey?: string;
2228

2329
/**
2430
* Creates an instance of MemoryStorage.
@@ -33,9 +39,102 @@ export class MemoryStorage extends Storage {
3339
// Validate MemoryStorageOptions
3440

3541
this.db = new Map<string, unknown>(options?.entries);
42+
43+
if (options.scope) {
44+
this.scopeIdsKey = `memory_storage_scope_${options.scope}_ids`;
45+
}
46+
3647
logger.trace('Memory storage initialized');
3748
}
3849

50+
/**
51+
* Retrieves Ids from scope
52+
*
53+
* @private
54+
* @returns {Set<string>}
55+
* @memberof MemoryStorage
56+
*/
57+
private getScopeIds(): Set<string> {
58+
if (!this.scopeIdsKey) {
59+
return new Set<string>();
60+
}
61+
62+
return (this.db.get(this.scopeIdsKey) as Set<string>) ?? new Set<string>();
63+
}
64+
65+
/**
66+
* Saves Ids to scope
67+
*
68+
* @private
69+
* @param {Set<string>} ids
70+
* @returns
71+
* @memberof MemoryStorage
72+
*/
73+
private saveScopeIds(ids: Set<string>) {
74+
if (!this.scopeIdsKey) {
75+
return;
76+
}
77+
78+
this.db.set(this.scopeIdsKey, ids);
79+
}
80+
81+
/**
82+
* Adds Id to scope
83+
*
84+
* @private
85+
* @param {string} id
86+
* @returns
87+
* @memberof MemoryStorage
88+
*/
89+
private addScopeId(id: string) {
90+
try {
91+
if (!this.scopeIdsKey) {
92+
return;
93+
}
94+
95+
const ids = this.getScopeIds();
96+
ids.add(id);
97+
this.saveScopeIds(ids);
98+
} catch (error) {
99+
logger.error('addScopeId', error);
100+
}
101+
}
102+
103+
/**
104+
* Deletes Id from scope
105+
*
106+
* @private
107+
* @param {string} id
108+
* @returns
109+
* @memberof MemoryStorage
110+
*/
111+
private deleteScopeId(id: string) {
112+
try {
113+
if (!this.scopeIdsKey) {
114+
return;
115+
}
116+
117+
const ids = this.getScopeIds();
118+
ids.delete(id);
119+
this.saveScopeIds(ids);
120+
} catch (error) {
121+
logger.error('addScopeId', error);
122+
}
123+
}
124+
125+
/**
126+
* Resets the db
127+
*
128+
* @internal
129+
*
130+
* @template ValueType
131+
* @memberof MemoryStorage
132+
*/
133+
// eslint-disable-next-line @typescript-eslint/require-await
134+
async reset<ValueType>() {
135+
this.db = new Map<string, ValueType>();
136+
}
137+
39138
/**
40139
* Sets the key to the storage
41140
*
@@ -47,6 +146,7 @@ export class MemoryStorage extends Storage {
47146
// eslint-disable-next-line @typescript-eslint/require-await
48147
async set<ValueType>(key: string, value: ValueType) {
49148
this.db.set(key, value);
149+
this.addScopeId(key);
50150
}
51151

52152
/**
@@ -71,7 +171,13 @@ export class MemoryStorage extends Storage {
71171
*/
72172
// eslint-disable-next-line @typescript-eslint/require-await
73173
async delete(key: string) {
74-
return this.db.delete(key);
174+
const isDeleted = this.db.delete(key);
175+
176+
if (isDeleted) {
177+
this.deleteScopeId(key);
178+
}
179+
180+
return isDeleted;
75181
}
76182

77183
/**
@@ -82,7 +188,34 @@ export class MemoryStorage extends Storage {
82188
* @memberof MemoryStorage
83189
*/
84190
entries<ValueType>(): IterableIterator<[string, ValueType]> {
85-
return this.db.entries() as IterableIterator<[string, ValueType]>;
191+
// eslint-disable-next-line @typescript-eslint/no-this-alias
192+
const source = this;
193+
const scopeEnabled = Boolean(this.scopeIdsKey);
194+
const ids = Array.from(this.getScopeIds());
195+
196+
function* entriesIterator(): IterableIterator<[string, ValueType]> {
197+
let index = 0;
198+
199+
while (index < source.db.size) {
200+
const key = ids[index++];
201+
202+
if (!key) {
203+
return;
204+
}
205+
206+
const value = source.db.get(key) as ValueType | undefined;
207+
208+
if (!value) {
209+
return;
210+
}
211+
212+
yield [key, value];
213+
}
214+
}
215+
216+
return scopeEnabled
217+
? entriesIterator()
218+
: (this.db.entries() as IterableIterator<[string, ValueType]>);
86219
}
87220
}
88221

0 commit comments

Comments
 (0)