Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
263 lines
7.5 KiB
TypeScript
263 lines
7.5 KiB
TypeScript
/**
|
|
* Utility Functions
|
|
*/
|
|
|
|
// =============================================================================
|
|
// ID GENERATION
|
|
// =============================================================================
|
|
|
|
export function generateId(prefix?: string): string {
|
|
const timestamp = Date.now().toString(36)
|
|
const random = Math.random().toString(36).substring(2, 9)
|
|
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`
|
|
}
|
|
|
|
export function generateUUID(): string {
|
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
return crypto.randomUUID()
|
|
}
|
|
// Fallback for older environments
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
const r = (Math.random() * 16) | 0
|
|
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
return v.toString(16)
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// DATE UTILITIES
|
|
// =============================================================================
|
|
|
|
export function formatDate(date: Date | string, locale = 'de-DE'): string {
|
|
const d = typeof date === 'string' ? new Date(date) : date
|
|
return d.toLocaleDateString(locale, {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
})
|
|
}
|
|
|
|
export function formatDateTime(date: Date | string, locale = 'de-DE'): string {
|
|
const d = typeof date === 'string' ? new Date(date) : date
|
|
return d.toLocaleString(locale, {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
}
|
|
|
|
export function isDateExpired(date: Date | string): boolean {
|
|
const d = typeof date === 'string' ? new Date(date) : date
|
|
return d < new Date()
|
|
}
|
|
|
|
export function addDays(date: Date, days: number): Date {
|
|
const result = new Date(date)
|
|
result.setDate(result.getDate() + days)
|
|
return result
|
|
}
|
|
|
|
export function daysBetween(date1: Date, date2: Date): number {
|
|
const oneDay = 24 * 60 * 60 * 1000
|
|
return Math.round(Math.abs((date1.getTime() - date2.getTime()) / oneDay))
|
|
}
|
|
|
|
// =============================================================================
|
|
// STRING UTILITIES
|
|
// =============================================================================
|
|
|
|
export function truncate(str: string, maxLength: number, suffix = '...'): string {
|
|
if (str.length <= maxLength) return str
|
|
return str.substring(0, maxLength - suffix.length) + suffix
|
|
}
|
|
|
|
export function slugify(str: string): string {
|
|
return str
|
|
.toLowerCase()
|
|
.replace(/[äöü]/g, c => ({ ä: 'ae', ö: 'oe', ü: 'ue' })[c] || c)
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-|-$/g, '')
|
|
}
|
|
|
|
export function capitalize(str: string): string {
|
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
|
|
}
|
|
|
|
// =============================================================================
|
|
// ARRAY UTILITIES
|
|
// =============================================================================
|
|
|
|
export function groupBy<T, K extends string | number>(
|
|
array: T[],
|
|
keyFn: (item: T) => K
|
|
): Record<K, T[]> {
|
|
return array.reduce(
|
|
(groups, item) => {
|
|
const key = keyFn(item)
|
|
if (!groups[key]) {
|
|
groups[key] = []
|
|
}
|
|
groups[key].push(item)
|
|
return groups
|
|
},
|
|
{} as Record<K, T[]>
|
|
)
|
|
}
|
|
|
|
export function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[] {
|
|
const seen = new Set()
|
|
return array.filter(item => {
|
|
const key = keyFn(item)
|
|
if (seen.has(key)) return false
|
|
seen.add(key)
|
|
return true
|
|
})
|
|
}
|
|
|
|
export function sortBy<T>(array: T[], keyFn: (item: T) => number | string, desc = false): T[] {
|
|
return [...array].sort((a, b) => {
|
|
const aKey = keyFn(a)
|
|
const bKey = keyFn(b)
|
|
if (aKey < bKey) return desc ? 1 : -1
|
|
if (aKey > bKey) return desc ? -1 : 1
|
|
return 0
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// OBJECT UTILITIES
|
|
// =============================================================================
|
|
|
|
export function deepClone<T>(obj: T): T {
|
|
if (obj === null || typeof obj !== 'object') return obj
|
|
if (obj instanceof Date) return new Date(obj.getTime()) as T
|
|
if (Array.isArray(obj)) return obj.map(item => deepClone(item)) as T
|
|
return Object.fromEntries(
|
|
Object.entries(obj as object).map(([key, value]) => [key, deepClone(value)])
|
|
) as T
|
|
}
|
|
|
|
export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
const result = { ...target }
|
|
for (const key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
const sourceValue = source[key]
|
|
const targetValue = target[key]
|
|
if (
|
|
typeof sourceValue === 'object' &&
|
|
sourceValue !== null &&
|
|
!Array.isArray(sourceValue) &&
|
|
typeof targetValue === 'object' &&
|
|
targetValue !== null &&
|
|
!Array.isArray(targetValue)
|
|
) {
|
|
result[key] = deepMerge(
|
|
targetValue as Record<string, unknown>,
|
|
sourceValue as Record<string, unknown>
|
|
) as T[Extract<keyof T, string>]
|
|
} else {
|
|
result[key] = sourceValue as T[Extract<keyof T, string>]
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
|
return keys.reduce(
|
|
(result, key) => {
|
|
if (key in obj) {
|
|
result[key] = obj[key]
|
|
}
|
|
return result
|
|
},
|
|
{} as Pick<T, K>
|
|
)
|
|
}
|
|
|
|
export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
|
const result = { ...obj }
|
|
keys.forEach(key => delete result[key])
|
|
return result
|
|
}
|
|
|
|
// =============================================================================
|
|
// VALIDATION UTILITIES
|
|
// =============================================================================
|
|
|
|
export function isValidEmail(email: string): boolean {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
return emailRegex.test(email)
|
|
}
|
|
|
|
export function isValidUrl(url: string): boolean {
|
|
try {
|
|
new URL(url)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export function isEmpty(value: unknown): boolean {
|
|
if (value === null || value === undefined) return true
|
|
if (typeof value === 'string') return value.trim().length === 0
|
|
if (Array.isArray(value)) return value.length === 0
|
|
if (typeof value === 'object') return Object.keys(value).length === 0
|
|
return false
|
|
}
|
|
|
|
// =============================================================================
|
|
// ASYNC UTILITIES
|
|
// =============================================================================
|
|
|
|
export function debounce<T extends (...args: unknown[]) => unknown>(
|
|
fn: T,
|
|
delay: number
|
|
): (...args: Parameters<T>) => void {
|
|
let timeoutId: ReturnType<typeof setTimeout>
|
|
return (...args: Parameters<T>) => {
|
|
clearTimeout(timeoutId)
|
|
timeoutId = setTimeout(() => fn(...args), delay)
|
|
}
|
|
}
|
|
|
|
export function throttle<T extends (...args: unknown[]) => unknown>(
|
|
fn: T,
|
|
limit: number
|
|
): (...args: Parameters<T>) => void {
|
|
let lastCall = 0
|
|
return (...args: Parameters<T>) => {
|
|
const now = Date.now()
|
|
if (now - lastCall >= limit) {
|
|
lastCall = now
|
|
fn(...args)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function sleep(ms: number): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
}
|
|
|
|
export async function retry<T>(
|
|
fn: () => Promise<T>,
|
|
maxRetries: number,
|
|
delay: number
|
|
): Promise<T> {
|
|
let lastError: Error | null = null
|
|
for (let i = 0; i <= maxRetries; i++) {
|
|
try {
|
|
return await fn()
|
|
} catch (error) {
|
|
lastError = error as Error
|
|
if (i < maxRetries) {
|
|
await sleep(delay * Math.pow(2, i))
|
|
}
|
|
}
|
|
}
|
|
throw lastError
|
|
}
|