diff --git a/README.md b/README.md
index a05ab5b..bf28a36 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A comprehensive collection of TypeScript utility functions for modern web develo
## Features
-- ๐ ๏ธ **Comprehensive**: String, object, cookie, number, validation, format, search query, device, type and common utilities
+- ๐ ๏ธ **Comprehensive**: String, object, cookie, number, validation, format, search query, device, type, storage and common utilities
- ๐ฆ **Tree-shakable**: Import only what you need
- ๐ **Type-safe**: Full TypeScript support with type definitions
- โก **Lightweight**: Minimal dependencies and optimized for performance
@@ -76,6 +76,11 @@ const encoded = commonUtil.encodeBase64("Hello ํ๊ธ!"); // Base64 encoded stri
const decoded = commonUtil.decodeBase64(encoded); // "Hello ํ๊ธ!"
const debouncedFn = commonUtil.debounce(() => console.log("Called!"), 300); // Debounced function
+// Storage
+commonUtil.storage.set("user", { id: 1, name: "John" }); // Stores object in localStorage
+const user = commonUtil.storage.get<{ id: number; name: string }>("user"); // Retrieves typed object
+commonUtil.storage.remove("user"); // Removes item from localStorage
+
// Search Query utilities
const queryParams = searchQueryUtil.getAllQuery(); // { key: ["value1", "value2"], id: "123" }
@@ -107,11 +112,13 @@ import { clearNullProperties, deepFreeze } from "kr-corekit";
import { escapeHtml } from "kr-corekit/stringUtil";
import { sum } from "kr-corekit/numberUtil";
import { clearNullProperties } from "kr-corekit/objectUtil";
+import { storage } from "kr-corekit/commonUtil";
// Usage remains the same
const escaped = escapeHtml("
Hello
");
const total = sum(1, 2, 3, 4, 5);
const cleaned = clearNullProperties({ a: 1, b: null, c: 3 });
+storage.set("data", { key: "value" });
```
### Bundle Size Comparison
@@ -151,6 +158,19 @@ const cleaned = clearNullProperties({ a: 1, b: null, c: 3 });
- `checkBase64(value: string): boolean` - Validates whether a string is a valid base64 encoded value
- `checkPassword(password: string, options?: { minLength?: number; maxLength?: number; requireUppercase?: boolean; requireLowercase?: boolean; requireNumber?: boolean; requireSpecialChar?: boolean }): boolean` - Validates password strength and requirements
+### StorageUtil
+
+- `set(key: string, value: T): void` - Stores a value in localStorage with automatic JSON serialization. Supports objects, arrays, and primitive types. Safe for SSR environments.
+- `get(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.
+- `remove(key: string): void` - Removes a specific item from localStorage. Safe for SSR environments.
+
+**Features:**
+
+- ๐ **SSR Safe**: All methods handle server-side rendering environments gracefully
+- ๐ฆ **Type Safe**: Full TypeScript support with generics
+- ๐ก๏ธ **Error Handling**: Comprehensive error handling with automatic cleanup of corrupted data
+- ๐ **Auto Serialization**: Automatic JSON serialization/deserialization for complex data types
+
### CommonUtil
- `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 });
- `encodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Encodes a string to Base64 format with optional special character handling
- `decodeBase64(str: string, options?: { convertSpecialChars?: boolean }): string` - Decodes a Base64 string back to original text with optional special character handling
- `debounce(fn: T, delay?: number): (...args: Parameters) => void` - Creates a debounced function that delays execution until after a specified delay (default 300ms) has passed since its last invocation
+- `storage.set(key: string, value: T): void` - Stores a value in localStorage with automatic JSON serialization. Supports objects, arrays, and primitive types. Safe for SSR environments.
+- `storage.get(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.
+- `storage.remove(key: string): void` - Removes a specific item from localStorage. Safe for SSR environments.
+
+**Storage Features:**
+
+- ๐ **SSR Safe**: All methods handle server-side rendering environments gracefully
+- ๐ฆ **Type Safe**: Full TypeScript support with generics
+- ๐ก๏ธ **Error Handling**: Comprehensive error handling with automatic cleanup of corrupted data
+- ๐ **Auto Serialization**: Automatic JSON serialization/deserialization for complex data types
### SearchQueryUtil
diff --git a/package/commonUtil/index.ts b/package/commonUtil/index.ts
index b1d4460..1235d4c 100644
--- a/package/commonUtil/index.ts
+++ b/package/commonUtil/index.ts
@@ -5,3 +5,4 @@ export { default as copyToClipboard } from "./copyToClipboard";
export { default as encodeBase64 } from "./encodeBase64";
export { default as decodeBase64 } from "./decodeBase64";
export { default as debounce } from "./debounce";
+export { default as storage } from "./storage";
diff --git a/package/commonUtil/storage/index.test.ts b/package/commonUtil/storage/index.test.ts
new file mode 100644
index 0000000..4908947
--- /dev/null
+++ b/package/commonUtil/storage/index.test.ts
@@ -0,0 +1,86 @@
+import { beforeEach, describe, expect, Mock, test, vi } from "vitest";
+import storage from ".";
+
+describe("storage", () => {
+ const mockLocalStorage = () => {
+ let store: Record = {};
+
+ return {
+ getItem: vi.fn((key: string) => store[key] || null),
+ setItem: vi.fn((key: string, value: string) => {
+ store[key] = value;
+ }),
+ removeItem: vi.fn((key: string) => {
+ delete store[key];
+ }),
+ clear: vi.fn(() => {
+ store = {};
+ }),
+ };
+ };
+
+ vi.stubGlobal("localStorage", mockLocalStorage());
+
+ describe("storage ์ ํธ๋ฆฌํฐ", () => {
+ beforeEach(() => {
+ (window.localStorage.clear as Mock)();
+ });
+
+ describe("set ๋ฐ get ๊ธฐ๋ฅ", () => {
+ test("๋ฌธ์์ด ๊ฐ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฅํ๊ณ ๋ถ๋ฌ์์ผ ํฉ๋๋ค.", () => {
+ const key = "test-string";
+ const value = "hello world";
+ storage.set(key, value);
+ expect(storage.get(key)).toBe(value);
+ });
+
+ test("๊ฐ์ฒด ๊ฐ์ JSON์ผ๋ก ๋ณํํ์ฌ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฅํ๊ณ , ํ์ฑํ์ฌ ๋ถ๋ฌ์์ผ ํฉ๋๋ค.", () => {
+ const key = "test-object";
+ const value = { name: "John Yeom", version: 9.9, isReady: true };
+ storage.set(key, value);
+ expect(storage.get(key)).toEqual(value);
+ });
+
+ test("๋ฐฐ์ด ๊ฐ์ JSON์ผ๋ก ๋ณํํ์ฌ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฅํ๊ณ , ํ์ฑํ์ฌ ๋ถ๋ฌ์์ผ ํฉ๋๋ค.", () => {
+ const key = "test-array";
+ const value = [1, "test", { id: 3 }];
+ storage.set(key, value);
+ expect(storage.get(key)).toEqual(value);
+ });
+
+ test("์กด์ฌํ์ง ์๋ ํค๋ฅผ ๋ถ๋ฌ์ค๋ ค๊ณ ํ๋ฉด null์ ๋ฐํํด์ผ ํฉ๋๋ค.", () => {
+ expect(storage.get("non-existent-key")).toBeNull();
+ });
+ });
+
+ describe("remove ๊ธฐ๋ฅ", () => {
+ test("์ ์ฅ๋ ๊ฐ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๊ฑฐํด์ผ ํฉ๋๋ค.", () => {
+ const key = "item-to-remove";
+ const value = "some data";
+
+ storage.set(key, value);
+ expect(storage.get(key)).toBe(value);
+
+ storage.remove(key);
+ expect(storage.get(key)).toBeNull();
+ });
+ });
+
+ describe("์์ธ ์ฒ๋ฆฌ", () => {
+ test("localStorage์ ์ ์ฅ๋ ๊ฐ์ด ์์๋ JSON์ผ ๊ฒฝ์ฐ, null์ ๋ฐํํด์ผ ํฉ๋๋ค.", () => {
+ const key = "corrupted-json";
+ const corruptedValue = '{"name": "Gemini", "version": 1.5,}'; // ๋ง์ง๋ง์ ์๋ชป๋ ์ผํ
+
+ (window.localStorage.getItem as Mock).mockReturnValueOnce(
+ corruptedValue
+ );
+
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+
+ expect(storage.get(key)).toBeNull();
+
+ spy.mockRestore();
+ });
+ });
+ });
+});
diff --git a/package/commonUtil/storage/index.ts b/package/commonUtil/storage/index.ts
new file mode 100644
index 0000000..e30b0cc
--- /dev/null
+++ b/package/commonUtil/storage/index.ts
@@ -0,0 +1,74 @@
+/**
+ * localStorage๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ ํธ๋ฆฌํฐ ํจ์๋ค์ ์ ๊ณตํฉ๋๋ค.
+ *
+ * * ๊ฐ์ฒด๋ ๋ฐฐ์ด๋ ์ ์ฅ ๋ฐ ์กฐํ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
+ * * SSR ํ๊ฒฝ์์๋ ๋์ํ์ง ์์ผ๋ฉฐ, ํด๋น ๊ฒฝ์ฐ์ ๋ํ ์ฒ๋ฆฌ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
+ */
+const storage = {
+ /**
+ * localStorage์ ๊ฐ์ ์ ์ฅํฉ๋๋ค.
+ * @param {string} key - ์ ์ฅํ ๋ฐ์ดํฐ์ ํค
+ * @param {T} value - ์ ์ฅํ ๋ฐ์ดํฐ. ๊ฐ์ฒด๋ ๋ฐฐ์ด๋ ๊ฐ๋ฅํฉ๋๋ค.
+ */
+ set(key: string, value: T): void {
+ if (typeof window === "undefined") {
+ console.warn(
+ `localStorage is not available in SSR environment. Set operation for key "${key}" was ignored.`
+ );
+ return;
+ }
+
+ try {
+ const serializedValue = JSON.stringify(value);
+ window.localStorage.setItem(key, serializedValue);
+ } catch (error) {
+ console.error(`Error setting item "${key}" to localStorage`, error);
+ }
+ },
+
+ /**
+ * localStorage์์ ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
+ * @param {string} key - ๊ฐ์ ธ์ฌ ๋ฐ์ดํฐ์ ํค
+ * @returns {T | null} ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํฉ๋๋ค. ๊ฐ์ด ์๊ฑฐ๋ ์๋ฌ ๋ฐ์ ์ null์ ๋ฐํํฉ๋๋ค.
+ */
+ get(key: string): T | null {
+ if (typeof window === "undefined") {
+ return null;
+ }
+
+ try {
+ const serializedValue = window.localStorage.getItem(key);
+
+ if (serializedValue === null) {
+ return null;
+ }
+
+ return JSON.parse(serializedValue) as T;
+ } catch (error) {
+ console.error(`Error getting item "${key}" from localStorage`, error);
+ window.localStorage.removeItem(key);
+ return null;
+ }
+ },
+
+ /**
+ * localStorage์์ ๊ฐ์ ์ ๊ฑฐํฉ๋๋ค.
+ * @param {string} key - ์ ๊ฑฐํ ๋ฐ์ดํฐ์ ํค
+ */
+ remove(key: string): void {
+ if (typeof window === "undefined") {
+ console.warn(
+ `localStorage is not available in SSR environment. Remove operation for key "${key}" was ignored.`
+ );
+ return;
+ }
+
+ try {
+ window.localStorage.removeItem(key);
+ } catch (error) {
+ console.error(`Error removing item "${key}" from localStorage`, error);
+ }
+ },
+};
+
+export default storage;