@formos/kernel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,372 @@
1
+ import { ValidationTrigger, NormalizedSchemaV1, Conditional } from '@formos/schema';
2
+
3
+ /**
4
+ * Type definitions for kernel adapters and execution context
5
+ */
6
+ /**
7
+ * Validator executor function signature.
8
+ *
9
+ * Adapters implement this interface to execute actual validation logic.
10
+ * The kernel calls validators with field values and expects either:
11
+ * - `null` if the value is valid
12
+ * - Error message string if invalid
13
+ *
14
+ * **IMPORTANT**: Return `null` for valid values, NOT `undefined`.
15
+ *
16
+ * Validators can be synchronous or async (returning a Promise).
17
+ *
18
+ * @param value - The field value to validate
19
+ * @param params - Validation parameters from the schema's `rule` property
20
+ * @param context - Execution context with access to other field values
21
+ * @returns Error message if invalid, `null` if valid
22
+ *
23
+ * @public
24
+ * @stable
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const required: ValidatorExecutor = (value, params, context) => {
29
+ * return value ? null : 'This field is required';
30
+ * };
31
+ *
32
+ * const matchesField: ValidatorExecutor = (value, params, context) => {
33
+ * const { targetField } = params as { targetField: string };
34
+ * const targetValue = context.getValue(targetField);
35
+ * return value === targetValue ? null : 'Fields must match';
36
+ * };
37
+ * ```
38
+ */
39
+ type ValidatorExecutor = (value: unknown, params: unknown, context: ValidationContext) => Promise<string | null> | string | null;
40
+ /**
41
+ * Validation execution context.
42
+ *
43
+ * Provides validators with read-only access to form state.
44
+ * Validators can check other field values for cross-field validation.
45
+ *
46
+ * @public
47
+ * @stable
48
+ */
49
+ interface ValidationContext {
50
+ /** Current field name being validated */
51
+ fieldName: string;
52
+ /** All form values */
53
+ values: ReadonlyMap<string, unknown>;
54
+ /** Get value of another field */
55
+ getValue: (fieldName: string) => unknown;
56
+ }
57
+ /**
58
+ * Effect executor function signature.
59
+ *
60
+ * Adapters implement this interface to execute side effects triggered
61
+ * by field changes. Common use cases:
62
+ * - Sync fields (e.g., calculate fullName from firstName + lastName)
63
+ * - Trigger API calls (e.g., fetch cities when country changes)
64
+ * - Custom business logic
65
+ *
66
+ * Effects have write access via `context.setValue()`.
67
+ *
68
+ * @param params - Effect configuration from the schema's `config` property
69
+ * @param context - Execution context with form values and setValue
70
+ * @returns Optional result value (typically unused)
71
+ *
72
+ * @public
73
+ * @stable
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const syncFullName: EffectExecutor = (params, context) => {
78
+ * const firstName = context.getValue('firstName') as string;
79
+ * const lastName = context.getValue('lastName') as string;
80
+ * context.setValue('fullName', `${firstName} ${lastName}`);\n * };
81
+ *
82
+ * const fetchCities: EffectExecutor = async (params, context) => {
83
+ * const country = context.sourceValue as string;
84
+ * const response = await fetch(`/api/cities?country=${country}`);
85
+ * const cities = await response.json();
86
+ * context.setValue('cities', cities);\n * };
87
+ * ```
88
+ */
89
+ type EffectExecutor = (params: unknown, context: EffectContext) => Promise<unknown> | unknown;
90
+ /**
91
+ * Effect execution context.
92
+ *
93
+ * Provides effects with read-write access to form state.
94
+ * Effects can read any field value and update the target field.
95
+ *
96
+ * @public
97
+ * @stable
98
+ */
99
+ interface EffectContext {
100
+ /** Source field that triggered the effect */
101
+ sourceField: string;
102
+ /** Source field's new value */
103
+ sourceValue: unknown;
104
+ /** Target field (if applicable) */
105
+ targetField?: string;
106
+ /** All form values */
107
+ values: ReadonlyMap<string, unknown>;
108
+ /** Get value of a field */
109
+ getValue: (fieldName: string) => unknown;
110
+ /** Set value of a field (for sync effects) */
111
+ setValue: (fieldName: string, value: unknown) => void;
112
+ }
113
+
114
+ /**
115
+ * Value management operations
116
+ * Handles getting and setting field values with validation and effects
117
+ */
118
+
119
+ /**
120
+ * Options for setValue operation
121
+ */
122
+ /**
123
+ * Options for setValue operations.
124
+ *
125
+ * @public
126
+ * @stable
127
+ */
128
+ interface SetValueOptions {
129
+ /** Validation trigger (default: "onChange") */
130
+ trigger?: ValidationTrigger | "manual";
131
+ /** If true, skip validation and effects */
132
+ silent?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Form lifecycle operations
137
+ * Handles submit and reset operations
138
+ */
139
+
140
+ /**
141
+ * Submit handler callback
142
+ */
143
+ /**
144
+ * Callback invoked when form is successfully submitted.
145
+ *
146
+ * Only called if all validations pass. Receives all form values.
147
+ *
148
+ * @public
149
+ * @stable
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const onSubmit: SubmitHandler = async (values) => {
154
+ * await fetch('/api/submit', {
155
+ * method: 'POST',
156
+ * body: JSON.stringify(values)
157
+ * });
158
+ * };
159
+ * ```
160
+ */
161
+ type SubmitHandler = (values: Record<string, unknown>) => void | Promise<void>;
162
+
163
+ /**
164
+ * Form engine factory
165
+ * Creates FormEngine instances with configuration
166
+ */
167
+
168
+ /**
169
+ * Configuration options for creating a form engine.
170
+ *
171
+ * All options use the **adapter pattern** to remain framework-agnostic.
172
+ * The kernel provides orchestration; adapters provide implementation.
173
+ *
174
+ * @public
175
+ * @stable
176
+ */
177
+ interface FormEngineOptions {
178
+ /** Custom validator executors */
179
+ validators?: Record<string, ValidatorExecutor>;
180
+ /** Custom effect executors */
181
+ effectExecutors?: Record<string, EffectExecutor>;
182
+ /** Submit handler */
183
+ onSubmit?: SubmitHandler;
184
+ }
185
+ /**
186
+ * Form engine interface - the primary API for interacting with forms.
187
+ *
188
+ * This headless engine manages all form logic:
189
+ * - State management (values, errors, touched, dirty)
190
+ * - Validation orchestration with pluggable validators
191
+ * - Effect execution for field interactions
192
+ * - Multi-step navigation with validation guards
193
+ *
194
+ * The engine is **completely UI-agnostic** - no React, no DOM.
195
+ * UI packages consume this API to build interactive forms.
196
+ *
197
+ * @public
198
+ * @stable
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * import { createFormEngine } from '@formos/kernel';
203
+ * import { normalizeSchema } from '@formos/schema';
204
+ *
205
+ * const normalized = normalizeSchema(mySchema);
206
+ * const engine = createFormEngine(normalized, {
207
+ * validators: { required: (value) => value ? null : 'Required' }
208
+ * });
209
+ *
210
+ * // Get/set values
211
+ * await engine.setValue('email', 'user@example.com');
212
+ * const email = engine.getValue('email');
213
+ *
214
+ * // Validate
215
+ * const isValid = await engine.validate();
216
+ *
217
+ * // Submit
218
+ * const success = await engine.submit();
219
+ * ```
220
+ */
221
+ interface FormEngine {
222
+ getValue(fieldName: string): unknown;
223
+ getError(fieldName: string): string | undefined;
224
+ getErrors(): Record<string, string | undefined>;
225
+ isValid(): boolean;
226
+ isDirty(fieldName?: string): boolean;
227
+ isTouched(fieldName: string): boolean;
228
+ setValue(fieldName: string, value: unknown, options?: SetValueOptions): Promise<void>;
229
+ setError(fieldName: string, error: string): void;
230
+ clearError(fieldName: string): void;
231
+ markTouched(fieldName: string): void;
232
+ validate(fieldName?: string, trigger?: "onChange" | "onBlur" | "onSubmit" | "manual"): Promise<boolean>;
233
+ submit(): Promise<boolean>;
234
+ reset(): void;
235
+ getCurrentStep?(): number;
236
+ getTotalSteps?(): number;
237
+ nextStep?(): Promise<boolean>;
238
+ prevStep?(): void;
239
+ goToStep?(stepIndex: number): Promise<boolean>;
240
+ canGoToStep?(stepIndex: number): boolean;
241
+ }
242
+ /**
243
+ * Create a form engine instance from a normalized schema.
244
+ *
245
+ * This is the primary entry point for the kernel. It instantiates
246
+ * a {@link FormEngine} that manages all form logic.
247
+ *
248
+ * The engine is stateful and mutable. Each call creates a new
249
+ * independent form instance with its own state.
250
+ *
251
+ * @param schema - Normalized schema from {@link normalizeSchema}
252
+ * @param options - Configuration with validators, effects, and submit handler
253
+ * @returns Fully configured form engine instance
254
+ *
255
+ * @public
256
+ * @stable
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * import { createFormEngine } from '@formos/kernel';
261
+ * import { normalizeSchema } from '@formos/schema';
262
+ *
263
+ * const schema = { version: 'v1', fields: [...] };
264
+ * const normalized = normalizeSchema(schema);
265
+ *
266
+ * const engine = createFormEngine(normalized, {
267
+ * validators: {
268
+ * required: (value) => value ? null : 'Required',
269
+ * email: (value) => /\\S+@\\S+/.test(value) ? null : 'Invalid'
270
+ * },
271
+ * onSubmit: async (values) => {
272
+ * await api.post('/submit', values);
273
+ * }
274
+ * });
275
+ * ```
276
+ */
277
+ declare function createFormEngine(schema: NormalizedSchemaV1, options?: FormEngineOptions): FormEngine;
278
+
279
+ /**
280
+ * Conditional evaluation engine
281
+ * Evaluates ConditionalRule and ConditionalGroup from schema
282
+ */
283
+
284
+ /**
285
+ * Evaluate a conditional expression against current form values.
286
+ *
287
+ * Handles both single {@link ConditionalRule} and complex {@link ConditionalGroup}
288
+ * with AND/OR logic. This is the core function for dynamic field behavior.
289
+ *
290
+ * @param conditional - Rule or group to evaluate
291
+ * @param getValue - Function to retrieve field values by name
292
+ * @returns True if the condition is satisfied
293
+ *
294
+ * @public
295
+ * @stable
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const condition: ConditionalRule = {
300
+ * field: 'age',
301
+ * operator: 'greaterThan',
302
+ * value: 18
303
+ * };
304
+ *
305
+ * const getValue = (name: string) => formValues[name];
306
+ * const result = evaluateConditional(condition, getValue);
307
+ * ```
308
+ */
309
+ declare function evaluateConditional(conditional: Conditional, getValue: (fieldName: string) => unknown): boolean;
310
+ /**
311
+ * Check if a field should be visible based on its visibleWhen condition.
312
+ *
313
+ * Returns `true` if:
314
+ * - Field has no `visibleWhen` property (always visible)
315
+ * - The `visibleWhen` condition evaluates to `true`
316
+ *
317
+ * @param field - Field schema with optional visibleWhen property
318
+ * @param getValue - Function to retrieve field values
319
+ * @returns True if field should be visible
320
+ *
321
+ * @public
322
+ * @stable
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * const field = {
327
+ * name: 'ssn',
328
+ * type: 'text',
329
+ * visibleWhen: { field: 'country', operator: 'equals', value: 'US' }
330
+ * };
331
+ *
332
+ * if (isFieldVisible(field, getValue)) {
333
+ * // Render the field
334
+ * }
335
+ * ```
336
+ */
337
+ declare function isFieldVisible(field: {
338
+ visibleWhen?: Conditional;
339
+ }, getValue: (fieldName: string) => unknown): boolean;
340
+ /**
341
+ * Check if a field is required based on its required configuration.
342
+ *
343
+ * Handles three cases:
344
+ * - `undefined`: Field is optional (returns `false`)
345
+ * - `boolean`: Returns the boolean value directly
346
+ * - `Conditional`: Evaluates the condition dynamically
347
+ *
348
+ * @param field - Field schema with optional required property
349
+ * @param getValue - Function to retrieve field values
350
+ * @returns True if field is required
351
+ *
352
+ * @public
353
+ * @stable
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * const field = {
358
+ * name: 'phone',
359
+ * type: 'tel',
360
+ * required: { field: 'contactMethod', operator: 'equals', value: 'phone' }
361
+ * };
362
+ *
363
+ * if (isFieldRequired(field, getValue)) {
364
+ * // Show required indicator
365
+ * }
366
+ * ```
367
+ */
368
+ declare function isFieldRequired(field: {
369
+ required?: boolean | Conditional;
370
+ }, getValue: (fieldName: string) => unknown): boolean;
371
+
372
+ export { type EffectContext, type EffectExecutor, type FormEngine, type FormEngineOptions, type SetValueOptions, type SubmitHandler, type ValidationContext, type ValidatorExecutor, createFormEngine, evaluateConditional, isFieldRequired, isFieldVisible };