State Management
Overview
Section titled “Overview”Formation’s frontend uses Svelte 5 runes ($state, $derived, $effect) for reactive state management, combined with OData stores for server data.
OData Store Factory
Section titled “OData Store Factory”All entity data is accessed through the odataStoreFactory:
// Get a cached store for an entity typeconst store = odataStoreFactory.get('Address')
// Create a transient store with a filterconst developmentsStore = odataStoreFactory.create('Development', { filter: `SchemeId eq '${schemeId}'`})Svelte 5 Runes
Section titled “Svelte 5 Runes”Avoiding Mutation in $derived
Section titled “Avoiding Mutation in $derived”Critical pitfall: Never mutate reactive state in $derived blocks:
// ❌ WRONG — mutates reactive statelet sorted = $derived(items.sort((a, b) => a.name.localeCompare(b.name)))
// ✅ CORRECT — creates immutable copy firstlet sorted = $derived(items.slice().sort((a, b) => a.name.localeCompare(b.name)))Effect Dependencies
Section titled “Effect Dependencies”Track IDs to prevent infinite loops in $effect:
let loadedSchemeId = $state<string | undefined>()
$effect(() => { if (schemeId && schemeId !== loadedSchemeId) { loadedSchemeId = schemeId // Load data... }})Service Layer
Section titled “Service Layer”All CRUD operations are centralised in service files:
// entity-operations.svelte.ts — Generic CRUDexport async function createEntity<T>(options: CreateOptions<T>): Promise<SaveResult<T>>export async function updateEntity<T>(options: UpdateOptions<T>): Promise<SaveResult<T>>export async function deleteEntity(type: EntityType, id: string): Promise<SaveResult<void>>
// scheme-operations.svelte.ts — Entity-specific wrapperexport async function createScheme(data: Partial<Scheme>)export async function updateScheme(id: string, original: Scheme, updated: Scheme)Components call services; services handle validation, API calls, and error handling.