diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 505e979..5e62636 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,14 +1,15 @@ -// import { DateService } from '@repo/utils'; -// import { CurrencyService } from '@repo/utils'; +// import { DateUtils } from '@repo/utils'; +// import { CurrencyUtils } from '@repo/utils'; import './main.css'; import { lazy, StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; const App = lazy(() => import('./apps')); -// DateService.setGlobalConfig('Asia/Jakarta'); -// DateService.setGlobalConfig('Asia/Makassar'); +// DateUtils.setGlobalConfig('Asia/Jakarta'); +// DateUtils.setGlobalConfig('Asia/Makassar'); -// CurrencyService.setGlobalPrefix('IDR '); +// CurrencyUtils.setGlobalPrefix('IDR '); +// CurrencyUtils.setGlobalDecimalSeparator(','); createRoot(document.getElementById('app')!).render( diff --git a/packages/ui/src/components/examples/date-example.tsx b/packages/ui/src/components/examples/date-example.tsx index 2b2e22a..361a284 100644 --- a/packages/ui/src/components/examples/date-example.tsx +++ b/packages/ui/src/components/examples/date-example.tsx @@ -1,12 +1,12 @@ -import { DateService } from '@repo/utils'; +import { DateUtils } from '@repo/utils'; import React, { useState, useEffect, useMemo } from 'react'; export const DateServiceExample: React.FC = () => { // --- 0. TIMEZONE CONFIGURATION (ROOT CONTROL) --- - const [currentZone, setCurrentZone] = useState(DateService.getGlobalTimezone()); + const [currentZone, setCurrentZone] = useState(DateUtils.getGlobalTimezone()); // Ambil list timezone dari helper static yang kita buat sebelumnya - const timezoneList = useMemo(() => DateService.getSupportedTimezones(), []); + const timezoneList = useMemo(() => DateUtils.getSupportedTimezones(), []); // --- 1. STATE & DATA --- @@ -17,40 +17,40 @@ export const DateServiceExample: React.FC = () => { { id: 1, title: 'Bayar Server (Lusa)', - dueDate: DateService.now().add(2, 'day').toISOString(), + dueDate: DateUtils.now().add(2, 'day').toISOString(), }, { id: 2, title: 'Laporan Bulanan (Lewat 5 Hari)', - dueDate: DateService.now().subtract(5, 'day').toISOString(), + dueDate: DateUtils.now().subtract(5, 'day').toISOString(), }, { id: 3, title: 'Meeting Tahunan (Bulan Depan)', - dueDate: DateService.now().add(1, 'month').toISOString(), + dueDate: DateUtils.now().add(1, 'month').toISOString(), }, { id: 4, title: 'Deadline Besok Pagi', - dueDate: DateService.now().add(1, 'day').startOf('day').toISOString(), + dueDate: DateUtils.now().add(1, 'day').startOf('day').toISOString(), }, ]; }, [currentZone]); // Dependency: currentZone - const [currentTime, setCurrentTime] = useState(DateService.now()); - const [selectedDate, setSelectedDate] = useState(DateService.now()); + const [currentTime, setCurrentTime] = useState(DateUtils.now()); + const [selectedDate, setSelectedDate] = useState(DateUtils.now()); // State demo logic calendar - const [calendarTarget, setCalendarTarget] = useState(() => DateService.now().add(1, 'day').startOf('day')); + const [calendarTarget, setCalendarTarget] = useState(() => DateUtils.now().add(1, 'day').startOf('day')); // Static date (Memo kosong) -> Akan tetap di Timezone saat pertama kali load (kecuali di-refresh) // Ini bagus untuk demo bahwa object lama tidak berubah (immutable) kecuali dibuat ulang. - const staticDate = useMemo(() => new DateService(), []); + const staticDate = useMemo(() => new DateUtils(), []); // Effect update jam tiap detik useEffect(() => { const timer = setInterval(() => { - setCurrentTime(DateService.now()); + setCurrentTime(DateUtils.now()); }, 1000); return () => clearInterval(timer); }, []); @@ -61,17 +61,17 @@ export const DateServiceExample: React.FC = () => { setCurrentZone(newZone); // 1. Set Global Config (Inti Perubahan) - DateService.setGlobalConfig(newZone); + DateUtils.setGlobalConfig(newZone); // 2. Force Refresh State agar UI langsung berubah // Kita buat instance baru agar mengambil config timezone terbaru - setCurrentTime(DateService.now()); + setCurrentTime(DateUtils.now()); // Kita recreate selectedDate dengan nilai raw yang sama, tapi context timezone baru - setSelectedDate((prev) => new DateService(prev.getRaw())); + setSelectedDate((prev) => new DateUtils(prev.getRaw())); // Recreate target demo calendar - setCalendarTarget(DateService.now().add(1, 'day').startOf('day')); + setCalendarTarget(DateUtils.now().add(1, 'day').startOf('day')); }; const handleAddDay = (days: number) => { @@ -82,7 +82,7 @@ export const DateServiceExample: React.FC = () => {
{/* HEADER & TIMEZONE CONTROLLER */}
-

DateService Feature Showcase

+

DateUtils Feature Showcase

