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