An advanced, high-performance, schema-first form management and validation engine for modern TypeScript and JavaScript applications.
This framework is framework-agnostic, type-safe, and optimized for complex enterprise-grade forms with zero runtime dependencies.
ServiceManager), allowing developers to swap or extend core services (validation, configurations, events) seamlessly.DirectSubmissionStrategy, ContextSubmissionStrategy) to adapt to different runtime contexts and state managers.Install the package via your preferred package manager:
pnpm add formular.dev
Or using npm or Yarn:
npm install formular.dev
# or
yarn add formular.dev
The Simple API allows you to create fully-functional forms in a single step, hiding the underlying Dependency Injection setup while keeping complete type safety.
import { createForm, f } from 'formular.dev';
// 1. Define your schema with type-safe constraints
const signUpSchema = f.object({
username: f.string().min(3).max(30).nonempty(),
email: f.string().email().nonempty(),
age: f.number().min(18).max(120),
acceptTerms: f.boolean().refine((val) => val === true, {
message: 'You must accept the terms and conditions'
})
});
// 2. Infer TypeScript types automatically from the schema
type SignUpData = f.infer<typeof signUpSchema>;
// 3. Create the form instance (asynchronous operation)
const form = await createForm({
schema: signUpSchema,
defaultValues: {
username: '',
email: '',
age: 18,
acceptTerms: false
},
onSubmit: async (data: SignUpData) => {
// data is strongly typed here
await api.post('/register', data);
},
onSuccess: (response, data) => {
console.log('Registration successful:', response);
},
onError: (error) => {
console.error('Registration failed:', error.message);
}
});
// 4. Introspect and update values programmatically
form.updateField('username', 'alex_dev');
console.log(form.getField('username')?.value); // 'alex_dev'
// 5. Run form submission (triggers schema validation automatically)
const success = await form.submit();
if (success) {
console.log('Form validated and submitted successfully');
}
// 6. Clean up resources and dispose of the IoC container
form.destroy();
The schema builder f provides a fluent, zero-dependency interface to build complex validation structures.
f.string(): Matches string values.f.number(): Matches numeric values.f.boolean(): Matches boolean values.f.date(): Matches Date objects.f.literal(value): Matches an exact literal value (string, number, or boolean).f.enum([val1, val2]): Matches any value in the provided read-only list.f.array(schema): Matches an array of elements conforming to the child schema.f.object(shape): Matches an object conforming to the given shape.f.union(schema1, schema2): Matches any of the provided sub-schemas.f.record(keySchema, valueSchema): Matches an object map of key-value pairs.Every schema type supports standard modifiers:
.optional(): Allows undefined values..nullable(): Allows null values..default(value): Sets a fallback value when the field is empty or undefined..transform(fn): Transforms the output value..refine(predicate, options): Applies a custom validation function with custom error messages.f.string()
.email() // Format check for email
.url() // Format check for URL
.min(length) // Minimum character length
.max(length) // Maximum character length
.length(exact) // Exact character length
.pattern(regex) // Custom regular expression match
.nonempty() // Requires a non-empty string
.trim() // Trims whitespace (transformation)
.toLowerCase() // Converts to lowercase (transformation)
.toUpperCase(); // Converts to uppercase (transformation)
f.number()
.min(value) // Minimum value
.max(value) // Maximum value
.int() // Requires an integer
.positive() // Greater than zero
.negative() // Less than zero
.nonnegative() // Greater than or equal to zero
.nonpositive() // Less than or equal to zero
.multipleOf(step) // Must be a multiple of the step value
.finite() // Prevents Infinity / -Infinity
.safe(); // Within Number.MIN_SAFE_INTEGER and MAX_SAFE_INTEGER
Validation for country-specific fields is built into the string schema builder:
// Phone number validation (CH, US, UK, FR, DE, IT, ES, CA, AU, JP, NL, BE, AT)
f.string().phone('CH');
// Postal/ZIP code validation
f.string().postalCode('US');
// Swiss AHV (social security number) validation
f.string().ahv();
Pre-configured schemas and layouts for standard forms are registered in the global registry and can be loaded directly:
import { createFormFromPreset } from 'formular.dev';
const loginForm = await createFormFromPreset('login', {
onSubmit: async (data) => {
await auth.login(data.email, data.password);
}
});
| Preset Name | Purpose | Fields Included |
|---|---|---|
login |
Standard authentication | email, password, rememberMe |
signup |
User registration | name, email, password, confirmPassword, acceptTerms |
contact |
Feedback or contact queries | name, email, subject, message |
profile |
User profile details | firstName, lastName, email, phone, bio, avatar |
address |
Shipping/Billing locations | street, city, postalCode, country |
payment |
Credit card credentials | cardNumber, cardHolder, expiryMonth, expiryYear, cvv |
swiss-user |
Switzerland-localized profile | firstName, lastName, email, phone, postalCode, ahv |
newsletter |
Newsletter subscription | email, preferences |
search |
Content querying and filtering | query, category, sortBy, dateFrom, dateTo |
Custom presets can be registered programmatically:
import { presetRegistry, f } from 'formular.dev';
presetRegistry.register({
name: 'custom-preset',
description: 'A custom organization form',
schema: f.object({
orgName: f.string().nonempty(),
taxId: f.string().nonempty()
}),
fields: {}
});
For complex, dynamic, or highly customized validation structures, you can interact directly with the Inversion of Control (IoC) container.
import { SetupHelpers, SFormularManager } from 'formular.dev';
import type { IFormularManager } from 'formular.dev';
// 1. Initialize a full-featured container
const serviceManager = SetupHelpers.forFormApplication();
// 2. Resolve the form manager service using its unique symbol
const formularManager = serviceManager.resolve<IFormularManager>(SFormularManager);
// 3. Create a form from field descriptors
const form = await formularManager.createFromDescriptors('advanced-form', [
{
id: 1,
name: 'email',
label: 'Email Address',
type: 'email',
value: '',
defaultValue: '',
isValid: false,
isDirty: false,
isPristine: true,
isFocus: false,
shouldValidate: true,
errors: [],
guides: [],
options: [],
validationOptions: {
required: { value: true, error: { message: 'Email is required', code: 'required' } }
}
}
]);
// 4. Validate the form by its registered key
const isValid = await formularManager.validate('advanced-form');
Submission strategies control form behaviors like validation callbacks and submission states:
The default strategy, which submits form data directly to a handler:
import { DirectSubmissionStrategy } from 'formular.dev';
const directStrategy = new DirectSubmissionStrategy(async (data) => {
return await api.submit(data);
});
A strategy designed for integration with stateful wrappers (like component providers) that need hooks for validation start/complete and dismissal checks:
import { ContextSubmissionStrategy } from 'formular.dev';
const contextStrategy = new ContextSubmissionStrategy(
async (data) => {
return await api.submit(data);
},
{
isDismissed: () => checkUserCanceled(),
onValidationStart: () => setValidationIndicator(true),
onValidationComplete: (isValid) => {
setValidationIndicator(false);
if (!isValid) reportFailures();
}
}
);
Validation failures throw a specialized SchemaValidationError containing context about the failing paths:
import { SchemaValidationError } from 'formular.dev';
try {
await form.submit();
} catch (error) {
if (error instanceof SchemaValidationError) {
console.error('Validation failed code:', error.code);
console.error('Validation error path:', error.path); // e.g. ['address', 'postalCode']
console.error('List of errors:', error.errors);
}
}
Traditional form engines validate by traversing entire object trees or triggering global context re-renders. formular.dev isolates fields through a channel-based pub-sub messaging system.
[Field Component] --(updates)--> [Notification Channel] --(triggers)--> [Specific Validator]
When a field changes or blurs:
field_name).This architecture ensures constant-time O(1) validation dispatching, even in forms with hundreds of inputs.
This project is licensed under the AGPLv3 License - see the LICENSE file for details.