Skip to content

Commit 121a779

Browse files
feat(key-storage): adds key based storage provider (#20)
1 parent a956e2c commit 121a779

File tree

11 files changed

+465
-0
lines changed

11 files changed

+465
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './key-storage';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {TestBed} from "@angular/core/testing";
2+
import {createKeyStorage} from "./create-key-storage";
3+
import {KeyStorage} from "./key-storage";
4+
import {InMemoryStore, LocalStorageStore} from "./stores";
5+
6+
describe('Create Key Storage', () => {
7+
8+
const key = 'key';
9+
const defaultValue = 'default-value';
10+
11+
it('should return an Angular injection token', () => {
12+
const token = createKeyStorage(key, defaultValue);
13+
const store = TestBed.inject(token);
14+
expect(store).toBeInstanceOf(KeyStorage);
15+
});
16+
17+
it('should configure the key storage correctly', () => {
18+
const token = createKeyStorage(key, defaultValue);
19+
const store = TestBed.inject(token);
20+
expect(store['_key']).toBe(key)
21+
expect(store['_defaultValue']).toBe(defaultValue)
22+
});
23+
24+
it('should configure the key storage to use the localStorageStore by default', () => {
25+
const token = createKeyStorage(key, defaultValue);
26+
const store = TestBed.inject(token);
27+
expect(store['_store']).toBeInstanceOf(LocalStorageStore)
28+
});
29+
30+
it('should configure the key storage to use the localStorage Store', () => {
31+
const token = createKeyStorage(key, defaultValue, );
32+
const store = TestBed.inject(token);
33+
expect(store['_store']).toBeInstanceOf(LocalStorageStore)
34+
});
35+
36+
it('should configure the key storage to use the InMemory Store', () => {
37+
const token = createKeyStorage(key, defaultValue, new InMemoryStore());
38+
const store = TestBed.inject(token);
39+
expect(store['_store']).toBeInstanceOf(InMemoryStore)
40+
});
41+
42+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {InjectionToken} from "@angular/core";
2+
import {KeyStorage} from "./key-storage";
3+
import {localStorageStore} from "./stores";
4+
5+
export const createKeyStorage = <T = unknown>(
6+
key: string,
7+
defaultValue: T,
8+
store: Storage = localStorageStore,
9+
) =>
10+
new InjectionToken(
11+
`storage-key-${key}`, {
12+
providedIn: 'root',
13+
factory: () => new KeyStorage<T>(key, defaultValue, store)
14+
});
15+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './stores';
2+
export * from './key-storage';
3+
export * from './create-key-storage';
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {KeyStorage} from "./key-storage";
2+
import {InMemoryStore} from "./stores";
3+
4+
describe('Key Storage', () => {
5+
6+
const key = 'key';
7+
const defaultValue = 'default';
8+
9+
let store: KeyStorage;
10+
11+
beforeEach(() => {
12+
store = new KeyStorage(
13+
key,
14+
defaultValue,
15+
new InMemoryStore(),
16+
);
17+
});
18+
19+
it('should return a stored record', () => {
20+
const record = 'value';
21+
store.setItem(record);
22+
23+
const returnedRecord = store.getItem();
24+
expect(returnedRecord).toEqual(record);
25+
});
26+
27+
it('should overwrite the value', () => {
28+
const record1 = 'value';
29+
const record2 = 'value-2';
30+
31+
store.setItem(record1);
32+
store.setItem(record2);
33+
34+
const returnedRecord = store.getItem();
35+
expect(returnedRecord).toEqual(record2);
36+
});
37+
38+
it('should return the default value if the record doesnt exist', () => {
39+
const returnedRecord = store.getItem();
40+
expect(returnedRecord).toBe(defaultValue);
41+
});
42+
43+
it('should return the default value for removed records', () => {
44+
const record = 'value';
45+
store.setItem(record);
46+
store.removeItem();
47+
48+
const returnedRecord = store.getItem();
49+
expect(returnedRecord).toEqual(defaultValue);
50+
});
51+
52+
})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export class KeyStorage<T = unknown> {
2+
3+
constructor(
4+
private _key: string,
5+
private _defaultValue: T,
6+
private _store: Storage,
7+
) {
8+
}
9+
10+
getItem(): T {
11+
const data = this._store.getItem(this._key);
12+
return data
13+
? JSON.parse(data)
14+
: this._defaultValue;
15+
}
16+
17+
setItem(value: T): void {
18+
const data = JSON.stringify(value);
19+
this._store.setItem(this._key, data);
20+
}
21+
22+
removeItem(): void {
23+
this._store.removeItem(this._key);
24+
}
25+
26+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {InMemoryStore} from "./in-memory.store";
2+
3+
describe('In Memory Store', () => {
4+
5+
let store: InMemoryStore;
6+
7+
beforeEach(() => {
8+
store = new InMemoryStore();
9+
});
10+
11+
it('should return the size of the store', () => {
12+
const key0 = 'key-0';
13+
const record0 = 'value';
14+
15+
const key1 = 'key-1';
16+
const record1 = 'value-1';
17+
18+
store.setItem(key0, record0);
19+
store.setItem(key1, record1);
20+
21+
const size = store.length;
22+
23+
expect(size).toEqual(2);
24+
});
25+
26+
it('should store a record', () => {
27+
const key = 'key';
28+
const record = 'value';
29+
store.setItem(key, record);
30+
expect(store.length).toBe(1);
31+
});
32+
33+
it('should return a stored record', () => {
34+
const key = 'key';
35+
const record = 'value';
36+
store.setItem(key, record);
37+
38+
const returnedRecord = store.getItem(key);
39+
expect(returnedRecord).toEqual(record);
40+
});
41+
42+
it('should respect the integrity of individual records', () => {
43+
const key1 = 'key';
44+
const record1 = 'value';
45+
46+
const key2 = 'key-2';
47+
const record2 = 'value-2';
48+
49+
store.setItem(key1, record1);
50+
store.setItem(key2, record2);
51+
52+
const returnedRecord1 = store.getItem(key1);
53+
const returnedRecord2 = store.getItem(key2);
54+
55+
expect(returnedRecord1).toEqual(record1);
56+
expect(returnedRecord2).toEqual(record2);
57+
});
58+
59+
it('should return null if a record doesnt exist', () => {
60+
const key = 'key';
61+
const returnedRecord = store.getItem(key);
62+
expect(returnedRecord).toBeNull();
63+
});
64+
65+
it('should remove records', () => {
66+
const key = 'key';
67+
const record = 'value';
68+
store.setItem(key, record);
69+
70+
expect(store.length).toBe(1);
71+
72+
store.removeItem(key);
73+
expect(store.length).toBe(0);
74+
});
75+
76+
it('should return null for removed records', () => {
77+
const key = 'key';
78+
const record = 'value';
79+
store.setItem(key, record);
80+
81+
expect(store.length).toBe(1);
82+
83+
store.removeItem(key);
84+
const returnedRecord = store.getItem(key);
85+
expect(returnedRecord).toBeNull();
86+
});
87+
88+
it('should return the zero indexed key for index 1', () => {
89+
const key0 = 'key-0';
90+
const record0 = 'value';
91+
92+
const key1 = 'key-1';
93+
const record1 = 'value-1';
94+
95+
store.setItem(key0, record0);
96+
store.setItem(key1, record1);
97+
98+
const returnedKey = store.key(1);
99+
expect(returnedKey).toEqual(key1);
100+
101+
});
102+
103+
it('should remove all keys', () => {
104+
const key0 = 'key-0';
105+
const record0 = 'value';
106+
107+
const key1 = 'key-1';
108+
const record1 = 'value-1';
109+
110+
store.setItem(key0, record0);
111+
store.setItem(key1, record1);
112+
113+
store.clear();
114+
const size = store.length;
115+
116+
expect(size).toEqual(0);
117+
});
118+
119+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export class InMemoryStore implements Storage {
2+
private store = new Map<string, string>()
3+
4+
get length(): number {
5+
return this.store.size;
6+
}
7+
8+
key(index: number): string | null {
9+
const keys = Object.keys(Object.fromEntries(this.store));
10+
return keys[index] || null;
11+
}
12+
13+
getItem(key: string): string | null {
14+
return this.store.get(key) || null;
15+
}
16+
17+
setItem(key: string, value: string): void {
18+
this.store.set(key, value);
19+
}
20+
21+
removeItem(key: string): void {
22+
this.store.delete(key);
23+
}
24+
25+
clear(): void {
26+
this.store.clear();
27+
}
28+
}
29+
30+
export const inMemoryStore = new InMemoryStore();
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './in-memory.store';
2+
export * from './local-storage.store';

0 commit comments

Comments
 (0)