feat: add DateUtils and StringUtils for date and string manipulation
- Implement DateUtils for handling date operations with timezone support. - Add unit tests for DateUtils to ensure functionality and edge cases. - Create StringUtils for string manipulation with a fluent API. - Include unit tests for StringUtils covering various string operations. - Introduce EncryptionUtils for secure data encryption and decryption. - Add tests for EncryptionUtils to validate encryption and decryption processes. - Update index.ts to export new utility modules. - Introduce encryption key management with a dedicated key file.
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* ------------------------------------------------------------
|
||||
* StringUtils
|
||||
* ------------------------------------------------------------
|
||||
* Centralized string manipulation utility.
|
||||
*
|
||||
* Features:
|
||||
* - 🛡️ Null-safe (handles null/undefined gracefully)
|
||||
* - ⛓️ Fluent and immutable API (chainable methods)
|
||||
* - 🛠️ Common formatting utilities (Capitalization, Truncation)
|
||||
* - 🎲 Generator utilities (Random string, Slugs)
|
||||
*
|
||||
* @example
|
||||
* // Basic Chaining
|
||||
* StringUtils.of(" hello world ")
|
||||
* .trim()
|
||||
* .capitalizeEachWord()
|
||||
* .value(); // "Hello World"
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
export type StringInput = string | number | null | undefined;
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Interfaces
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
interface IStringUtils {
|
||||
// --- Output & Conversion ---
|
||||
value(): string;
|
||||
toString(): string;
|
||||
toJSON(): string;
|
||||
|
||||
// --- Manipulations (Chainable) ---
|
||||
upperCase(): IStringUtils;
|
||||
lowerCase(): IStringUtils;
|
||||
capitalizeFirst(): IStringUtils;
|
||||
capitalizeEachWord(): IStringUtils;
|
||||
limit(maxLength: number, suffix?: string): IStringUtils;
|
||||
trim(): IStringUtils;
|
||||
slugify(): IStringUtils;
|
||||
mask(visibleStart: number, visibleEnd: number, maskChar?: string): IStringUtils;
|
||||
|
||||
// --- Inspection & Fallback ---
|
||||
isEmpty(): boolean;
|
||||
orElse(defaultValue: string): string;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* StringUtils
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
export class StringUtils implements IStringUtils {
|
||||
private readonly _value: string;
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Constructor & Static Initializers
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
constructor(value?: StringInput) {
|
||||
// Defensive coding: Convert null/undefined/number to string immediately
|
||||
if (value === null || value === undefined) {
|
||||
this._value = '';
|
||||
} else {
|
||||
this._value = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method for cleaner chaining.
|
||||
*
|
||||
* @param value - The input string, number, or null/undefined.
|
||||
* @example
|
||||
* StringUtils.of("hello").upperCase().value()
|
||||
*/
|
||||
static of(value?: StringInput): StringUtils {
|
||||
return new StringUtils(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random alphanumeric string.
|
||||
* Useful for IDs, tokens, or temporary passwords.
|
||||
*
|
||||
* @param length - Length of the generated string (default: 10).
|
||||
* @example
|
||||
* StringUtils.random(8).value() // "aB9x2Z1m"
|
||||
*/
|
||||
static random(length: number = 10): StringUtils {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return new StringUtils(result);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Core Manipulations
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Converts string to UPPERCASE.
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of("hello").upperCase().value() // "HELLO"
|
||||
*/
|
||||
upperCase(): StringUtils {
|
||||
return new StringUtils(this._value.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string to lowercase.
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of("HELLO").lowerCase().value() // "hello"
|
||||
*/
|
||||
lowerCase(): StringUtils {
|
||||
return new StringUtils(this._value.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes only the first letter of the entire string.
|
||||
* Remainder is forced to lowercase (Sentence case).
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of("HELLO world").capitalizeFirst().value() // "Hello world"
|
||||
*/
|
||||
capitalizeFirst(): StringUtils {
|
||||
if (!this._value) return this;
|
||||
const lower = this._value.toLowerCase();
|
||||
return new StringUtils(lower.charAt(0).toUpperCase() + lower.slice(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes the first letter of every word (Title Case).
|
||||
* Automatically handles multiple spaces.
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World"
|
||||
* StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World"
|
||||
*/
|
||||
capitalizeEachWord(): StringUtils {
|
||||
if (!this._value) return this;
|
||||
const transformed = this._value
|
||||
.toLowerCase()
|
||||
.split(/\s+/) // Split by any whitespace regex to handle double spaces
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
return new StringUtils(transformed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates string to a specific length and adds a suffix.
|
||||
*
|
||||
* @param maxLength - The character limit.
|
||||
* @param suffix - The string to append if truncated (default: "...").
|
||||
* @example
|
||||
* StringUtils.of("Lorem Ipsum").limit(5).value() // "Lorem..."
|
||||
* StringUtils.of("Lorem Ipsum").limit(5, "").value() // "Lorem"
|
||||
*/
|
||||
limit(maxLength: number, suffix: string = '...'): StringUtils {
|
||||
if (this._value.length <= maxLength) return this;
|
||||
return new StringUtils(this._value.substring(0, maxLength) + suffix);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Utility Extensions
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Removes whitespace from both ends of the string.
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of(" data ").trim().value() // "data"
|
||||
*/
|
||||
trim(): StringUtils {
|
||||
return new StringUtils(this._value.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string to URL-friendly slug.
|
||||
* Removes special characters and replaces spaces with dashes.
|
||||
*
|
||||
* @example
|
||||
* StringUtils.of("Hello World!").slugify().value() // "hello-world"
|
||||
* StringUtils.of("C# & .NET").slugify().value() // "c-net"
|
||||
*/
|
||||
slugify(): StringUtils {
|
||||
const slug = this._value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\w\s-]/g, '') // Remove non-word chars
|
||||
.replace(/[\s_-]+/g, '-') // Replace spaces and underscores with -
|
||||
.replace(/^-+|-+$/g, ''); // Remove leading/trailing -
|
||||
return new StringUtils(slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Masks part of the string. Useful for PII (emails/phones).
|
||||
*
|
||||
* @param visibleStart - Number of characters to show at the start.
|
||||
* @param visibleEnd - Number of characters to show at the end.
|
||||
* @param maskChar - The character to use for masking (default: "*").
|
||||
* @example
|
||||
* StringUtils.of("08123456789").mask(4, 3).value() // "0812****789"
|
||||
*/
|
||||
mask(visibleStart: number = 0, visibleEnd: number = 0, maskChar: string = '*'): StringUtils {
|
||||
if (this._value.length <= visibleStart + visibleEnd) return this;
|
||||
|
||||
const start = this._value.slice(0, visibleStart);
|
||||
const end = this._value.slice(-visibleEnd);
|
||||
const middle = maskChar.repeat(this._value.length - visibleStart - visibleEnd);
|
||||
|
||||
return new StringUtils(start + middle + end);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Inspection & Output
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Checks if the string is empty or contains only whitespace.
|
||||
*
|
||||
* @returns true if string is empty or whitespace-only.
|
||||
* @example
|
||||
* StringUtils.of("").isEmpty() // true
|
||||
* StringUtils.of(" ").isEmpty() // true
|
||||
*/
|
||||
isEmpty(): boolean {
|
||||
return this._value.trim().length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw string value.
|
||||
*/
|
||||
value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default value if current string is empty.
|
||||
*
|
||||
* @param defaultValue - The fallback string.
|
||||
* @example
|
||||
* StringUtils.of(null).orElse("N/A") // "N/A"
|
||||
*/
|
||||
orElse(defaultValue: string): string {
|
||||
return this.isEmpty() ? defaultValue : this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows default JS string interpolation to work.
|
||||
*
|
||||
* @example
|
||||
* const name = StringUtils.of("John");
|
||||
* console.log(`Hello ${name}`); // "Hello John"
|
||||
*/
|
||||
toString(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JSON.stringify to serialize just the string, not the object wrapper.
|
||||
*
|
||||
* @example
|
||||
* JSON.stringify({ name: StringUtils.of("John") }) // '{"name":"John"}'
|
||||
*/
|
||||
toJSON(): string {
|
||||
return this._value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user