@@ -278,8 +278,8 @@ export const DateServiceExample: React.FC = () => { {tasks.map((task) => { - const dueDate = new DateService(task.dueDate); - const now = DateService.now(); + const dueDate = new DateUtils(task.dueDate); + const now = DateUtils.now(); const isOverdue = now.isAfter(dueDate); const daysDiff = dueDate.diffCalendarDay(now); diff --git a/packages/ui/src/components/examples/encryption-example.tsx b/packages/ui/src/components/examples/encryption-example.tsx index 561584f..9f51a45 100644 --- a/packages/ui/src/components/examples/encryption-example.tsx +++ b/packages/ui/src/components/examples/encryption-example.tsx @@ -1,10 +1,10 @@ -import { EncryptionService } from '@repo/utils'; +import { EncryptionUtils } from '@repo/utils'; import React, { useState } from 'react'; export const EncryptionExample: React.FC = () => { // --- 1. SETUP INSTANCE --- // Kita gunakan Singleton agar hemat memory - const cryptoService = EncryptionService.getInstance(); + const cryptoService = EncryptionUtils.getInstance(); // --- 2. STATE --- const [plainText, setPlainText] = useState(''); diff --git a/packages/ui/src/components/examples/style-guide-2.tsx b/packages/ui/src/components/examples/style-guide-2.tsx index a08b183..ecb58e9 100644 --- a/packages/ui/src/components/examples/style-guide-2.tsx +++ b/packages/ui/src/components/examples/style-guide-2.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; // ========================================================== // 1. ATOMIC COMPONENTS (Berdasar Config 13px & Burgundy) diff --git a/packages/ui/src/components/forms/input/input-currency.component.tsx b/packages/ui/src/components/forms/input/input-currency.component.tsx index e04b065..fc56df1 100644 --- a/packages/ui/src/components/forms/input/input-currency.component.tsx +++ b/packages/ui/src/components/forms/input/input-currency.component.tsx @@ -1,4 +1,4 @@ -import { CurrencyService } from '@repo/utils'; +import { CurrencyUtils } from '@repo/utils'; import { InputCurrencyProps } from '../types'; import { InputNumber } from './input-number.component'; @@ -6,13 +6,13 @@ import { InputNumber } from './input-number.component'; * InputCurrency * * A controlled InputNumber component with currency formatting. - * Delegates all formatting/parsing to CurrencyService. + * Delegates all formatting/parsing to CurrencyUtils. * * Features: * - Display currency prefix (e.g., Rp, $) * - Thousand separator formatting * - Optional decimal rounding - * - Global prefix support via CurrencyService + * - Global prefix support via CurrencyUtils */ export const InputCurrency = (props: InputCurrencyProps) => { const { @@ -22,8 +22,8 @@ export const InputCurrency = (props: InputCurrencyProps) => { ...restProps } = props; - // Create a CurrencyService instance for this input - const currencyService = new CurrencyService({ + // Create a CurrencyUtils instance for this input + const currencyService = new CurrencyUtils({ prefix, decimalSeparator, decimalScale, diff --git a/packages/utils/src/currency-service/currency-service.test.ts b/packages/utils/src/currency/currency.utils.test.ts similarity index 77% rename from packages/utils/src/currency-service/currency-service.test.ts rename to packages/utils/src/currency/currency.utils.test.ts index 3703aa2..de1ea35 100644 --- a/packages/utils/src/currency-service/currency-service.test.ts +++ b/packages/utils/src/currency/currency.utils.test.ts @@ -1,20 +1,20 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { CurrencyService } from './currency-service'; +import { CurrencyUtils } from './currency.utils'; -describe('CurrencyService', () => { - let currency: CurrencyService; +describe('CurrencyUtils', () => { + let currency: CurrencyUtils; beforeEach(() => { // Reset global prefix and decimal separator before each test - CurrencyService.setGlobalPrefix('Rp '); - CurrencyService.setGlobalDecimalSeparator(','); + CurrencyUtils.setGlobalPrefix('Rp '); + CurrencyUtils.setGlobalDecimalSeparator(','); // Mock console to keep tests clean vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); // Default instance for testing - currency = new CurrencyService(); + currency = new CurrencyUtils(); }); afterEach(() => { @@ -23,37 +23,37 @@ describe('CurrencyService', () => { describe('Global Prefix', () => { it('should set and get global prefix', () => { - CurrencyService.setGlobalPrefix('USD '); - expect(CurrencyService.getGlobalPrefix()).toBe('USD '); + CurrencyUtils.setGlobalPrefix('USD '); + expect(CurrencyUtils.getGlobalPrefix()).toBe('USD '); }); it('should use global prefix if instance prefix is not provided', () => { - CurrencyService.setGlobalPrefix('USD '); - const c = new CurrencyService(); + CurrencyUtils.setGlobalPrefix('USD '); + const c = new CurrencyUtils(); expect(c.format(1000)).toBe('USD 1.000'); }); it('should override instance prefix', () => { - const c = new CurrencyService({ prefix: '€ ' }); + const c = new CurrencyUtils({ prefix: '€ ' }); expect(c.format(1000)).toBe('€ 1.000'); }); }); describe('Global Decimal Separator', () => { it('should set and get global decimal separator', () => { - CurrencyService.setGlobalDecimalSeparator('.'); - expect(CurrencyService.getGlobalDecimalSeparator()).toBe('.'); + CurrencyUtils.setGlobalDecimalSeparator('.'); + expect(CurrencyUtils.getGlobalDecimalSeparator()).toBe('.'); }); it('should use global decimal separator if instance separator is not provided', () => { - CurrencyService.setGlobalDecimalSeparator('.'); - const c = new CurrencyService(); + CurrencyUtils.setGlobalDecimalSeparator('.'); + const c = new CurrencyUtils(); expect(c.format(1234.56)).toBe('Rp 1,234.56'); expect(c.parseToRaw('Rp 1,234.56')).toBe('1234.56'); }); it('should override instance decimal separator', () => { - const c = new CurrencyService({ decimalSeparator: '.' }); + const c = new CurrencyUtils({ decimalSeparator: '.' }); expect(c.format(1234.56)).toBe('Rp 1,234.56'); expect(c.parseToRaw('Rp 1,234.56')).toBe('1234.56'); }); @@ -69,7 +69,7 @@ describe('CurrencyService', () => { }); it('should round decimal when decimalScale is set', () => { - const c = new CurrencyService({ decimalScale: 2 }); + const c = new CurrencyUtils({ decimalScale: 2 }); expect(c.format(1234.5678)).toBe('Rp 1.234,56'); }); @@ -107,7 +107,7 @@ describe('CurrencyService', () => { }); it('should format numbers with multiple decimals correctly when decimalScale is set', () => { - const c = new CurrencyService({ decimalScale: 3 }); + const c = new CurrencyUtils({ decimalScale: 3 }); expect(c.format(1234.56789)).toBe('Rp 1.234,567'); }); }); diff --git a/packages/utils/src/currency-service/currency-service.ts b/packages/utils/src/currency/currency.utils.ts similarity index 93% rename from packages/utils/src/currency-service/currency-service.ts rename to packages/utils/src/currency/currency.utils.ts index bfa7d43..a48e3c5 100644 --- a/packages/utils/src/currency-service/currency-service.ts +++ b/packages/utils/src/currency/currency.utils.ts @@ -1,6 +1,6 @@ /** * ------------------------------------------------------------ - * CurrencyService + * CurrencyUtils * ------------------------------------------------------------ * Centralized currency utility for formatting and parsing numeric values. * @@ -32,7 +32,7 @@ export interface CurrencyOptions { decimalScale?: number; } -export class CurrencyService { +export class CurrencyUtils { /** ---------------------------------------------------------------- * Global default prefix shared across all instances * ---------------------------------------------------------------- */ @@ -54,14 +54,14 @@ export class CurrencyService { private readonly thousandSeparator: string; /** - * Constructor for CurrencyService instance. + * Constructor for CurrencyUtils instance. * Allows overriding prefix, decimal separator, and rounding per instance. * * @param options CurrencyOptions for prefix, separator, and rounding */ constructor(options?: CurrencyOptions) { - this.prefix = options?.prefix ?? CurrencyService._globalPrefix; - this.decimalSeparator = options?.decimalSeparator ?? CurrencyService._globalDecimalSeparator; + this.prefix = options?.prefix ?? CurrencyUtils._globalPrefix; + this.decimalSeparator = options?.decimalSeparator ?? CurrencyUtils._globalDecimalSeparator; this.decimalScale = options?.decimalScale; this.thousandSeparator = this.decimalSeparator === ',' ? '.' : ','; } @@ -77,7 +77,7 @@ export class CurrencyService { * @param prefix New global prefix string */ static setGlobalPrefix(prefix: string) { - CurrencyService._globalPrefix = prefix; + CurrencyUtils._globalPrefix = prefix; } /** @@ -86,7 +86,7 @@ export class CurrencyService { * @returns Current global prefix string */ static getGlobalPrefix(): string { - return CurrencyService._globalPrefix; + return CurrencyUtils._globalPrefix; } /** ------------------------------------------------------------ @@ -94,11 +94,11 @@ export class CurrencyService { * ------------------------------------------------------------ */ static setGlobalDecimalSeparator(separator: ',' | '.') { - CurrencyService._globalDecimalSeparator = separator; + CurrencyUtils._globalDecimalSeparator = separator; } static getGlobalDecimalSeparator(): ',' | '.' { - return CurrencyService._globalDecimalSeparator; + return CurrencyUtils._globalDecimalSeparator; } /** ------------------------------------------------------------ diff --git a/packages/utils/src/date-service/date-service.test.ts b/packages/utils/src/date/date.utils.test.ts similarity index 70% rename from packages/utils/src/date-service/date-service.test.ts rename to packages/utils/src/date/date.utils.test.ts index ba51ca6..98f84df 100644 --- a/packages/utils/src/date-service/date-service.test.ts +++ b/packages/utils/src/date/date.utils.test.ts @@ -1,11 +1,11 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { DateService } from './date-service'; +import { DateUtils } from './date.utils'; -describe('DateService', () => { +describe('DateUtils', () => { // Setup standard environment before each test beforeEach(() => { // 1. Reset Global Timezone to UTC for deterministic results across environments - DateService.setGlobalConfig('UTC'); + DateUtils.setGlobalConfig('UTC'); // 2. Mock console warn/error to keep the terminal clean during error handling tests vi.spyOn(console, 'warn').mockImplementation(() => {}); @@ -20,21 +20,21 @@ describe('DateService', () => { describe('Initialization', () => { it('should create an instance with the current time (now) using static method', () => { - const date = DateService.now(); - expect(date).toBeInstanceOf(DateService); + const date = DateUtils.now(); + expect(date).toBeInstanceOf(DateUtils); expect(date.toDate()).toBeInstanceOf(Date); }); it('should create an instance from a specific ISO string', () => { // Use full ISO format (.000Z) for precision const input = '2025-01-01T10:00:00.000Z'; - const date = new DateService(input); + const date = new DateUtils(input); expect(date.toISOString()).toBe(input); }); - it('should create a clone from another DateService instance', () => { - const original = new DateService('2025-01-01T00:00:00.000Z'); - const clone = new DateService(original); + it('should create a clone from another DateUtils instance', () => { + const original = new DateUtils('2025-01-01T00:00:00.000Z'); + const clone = new DateUtils(original); expect(clone.toISOString()).toBe(original.toISOString()); expect(clone).not.toBe(original); // Ensure references are different (Memory Address check) @@ -46,7 +46,7 @@ describe('DateService', () => { vi.useFakeTimers(); vi.setSystemTime(fixedTime); - const invalidDate = new DateService('invalid-date-string-xyz'); + const invalidDate = new DateUtils('invalid-date-string-xyz'); expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Invalid date input')); expect(invalidDate.toISOString()).toBe(fixedTime.toISOString()); @@ -55,29 +55,29 @@ describe('DateService', () => { describe('Global Configuration', () => { it('should update global timezone correctly', () => { - DateService.setGlobalConfig('Asia/Jakarta'); - expect(DateService.getGlobalTimezone()).toBe('Asia/Jakarta'); + DateUtils.setGlobalConfig('Asia/Jakarta'); + expect(DateUtils.getGlobalTimezone()).toBe('Asia/Jakarta'); }); it('should handle invalid timezone gracefully', () => { - const initialTz = DateService.getGlobalTimezone(); - DateService.setGlobalConfig('Mars/Alien_City'); + const initialTz = DateUtils.getGlobalTimezone(); + DateUtils.setGlobalConfig('Mars/Alien_City'); expect(console.error).toHaveBeenCalled(); - expect(DateService.getGlobalTimezone()).toBe(initialTz); + expect(DateUtils.getGlobalTimezone()).toBe(initialTz); }); it('should apply timezone to new instances', () => { const utcString = '2025-01-01T00:00:00Z'; // Test UTC - DateService.setGlobalConfig('UTC'); - const dateUtc = new DateService(utcString); + DateUtils.setGlobalConfig('UTC'); + const dateUtc = new DateUtils(utcString); expect(dateUtc.format('HH:mm')).toBe('00:00'); // Test Jakarta (UTC+7) - DateService.setGlobalConfig('Asia/Jakarta'); - const dateJkt = new DateService(utcString); + DateUtils.setGlobalConfig('Asia/Jakarta'); + const dateJkt = new DateUtils(utcString); expect(dateJkt.format('HH:mm')).toBe('07:00'); }); }); @@ -85,7 +85,7 @@ describe('DateService', () => { describe('Immutability & Manipulation', () => { // This verifies that .clone() is working effectively it('should be immutable on "add"', () => { - const start = new DateService('2025-01-01T00:00:00.000Z'); + const start = new DateUtils('2025-01-01T00:00:00.000Z'); const nextDay = start.add(1, 'day'); expect(start.format('DD')).toBe('01'); // Original instance MUST NOT change @@ -94,7 +94,7 @@ describe('DateService', () => { }); it('should be immutable on "subtract"', () => { - const start = new DateService('2025-01-02T00:00:00.000Z'); + const start = new DateUtils('2025-01-02T00:00:00.000Z'); const prevDay = start.subtract(1, 'day'); expect(start.format('DD')).toBe('02'); // Original instance MUST NOT change @@ -103,7 +103,7 @@ describe('DateService', () => { }); it('should be immutable on "startOf"', () => { - const date = new DateService('2025-01-15T12:00:00.000Z'); + const date = new DateUtils('2025-01-15T12:00:00.000Z'); const startOfMonth = date.startOf('month'); expect(date.format('DD')).toBe('15'); // Original remains 15th @@ -112,7 +112,7 @@ describe('DateService', () => { }); it('should be immutable on "endOf"', () => { - const date = new DateService('2025-01-01T00:00:00.000Z'); + const date = new DateUtils('2025-01-01T00:00:00.000Z'); const endOfDay = date.endOf('day'); // Format HH:mm:ss depends on global timezone (currently UTC) @@ -123,20 +123,20 @@ describe('DateService', () => { describe('Formatting & Comparison', () => { it('should format date string correctly', () => { - const date = new DateService('2025-12-25T00:00:00.000Z'); + const date = new DateUtils('2025-12-25T00:00:00.000Z'); expect(date.format('DD/MM/YYYY')).toBe('25/12/2025'); }); it('should return strict ISO 8601 string (always UTC)', () => { - DateService.setGlobalConfig('Asia/Jakarta'); + DateUtils.setGlobalConfig('Asia/Jakarta'); // Input Jakarta local time 7 AM = 0 AM UTC - const date = new DateService('2025-01-01T07:00:00+07:00'); + const date = new DateUtils('2025-01-01T07:00:00+07:00'); expect(date.toISOString()).toBe('2025-01-01T00:00:00.000Z'); }); it('should compare dates correctly', () => { - const d1 = new DateService('2025-01-01T00:00:00.000Z'); - const d2 = new DateService('2025-01-02T00:00:00.000Z'); + const d1 = new DateUtils('2025-01-01T00:00:00.000Z'); + const d2 = new DateUtils('2025-01-02T00:00:00.000Z'); expect(d1.isBefore(d2)).toBe(true); expect(d2.isAfter(d1)).toBe(true); @@ -144,16 +144,16 @@ describe('DateService', () => { }); it('should calculate precise diff', () => { - const d1 = new DateService('2025-01-01T00:00:00.000Z'); - const d2 = new DateService('2025-01-03T00:00:00.000Z'); + const d1 = new DateUtils('2025-01-01T00:00:00.000Z'); + const d2 = new DateUtils('2025-01-03T00:00:00.000Z'); expect(d2.diff(d1, 'day')).toBe(2); }); it('should calculate calendar day diff (ignoring time)', () => { // Case: 23:00 vs 01:00 the next day // Technically only 2 hours difference (0 full days), but 1 calendar day difference. - const d1 = new DateService('2025-01-01T23:00:00.000Z'); - const d2 = new DateService('2025-01-02T01:00:00.000Z'); + const d1 = new DateUtils('2025-01-01T23:00:00.000Z'); + const d2 = new DateUtils('2025-01-02T01:00:00.000Z'); expect(d2.diff(d1, 'day')).toBe(0); // Standard diff (less than 24h) expect(d2.diffCalendarDay(d1)).toBe(1); // Calendar diff @@ -162,26 +162,26 @@ describe('DateService', () => { describe('Timezone Utilities (Indonesian & Offset)', () => { it('should return custom mapping for Indonesia (WIB)', () => { - DateService.setGlobalConfig('Asia/Jakarta'); - const date = new DateService(); + DateUtils.setGlobalConfig('Asia/Jakarta'); + const date = new DateUtils(); expect(date.timezoneAbbr).toBe('WIB'); }); it('should return custom mapping for Indonesia (WITA)', () => { - DateService.setGlobalConfig('Asia/Makassar'); - const date = new DateService(); + DateUtils.setGlobalConfig('Asia/Makassar'); + const date = new DateUtils(); expect(date.timezoneAbbr).toBe('WITA'); }); it('should return custom mapping for Indonesia (WIT)', () => { - DateService.setGlobalConfig('Asia/Jayapura'); - const date = new DateService(); + DateUtils.setGlobalConfig('Asia/Jayapura'); + const date = new DateUtils(); expect(date.timezoneAbbr).toBe('WIT'); }); it('should fallback to standard abbr for other zones', () => { - DateService.setGlobalConfig('UTC'); - const date = new DateService(); + DateUtils.setGlobalConfig('UTC'); + const date = new DateUtils(); expect(date.timezoneAbbr).toBe('UTC'); }); @@ -190,20 +190,20 @@ describe('DateService', () => { const isoInput = '2025-01-01T12:00:00Z'; // Input in UTC // Case 1: Jakarta - DateService.setGlobalConfig('Asia/Jakarta'); - const dateJkt = new DateService(isoInput); + DateUtils.setGlobalConfig('Asia/Jakarta'); + const dateJkt = new DateUtils(isoInput); // Jakarta = UTC+7 expect(dateJkt.timezoneWithOffset).toBe('WIB (+07:00)'); // Case 2: Makassar - DateService.setGlobalConfig('Asia/Makassar'); - const dateMks = new DateService(isoInput); + DateUtils.setGlobalConfig('Asia/Makassar'); + const dateMks = new DateUtils(isoInput); // Makassar = UTC+8 expect(dateMks.timezoneWithOffset).toBe('WITA (+08:00)'); // Case 3: UTC - DateService.setGlobalConfig('UTC'); - const dateUtc = new DateService(isoInput); + DateUtils.setGlobalConfig('UTC'); + const dateUtc = new DateUtils(isoInput); expect(dateUtc.timezoneWithOffset).toBe('UTC (+00:00)'); }); }); @@ -211,7 +211,7 @@ describe('DateService', () => { describe('Epoch & Helpers', () => { it('should return correct epoch values', () => { const input = 1704067200000; // 2024-01-01 00:00:00 UTC - const date = new DateService(input); + const date = new DateUtils(input); expect(date.timestamp).toBe(input); expect(date.epochMillis).toBe(input); @@ -219,7 +219,7 @@ describe('DateService', () => { }); it('should return supported timezones list', () => { - const timezones = DateService.getSupportedTimezones(); + const timezones = DateUtils.getSupportedTimezones(); expect(Array.isArray(timezones)).toBe(true); expect(timezones).toContain('Asia/Jakarta'); }); diff --git a/packages/utils/src/date-service/date-service.ts b/packages/utils/src/date/date.utils.ts similarity index 72% rename from packages/utils/src/date-service/date-service.ts rename to packages/utils/src/date/date.utils.ts index fad0185..1764bba 100644 --- a/packages/utils/src/date-service/date-service.ts +++ b/packages/utils/src/date/date.utils.ts @@ -1,6 +1,6 @@ /** * ------------------------------------------------------------ - * DateService + * DateUtils * ------------------------------------------------------------ * Centralized date and time utility built on top of Day.js. * @@ -11,7 +11,7 @@ * - ISO 8601 compliant output * - Explicit Indonesian timezone abbreviation support * - * ⚠️ Day.js plugins MUST be initialized before using DateService. + * ⚠️ Day.js plugins MUST be initialized before using DateUtils. * ------------------------------------------------------------ */ @@ -33,7 +33,7 @@ dayjs.extend(advancedFormat); * ------------------------------------------------------------------ */ /** - * Accepted input formats for DateService. + * Accepted input formats for DateUtils. */ export type DateInput = string | number | Date | Dayjs | null | undefined; @@ -67,23 +67,23 @@ const INDONESIA_TZ_MAP: Record = { * Fluent date manipulation interface. * All methods are immutable and return a new instance. */ -interface IDateService { +interface IDateUtils { format(format?: string): string; - add(value: number, unit: TimeUnit): IDateService; - subtract(value: number, unit: TimeUnit): IDateService; - isBefore(date: DateInput | DateService): boolean; - isAfter(date: DateInput | DateService): boolean; - isSame(date: DateInput | DateService, unit?: TimeUnit): boolean; - diff(date: DateInput | DateService, unit: TimeUnit, precise?: boolean): number; - diffCalendarDay(date: DateInput | DateService): number; - startOf(unit: TimeUnit): IDateService; - endOf(unit: TimeUnit): IDateService; + add(value: number, unit: TimeUnit): IDateUtils; + subtract(value: number, unit: TimeUnit): IDateUtils; + isBefore(date: DateInput | DateUtils): boolean; + isAfter(date: DateInput | DateUtils): boolean; + isSame(date: DateInput | DateUtils, unit?: TimeUnit): boolean; + diff(date: DateInput | DateUtils, unit: TimeUnit, precise?: boolean): number; + diffCalendarDay(date: DateInput | DateUtils): number; + startOf(unit: TimeUnit): IDateUtils; + endOf(unit: TimeUnit): IDateUtils; toISOString(): string; toDate(): Date; } /* ------------------------------------------------------------------ - * DateService + * DateUtils * ------------------------------------------------------------------ */ /** @@ -92,12 +92,12 @@ interface IDateService { * All instances are automatically normalized * to a single global timezone. */ -export class DateService implements IDateService { +export class DateUtils implements IDateUtils { private readonly _date: Dayjs; /** * Global default timezone. - * Used by all DateService instances. + * Used by all DateUtils instances. */ private static _defaultTimezone: string = dayjs.tz.guess(); @@ -109,14 +109,14 @@ export class DateService implements IDateService { * Set the global timezone for the application. * * @example - * DateService.setGlobalConfig('Asia/Jakarta'); + * DateUtils.setGlobalConfig('Asia/Jakarta'); */ static setGlobalConfig(timezone: string): void { try { dayjs().tz(timezone); - DateService._defaultTimezone = timezone; + DateUtils._defaultTimezone = timezone; } catch { - console.error(`[DateService] Invalid timezone "${timezone}". Using previous value.`); + console.error(`[DateUtils] Invalid timezone "${timezone}". Using previous value.`); } } @@ -124,30 +124,30 @@ export class DateService implements IDateService { * Get the currently active global timezone. */ static getGlobalTimezone(): string { - return DateService._defaultTimezone; + return DateUtils._defaultTimezone; } /** - * Create a DateService instance representing the current moment. + * Create a DateUtils instance representing the current moment. */ - static now(): DateService { - return new DateService(); + static now(): DateUtils { + return new DateUtils(); } /* ---------------------------------------------------------------- * Constructor * ---------------------------------------------------------------- */ - constructor(date?: DateInput | DateService) { - if (date instanceof DateService) { - this._date = date.getRaw().tz(DateService._defaultTimezone); + constructor(date?: DateInput | DateUtils) { + if (date instanceof DateUtils) { + this._date = date.getRaw().tz(DateUtils._defaultTimezone); } else { - this._date = dayjs(date).tz(DateService._defaultTimezone); + this._date = dayjs(date).tz(DateUtils._defaultTimezone); } if (!this._date.isValid()) { - console.warn('[DateService] Invalid date input. Falling back to now().'); - this._date = dayjs().tz(DateService._defaultTimezone); + console.warn('[DateUtils] Invalid date input. Falling back to now().'); + this._date = dayjs().tz(DateUtils._defaultTimezone); } } @@ -166,8 +166,8 @@ export class DateService implements IDateService { /** * Normalize input into Day.js using the global timezone. */ - private toDayjs(date: DateInput | DateService): Dayjs { - return date instanceof DateService ? date.getRaw() : dayjs(date).tz(DateService._defaultTimezone); + private toDayjs(date: DateInput | DateUtils): Dayjs { + return date instanceof DateUtils ? date.getRaw() : dayjs(date).tz(DateUtils._defaultTimezone); } /* ---------------------------------------------------------------- @@ -191,46 +191,46 @@ export class DateService implements IDateService { * Manipulation * ---------------------------------------------------------------- */ - add(value: number, unit: TimeUnit): DateService { - return new DateService(this._date.clone().add(value, unit as ManipulateType)); + add(value: number, unit: TimeUnit): DateUtils { + return new DateUtils(this._date.clone().add(value, unit as ManipulateType)); } - subtract(value: number, unit: TimeUnit): DateService { - return new DateService(this._date.clone().subtract(value, unit as ManipulateType)); + subtract(value: number, unit: TimeUnit): DateUtils { + return new DateUtils(this._date.clone().subtract(value, unit as ManipulateType)); } - startOf(unit: TimeUnit): DateService { - return new DateService(this._date.clone().startOf(unit as OpUnitType)); + startOf(unit: TimeUnit): DateUtils { + return new DateUtils(this._date.clone().startOf(unit as OpUnitType)); } - endOf(unit: TimeUnit): DateService { - return new DateService(this._date.clone().endOf(unit as OpUnitType)); + endOf(unit: TimeUnit): DateUtils { + return new DateUtils(this._date.clone().endOf(unit as OpUnitType)); } /* ---------------------------------------------------------------- * Comparison * ---------------------------------------------------------------- */ - isBefore(date: DateInput | DateService): boolean { + isBefore(date: DateInput | DateUtils): boolean { return this._date.isBefore(this.toDayjs(date)); } - isAfter(date: DateInput | DateService): boolean { + isAfter(date: DateInput | DateUtils): boolean { return this._date.isAfter(this.toDayjs(date)); } - isSame(date: DateInput | DateService, unit?: TimeUnit): boolean { + isSame(date: DateInput | DateUtils, unit?: TimeUnit): boolean { return this._date.isSame(this.toDayjs(date), unit as OpUnitType); } - diff(date: DateInput | DateService, unit: TimeUnit, precise: boolean = false): number { + diff(date: DateInput | DateUtils, unit: TimeUnit, precise: boolean = false): number { return this._date.diff(this.toDayjs(date), unit as OpUnitType, precise); } /** * Calendar-day difference ignoring time components. */ - diffCalendarDay(date: DateInput | DateService): number { + diffCalendarDay(date: DateInput | DateUtils): number { const target = this.toDayjs(date); return this._date.startOf('day').diff(target.startOf('day'), 'day'); } @@ -263,7 +263,7 @@ export class DateService implements IDateService { try { return Intl.supportedValuesOf('timeZone'); } catch { - console.warn('[DateService] Failed to retrieve timezones via Intl.'); + console.warn('[DateUtils] Failed to retrieve timezones via Intl.'); } } @@ -290,7 +290,7 @@ export class DateService implements IDateService { * 2. Day.js dynamic abbreviation (DST-safe) */ get timezoneAbbr(): string { - const tz = DateService._defaultTimezone; + const tz = DateUtils._defaultTimezone; return INDONESIA_TZ_MAP[tz] ?? this._date.format('z'); } diff --git a/packages/utils/src/encryption-service/encryption-key.ts b/packages/utils/src/encryption/encryption-key.ts similarity index 100% rename from packages/utils/src/encryption-service/encryption-key.ts rename to packages/utils/src/encryption/encryption-key.ts diff --git a/packages/utils/src/encryption-service/encryption-service.test.ts b/packages/utils/src/encryption/encryption.utils.test.ts similarity index 81% rename from packages/utils/src/encryption-service/encryption-service.test.ts rename to packages/utils/src/encryption/encryption.utils.test.ts index 83b1b47..1a5ea62 100644 --- a/packages/utils/src/encryption-service/encryption-service.test.ts +++ b/packages/utils/src/encryption/encryption.utils.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { EncryptionService } from './encryption-service'; +import { EncryptionUtils } from './encryption.utils'; import { AES } from 'crypto-js'; // 1. Mock the key module to ensure test consistency @@ -7,15 +7,15 @@ vi.mock('./encryption-key', () => ({ ENC_STORAGE_KEY: 'default-test-key-123', })); -describe('EncryptionService', () => { +describe('EncryptionUtils', () => { const TEST_KEY = 'secret-key-xyz'; - let service: EncryptionService; + let service: EncryptionUtils; beforeEach(() => { // Reset the Singleton instance to ensure clean state for every test // eslint-disable-next-line @typescript-eslint/no-explicit-any - (EncryptionService as any).instance = undefined; - service = new EncryptionService(TEST_KEY); + (EncryptionUtils as any).instance = undefined; + service = new EncryptionUtils(TEST_KEY); }); afterEach(() => { @@ -24,12 +24,12 @@ describe('EncryptionService', () => { describe('Constructor', () => { it('should create an instance with the provided key', () => { - const instance = new EncryptionService('custom-key'); - expect(instance).toBeInstanceOf(EncryptionService); + const instance = new EncryptionUtils('custom-key'); + expect(instance).toBeInstanceOf(EncryptionUtils); }); it('should use the default ENC_STORAGE_KEY if no key is provided', () => { - const instance = new EncryptionService(); + const instance = new EncryptionUtils(); // Verify functionality with default key const data = 'test-default'; const encrypted = instance.encrypt(data); @@ -37,7 +37,7 @@ describe('EncryptionService', () => { }); it('should throw an Error if the provided key is empty', () => { - expect(() => new EncryptionService('')).toThrow('[EncryptionService] Encryption key is missing/empty.'); + expect(() => new EncryptionUtils('')).toThrow('[EncryptionUtils] Encryption key is missing/empty.'); }); }); @@ -76,7 +76,7 @@ describe('EncryptionService', () => { const encrypted = service.encrypt(plainText); // Decrypt with Service B (WRONG_KEY) - const wrongService = new EncryptionService('wrong-key-999'); + const wrongService = new EncryptionUtils('wrong-key-999'); const result = wrongService.decrypt(encrypted); expect(result).toBe(''); @@ -110,14 +110,14 @@ describe('EncryptionService', () => { expect(result).toBe(''); // Now we expect the catch block to be executed - expect(consoleSpy).toHaveBeenCalledWith('[EncryptionService] Decryption failed:', expect.any(Error)); + expect(consoleSpy).toHaveBeenCalledWith('[EncryptionUtils] Decryption failed:', expect.any(Error)); }); }); describe('getInstance() (Singleton)', () => { it('should return the same instance reference', () => { - const instance1 = EncryptionService.getInstance(); - const instance2 = EncryptionService.getInstance(); + const instance1 = EncryptionUtils.getInstance(); + const instance2 = EncryptionUtils.getInstance(); expect(instance1).toBe(instance2); }); diff --git a/packages/utils/src/encryption-service/encryption-service.ts b/packages/utils/src/encryption/encryption.utils.ts similarity index 70% rename from packages/utils/src/encryption-service/encryption-service.ts rename to packages/utils/src/encryption/encryption.utils.ts index 32f798d..6b98e32 100644 --- a/packages/utils/src/encryption-service/encryption-service.ts +++ b/packages/utils/src/encryption/encryption.utils.ts @@ -4,12 +4,12 @@ import { ENC_STORAGE_KEY } from './encryption-key'; /** * Interface defining encryption service methods. */ -interface IEncryptionService { +interface IEncryptionUtils { encrypt(data: string): string; decrypt(encryptedData: string): string; } -export class EncryptionService implements IEncryptionService { +export class EncryptionUtils implements IEncryptionUtils { private readonly _key: string; /** @@ -18,7 +18,7 @@ export class EncryptionService implements IEncryptionService { */ constructor(key: string = ENC_STORAGE_KEY) { if (!key) { - throw new Error('[EncryptionService] Encryption key is missing/empty.'); + throw new Error('[EncryptionUtils] Encryption key is missing/empty.'); } this._key = key; } @@ -54,23 +54,23 @@ export class EncryptionService implements IEncryptionService { return originalText; } catch (error) { - console.error('[EncryptionService] Decryption failed:', error); + console.error('[EncryptionUtils] Decryption failed:', error); return ''; } } /** - * Get the singleton instance of EncryptionService. + * Get the singleton instance of EncryptionUtils. * Uses the default ENC_STORAGE_KEY. - * @return EncryptionService instance + * @return EncryptionUtils instance */ - private static instance: EncryptionService; + private static instance: EncryptionUtils; - public static getInstance(): EncryptionService { - if (!EncryptionService.instance) { - EncryptionService.instance = new EncryptionService(); + public static getInstance(): EncryptionUtils { + if (!EncryptionUtils.instance) { + EncryptionUtils.instance = new EncryptionUtils(); } - return EncryptionService.instance; + return EncryptionUtils.instance; } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 070d0a9..017eb39 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,6 +1,6 @@ -export * from './encryption-service/encryption-key'; -export * from './encryption-service/encryption-service'; +export * from './encryption/encryption-key'; +export * from './encryption/encryption.utils'; -export * from './date-service/date-service'; -export * from './currency-service/currency-service'; -export * from './string-service/string-service'; +export * from './date/date.utils'; +export * from './currency/currency.utils'; +export * from './string/string.utils'; diff --git a/packages/utils/src/string-service/string-service.test.ts b/packages/utils/src/string/string.utils.test.ts similarity index 63% rename from packages/utils/src/string-service/string-service.test.ts rename to packages/utils/src/string/string.utils.test.ts index ae228c1..8eb5281 100644 --- a/packages/utils/src/string-service/string-service.test.ts +++ b/packages/utils/src/string/string.utils.test.ts @@ -1,45 +1,45 @@ import { describe, it, expect } from 'vitest'; -import { StringService } from './string-service'; +import { StringUtils } from './string.utils'; -describe('StringService', () => { +describe('StringUtils', () => { // ---------------------------------------------------------------- // Instantiation & Static Methods // ---------------------------------------------------------------- describe('Initialization', () => { it('should handle string input correctly', () => { - const svc = new StringService('Hello'); + const svc = new StringUtils('Hello'); expect(svc.value()).toBe('Hello'); }); it('should handle number input by converting to string', () => { - const svc = new StringService(123); + const svc = new StringUtils(123); expect(svc.value()).toBe('123'); }); it('should handle null input gracefully (default to empty string)', () => { - const svc = new StringService(null); + const svc = new StringUtils(null); expect(svc.value()).toBe(''); }); it('should handle undefined input gracefully', () => { - const svc = new StringService(undefined); + const svc = new StringUtils(undefined); expect(svc.value()).toBe(''); }); it('should support static factory method .of()', () => { - const svc = StringService.of('Factory'); + const svc = StringUtils.of('Factory'); expect(svc.value()).toBe('Factory'); }); }); describe('random()', () => { it('should generate string with specified length', () => { - const random = StringService.random(15); + const random = StringUtils.random(15); expect(random.value()).toHaveLength(15); }); it('should generate alphanumeric characters only', () => { - const random = StringService.random(100); + const random = StringUtils.random(100); expect(random.value()).toMatch(/^[A-Za-z0-9]+$/); }); }); @@ -49,19 +49,19 @@ describe('StringService', () => { // ---------------------------------------------------------------- describe('Transformations', () => { it('should convert to upperCase', () => { - expect(StringService.of('hello').upperCase().value()).toBe('HELLO'); + expect(StringUtils.of('hello').upperCase().value()).toBe('HELLO'); }); it('should convert to lowerCase', () => { - expect(StringService.of('HELLO').lowerCase().value()).toBe('hello'); + expect(StringUtils.of('HELLO').lowerCase().value()).toBe('hello'); }); it('should trim whitespace', () => { - expect(StringService.of(' hello ').trim().value()).toBe('hello'); + expect(StringUtils.of(' hello ').trim().value()).toBe('hello'); }); it('should support method chaining (Immutable)', () => { - const original = StringService.of(' hello '); + const original = StringUtils.of(' hello '); const modified = original.trim().upperCase(); expect(original.value()).toBe(' hello '); // Original untouched @@ -71,16 +71,16 @@ describe('StringService', () => { describe('Capitalization', () => { it('should capitalize first letter only', () => { - expect(StringService.of('hELLO world').capitalizeFirst().value()).toBe('Hello world'); + expect(StringUtils.of('hELLO world').capitalizeFirst().value()).toBe('Hello world'); }); it('should capitalize each word', () => { - expect(StringService.of('hello world').capitalizeEachWord().value()).toBe('Hello World'); + expect(StringUtils.of('hello world').capitalizeEachWord().value()).toBe('Hello World'); }); it('should handle double spaces in capitalizeEachWord', () => { // Test regex logic for splitting words - expect(StringService.of('hello world').capitalizeEachWord().value()).toBe('Hello World'); + expect(StringUtils.of('hello world').capitalizeEachWord().value()).toBe('Hello World'); }); }); @@ -89,29 +89,29 @@ describe('StringService', () => { // ---------------------------------------------------------------- describe('slugify()', () => { it('should create valid slugs', () => { - expect(StringService.of('Hello World!').slugify().value()).toBe('hello-world'); + expect(StringUtils.of('Hello World!').slugify().value()).toBe('hello-world'); }); it('should handle complex characters', () => { - expect(StringService.of('C# & .NET Core').slugify().value()).toBe('c-net-core'); + expect(StringUtils.of('C# & .NET Core').slugify().value()).toBe('c-net-core'); }); it('should remove leading/trailing separators', () => { - expect(StringService.of('---Hello---').slugify().value()).toBe('hello'); + expect(StringUtils.of('---Hello---').slugify().value()).toBe('hello'); }); }); describe('limit()', () => { it('should truncate string if longer than max', () => { - expect(StringService.of('Hello World').limit(5).value()).toBe('Hello...'); + expect(StringUtils.of('Hello World').limit(5).value()).toBe('Hello...'); }); it('should not truncate if shorter than max', () => { - expect(StringService.of('Hi').limit(5).value()).toBe('Hi'); + expect(StringUtils.of('Hi').limit(5).value()).toBe('Hi'); }); it('should support custom suffix', () => { - expect(StringService.of('Hello World').limit(5, '!!!').value()).toBe('Hello!!!'); + expect(StringUtils.of('Hello World').limit(5, '!!!').value()).toBe('Hello!!!'); }); }); @@ -121,15 +121,15 @@ describe('StringService', () => { // Start 4: 0812 // End 3: 890 // Middle masked: ***** - expect(StringService.of('081234567890').mask(4, 3).value()).toBe('0812*****890'); + expect(StringUtils.of('081234567890').mask(4, 3).value()).toBe('0812*****890'); }); it('should handle custom mask char', () => { - expect(StringService.of('123456').mask(2, 2, 'X').value()).toBe('12XX56'); + expect(StringUtils.of('123456').mask(2, 2, 'X').value()).toBe('12XX56'); }); it('should return original if string is shorter than visible parts', () => { - expect(StringService.of('123').mask(5, 5).value()).toBe('123'); + expect(StringUtils.of('123').mask(5, 5).value()).toBe('123'); }); }); @@ -138,26 +138,26 @@ describe('StringService', () => { // ---------------------------------------------------------------- describe('Inspection & Outputs', () => { it('should detect empty strings correctly', () => { - expect(StringService.of('').isEmpty()).toBe(true); - expect(StringService.of(' ').isEmpty()).toBe(true); // Trim check - expect(StringService.of(null).isEmpty()).toBe(true); - expect(StringService.of('a').isEmpty()).toBe(false); + expect(StringUtils.of('').isEmpty()).toBe(true); + expect(StringUtils.of(' ').isEmpty()).toBe(true); // Trim check + expect(StringUtils.of(null).isEmpty()).toBe(true); + expect(StringUtils.of('a').isEmpty()).toBe(false); }); it('should return default value with orElse', () => { - expect(StringService.of(null).orElse('Default')).toBe('Default'); - expect(StringService.of('Valid').orElse('Default')).toBe('Valid'); + expect(StringUtils.of(null).orElse('Default')).toBe('Default'); + expect(StringUtils.of('Valid').orElse('Default')).toBe('Valid'); }); it('should support native string interpolation (toString)', () => { - const name = StringService.of('World'); + const name = StringUtils.of('World'); expect(`Hello ${name}`).toBe('Hello World'); }); it('should support JSON serialization (toJSON)', () => { const data = { id: 1, - name: StringService.of('Product A'), + name: StringUtils.of('Product A'), }; // JSON.stringify automatically calls .toJSON() diff --git a/packages/utils/src/string-service/string-service.ts b/packages/utils/src/string/string.utils.ts similarity index 70% rename from packages/utils/src/string-service/string-service.ts rename to packages/utils/src/string/string.utils.ts index 223fc3a..0e50a1b 100644 --- a/packages/utils/src/string-service/string-service.ts +++ b/packages/utils/src/string/string.utils.ts @@ -1,6 +1,6 @@ /** * ------------------------------------------------------------ - * StringService + * StringUtils * ------------------------------------------------------------ * Centralized string manipulation utility. * @@ -12,7 +12,7 @@ * * @example * // Basic Chaining - * StringService.of(" hello world ") + * StringUtils.of(" hello world ") * .trim() * .capitalizeEachWord() * .value(); // "Hello World" @@ -29,21 +29,21 @@ export type StringInput = string | number | null | undefined; * Interfaces * ------------------------------------------------------------------ */ -interface IStringService { +interface IStringUtils { // --- Output & Conversion --- value(): string; toString(): string; toJSON(): string; // --- Manipulations (Chainable) --- - upperCase(): IStringService; - lowerCase(): IStringService; - capitalizeFirst(): IStringService; - capitalizeEachWord(): IStringService; - limit(maxLength: number, suffix?: string): IStringService; - trim(): IStringService; - slugify(): IStringService; - mask(visibleStart: number, visibleEnd: number, maskChar?: string): IStringService; + 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; @@ -51,10 +51,10 @@ interface IStringService { } /* ------------------------------------------------------------------ - * StringService + * StringUtils * ------------------------------------------------------------------ */ -export class StringService implements IStringService { +export class StringUtils implements IStringUtils { private readonly _value: string; /* ---------------------------------------------------------------- @@ -75,10 +75,10 @@ export class StringService implements IStringService { * * @param value - The input string, number, or null/undefined. * @example - * StringService.of("hello").upperCase().value() + * StringUtils.of("hello").upperCase().value() */ - static of(value?: StringInput): StringService { - return new StringService(value); + static of(value?: StringInput): StringUtils { + return new StringUtils(value); } /** @@ -87,15 +87,15 @@ export class StringService implements IStringService { * * @param length - Length of the generated string (default: 10). * @example - * StringService.random(8).value() // "aB9x2Z1m" + * StringUtils.random(8).value() // "aB9x2Z1m" */ - static random(length: number = 10): StringService { + 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 StringService(result); + return new StringUtils(result); } /* ---------------------------------------------------------------- @@ -106,20 +106,20 @@ export class StringService implements IStringService { * Converts string to UPPERCASE. * * @example - * StringService.of("hello").upperCase().value() // "HELLO" + * StringUtils.of("hello").upperCase().value() // "HELLO" */ - upperCase(): StringService { - return new StringService(this._value.toUpperCase()); + upperCase(): StringUtils { + return new StringUtils(this._value.toUpperCase()); } /** * Converts string to lowercase. * * @example - * StringService.of("HELLO").lowerCase().value() // "hello" + * StringUtils.of("HELLO").lowerCase().value() // "hello" */ - lowerCase(): StringService { - return new StringService(this._value.toLowerCase()); + lowerCase(): StringUtils { + return new StringUtils(this._value.toLowerCase()); } /** @@ -127,12 +127,12 @@ export class StringService implements IStringService { * Remainder is forced to lowercase (Sentence case). * * @example - * StringService.of("HELLO world").capitalizeFirst().value() // "Hello world" + * StringUtils.of("HELLO world").capitalizeFirst().value() // "Hello world" */ - capitalizeFirst(): StringService { + capitalizeFirst(): StringUtils { if (!this._value) return this; const lower = this._value.toLowerCase(); - return new StringService(lower.charAt(0).toUpperCase() + lower.slice(1)); + return new StringUtils(lower.charAt(0).toUpperCase() + lower.slice(1)); } /** @@ -140,17 +140,17 @@ export class StringService implements IStringService { * Automatically handles multiple spaces. * * @example - * StringService.of("hello world").capitalizeEachWord().value() // "Hello World" - * StringService.of("hello world").capitalizeEachWord().value() // "Hello World" + * StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World" + * StringUtils.of("hello world").capitalizeEachWord().value() // "Hello World" */ - capitalizeEachWord(): StringService { + 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 StringService(transformed); + return new StringUtils(transformed); } /** @@ -159,12 +159,12 @@ export class StringService implements IStringService { * @param maxLength - The character limit. * @param suffix - The string to append if truncated (default: "..."). * @example - * StringService.of("Lorem Ipsum").limit(5).value() // "Lorem..." - * StringService.of("Lorem Ipsum").limit(5, "").value() // "Lorem" + * StringUtils.of("Lorem Ipsum").limit(5).value() // "Lorem..." + * StringUtils.of("Lorem Ipsum").limit(5, "").value() // "Lorem" */ - limit(maxLength: number, suffix: string = '...'): StringService { + limit(maxLength: number, suffix: string = '...'): StringUtils { if (this._value.length <= maxLength) return this; - return new StringService(this._value.substring(0, maxLength) + suffix); + return new StringUtils(this._value.substring(0, maxLength) + suffix); } /* ---------------------------------------------------------------- @@ -175,10 +175,10 @@ export class StringService implements IStringService { * Removes whitespace from both ends of the string. * * @example - * StringService.of(" data ").trim().value() // "data" + * StringUtils.of(" data ").trim().value() // "data" */ - trim(): StringService { - return new StringService(this._value.trim()); + trim(): StringUtils { + return new StringUtils(this._value.trim()); } /** @@ -186,17 +186,17 @@ export class StringService implements IStringService { * Removes special characters and replaces spaces with dashes. * * @example - * StringService.of("Hello World!").slugify().value() // "hello-world" - * StringService.of("C# & .NET").slugify().value() // "c-net" + * StringUtils.of("Hello World!").slugify().value() // "hello-world" + * StringUtils.of("C# & .NET").slugify().value() // "c-net" */ - slugify(): StringService { + 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 StringService(slug); + return new StringUtils(slug); } /** @@ -206,16 +206,16 @@ export class StringService implements IStringService { * @param visibleEnd - Number of characters to show at the end. * @param maskChar - The character to use for masking (default: "*"). * @example - * StringService.of("08123456789").mask(4, 3).value() // "0812****789" + * StringUtils.of("08123456789").mask(4, 3).value() // "0812****789" */ - mask(visibleStart: number = 0, visibleEnd: number = 0, maskChar: string = '*'): StringService { + 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 StringService(start + middle + end); + return new StringUtils(start + middle + end); } /* ---------------------------------------------------------------- @@ -227,8 +227,8 @@ export class StringService implements IStringService { * * @returns true if string is empty or whitespace-only. * @example - * StringService.of("").isEmpty() // true - * StringService.of(" ").isEmpty() // true + * StringUtils.of("").isEmpty() // true + * StringUtils.of(" ").isEmpty() // true */ isEmpty(): boolean { return this._value.trim().length === 0; @@ -246,7 +246,7 @@ export class StringService implements IStringService { * * @param defaultValue - The fallback string. * @example - * StringService.of(null).orElse("N/A") // "N/A" + * StringUtils.of(null).orElse("N/A") // "N/A" */ orElse(defaultValue: string): string { return this.isEmpty() ? defaultValue : this._value; @@ -256,7 +256,7 @@ export class StringService implements IStringService { * Allows default JS string interpolation to work. * * @example - * const name = StringService.of("John"); + * const name = StringUtils.of("John"); * console.log(`Hello ${name}`); // "Hello John" */ toString(): string { @@ -267,7 +267,7 @@ export class StringService implements IStringService { * Allows JSON.stringify to serialize just the string, not the object wrapper. * * @example - * JSON.stringify({ name: StringService.of("John") }) // '{"name":"John"}' + * JSON.stringify({ name: StringUtils.of("John") }) // '{"name":"John"}' */ toJSON(): string { return this._value;