Utility Functions
Utility functions and helper patterns used throughout DesQTA
Utilities Overview
DesQTA includes various utility functions and helpers located in src/lib/utils/ and src/utils/. These utilities provide common functionality used across the application.
Utility Categories
Data Loading Utilities
useDataLoader
A composable for loading data with caching and error handling.
Location: src/lib/utils/useDataLoader.ts
Usage:
import { useDataLoader } from '$lib/utils/useDataLoader';
const { data, loading, error, refetch } = useDataLoader({
cacheKey: 'assessments',
fetcher: async () => {
return await invoke('get_assessments');
},
ttl: 5 * 60 * 1000 // 5 minutes
});
Options:
interface DataLoaderOptions<T> {
cacheKey: string; // Cache key for storage
fetcher: () => Promise<T>; // Function to fetch data
ttl?: number; // Time to live in milliseconds
initialData?: T; // Initial data value
onError?: (error: Error) => void; // Error handler
}
Returns:
interface DataLoaderResult<T> {
data: Readable<T | null>;
loading: Readable<boolean>;
error: Readable<Error | null>;
refetch: () => Promise<void>;
}
Example:
<script lang="ts">
import { useDataLoader } from '$lib/utils/useDataLoader';
import { invoke } from '@tauri-apps/api/core';
const { data, loading, error, refetch } = useDataLoader({
cacheKey: 'user-assessments',
fetcher: async () => {
return await invoke('get_processed_assessments');
},
ttl: 10 * 60 * 1000 // 10 minutes
});
</script>
{#if $loading}
<LoadingSpinner />
{:else if $error}
<ErrorMessage error={$error} onRetry={refetch} />
{:else if $data}
<AssessmentList assessments={$data} />
{/if}
Offline Mode Utilities
offlineMode
Utilities for handling offline mode and connectivity.
Location: src/lib/utils/offlineMode.ts
Usage:
import { offlineMode } from '$lib/utils/offlineMode';
// Check if offline
const isOffline = offlineMode.isOffline();
// Check connectivity
const isOnline = offlineMode.isOnline();
// Listen for connectivity changes
offlineMode.onStatusChange((online) => {
if (online) {
console.log('Back online');
} else {
console.log('Gone offline');
}
});
API:
interface OfflineMode {
isOffline(): boolean;
isOnline(): boolean;
onStatusChange(callback: (online: boolean) => void): () => void;
enableOfflineMode(): void;
disableOfflineMode(): void;
}
Logger Utilities
Logger
Centralized logging system with different log levels.
Location: src/utils/logger.ts
Usage:
import { logger } from '$lib/utils/logger';
// Log levels
logger.debug('context', 'function', 'Debug message', { data });
logger.info('context', 'function', 'Info message', { data });
logger.warn('context', 'function', 'Warning message', { data });
logger.error('context', 'function', 'Error message', { error });
// Performance logging
logger.logPerformance('context', 'function', startTime, endTime);
// Function entry/exit
logger.logFunctionEntry('context', 'function');
logger.logFunctionExit('context', 'function', result);
Example:
import { logger } from '$lib/utils/logger';
async function loadAssessments() {
logger.logFunctionEntry('assessmentService', 'loadAssessments');
const startTime = performance.now();
try {
const data = await invoke('get_assessments');
const endTime = performance.now();
logger.logPerformance('assessmentService', 'loadAssessments', startTime, endTime);
logger.info('assessmentService', 'loadAssessments', 'Loaded assessments', { count: data.length });
logger.logFunctionExit('assessmentService', 'loadAssessments', data);
return data;
} catch (error) {
logger.error('assessmentService', 'loadAssessments', 'Failed to load', { error });
throw error;
}
}
Network Utilities
seqtaFetch
Custom fetch wrapper for SEQTA API requests.
Location: src/utils/netUtil.ts
Usage:
import { seqtaFetch } from '$lib/utils/netUtil';
const response = await seqtaFetch('/api/assessments', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
Features:
- Automatic session handling
- Cookie management
- Error handling
- Request logging
getRandomDicebearAvatar
Generate random avatar URLs using Dicebear API.
Location: src/utils/netUtil.ts
Usage:
import { getRandomDicebearAvatar } from '$lib/utils/netUtil';
const avatarUrl = getRandomDicebearAvatar();
// Returns: https://api.dicebear.com/7.x/avataaars/svg?seed=...
Cache Utilities
Cache
In-memory cache with TTL support.
Location: src/utils/cache.ts
Usage:
import { cache } from '$lib/utils/cache';
// Set cache
cache.set('key', value, 5 * 60 * 1000); // 5 minutes TTL
// Get cache
const value = cache.get('key');
// Check if exists
if (cache.has('key')) {
// Use cached value
}
// Clear cache
cache.clear();
// Clear expired entries
cache.cleanup();
Cache TTL Presets:
const TTL = {
SHORT: 5 * 60 * 1000, // 5 minutes
MEDIUM: 15 * 60 * 1000, // 15 minutes
LONG: 60 * 60 * 1000, // 60 minutes
VERY_LONG: 24 * 60 * 60 * 1000 // 24 hours
};
cache.set('key', value, TTL.SHORT);
Date Utilities
Date Formatting
Common date formatting functions.
Usage:
// Format date for display
function formatDate(date: Date): string {
return new Intl.DateTimeFormat('en-AU', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
}
// Format time
function formatTime(date: Date): string {
return new Intl.DateTimeFormat('en-AU', {
hour: '2-digit',
minute: '2-digit'
}).format(date);
}
// Format relative time
function formatRelative(date: Date): string {
const now = new Date();
const diff = now.getTime() - date.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) return 'Today';
if (days === 1) return 'Yesterday';
if (days < 7) return `${days} days ago`;
return formatDate(date);
}
String Utilities
String Manipulation
Common string utility functions.
// Truncate string
function truncate(str: string, length: number): string {
return str.length > length ? str.slice(0, length) + '...' : str;
}
// Capitalize first letter
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// Slugify
function slugify(str: string): string {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// Format file size
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
Array Utilities
Array Helpers
Common array manipulation functions.
// Group by key
function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
return array.reduce((groups, item) => {
const group = String(item[key]);
if (!groups[group]) groups[group] = [];
groups[group].push(item);
return groups;
}, {} as Record<string, T[]>);
}
// Sort by key
function sortBy<T>(array: T[], key: keyof T, order: 'asc' | 'desc' = 'asc'): T[] {
return [...array].sort((a, b) => {
const aVal = a[key];
const bVal = b[key];
if (aVal < bVal) return order === 'asc' ? -1 : 1;
if (aVal > bVal) return order === 'asc' ? 1 : -1;
return 0;
});
}
// Unique by key
function uniqueBy<T>(array: T[], key: keyof T): T[] {
const seen = new Set();
return array.filter(item => {
const value = item[key];
if (seen.has(value)) return false;
seen.add(value);
return true;
});
}
// Chunk array
function chunk<T>(array: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
Object Utilities
Object Helpers
Common object manipulation functions.
// Deep clone
function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
// Pick properties
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
if (key in obj) {
result[key] = obj[key];
}
});
return result;
}
// Omit properties
function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const result = { ...obj };
keys.forEach(key => delete result[key]);
return result;
}
// Merge objects
function merge<T extends Record<string, any>>(target: T, ...sources: Partial<T>[]): T {
return Object.assign({}, target, ...sources);
}
Validation Utilities
Form Validation
Common validation functions.
// Email validation
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// URL validation
function isValidUrl(url: string): boolean {
try {
new URL(url);
return true;
} catch {
return false;
}
}
// Required field
function isRequired(value: any): boolean {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
return true;
}
// Min length
function minLength(value: string, min: number): boolean {
return value.length >= min;
}
// Max length
function maxLength(value: string, max: number): boolean {
return value.length <= max;
}
Debounce & Throttle
Performance Utilities
// Debounce function
function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout>;
return function executedFunction(...args: Parameters<T>) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle function
function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return function executedFunction(...args: Parameters<T>) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
Usage:
<script lang="ts">
import { debounce } from '$lib/utils/helpers';
let searchQuery = $state('');
const debouncedSearch = debounce((query: string) => {
console.log('Searching for:', query);
// Perform search
}, 300);
function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
searchQuery = target.value;
debouncedSearch(searchQuery);
}
</script>
<input type="text" oninput={handleInput} />
Error Utilities
Error Handling
// Extract error message
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'An unknown error occurred';
}
// Error with context
class AppError extends Error {
constructor(
message: string,
public context?: Record<string, any>
) {
super(message);
this.name = 'AppError';
}
}
// Retry function
async function retry<T>(
fn: () => Promise<T>,
maxAttempts: number = 3,
delay: number = 1000
): Promise<T> {
for (let i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (error) {
if (i === maxAttempts - 1) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max attempts reached');
}
Platform Utilities
Platform Detection
import { usePlatform } from '$lib/composables/usePlatform';
const platform = usePlatform();
// Check platform
platform.isWindows; // true/false
platform.isMac; // true/false
platform.isLinux; // true/false
// Platform-specific code
if (platform.isMac) {
// macOS specific code
}
Best Practices
1. Create Reusable Utilities
// ✅ Good - Reusable utility
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-AU', {
style: 'currency',
currency: 'AUD'
}).format(amount);
}
// ❌ Avoid - Inline logic
const formatted = new Intl.NumberFormat('en-AU', {
style: 'currency',
currency: 'AUD'
}).format(amount);
2. Type Your Utilities
// ✅ Good - Typed utility
export function sortBy<T>(array: T[], key: keyof T): T[] {
// Implementation
}
// ❌ Avoid - Untyped utility
export function sortBy(array: any[], key: string): any[] {
// Implementation
}
3. Document Complex Utilities
/**
* Groups an array of objects by a specified key
* @param array - Array to group
* @param key - Key to group by
* @returns Object with keys as group values and arrays as items
* @example
* const users = [{ role: 'admin', name: 'John' }, { role: 'user', name: 'Jane' }];
* groupBy(users, 'role'); // { admin: [...], user: [...] }
*/
export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
// Implementation
}
4. Use Composition
// ✅ Good - Composed utilities
export const dateUtils = {
format: formatDate,
formatTime: formatTime,
formatRelative: formatRelative,
isValid: isValidDate
};
Next Steps
- Services - Service layer patterns
- Stores - State management
- Component Architecture - Component patterns
On this page