style: auto-format codebase with Prettier

Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
This commit is contained in:
Wuesteon 2025-11-27 18:33:16 +01:00
parent 0241f5554c
commit d36b321d9d
3952 changed files with 661498 additions and 739751 deletions

View file

@ -1,22 +1,22 @@
{
"name": "@manacore/shared-utils",
"version": "0.1.0",
"private": true,
"description": "Shared utility functions for Manacore monorepo",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"type-check": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"date-fns": "^4.1.0"
},
"devDependencies": {
"@types/node": "^24.10.1",
"typescript": "^5.9.3"
}
"name": "@manacore/shared-utils",
"version": "0.1.0",
"private": true,
"description": "Shared utility functions for Manacore monorepo",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"type-check": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"date-fns": "^4.1.0"
},
"devDependencies": {
"@types/node": "^24.10.1",
"typescript": "^5.9.3"
}
}

View file

@ -6,66 +6,61 @@
* Sleep for a specified number of milliseconds
*/
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Retry a function with exponential backoff
*/
export async function retry<T>(
fn: () => Promise<T>,
options: {
maxAttempts?: number;
initialDelay?: number;
maxDelay?: number;
backoffMultiplier?: number;
} = {}
fn: () => Promise<T>,
options: {
maxAttempts?: number;
initialDelay?: number;
maxDelay?: number;
backoffMultiplier?: number;
} = {}
): Promise<T> {
const {
maxAttempts = 3,
initialDelay = 1000,
maxDelay = 10000,
backoffMultiplier = 2,
} = options;
const { maxAttempts = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2 } = options;
let lastError: Error | undefined;
let delay = initialDelay;
let lastError: Error | undefined;
let delay = initialDelay;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt === maxAttempts) {
break;
}
if (attempt === maxAttempts) {
break;
}
await sleep(delay);
delay = Math.min(delay * backoffMultiplier, maxDelay);
}
}
await sleep(delay);
delay = Math.min(delay * backoffMultiplier, maxDelay);
}
}
throw lastError;
throw lastError;
}
/**
* Debounce a function
*/
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout | null = null;
let timeoutId: NodeJS.Timeout | null = null;
return (...args: Parameters<T>) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
return (...args: Parameters<T>) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fn(...args);
timeoutId = null;
}, delay);
};
timeoutId = setTimeout(() => {
fn(...args);
timeoutId = null;
}, delay);
};
}

View file

@ -111,7 +111,7 @@ export function createCache<T = string>(config: CacheConfig) {
id,
data,
expires: Date.now() + expiresInMs,
createdAt: Date.now()
createdAt: Date.now(),
};
store.put(entry);
@ -180,7 +180,7 @@ export function createCache<T = string>(config: CacheConfig) {
async has(id: string): Promise<boolean> {
const data = await this.get(id);
return data !== null;
}
},
};
}
@ -188,7 +188,7 @@ export function createCache<T = string>(config: CacheConfig) {
const DEFAULT_URL_CACHE_CONFIG: CacheConfig = {
dbName: 'manacore-cache',
storeName: 'urls',
version: 1
version: 1,
};
/**

View file

@ -6,8 +6,8 @@ import { format, formatDistanceToNow, parseISO, isToday, isYesterday } from 'dat
import { de, enUS } from 'date-fns/locale';
const locales = {
de,
en: enUS,
de,
en: enUS,
};
export type LocaleKey = keyof typeof locales;
@ -16,30 +16,30 @@ export type LocaleKey = keyof typeof locales;
* Format a date string to a readable format
*/
export function formatDate(
date: string | Date,
formatStr: string = 'PPP',
locale: LocaleKey = 'de'
date: string | Date,
formatStr: string = 'PPP',
locale: LocaleKey = 'de'
): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date;
return format(dateObj, formatStr, { locale: locales[locale] });
const dateObj = typeof date === 'string' ? parseISO(date) : date;
return format(dateObj, formatStr, { locale: locales[locale] });
}
/**
* Get relative time from now (e.g., "2 hours ago")
*/
export function formatRelativeTime(date: string | Date, locale: LocaleKey = 'de'): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date;
return formatDistanceToNow(dateObj, {
addSuffix: true,
locale: locales[locale],
});
const dateObj = typeof date === 'string' ? parseISO(date) : date;
return formatDistanceToNow(dateObj, {
addSuffix: true,
locale: locales[locale],
});
}
/**
* Format a date for API requests (ISO string)
*/
export function toISOString(date: Date): string {
return date.toISOString();
return date.toISOString();
}
/**
@ -50,28 +50,25 @@ export function toISOString(date: Date): string {
* - Yesterday "Yesterday, 14:30" / "Gestern, 14:30"
* - Other "15. März 2024, 14:30" / "March 15, 2024, 2:30 PM"
*/
export function formatTimestamp(
date: string | Date,
locale: LocaleKey = 'de'
): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date;
const timeFormat = locale === 'de' ? 'HH:mm' : 'h:mm a';
export function formatTimestamp(date: string | Date, locale: LocaleKey = 'de'): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date;
const timeFormat = locale === 'de' ? 'HH:mm' : 'h:mm a';
const labels = {
de: { today: 'Heute', yesterday: 'Gestern' },
en: { today: 'Today', yesterday: 'Yesterday' },
};
const labels = {
de: { today: 'Heute', yesterday: 'Gestern' },
en: { today: 'Today', yesterday: 'Yesterday' },
};
if (isToday(dateObj)) {
return `${labels[locale].today}, ${format(dateObj, timeFormat)}`;
}
if (isToday(dateObj)) {
return `${labels[locale].today}, ${format(dateObj, timeFormat)}`;
}
if (isYesterday(dateObj)) {
return `${labels[locale].yesterday}, ${format(dateObj, timeFormat)}`;
}
if (isYesterday(dateObj)) {
return `${labels[locale].yesterday}, ${format(dateObj, timeFormat)}`;
}
const dateFormat = locale === 'de' ? 'd. MMMM yyyy' : 'MMMM d, yyyy';
return `${format(dateObj, dateFormat, { locale: locales[locale] })}, ${format(dateObj, timeFormat)}`;
const dateFormat = locale === 'de' ? 'd. MMMM yyyy' : 'MMMM d, yyyy';
return `${format(dateObj, dateFormat, { locale: locales[locale] })}, ${format(dateObj, timeFormat)}`;
}
/**
@ -87,7 +84,7 @@ export { isToday, isYesterday } from 'date-fns';
* @returns Formatted short date string (e.g., "15 Mar 2024" or "15. Mär. 2024")
*/
export function formatShortDate(date: Date | string, locale: LocaleKey = 'de'): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date;
const dateObj = typeof date === 'string' ? parseISO(date) : date;
return format(dateObj, 'dd MMM yyyy', { locale: locales[locale] });
return format(dateObj, 'dd MMM yyyy', { locale: locales[locale] });
}

