Interface-first form state management with headless components for Svelte 5
npm install @shelchin/formstateA powerful, type-safe form state management library with support for recursive nesting, dynamic fields, and async validation.
- Interface-first design - Program against interfaces for easy extension and testing
- Framework adapters - Core logic is framework-agnostic, with Svelte 5 adapter included
- Svelte 5 Runes - Uses the latest Svelte 5 reactive API
- Recursive nesting - Deep object and array paths (user.addresses[0].street)
- Dynamic fields - Add/remove fields at runtime
- Async validation - Support for async validators with auto-debounce and cancellation
- Composable UI - Headless components + optional UI components
- Schema-driven - Support for configuration-driven form rendering
- TypeScript - Full type support
- Batch updates - Efficient batch updates to prevent multiple re-renders
- Serialization - Safe serialization supporting BigInt, Date, Map, Set
``bash`
npm install @shelchin/formstateor
bun add @shelchin/formstate
`svelte
$3
`svelte
`$3
`svelte
`Core API
$3
The core state manager handles all form logic.
`typescript
import { FormStateManager, Validators } from '@shelchin/formstate';const manager = new FormStateManager({
validateOnChange: true,
validateOnBlur: true,
fields: {
email: {
defaultValue: '',
validator: Validators.email()
}
}
});
// Set values
manager.setValue('email', 'test@example.com');
// Get values
manager.getValue('email'); // 'test@example.com'
manager.getValues(); // { email: 'test@example.com' }
// Validation
await manager.validateField('email');
await manager.validateForm();
// State queries
manager.isDirty(); // true if any field changed
manager.isValid(); // true if no errors
manager.isValidating(); // true if validation in progress
manager.getErrors(); // { field: 'error message', ... }
manager.getDirtyFields(); // ['email', ...]
manager.getDirtyValues(); // { email: '...' }
// Submit
const success = await manager.submit(async (values) => {
await saveToServer(values);
});
// Reset
manager.reset();
manager.reset({ email: 'new@example.com' }); // Reset with new initial values
`$3
Batch multiple updates to prevent multiple re-renders:
`typescript
manager.batchUpdate(() => {
manager.setValue('firstName', 'John', false);
manager.setValue('lastName', 'Doe', false);
manager.setValue('email', 'john@example.com', false);
}); // Only triggers one UI update
`$3
Built-in validators and composition:
`typescript
import { Validators, createValidator, createCustomValidator } from '@shelchin/formstate';// Built-in validators
Validators.required('Custom message');
Validators.email();
Validators.minLength(3);
Validators.maxLength(100);
Validators.min(0);
Validators.max(100);
Validators.pattern(/^[A-Z]+$/);
// Compose validators
const emailValidator = Validators.compose(
Validators.required(),
Validators.email(),
Validators.maxLength(255)
);
// Custom validator
const passwordMatch = createValidator((value, allValues) => {
return value === allValues.password ? null : 'Passwords must match';
});
// Async custom validator
const usernameAvailable = createCustomValidator(async (value) => {
const res = await fetch(
/api/check-username?name=${value});
return (await res.json()).available;
}, 'Username is already taken');// Use with dependencies
const form = useFormState({
fields: {
confirmPassword: {
validator: passwordMatch,
dependencies: ['password'] // Re-validate when password changes
}
}
});
`$3
Transform values on input:
`typescript
import { Transformers } from '@shelchin/formstate';const form = useFormState({
fields: {
username: {
transformer: Transformers.trim
},
code: {
transformer: Transformers.toUpperCase
},
amount: {
transformer: Transformers.toNumber
},
email: {
transformer: Transformers.compose(Transformers.trim, Transformers.toLowerCase)
}
}
});
`$3
Utility for working with nested paths:
`typescript
import { PathUtils } from '@shelchin/formstate';const obj = { user: { addresses: [{ city: 'NYC' }] } };
PathUtils.get(obj, 'user.addresses[0].city'); // 'NYC'
PathUtils.set(obj, 'user.addresses[0].city', 'LA'); // Immutable update
PathUtils.delete(obj, 'user.addresses[0]'); // Remove array element
PathUtils.push(obj, 'user.addresses', { city: 'Boston' }); // Add to array
PathUtils.move(obj, 'user.addresses', 0, 1); // Reorder array
`$3
Safe serialization with BigInt, Date, Map, Set support:
`typescript
import { safeStringify, safeParse, cloneViaSerialization } from '@shelchin/formstate';const data = {
amount: 123456789012345678901234567890n,
createdAt: new Date(),
tags: new Set(['a', 'b']),
metadata: new Map([['key', 'value']])
};
const json = safeStringify(data);
const restored = safeParse(json);
// Deep clone
const clone = cloneViaSerialization(data);
`$3
Validate after input completion:
`typescript
const form = useFormState({
fields: {
search: {
validateOnComplete: true,
debounceMs: 300,
completeCondition: (value) => value.length >= 3,
validator: async (value) => {
// Only runs after user stops typing for 300ms
// and input is at least 3 characters
}
}
}
});
`Components
| Component | Description |
| ------------------- | ------------------------------ |
|
Form | Form root, provides context |
| Field | Headless field component |
| FormField | Styled field component |
| FieldArray | Dynamic array field management |
| SchemaRenderer | Schema-driven form renderer |
| FieldLabel | Label component |
| FieldErrorDisplay | Error message display |
| FieldDescription | Help text/description |
| FieldValidating | Loading indicator during async |Testing
`bash
Run tests
bun run testWatch mode
bun run test:watch
`The package includes comprehensive tests for:
- FormStateManager (69 tests)
- Validators (34 tests)
- Transformers (18 tests)
- PathUtils (27 tests)
- Serialization (25 tests)
Architecture
`
packages/formstate/
├── src/
│ ├── core/ # Framework-agnostic core
│ │ ├── interfaces.ts # Type definitions
│ │ ├── FormStateManager.ts # State management
│ │ ├── Validators.ts # Validation system
│ │ └── Transformers.ts # Value transformers
│ ├── utils/
│ │ ├── PathUtils.ts # Path utilities
│ │ └── serialize.ts # Safe serialization
│ └── adapters/
│ └── svelte/ # Svelte 5 adapter
│ ├── useFormState.svelte.ts
│ ├── schema.ts
│ └── components/
└── examples/ # Example implementations
``MIT