Skip to content

Commit 223001d

Browse files
committed
refactor(core): HawkStorage abstraction added
1 parent 39d9d88 commit 223001d

File tree

7 files changed

+215
-3
lines changed

7 files changed

+215
-3
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type { HawkStorage } from './storages/hawk-storage';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Abstract key–value storage contract used by Hawk internals to persist data across sessions.
3+
*/
4+
export interface HawkStorage {
5+
/**
6+
* Returns the value associated with the given key, or `null` if none exists.
7+
*
8+
* @param key - Storage key to look up.
9+
*/
10+
getItem(key: string): string | null
11+
12+
/**
13+
* Persists a value under the given key.
14+
*
15+
* @param key - Storage key.
16+
* @param value - Value to store.
17+
*/
18+
setItem(key: string, value: string): void
19+
20+
/**
21+
* Removes the entry for the given key.
22+
*
23+
* @param key - Storage key to remove.
24+
*/
25+
removeItem(key: string): void
26+
}

packages/javascript/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"build": "vite build",
2222
"stats": "size-limit > stats.txt",
2323
"test": "vitest run",
24+
"test:coverage": "vitest run --coverage",
2425
"test:watch": "vitest",
2526
"lint": "eslint --fix \"src/**/*.{js,ts}\""
2627
},
@@ -43,6 +44,7 @@
4344
},
4445
"devDependencies": {
4546
"@hawk.so/types": "0.5.8",
47+
"@vitest/coverage-v8": "^4.0.18",
4648
"jsdom": "^28.0.0",
4749
"vite": "^7.3.1",
4850
"vite-plugin-dts": "^4.2.4",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { HawkStorage } from '@hawk.so/core';
2+
3+
/**
4+
* {@link HawkStorage} implementation backed by the browser's {@linkcode localStorage}.
5+
*/
6+
export class HawkLocalStorage implements HawkStorage {
7+
/** @inheritDoc */
8+
public getItem(key: string): string | null {
9+
return localStorage.getItem(key);
10+
}
11+
12+
/** @inheritDoc */
13+
public setItem(key: string, value: string): void {
14+
localStorage.setItem(key, value);
15+
}
16+
17+
/** @inheritDoc */
18+
public removeItem(key: string): void {
19+
localStorage.removeItem(key);
20+
}
21+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { beforeEach, afterEach, describe, it, expect, vi, Mock } from "vitest";
2+
import { HawkLocalStorage } from "../../src/storages/hawk-local-storage";
3+
4+
describe('HawkLocalStorage', () => {
5+
let getItemSpy: Mock<(key: string) => string | null>;
6+
let setItemSpy: Mock<(key: string, value: string) => void>;
7+
let removeItemSpy: Mock<(key: string) => void>;
8+
let storage: HawkLocalStorage;
9+
10+
beforeEach(() => {
11+
localStorage.clear();
12+
storage = new HawkLocalStorage();
13+
getItemSpy = vi.spyOn(Storage.prototype, 'getItem');
14+
setItemSpy = vi.spyOn(Storage.prototype, 'setItem');
15+
removeItemSpy = vi.spyOn(Storage.prototype, 'removeItem');
16+
});
17+
18+
afterEach(() => {
19+
vi.restoreAllMocks();
20+
});
21+
22+
it('should return null when key does not exist', () => {
23+
expect(storage.getItem('foo')).toBeNull();
24+
expect(getItemSpy).toHaveBeenCalledOnce();
25+
});
26+
27+
it('should return value when key exists in storage', () => {
28+
localStorage.setItem('foo', 'bar');
29+
30+
expect(storage.getItem('foo')).toEqual('bar');
31+
expect(getItemSpy).toHaveBeenCalledWith('foo');
32+
});
33+
34+
it('should persist item via setItem()', () => {
35+
storage.setItem('foo', 'bar');
36+
37+
expect(setItemSpy).toHaveBeenCalledWith('foo', 'bar');
38+
expect(localStorage.getItem('foo')).toEqual('bar');
39+
});
40+
41+
it('should remove item via removeItem()', () => {
42+
localStorage.setItem('foo', 'bar');
43+
storage.removeItem('foo');
44+
45+
expect(removeItemSpy).toHaveBeenCalledWith('foo');
46+
expect(localStorage.getItem('foo')).toBeNull();
47+
});
48+
49+
it('should not affect other keys when removing', () => {
50+
localStorage.setItem('foo', 'bar');
51+
storage.removeItem('baz');
52+
53+
expect(removeItemSpy).toHaveBeenCalledWith('baz');
54+
expect(localStorage.getItem('foo')).toEqual('bar');
55+
});
56+
});

packages/javascript/tsconfig.test.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"extends": "./tsconfig.json",
33
"compilerOptions": {
44
"outDir": null,
5-
"declaration": false
5+
"declaration": false,
6+
"types": ["vitest/globals"]
67
},
78
"include": [
89
"src/**/*",

yarn.lock

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ __metadata:
8080
languageName: node
8181
linkType: hard
8282

83-
"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.28.5":
83+
"@babel/parser@npm:^7.23.5, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.29.0":
8484
version: 7.29.0
8585
resolution: "@babel/parser@npm:7.29.0"
8686
dependencies:
@@ -101,6 +101,13 @@ __metadata:
101101
languageName: node
102102
linkType: hard
103103

104+
"@bcoe/v8-coverage@npm:^1.0.2":
105+
version: 1.0.2
106+
resolution: "@bcoe/v8-coverage@npm:1.0.2"
107+
checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3
108+
languageName: node
109+
linkType: hard
110+
104111
"@csstools/color-helpers@npm:^6.0.1":
105112
version: 6.0.1
106113
resolution: "@csstools/color-helpers@npm:6.0.1"
@@ -590,6 +597,7 @@ __metadata:
590597
resolution: "@hawk.so/javascript@workspace:packages/javascript"
591598
dependencies:
592599
"@hawk.so/types": "npm:0.5.8"
600+
"@vitest/coverage-v8": "npm:^4.0.18"
593601
error-stack-parser: "npm:^2.1.4"
594602
jsdom: "npm:^28.0.0"
595603
vite: "npm:^7.3.1"
@@ -708,7 +716,7 @@ __metadata:
708716
languageName: node
709717
linkType: hard
710718

711-
"@jridgewell/trace-mapping@npm:^0.3.24":
719+
"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.31":
712720
version: 0.3.31
713721
resolution: "@jridgewell/trace-mapping@npm:0.3.31"
714722
dependencies:
@@ -1385,6 +1393,30 @@ __metadata:
13851393
languageName: node
13861394
linkType: hard
13871395

1396+
"@vitest/coverage-v8@npm:^4.0.18":
1397+
version: 4.0.18
1398+
resolution: "@vitest/coverage-v8@npm:4.0.18"
1399+
dependencies:
1400+
"@bcoe/v8-coverage": "npm:^1.0.2"
1401+
"@vitest/utils": "npm:4.0.18"
1402+
ast-v8-to-istanbul: "npm:^0.3.10"
1403+
istanbul-lib-coverage: "npm:^3.2.2"
1404+
istanbul-lib-report: "npm:^3.0.1"
1405+
istanbul-reports: "npm:^3.2.0"
1406+
magicast: "npm:^0.5.1"
1407+
obug: "npm:^2.1.1"
1408+
std-env: "npm:^3.10.0"
1409+
tinyrainbow: "npm:^3.0.3"
1410+
peerDependencies:
1411+
"@vitest/browser": 4.0.18
1412+
vitest: 4.0.18
1413+
peerDependenciesMeta:
1414+
"@vitest/browser":
1415+
optional: true
1416+
checksum: 10c0/e23e0da86f0b2a020c51562bc40ebdc7fc7553c24f8071dfb39a6df0161badbd5eaf2eebbf8ceaef18933a18c1934ff52d1c0c4bde77bb87e0c1feb0c8cbee4d
1417+
languageName: node
1418+
linkType: hard
1419+
13881420
"@vitest/expect@npm:4.0.18":
13891421
version: 4.0.18
13901422
resolution: "@vitest/expect@npm:4.0.18"
@@ -1846,6 +1878,17 @@ __metadata:
18461878
languageName: node
18471879
linkType: hard
18481880

1881+
"ast-v8-to-istanbul@npm:^0.3.10":
1882+
version: 0.3.11
1883+
resolution: "ast-v8-to-istanbul@npm:0.3.11"
1884+
dependencies:
1885+
"@jridgewell/trace-mapping": "npm:^0.3.31"
1886+
estree-walker: "npm:^3.0.3"
1887+
js-tokens: "npm:^10.0.0"
1888+
checksum: 10c0/0667dcb5f42bd16f5d50b8687f3471f9b9d000ea7f8808c3cd0ddabc1ef7d5b1a61e19f498d5ca7b1285e6c185e11d0ae724c4f9291491b50b6340110ce63108
1889+
languageName: node
1890+
linkType: hard
1891+
18491892
"astral-regex@npm:^2.0.0":
18501893
version: 2.0.0
18511894
resolution: "astral-regex@npm:2.0.0"
@@ -3602,6 +3645,13 @@ __metadata:
36023645
languageName: node
36033646
linkType: hard
36043647

3648+
"html-escaper@npm:^2.0.0":
3649+
version: 2.0.2
3650+
resolution: "html-escaper@npm:2.0.2"
3651+
checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0
3652+
languageName: node
3653+
linkType: hard
3654+
36053655
"http-cache-semantics@npm:^4.1.1":
36063656
version: 4.2.0
36073657
resolution: "http-cache-semantics@npm:4.2.0"
@@ -3997,6 +4047,34 @@ __metadata:
39974047
languageName: node
39984048
linkType: hard
39994049

4050+
"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2":
4051+
version: 3.2.2
4052+
resolution: "istanbul-lib-coverage@npm:3.2.2"
4053+
checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b
4054+
languageName: node
4055+
linkType: hard
4056+
4057+
"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1":
4058+
version: 3.0.1
4059+
resolution: "istanbul-lib-report@npm:3.0.1"
4060+
dependencies:
4061+
istanbul-lib-coverage: "npm:^3.0.0"
4062+
make-dir: "npm:^4.0.0"
4063+
supports-color: "npm:^7.1.0"
4064+
checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7
4065+
languageName: node
4066+
linkType: hard
4067+
4068+
"istanbul-reports@npm:^3.2.0":
4069+
version: 3.2.0
4070+
resolution: "istanbul-reports@npm:3.2.0"
4071+
dependencies:
4072+
html-escaper: "npm:^2.0.0"
4073+
istanbul-lib-report: "npm:^3.0.0"
4074+
checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc
4075+
languageName: node
4076+
linkType: hard
4077+
40004078
"jiti@npm:^2.4.2":
40014079
version: 2.6.1
40024080
resolution: "jiti@npm:2.6.1"
@@ -4013,6 +4091,13 @@ __metadata:
40134091
languageName: node
40144092
linkType: hard
40154093

4094+
"js-tokens@npm:^10.0.0":
4095+
version: 10.0.0
4096+
resolution: "js-tokens@npm:10.0.0"
4097+
checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64
4098+
languageName: node
4099+
linkType: hard
4100+
40164101
"js-tokens@npm:^4.0.0":
40174102
version: 4.0.0
40184103
resolution: "js-tokens@npm:4.0.0"
@@ -4249,6 +4334,26 @@ __metadata:
42494334
languageName: node
42504335
linkType: hard
42514336

4337+
"magicast@npm:^0.5.1":
4338+
version: 0.5.2
4339+
resolution: "magicast@npm:0.5.2"
4340+
dependencies:
4341+
"@babel/parser": "npm:^7.29.0"
4342+
"@babel/types": "npm:^7.29.0"
4343+
source-map-js: "npm:^1.2.1"
4344+
checksum: 10c0/924af677643c5a0a7d6cdb3247c0eb96fa7611b2ba6a5e720d35d81c503d3d9f5948eb5227f80f90f82ea3e7d38cffd10bb988f3fc09020db428e14f26e960d7
4345+
languageName: node
4346+
linkType: hard
4347+
4348+
"make-dir@npm:^4.0.0":
4349+
version: 4.0.0
4350+
resolution: "make-dir@npm:4.0.0"
4351+
dependencies:
4352+
semver: "npm:^7.5.3"
4353+
checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68
4354+
languageName: node
4355+
linkType: hard
4356+
42524357
"make-fetch-happen@npm:^15.0.0":
42534358
version: 15.0.3
42544359
resolution: "make-fetch-happen@npm:15.0.3"

0 commit comments

Comments
 (0)