View file

@ -6,149 +6,138 @@
* Format duration from seconds to MM:SS or HH:MM:SS format
*/
export function formatDuration(seconds: number): string {
if (!Number.isFinite(seconds) || seconds < 0) {
return '--:--';
}
if (!Number.isFinite(seconds) || seconds < 0) {
return '--:--';
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
/**
* Format duration from milliseconds
*/
export function formatDurationFromMs(milliseconds: number): string {
return formatDuration(milliseconds / 1000);
return formatDuration(milliseconds / 1000);
}
/**
* Format duration with units (e.g., "2 min 30 sec" or "1h 23m")
*/
export function formatDurationWithUnits(
seconds: number,
locale: 'en' | 'de' = 'en'
): string {
if (!Number.isFinite(seconds) || seconds < 0) {
return locale === 'de' ? 'keine Zeit' : 'no time';
}
export function formatDurationWithUnits(seconds: number, locale: 'en' | 'de' = 'en'): string {
if (!Number.isFinite(seconds) || seconds < 0) {
return locale === 'de' ? 'keine Zeit' : 'no time';
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
if (hours > 0) {
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
}
if (hours > 0) {
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
}
if (minutes > 0) {
return remainingSeconds > 0 && minutes < 5
? `${minutes}m ${remainingSeconds}s`
: `${minutes}m`;
}
if (minutes > 0) {
return remainingSeconds > 0 && minutes < 5 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
}
return `${remainingSeconds}s`;
return `${remainingSeconds}s`;
}
/**
* Format duration to human readable text
*/
export function formatDurationHumanReadable(
seconds: number,
locale: 'en' | 'de' = 'de'
): string {
if (!Number.isFinite(seconds) || seconds < 0) {
return locale === 'de' ? 'keine Zeit' : 'no time';
}
export function formatDurationHumanReadable(seconds: number, locale: 'en' | 'de' = 'de'): string {
if (!Number.isFinite(seconds) || seconds < 0) {
return locale === 'de' ? 'keine Zeit' : 'no time';
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
const parts: string[] = [];
const parts: string[] = [];
if (locale === 'de') {
if (hours > 0) {
parts.push(`${hours} ${hours === 1 ? 'Stunde' : 'Stunden'}`);
}
if (minutes > 0) {
parts.push(`${minutes} ${minutes === 1 ? 'Minute' : 'Minuten'}`);
}
if (remainingSeconds > 0 && hours === 0) {
parts.push(`${remainingSeconds} ${remainingSeconds === 1 ? 'Sekunde' : 'Sekunden'}`);
}
return parts.length === 0 ? '0 Sekunden' : parts.join(' ');
} else {
if (hours > 0) {
parts.push(`${hours} ${hours === 1 ? 'hour' : 'hours'}`);
}
if (minutes > 0) {
parts.push(`${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`);
}
if (remainingSeconds > 0 && hours === 0) {
parts.push(`${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`);
}
return parts.length === 0 ? '0 seconds' : parts.join(' ');
}
if (locale === 'de') {
if (hours > 0) {
parts.push(`${hours} ${hours === 1 ? 'Stunde' : 'Stunden'}`);
}
if (minutes > 0) {
parts.push(`${minutes} ${minutes === 1 ? 'Minute' : 'Minuten'}`);
}
if (remainingSeconds > 0 && hours === 0) {
parts.push(`${remainingSeconds} ${remainingSeconds === 1 ? 'Sekunde' : 'Sekunden'}`);
}
return parts.length === 0 ? '0 Sekunden' : parts.join(' ');
} else {
if (hours > 0) {
parts.push(`${hours} ${hours === 1 ? 'hour' : 'hours'}`);
}
if (minutes > 0) {
parts.push(`${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`);
}
if (remainingSeconds > 0 && hours === 0) {
parts.push(`${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`);
}
return parts.length === 0 ? '0 seconds' : parts.join(' ');
}
}
/**
* Format file size from bytes to human readable string
*/
export function formatFileSize(bytes: number, decimals: number = 1): string {
if (bytes === 0) return '0 B';
if (!Number.isFinite(bytes) || bytes < 0) return '--';
if (bytes === 0) return '0 B';
if (!Number.isFinite(bytes) || bytes < 0) return '--';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
}
/**
* Format number with thousands separator
*/
export function formatNumber(
num: number,
locale: string = 'de-DE'
): string {
return num.toLocaleString(locale);
export function formatNumber(num: number, locale: string = 'de-DE'): string {
return num.toLocaleString(locale);
}
/**
* Format currency
*/
export function formatCurrency(
amount: number,
currency: string = 'EUR',
locale: string = 'de-DE'
amount: number,
currency: string = 'EUR',
locale: string = 'de-DE'
): string {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount);
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount);
}
/**
* Format percentage
*/
export function formatPercent(
value: number,
decimals: number = 0,
locale: string = 'de-DE'
value: number,
decimals: number = 0,
locale: string = 'de-DE'
): string {
return new Intl.NumberFormat(locale, {
style: 'percent',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
return new Intl.NumberFormat(locale, {
style: 'percent',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value);
}
/**
@ -164,15 +153,15 @@ export const formatDurationCompact = formatDuration;
* @returns Duration in seconds
*/
export function parseDuration(duration: string): number {
const parts = duration.split(':').map(Number);
const parts = duration.split(':').map(Number);
if (parts.length === 3) {
// Hours:Minutes:Seconds
return parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// Minutes:Seconds
return parts[0] * 60 + parts[1];
}
if (parts.length === 3) {
// Hours:Minutes:Seconds
return parts[0] * 3600 + parts[1] * 60 + parts[2];
} else if (parts.length === 2) {
// Minutes:Seconds
return parts[0] * 60 + parts[1];
}
return parts[0] || 0;
return parts[0] || 0;
}

View file

@ -24,7 +24,9 @@ export interface ShortcutGroup {
*/
export function matchesShortcut(event: KeyboardEvent, shortcut: ShortcutAction): boolean {
const keyMatches = event.key.toLowerCase() === shortcut.key.toLowerCase();
const ctrlMatches = shortcut.ctrl ? event.ctrlKey || event.metaKey : !event.ctrlKey && !event.metaKey;
const ctrlMatches = shortcut.ctrl
? event.ctrlKey || event.metaKey
: !event.ctrlKey && !event.metaKey;
const shiftMatches = shortcut.shift ? event.shiftKey : !event.shiftKey;
const altMatches = shortcut.alt ? event.altKey : !event.altKey;
@ -75,11 +77,7 @@ export function createShortcutHandler(
// Don't handle shortcuts if user is typing in an input (unless explicitly allowed)
if (!options?.allowInInputs) {
const target = event.target as HTMLElement;
if (
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable
) {
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
}
@ -120,7 +118,7 @@ export function createShortcuts(actions: {
key: 'f',
ctrl: true,
description: 'Search',
action: actions.onSearch
action: actions.onSearch,
});
}
@ -129,7 +127,7 @@ export function createShortcuts(actions: {
key: 's',
ctrl: true,
description: 'Save',
action: actions.onSave
action: actions.onSave,
});
}
@ -138,7 +136,7 @@ export function createShortcuts(actions: {
key: 'e',
ctrl: true,
description: 'Edit',
action: actions.onEdit
action: actions.onEdit,
});
}
@ -146,7 +144,7 @@ export function createShortcuts(actions: {
generalShortcuts.push({
key: 'Escape',
description: 'Cancel',
action: actions.onCancel
action: actions.onCancel,
});
}
@ -155,7 +153,7 @@ export function createShortcuts(actions: {
key: 'n',
ctrl: true,
description: 'New',
action: actions.onNew
action: actions.onNew,
});
}
@ -164,7 +162,7 @@ export function createShortcuts(actions: {
key: 'Delete',
ctrl: true,
description: 'Delete',
action: actions.onDelete
action: actions.onDelete,
});
}
@ -174,7 +172,7 @@ export function createShortcuts(actions: {
ctrl: true,
shift: true,
description: 'Copy',
action: actions.onCopy
action: actions.onCopy,
});
}
@ -183,7 +181,7 @@ export function createShortcuts(actions: {
key: 'z',
ctrl: true,
description: 'Undo',
action: actions.onUndo
action: actions.onUndo,
});
}
@ -193,14 +191,14 @@ export function createShortcuts(actions: {
ctrl: true,
shift: true,
description: 'Redo',
action: actions.onRedo
action: actions.onRedo,
});
}
if (generalShortcuts.length > 0) {
shortcuts.push({
name: 'General',
shortcuts: generalShortcuts
shortcuts: generalShortcuts,
});
}
@ -224,7 +222,7 @@ export function shortcuts(node: HTMLElement, shortcutActions: ShortcutAction[])
node.removeEventListener('keydown', handler);
const newHandler = createShortcutHandler(newShortcutActions);
node.addEventListener('keydown', newHandler);
}
},
};
}

View file

@ -6,38 +6,38 @@
* Truncate a string to a maximum length with ellipsis
*/
export function truncate(str: string, maxLength: number): string {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + '...';
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + '...';
}
/**
* Capitalize the first letter of a string
*/
export function capitalize(str: string): string {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Generate a random string ID
*/
export function generateId(length: number = 8): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* Slugify a string for URLs
*/
export function slugify(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}

View file

@ -6,29 +6,29 @@
* Validate email address format
*/
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validate URL format
*/
export function isValidUrl(url: string): boolean {
try {
new URL(url);
return true;
} catch {
return false;
}
try {
new URL(url);
return true;
} catch {
return false;
}
}
/**
* Validate phone number (basic international format)
*/
export function isValidPhone(phone: string): boolean {
// Allows: +49123456789, 0123456789, +1 (555) 123-4567
const phoneRegex = /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/;
return phoneRegex.test(phone) && phone.replace(/\D/g, '').length >= 6;
// Allows: +49123456789, 0123456789, +1 (555) 123-4567
const phoneRegex = /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/;
return phoneRegex.test(phone) && phone.replace(/\D/g, '').length >= 6;
}
/**
@ -36,67 +36,68 @@ export function isValidPhone(phone: string): boolean {
* Returns an object with validation details
*/
export function validatePassword(password: string): {
isValid: boolean;
hasMinLength: boolean;
hasUppercase: boolean;
hasLowercase: boolean;
hasNumber: boolean;
hasSpecialChar: boolean;
score: number;
isValid: boolean;
hasMinLength: boolean;
hasUppercase: boolean;
hasLowercase: boolean;
hasNumber: boolean;
hasSpecialChar: boolean;
score: number;
} {
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /[0-9]/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /[0-9]/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const score = [hasMinLength, hasUppercase, hasLowercase, hasNumber, hasSpecialChar]
.filter(Boolean).length;
const score = [hasMinLength, hasUppercase, hasLowercase, hasNumber, hasSpecialChar].filter(
Boolean
).length;
return {
isValid: hasMinLength && hasUppercase && hasLowercase && hasNumber,
hasMinLength,
hasUppercase,
hasLowercase,
hasNumber,
hasSpecialChar,
score,
};
return {
isValid: hasMinLength && hasUppercase && hasLowercase && hasNumber,
hasMinLength,
hasUppercase,
hasLowercase,
hasNumber,
hasSpecialChar,
score,
};
}
/**
* Check if a string is empty or only whitespace
*/
export function isEmpty(value: string | null | undefined): boolean {
return !value || value.trim().length === 0;
return !value || value.trim().length === 0;
}
/**
* Check if a string exceeds max length
*/
export function isWithinMaxLength(value: string, maxLength: number): boolean {
return value.length <= maxLength;
return value.length <= maxLength;
}
/**
* Check if a string meets minimum length
*/
export function meetsMinLength(value: string, minLength: number): boolean {
return value.length >= minLength;
return value.length >= minLength;
}
/**
* Validate UUID format
*/
export function isValidUuid(uuid: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}
/**
* Validate hex color format
*/
export function isValidHexColor(color: string): boolean {
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
return hexRegex.test(color);
const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
return hexRegex.test(color);
}

View file

@ -1,18 +1,18 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}