Files
breakpilot-compliance/breakpilot-compliance-sdk/packages/core/src/utils.ts
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
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>
2026-02-11 23:47:28 +01:00

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
}