Frontend Composables API
API reference for business logic composables that abstract domain operations and provide component-ready interfaces.
Composable Architecture
Composables sit between components and stores, providing business logic abstraction and error boundary management.
Base Composable Pattern
export function useDomainManagement() {
// Create domain store
const store = createDomainStore();
// Error boundaries for different operations
const loadingErrorBoundary = useErrorBoundary({
context: DOMAIN_CONTEXTS.GET_ALL_ENTITIES,
enableLogging: true,
enableFallback: true,
maxRetries: 2,
retryDelay: 1000,
});
// Reactive state for components
let state = $state({
entities: store.entities,
isLoading: store.isLoading,
error: store.error,
// Derived state
approvedEntities: () =>
store.entities().filter((e) => e.status === "approved"),
pendingEntities: () =>
store.entities().filter((e) => e.status === "pending"),
});
// Business operations with error handling
const operations = {
async loadEntities() {
await loadingErrorBoundary.execute(store.fetchEntities, []);
},
async createEntity(input: CreateEntityInput) {
return await createErrorBoundary.execute(store.createEntity(input));
},
};
// Lifecycle management
$effect(() => {
operations.loadEntities();
});
return {
state,
operations,
loadingErrorBoundary,
};
}
Domain Composables
Service Types Management
File: ui/src/lib/composables/domain/service-types/useServiceTypesManagement.svelte.ts
export function useServiceTypesManagement() {
const store = createServiceTypesStore();
const { loadingErrorBoundary, createErrorBoundary, updateErrorBoundary } =
useServiceTypeErrorBoundaries();
const state = $state({
entities: store.entities,
isLoading: store.isLoading,
error: store.error,
approvedServiceTypes: () =>
store.entities().filter((st) => st.status === "approved"),
pendingServiceTypes: () =>
store.entities().filter((st) => st.status === "pending"),
rejectedServiceTypes: () =>
store.entities().filter((st) => st.status === "rejected"),
});
const operations = {
async loadServiceTypes() {
/* Implementation */
},
async createServiceType(input: CreateServiceTypeInput) {
/* Implementation */
},
async approveServiceType(hash: ActionHash) {
/* Implementation */
},
async rejectServiceType(hash: ActionHash) {
/* Implementation */
},
async searchServiceTypes(query: string) {
/* Implementation */
},
};
return {
state,
operations,
loadingErrorBoundary,
createErrorBoundary,
updateErrorBoundary,
};
}
Service Type Sorting ✨
File: ui/src/lib/composables/search/useServiceTypeSorting.svelte.ts
Purpose: Provides multi-field sorting functionality for service types with intelligent defaults and state management.
export interface ServiceTypeSortReturn {
sortState: ServiceTypeSortState;
sortServiceTypes: (serviceTypes: UIServiceType[]) => UIServiceType[];
updateSort: (
field: ServiceTypeSortField,
direction?: ServiceTypeSortDirection,
) => void;
toggleSort: (field: ServiceTypeSortField) => void;
getSortIcon: (field: ServiceTypeSortField) => string;
isSortedBy: (field: ServiceTypeSortField) => boolean;
}
export function useServiceTypeSorting(
initialField: ServiceTypeSortField = "type",
initialDirection: ServiceTypeSortDirection = "asc",
): ServiceTypeSortReturn {
const state = $state<ServiceTypeSortState>({
field: initialField,
direction: initialDirection,
});
const sortServiceTypes = (serviceTypes: UIServiceType[]): UIServiceType[] => {
return [...serviceTypes].sort((a, b) => {
let result = 0;
switch (state.field) {
case "name":
result = a.name.localeCompare(b.name);
break;
case "type":
// Non-technical first, then by name as secondary sort
if (a.technical === b.technical) {
result = a.name.localeCompare(b.name);
} else {
result = a.technical ? 1 : -1;
}
break;
case "created_at":
result = (a.created_at || 0) - (b.created_at || 0);
break;
case "updated_at":
result = (a.updated_at || 0) - (b.updated_at || 0);
break;
}
return state.direction === "desc" ? -result : result;
});
};
return {
sortState: state,
sortServiceTypes,
updateSort: (field, direction) => {
/* Implementation */
},
toggleSort: (field) => {
/* Implementation */
},
getSortIcon: (field) => {
/* Implementation */
},
isSortedBy: (field) => state.field === field,
};
}
Key Features:
- Multi-field Sorting: Supports name, type, created_at, updated_at
- Secondary Sort: Automatic fallback to name sorting for type field
- Smart Defaults: Type field defaults to 'asc' (non-technical first), others to 'desc'
- Icon Management: Dynamic sort direction indicators (↑, ↓, ↕️)
- State Persistence: Maintains sort preferences within component lifecycle
Requests Management
File: ui/src/lib/composables/domain/requests/useRequestsManagement.svelte.ts
Similar pattern with request-specific operations and state management.
Error Boundary Composables
File: ui/src/lib/composables/useErrorBoundary.svelte.ts
export function useErrorBoundary(config: ErrorBoundaryConfig) {
let state = $state({
error: null as DomainError | null,
isRetrying: false,
retryCount: 0,
});
const execute = async <T>(operation: Effect.Effect<T, DomainError>) => {
try {
const result = await Effect.runPromise(operation);
state.error = null;
state.retryCount = 0;
return result;
} catch (error) {
handleError(error);
throw error;
}
};
const clearError = () => {
state.error = null;
state.retryCount = 0;
};
return { state, execute, clearError };
}
This composable layer provides clean business logic abstraction for components while maintaining proper error handling and state management.