Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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" }

Expand Down Expand Up @@ -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("<div>Hello</div>");
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
Expand Down Expand Up @@ -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<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.
- `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.
- `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)
Expand All @@ -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<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
- `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.
- `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.
- `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

Expand Down
1 change: 1 addition & 0 deletions package/commonUtil/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
86 changes: 86 additions & 0 deletions package/commonUtil/storage/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, Mock, test, vi } from "vitest";
import storage from ".";

describe("storage", () => {
const mockLocalStorage = () => {
let store: Record<string, string> = {};

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();
});
});
});
});
74 changes: 74 additions & 0 deletions package/commonUtil/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* localStorage를 편리하게 사용할 수 있는 유틸리티 함수들을 제공합니다.
*
* * 객체나 배열도 저장 및 조회가 가능합니다.
* * SSR 환경에서는 동작하지 않으며, 해당 경우에 대한 처리가 포함되어 있습니다.
*/
const storage = {
/**
* localStorage에 값을 저장합니다.
* @param {string} key - 저장할 데이터의 키
* @param {T} value - 저장할 데이터. 객체나 배열도 가능합니다.
*/
set<T>(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<T>(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;