Skip to content

State Management

Formation’s frontend uses Svelte 5 runes ($state, $derived, $effect) for reactive state management, combined with OData stores for server data.

All entity data is accessed through the odataStoreFactory:

// Get a cached store for an entity type
const store = odataStoreFactory.get('Address')
// Create a transient store with a filter
const developmentsStore = odataStoreFactory.create('Development', {
filter: `SchemeId eq '${schemeId}'`
})

Critical pitfall: Never mutate reactive state in $derived blocks:

// ❌ WRONG — mutates reactive state
let sorted = $derived(items.sort((a, b) => a.name.localeCompare(b.name)))
// ✅ CORRECT — creates immutable copy first
let sorted = $derived(items.slice().sort((a, b) => a.name.localeCompare(b.name)))

Track IDs to prevent infinite loops in $effect:

let loadedSchemeId = $state<string | undefined>()
$effect(() => {
if (schemeId && schemeId !== loadedSchemeId) {
loadedSchemeId = schemeId
// Load data...
}
})

All CRUD operations are centralised in service files:

// entity-operations.svelte.ts — Generic CRUD
export 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 wrapper
export 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.