Skip to content

Commit dc831e1

Browse files
authored
feat: Added storage utility functions (#59)
* feat: add storage util functions * feat: add storage test code * feat: add storage description on readme * fix: readme storage descriptions
1 parent 5d2c01f commit dc831e1

4 files changed

Lines changed: 192 additions & 1 deletion

File tree

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A comprehensive collection of TypeScript utility functions for modern web develo
44

55
## Features
66

7-
- 🛠️ **Comprehensive**: String, object, cookie, number, validation, format, search query, device, type and common utilities
7+
- 🛠️ **Comprehensive**: String, object, cookie, number, validation, format, search query, device, type, storage and common utilities
88
- 📦 **Tree-shakable**: Import only what you need
99
- 🔒 **Type-safe**: Full TypeScript support with type definitions
1010
-**Lightweight**: Minimal dependencies and optimized for performance
@@ -76,6 +76,11 @@ const encoded = commonUtil.encodeBase64("Hello 한글!"); // Base64 encoded stri
7676
const decoded = commonUtil.decodeBase64(encoded); // "Hello 한글!"
7777
const debouncedFn = commonUtil.debounce(() => console.log("Called!"), 300); // Debounced function
7878

79+
// Storage
80+
commonUtil.storage.set("user", { id: 1, name: "John" }); // Stores object in localStorage
81+
const user = commonUtil.storage.get<{ id: number; name: string }>("user"); // Retrieves typed object
82+
commonUtil.storage.remove("user"); // Removes item from localStorage
83+
7984
// Search Query utilities
8085
const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" }
8186

@@ -107,11 +112,13 @@ import { clearNullProperties, deepFreeze } from "kr-corekit";
107112
import { escapeHtml } from "kr-corekit/stringUtil";
108113
import { sum } from "kr-corekit/numberUtil";
109114
import { clearNullProperties } from "kr-corekit/objectUtil";
115+
import { storage } from "kr-corekit/commonUtil";
110116

111117
// Usage remains the same
112118
const escaped = escapeHtml("<div>Hello</div>");
113119
const total = sum(1, 2, 3, 4, 5);
114120
const cleaned = clearNullProperties({ a: 1, b: null, c: 3 });
121+
storage.set("data", { key: "value" });
115122
```
116123

117124
### Bundle Size Comparison
@@ -151,6 +158,19 @@ const cleaned = clearNullProperties({ a: 1, b: null, c: 3 });
151158
- `checkBase64(value: string): boolean` - Validates whether a string is a valid base64 encoded value
152159
- `checkPassword(password: string, options?: { minLength?: number; maxLength?: number; requireUppercase?: boolean; requireLowercase?: boolean; requireNumber?: boolean; requireSpecialChar?: boolean }): boolean` - Validates password strength and requirements
153160

161+
### StorageUtil
162+
163+
- `set<T>(key: string, value: T): void` - Stores a value in localStorage with automatic JSON serialization. Supports objects, arrays, and primitive types. Safe for SSR environments.
164+
- `get<T>(key: string): T | null` - Retrieves a value from localStorage with automatic JSON parsing. Returns null if key doesn't exist or parsing fails. Type-safe with generic support.
165+
- `remove(key: string): void` - Removes a specific item from localStorage. Safe for SSR environments.
166+
167+
**Features:**
168+
169+
- 🔒 **SSR Safe**: All methods handle server-side rendering environments gracefully
170+
- 📦 **Type Safe**: Full TypeScript support with generics
171+
- 🛡️ **Error Handling**: Comprehensive error handling with automatic cleanup of corrupted data
172+
- 🔄 **Auto Serialization**: Automatic JSON serialization/deserialization for complex data types
173+
154174
### CommonUtil
155175

156176
- `isEmpty(value: unknown): boolean` - Checks if a value is empty (null, undefined, "", 0, [], {}, empty Set/Map, NaN, or invalid Date)
@@ -160,6 +180,16 @@ const cleaned = clearNullProperties({ a: 1, b: null, c: 3 });
160180
- `encodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Encodes a string to Base64 format with optional special character handling
161181
- `decodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Decodes a Base64 string back to original text with optional special character handling
162182
- `debounce<T>(fn: T, delay?: number): (...args: Parameters<T>) => void` - Creates a debounced function that delays execution until after a specified delay (default 300ms) has passed since its last invocation
183+
- `storage.set<T>(key: string, value: T): void` - Stores a value in localStorage with automatic JSON serialization. Supports objects, arrays, and primitive types. Safe for SSR environments.
184+
- `storage.get<T>(key: string): T | null` - Retrieves a value from localStorage with automatic JSON parsing. Returns null if key doesn't exist or parsing fails. Type-safe with generic support.
185+
- `storage.remove(key: string): void` - Removes a specific item from localStorage. Safe for SSR environments.
186+
187+
**Storage Features:**
188+
189+
- 🔒 **SSR Safe**: All methods handle server-side rendering environments gracefully
190+
- 📦 **Type Safe**: Full TypeScript support with generics
191+
- 🛡️ **Error Handling**: Comprehensive error handling with automatic cleanup of corrupted data
192+
- 🔄 **Auto Serialization**: Automatic JSON serialization/deserialization for complex data types
163193

164194
### SearchQueryUtil
165195

package/commonUtil/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { default as copyToClipboard } from "./copyToClipboard";
55
export { default as encodeBase64 } from "./encodeBase64";
66
export { default as decodeBase64 } from "./decodeBase64";
77
export { default as debounce } from "./debounce";
8+
export { default as storage } from "./storage";
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { beforeEach, describe, expect, Mock, test, vi } from "vitest";
2+
import storage from ".";
3+
4+
describe("storage", () => {
5+
const mockLocalStorage = () => {
6+
let store: Record<string, string> = {};
7+
8+
return {
9+
getItem: vi.fn((key: string) => store[key] || null),
10+
setItem: vi.fn((key: string, value: string) => {
11+
store[key] = value;
12+
}),
13+
removeItem: vi.fn((key: string) => {
14+
delete store[key];
15+
}),
16+
clear: vi.fn(() => {
17+
store = {};
18+
}),
19+
};
20+
};
21+
22+
vi.stubGlobal("localStorage", mockLocalStorage());
23+
24+
describe("storage 유틸리티", () => {
25+
beforeEach(() => {
26+
(window.localStorage.clear as Mock)();
27+
});
28+
29+
describe("set 및 get 기능", () => {
30+
test("문자열 값을 올바르게 저장하고 불러와야 합니다.", () => {
31+
const key = "test-string";
32+
const value = "hello world";
33+
storage.set(key, value);
34+
expect(storage.get(key)).toBe(value);
35+
});
36+
37+
test("객체 값을 JSON으로 변환하여 올바르게 저장하고, 파싱하여 불러와야 합니다.", () => {
38+
const key = "test-object";
39+
const value = { name: "John Yeom", version: 9.9, isReady: true };
40+
storage.set(key, value);
41+
expect(storage.get(key)).toEqual(value);
42+
});
43+
44+
test("배열 값을 JSON으로 변환하여 올바르게 저장하고, 파싱하여 불러와야 합니다.", () => {
45+
const key = "test-array";
46+
const value = [1, "test", { id: 3 }];
47+
storage.set(key, value);
48+
expect(storage.get(key)).toEqual(value);
49+
});
50+
51+
test("존재하지 않는 키를 불러오려고 하면 null을 반환해야 합니다.", () => {
52+
expect(storage.get("non-existent-key")).toBeNull();
53+
});
54+
});
55+
56+
describe("remove 기능", () => {
57+
test("저장된 값을 올바르게 제거해야 합니다.", () => {
58+
const key = "item-to-remove";
59+
const value = "some data";
60+
61+
storage.set(key, value);
62+
expect(storage.get(key)).toBe(value);
63+
64+
storage.remove(key);
65+
expect(storage.get(key)).toBeNull();
66+
});
67+
});
68+
69+
describe("예외 처리", () => {
70+
test("localStorage에 저장된 값이 손상된 JSON일 경우, null을 반환해야 합니다.", () => {
71+
const key = "corrupted-json";
72+
const corruptedValue = '{"name": "Gemini", "version": 1.5,}'; // 마지막에 잘못된 쉼표
73+
74+
(window.localStorage.getItem as Mock).mockReturnValueOnce(
75+
corruptedValue
76+
);
77+
78+
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
79+
80+
expect(storage.get(key)).toBeNull();
81+
82+
spy.mockRestore();
83+
});
84+
});
85+
});
86+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* localStorage를 편리하게 사용할 수 있는 유틸리티 함수들을 제공합니다.
3+
*
4+
* * 객체나 배열도 저장 및 조회가 가능합니다.
5+
* * SSR 환경에서는 동작하지 않으며, 해당 경우에 대한 처리가 포함되어 있습니다.
6+
*/
7+
const storage = {
8+
/**
9+
* localStorage에 값을 저장합니다.
10+
* @param {string} key - 저장할 데이터의 키
11+
* @param {T} value - 저장할 데이터. 객체나 배열도 가능합니다.
12+
*/
13+
set<T>(key: string, value: T): void {
14+
if (typeof window === "undefined") {
15+
console.warn(
16+
`localStorage is not available in SSR environment. Set operation for key "${key}" was ignored.`
17+
);
18+
return;
19+
}
20+
21+
try {
22+
const serializedValue = JSON.stringify(value);
23+
window.localStorage.setItem(key, serializedValue);
24+
} catch (error) {
25+
console.error(`Error setting item "${key}" to localStorage`, error);
26+
}
27+
},
28+
29+
/**
30+
* localStorage에서 값을 가져옵니다.
31+
* @param {string} key - 가져올 데이터의 키
32+
* @returns {T | null} 저장된 데이터를 반환합니다. 값이 없거나 에러 발생 시 null을 반환합니다.
33+
*/
34+
get<T>(key: string): T | null {
35+
if (typeof window === "undefined") {
36+
return null;
37+
}
38+
39+
try {
40+
const serializedValue = window.localStorage.getItem(key);
41+
42+
if (serializedValue === null) {
43+
return null;
44+
}
45+
46+
return JSON.parse(serializedValue) as T;
47+
} catch (error) {
48+
console.error(`Error getting item "${key}" from localStorage`, error);
49+
window.localStorage.removeItem(key);
50+
return null;
51+
}
52+
},
53+
54+
/**
55+
* localStorage에서 값을 제거합니다.
56+
* @param {string} key - 제거할 데이터의 키
57+
*/
58+
remove(key: string): void {
59+
if (typeof window === "undefined") {
60+
console.warn(
61+
`localStorage is not available in SSR environment. Remove operation for key "${key}" was ignored.`
62+
);
63+
return;
64+
}
65+
66+
try {
67+
window.localStorage.removeItem(key);
68+
} catch (error) {
69+
console.error(`Error removing item "${key}" from localStorage`, error);
70+
}
71+
},
72+
};
73+
74+
export default storage;

0 commit comments

Comments
 (0)