pulse-js-framework 1.7.5 → 1.7.8

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,310 @@
1
+ /**
2
+ * Pulse Framework - Async Primitives Type Definitions
3
+ * @module pulse-js-framework/runtime/async
4
+ */
5
+
6
+ import { Pulse } from './pulse';
7
+
8
+ // ============================================================================
9
+ // Versioned Async - Race Condition Handling
10
+ // ============================================================================
11
+
12
+ /** Context returned by begin() for tracking async operation validity */
13
+ export interface VersionedContext {
14
+ /** Check if this operation is still valid */
15
+ isCurrent(): boolean;
16
+
17
+ /** Check if this operation has been superseded */
18
+ isStale(): boolean;
19
+
20
+ /** Execute callback only if this operation is still current */
21
+ ifCurrent<T>(fn: () => T): T | undefined;
22
+
23
+ /** Set a timeout that auto-clears when operation becomes stale */
24
+ setTimeout(fn: () => void, ms: number): number;
25
+
26
+ /** Set an interval that auto-clears when operation becomes stale */
27
+ setInterval(fn: () => void, ms: number): number;
28
+
29
+ /** Clear a registered timeout */
30
+ clearTimeout(id: number): void;
31
+
32
+ /** Clear a registered interval */
33
+ clearInterval(id: number): void;
34
+ }
35
+
36
+ /** Controller for managing versioned async operations */
37
+ export interface VersionedAsyncController {
38
+ /** Start a new versioned operation, invalidating previous ones */
39
+ begin(): VersionedContext;
40
+
41
+ /** Abort the current operation */
42
+ abort(): void;
43
+
44
+ /** Get current version number */
45
+ getVersion(): number;
46
+
47
+ /** Clean up all timers without aborting */
48
+ cleanup(): void;
49
+ }
50
+
51
+ /** Options for createVersionedAsync */
52
+ export interface VersionedAsyncOptions {
53
+ /** Callback invoked when operation is aborted */
54
+ onAbort?: () => void;
55
+ }
56
+
57
+ /**
58
+ * Create a versioned async controller for race condition handling.
59
+ *
60
+ * @example
61
+ * const controller = createVersionedAsync();
62
+ *
63
+ * async function fetchData() {
64
+ * const ctx = controller.begin();
65
+ * const data = await fetch('/api/data').then(r => r.json());
66
+ * ctx.ifCurrent(() => setState(data));
67
+ * }
68
+ */
69
+ export declare function createVersionedAsync(
70
+ options?: VersionedAsyncOptions
71
+ ): VersionedAsyncController;
72
+
73
+ // ============================================================================
74
+ // useAsync - Reactive Async Operation Handler
75
+ // ============================================================================
76
+
77
+ /** Status of an async operation */
78
+ export type AsyncStatus = 'idle' | 'loading' | 'success' | 'error';
79
+
80
+ /** Options for useAsync */
81
+ export interface UseAsyncOptions<T> {
82
+ /** Execute immediately on creation (default: true) */
83
+ immediate?: boolean;
84
+
85
+ /** Initial data value (default: null) */
86
+ initialData?: T | null;
87
+
88
+ /** Callback invoked on error */
89
+ onError?: (error: Error) => void;
90
+
91
+ /** Callback invoked on success */
92
+ onSuccess?: (data: T) => void;
93
+
94
+ /** Number of retry attempts on failure (default: 0) */
95
+ retries?: number;
96
+
97
+ /** Delay between retries in ms (default: 1000) */
98
+ retryDelay?: number;
99
+ }
100
+
101
+ /** Return type of useAsync */
102
+ export interface UseAsyncReturn<T> {
103
+ /** Reactive data value */
104
+ data: Pulse<T | null>;
105
+
106
+ /** Reactive error state */
107
+ error: Pulse<Error | null>;
108
+
109
+ /** Reactive loading state */
110
+ loading: Pulse<boolean>;
111
+
112
+ /** Reactive status ('idle' | 'loading' | 'success' | 'error') */
113
+ status: Pulse<AsyncStatus>;
114
+
115
+ /** Execute the async function */
116
+ execute(...args: unknown[]): Promise<T | null>;
117
+
118
+ /** Reset state to initial values */
119
+ reset(): void;
120
+
121
+ /** Abort current execution */
122
+ abort(): void;
123
+ }
124
+
125
+ /**
126
+ * Create a reactive async operation handler.
127
+ *
128
+ * @example
129
+ * const { data, loading, error, execute } = useAsync(
130
+ * () => fetch('/api/users').then(r => r.json()),
131
+ * { retries: 3, onSuccess: (data) => console.log('Got:', data) }
132
+ * );
133
+ */
134
+ export declare function useAsync<T>(
135
+ asyncFn: (...args: unknown[]) => Promise<T>,
136
+ options?: UseAsyncOptions<T>
137
+ ): UseAsyncReturn<T>;
138
+
139
+ // ============================================================================
140
+ // useResource - SWR-style Resource Fetching
141
+ // ============================================================================
142
+
143
+ /** Options for useResource */
144
+ export interface ResourceOptions<T> {
145
+ /** Auto-refresh interval in ms */
146
+ refreshInterval?: number;
147
+
148
+ /** Refresh when window regains focus (default: false) */
149
+ refreshOnFocus?: boolean;
150
+
151
+ /** Refresh when network reconnects (default: false) */
152
+ refreshOnReconnect?: boolean;
153
+
154
+ /** Initial data value (default: null) */
155
+ initialData?: T | null;
156
+
157
+ /** Callback invoked on error */
158
+ onError?: (error: Error) => void;
159
+
160
+ /** Time in ms before data is considered stale (default: 0) */
161
+ staleTime?: number;
162
+
163
+ /** Time in ms to keep data in cache (default: 300000 = 5 min) */
164
+ cacheTime?: number;
165
+ }
166
+
167
+ /** Return type of useResource */
168
+ export interface UseResourceReturn<T> {
169
+ /** Reactive data value */
170
+ data: Pulse<T | null>;
171
+
172
+ /** Reactive error state */
173
+ error: Pulse<Error | null>;
174
+
175
+ /** Reactive loading state (initial fetch) */
176
+ loading: Pulse<boolean>;
177
+
178
+ /** Reactive stale state */
179
+ isStale: Pulse<boolean>;
180
+
181
+ /** Reactive validating state (background refresh) */
182
+ isValidating: Pulse<boolean>;
183
+
184
+ /** Last fetch timestamp */
185
+ lastFetchTime: Pulse<number>;
186
+
187
+ /** Fetch data (uses cache if available and fresh) */
188
+ fetch(): Promise<T | null>;
189
+
190
+ /** Force refresh, ignoring cache */
191
+ refresh(): Promise<T | null>;
192
+
193
+ /** Mutate data optimistically */
194
+ mutate(newData: T | ((current: T | null) => T), shouldRevalidate?: boolean): void;
195
+
196
+ /** Clear cache for this resource */
197
+ invalidate(): void;
198
+ }
199
+
200
+ /**
201
+ * Create a reactive resource with caching and stale-while-revalidate.
202
+ *
203
+ * @example
204
+ * // Static key
205
+ * const users = useResource('users', () => fetch('/api/users').then(r => r.json()));
206
+ *
207
+ * // Dynamic key (re-fetches when userId changes)
208
+ * const userId = pulse(1);
209
+ * const user = useResource(
210
+ * () => `user-${userId.get()}`,
211
+ * () => fetch(`/api/users/${userId.get()}`).then(r => r.json())
212
+ * );
213
+ */
214
+ export declare function useResource<T>(
215
+ key: string | (() => string),
216
+ fetcher: () => Promise<T>,
217
+ options?: ResourceOptions<T>
218
+ ): UseResourceReturn<T>;
219
+
220
+ // ============================================================================
221
+ // usePolling - Repeated Async Operations
222
+ // ============================================================================
223
+
224
+ /** Options for usePolling */
225
+ export interface PollingOptions {
226
+ /** Polling interval in ms (required) */
227
+ interval: number;
228
+
229
+ /** Execute immediately on start (default: true) */
230
+ immediate?: boolean;
231
+
232
+ /** Pause when page is hidden (default: true) */
233
+ pauseOnHidden?: boolean;
234
+
235
+ /** Pause when offline (default: true) */
236
+ pauseOnOffline?: boolean;
237
+
238
+ /** Max consecutive errors before stopping (default: 3) */
239
+ maxErrors?: number;
240
+
241
+ /** Callback invoked on error */
242
+ onError?: (error: Error) => void;
243
+ }
244
+
245
+ /** Return type of usePolling */
246
+ export interface UsePollingReturn<T> {
247
+ /** Reactive data value */
248
+ data: Pulse<T | null>;
249
+
250
+ /** Reactive error state */
251
+ error: Pulse<Error | null>;
252
+
253
+ /** Reactive polling state */
254
+ isPolling: Pulse<boolean>;
255
+
256
+ /** Consecutive error count */
257
+ errorCount: Pulse<number>;
258
+
259
+ /** Start polling */
260
+ start(): void;
261
+
262
+ /** Stop polling */
263
+ stop(): void;
264
+
265
+ /** Pause polling (without resetting state) */
266
+ pause(): void;
267
+
268
+ /** Resume polling */
269
+ resume(): void;
270
+ }
271
+
272
+ /**
273
+ * Create a polling mechanism for repeated async operations.
274
+ *
275
+ * @example
276
+ * const { data, start, stop, isPolling } = usePolling(
277
+ * () => fetch('/api/status').then(r => r.json()),
278
+ * { interval: 5000, pauseOnHidden: true }
279
+ * );
280
+ *
281
+ * start(); // Begin polling
282
+ * stop(); // Stop when done
283
+ */
284
+ export declare function usePolling<T>(
285
+ asyncFn: () => Promise<T>,
286
+ options: PollingOptions
287
+ ): UsePollingReturn<T>;
288
+
289
+ // ============================================================================
290
+ // Cache Utilities
291
+ // ============================================================================
292
+
293
+ /**
294
+ * Clear the entire resource cache
295
+ */
296
+ export declare function clearResourceCache(): void;
297
+
298
+ /** Cache statistics */
299
+ export interface ResourceCacheStats {
300
+ /** Number of cached entries */
301
+ size: number;
302
+
303
+ /** List of cached keys */
304
+ keys: string[];
305
+ }
306
+
307
+ /**
308
+ * Get resource cache statistics
309
+ */
310
+ export declare function getResourceCacheStats(): ResourceCacheStats;
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Pulse Framework - Form Management Type Definitions
3
+ * @module pulse-js-framework/runtime/form
4
+ */
5
+
6
+ import { Pulse } from './pulse';
7
+
8
+ // ============================================================================
9
+ // Validation Rules
10
+ // ============================================================================
11
+
12
+ /** Sync validation result - true for valid, string for error message */
13
+ export type ValidationResult = true | string;
14
+
15
+ /** Async validation result */
16
+ export type AsyncValidationResult = Promise<ValidationResult>;
17
+
18
+ /** Base validation rule */
19
+ export interface ValidationRule {
20
+ /** Validation function */
21
+ validate: (value: unknown, allValues: Record<string, unknown>) => ValidationResult;
22
+
23
+ /** Default error message */
24
+ message?: string;
25
+ }
26
+
27
+ /** Async validation rule */
28
+ export interface AsyncValidationRule {
29
+ /** Mark as async validator */
30
+ async: true;
31
+
32
+ /** Debounce delay in ms */
33
+ debounce?: number;
34
+
35
+ /** Async validation function */
36
+ validate: (value: unknown, allValues: Record<string, unknown>) => AsyncValidationResult;
37
+
38
+ /** Default error message */
39
+ message?: string;
40
+ }
41
+
42
+ /** Any validation rule (sync or async) */
43
+ export type AnyValidationRule = ValidationRule | AsyncValidationRule;
44
+
45
+ /** Async validator options */
46
+ export interface AsyncValidatorOptions {
47
+ /** Debounce delay in ms (default: 300) */
48
+ debounce?: number;
49
+ }
50
+
51
+ /** Built-in validators */
52
+ export interface Validators {
53
+ /** Required field validation */
54
+ required(message?: string): ValidationRule;
55
+
56
+ /** Minimum length validation */
57
+ minLength(length: number, message?: string): ValidationRule;
58
+
59
+ /** Maximum length validation */
60
+ maxLength(length: number, message?: string): ValidationRule;
61
+
62
+ /** Email format validation */
63
+ email(message?: string): ValidationRule;
64
+
65
+ /** URL format validation */
66
+ url(message?: string): ValidationRule;
67
+
68
+ /** Regex pattern validation */
69
+ pattern(pattern: RegExp, message?: string): ValidationRule;
70
+
71
+ /** Minimum value validation (for numbers) */
72
+ min(value: number, message?: string): ValidationRule;
73
+
74
+ /** Maximum value validation (for numbers) */
75
+ max(value: number, message?: string): ValidationRule;
76
+
77
+ /** Custom validation function */
78
+ custom(
79
+ fn: (value: unknown, allValues: Record<string, unknown>) => ValidationResult
80
+ ): ValidationRule;
81
+
82
+ /** Match another field value */
83
+ matches(fieldName: string, message?: string): ValidationRule;
84
+
85
+ /** Async custom validation */
86
+ asyncCustom(
87
+ fn: (value: unknown, allValues: Record<string, unknown>) => AsyncValidationResult,
88
+ options?: AsyncValidatorOptions
89
+ ): AsyncValidationRule;
90
+
91
+ /** Async email validation (check availability via API) */
92
+ asyncEmail(
93
+ checkFn: (email: string, allValues: Record<string, unknown>) => Promise<boolean>,
94
+ message?: string,
95
+ options?: AsyncValidatorOptions
96
+ ): AsyncValidationRule;
97
+
98
+ /** Async unique validation (check uniqueness via API) */
99
+ asyncUnique(
100
+ checkFn: (value: unknown, allValues: Record<string, unknown>) => Promise<boolean>,
101
+ message?: string,
102
+ options?: AsyncValidatorOptions
103
+ ): AsyncValidationRule;
104
+
105
+ /** Async server-side validation */
106
+ asyncServer(
107
+ validateFn: (value: unknown, allValues: Record<string, unknown>) => Promise<string | null>,
108
+ options?: AsyncValidatorOptions
109
+ ): AsyncValidationRule;
110
+ }
111
+
112
+ /** Exported validators object */
113
+ export declare const validators: Validators;
114
+
115
+ // ============================================================================
116
+ // Field Types
117
+ // ============================================================================
118
+
119
+ /** Field state and controls */
120
+ export interface Field<T = unknown> {
121
+ /** Reactive field value */
122
+ value: Pulse<T>;
123
+
124
+ /** Reactive error message (null if valid) */
125
+ error: Pulse<string | null>;
126
+
127
+ /** Reactive touched state (true after blur) */
128
+ touched: Pulse<boolean>;
129
+
130
+ /** Reactive dirty state (true if value changed from initial) */
131
+ dirty: Pulse<boolean>;
132
+
133
+ /** Reactive valid state (no error and not validating) */
134
+ valid: Pulse<boolean>;
135
+
136
+ /** Reactive validating state (true during async validation) */
137
+ validating: Pulse<boolean>;
138
+
139
+ /** Validate field (sync + async) */
140
+ validate(): Promise<boolean | null>;
141
+
142
+ /** Validate field (sync only) */
143
+ validateSync(): boolean;
144
+
145
+ /** Handle input change (works with events or raw values) */
146
+ onChange(eventOrValue: Event | T): void;
147
+
148
+ /** Handle blur event */
149
+ onBlur(): void;
150
+
151
+ /** Handle focus event */
152
+ onFocus?(): void;
153
+
154
+ /** Reset field to initial value */
155
+ reset(): void;
156
+
157
+ /** Set error message manually */
158
+ setError(message: string): void;
159
+
160
+ /** Clear error message */
161
+ clearError(): void;
162
+ }
163
+
164
+ // ============================================================================
165
+ // useForm
166
+ // ============================================================================
167
+
168
+ /** Validation schema - maps field names to validation rules */
169
+ export type ValidationSchema<T> = {
170
+ [K in keyof T]?: AnyValidationRule[];
171
+ };
172
+
173
+ /** Form options */
174
+ export interface FormOptions<T> {
175
+ /** Validate on value change (default: true) */
176
+ validateOnChange?: boolean;
177
+
178
+ /** Validate on blur (default: true) */
179
+ validateOnBlur?: boolean;
180
+
181
+ /** Validate on submit (default: true) */
182
+ validateOnSubmit?: boolean;
183
+
184
+ /** Submit handler */
185
+ onSubmit?: (values: T) => void | Promise<void>;
186
+
187
+ /** Error handler */
188
+ onError?: (errors: Record<string, string>) => void;
189
+
190
+ /** Validation mode: when to start validating (default: 'onChange') */
191
+ mode?: 'onChange' | 'onBlur' | 'onSubmit';
192
+ }
193
+
194
+ /** Form fields - maps field names to Field objects */
195
+ export type FormFields<T> = {
196
+ [K in keyof T]: Field<T[K]>;
197
+ };
198
+
199
+ /** Form errors - maps field names to error messages */
200
+ export type FormErrors<T> = {
201
+ [K in keyof T]?: string;
202
+ };
203
+
204
+ /** Return type of useForm */
205
+ export interface UseFormReturn<T extends Record<string, unknown>> {
206
+ /** Field states and controls */
207
+ fields: FormFields<T>;
208
+
209
+ /** Reactive overall validity state */
210
+ isValid: Pulse<boolean>;
211
+
212
+ /** Reactive validating state (any field is validating) */
213
+ isValidating: Pulse<boolean>;
214
+
215
+ /** Reactive dirty state (any field is dirty) */
216
+ isDirty: Pulse<boolean>;
217
+
218
+ /** Reactive touched state (any field is touched) */
219
+ isTouched: Pulse<boolean>;
220
+
221
+ /** Reactive submitting state */
222
+ isSubmitting: Pulse<boolean>;
223
+
224
+ /** Number of submit attempts */
225
+ submitCount: Pulse<number>;
226
+
227
+ /** Reactive errors object */
228
+ errors: Pulse<FormErrors<T>>;
229
+
230
+ /** Get current form values */
231
+ getValues(): T;
232
+
233
+ /** Set multiple values at once */
234
+ setValues(values: Partial<T>, shouldValidate?: boolean): void;
235
+
236
+ /** Set a single field value */
237
+ setValue<K extends keyof T>(name: K, value: T[K], shouldValidate?: boolean): void;
238
+
239
+ /** Validate all fields (sync + async) */
240
+ validateAll(): Promise<boolean>;
241
+
242
+ /** Validate all fields (sync only) */
243
+ validateAllSync(): boolean;
244
+
245
+ /** Reset form to initial values */
246
+ reset(newValues?: Partial<T>): void;
247
+
248
+ /** Handle form submission */
249
+ handleSubmit(event?: Event): Promise<boolean>;
250
+
251
+ /** Set field errors manually */
252
+ setErrors(errors: FormErrors<T>): void;
253
+
254
+ /** Clear all errors */
255
+ clearErrors(): void;
256
+ }
257
+
258
+ /**
259
+ * Create a reactive form with validation.
260
+ *
261
+ * @example
262
+ * const { fields, handleSubmit, isValid, reset } = useForm(
263
+ * { email: '', password: '' },
264
+ * {
265
+ * email: [validators.required(), validators.email()],
266
+ * password: [validators.required(), validators.minLength(8)]
267
+ * },
268
+ * {
269
+ * onSubmit: (values) => console.log('Submit:', values)
270
+ * }
271
+ * );
272
+ */
273
+ export declare function useForm<T extends Record<string, unknown>>(
274
+ initialValues: T,
275
+ validationSchema?: ValidationSchema<T>,
276
+ options?: FormOptions<T>
277
+ ): UseFormReturn<T>;
278
+
279
+ // ============================================================================
280
+ // useField
281
+ // ============================================================================
282
+
283
+ /** Options for useField */
284
+ export interface UseFieldOptions {
285
+ /** Validate on change after touched (default: true) */
286
+ validateOnChange?: boolean;
287
+
288
+ /** Validate on blur (default: true) */
289
+ validateOnBlur?: boolean;
290
+ }
291
+
292
+ /**
293
+ * Create a single reactive field (for use outside of useForm).
294
+ *
295
+ * @example
296
+ * const email = useField('', [validators.required(), validators.email()]);
297
+ *
298
+ * // With async validation
299
+ * const username = useField('', [
300
+ * validators.required(),
301
+ * validators.asyncUnique(async (value) => checkUsernameAvailable(value))
302
+ * ]);
303
+ */
304
+ export declare function useField<T = unknown>(
305
+ initialValue: T,
306
+ rules?: AnyValidationRule[],
307
+ options?: UseFieldOptions
308
+ ): Field<T>;
309
+
310
+ // ============================================================================
311
+ // useFieldArray
312
+ // ============================================================================
313
+
314
+ /** Field in a field array */
315
+ export interface FieldArrayItem<T> extends Field<T> {}
316
+
317
+ /** Return type of useFieldArray */
318
+ export interface UseFieldArrayReturn<T> {
319
+ /** Reactive array of fields */
320
+ fields: Pulse<FieldArrayItem<T>[]>;
321
+
322
+ /** Reactive array of current values */
323
+ values: Pulse<T[]>;
324
+
325
+ /** Reactive array of errors */
326
+ errors: Pulse<(string | null)[]>;
327
+
328
+ /** Reactive overall validity */
329
+ isValid: Pulse<boolean>;
330
+
331
+ /** Append a new field at the end */
332
+ append(value: T): void;
333
+
334
+ /** Prepend a new field at the beginning */
335
+ prepend(value: T): void;
336
+
337
+ /** Insert a new field at specific index */
338
+ insert(index: number, value: T): void;
339
+
340
+ /** Remove field at index */
341
+ remove(index: number): void;
342
+
343
+ /** Move field from one index to another */
344
+ move(from: number, to: number): void;
345
+
346
+ /** Swap two fields */
347
+ swap(indexA: number, indexB: number): void;
348
+
349
+ /** Replace field at index with new value */
350
+ replace(index: number, value: T): void;
351
+
352
+ /** Reset to initial values */
353
+ reset(newValues?: T[]): void;
354
+
355
+ /** Validate all fields (sync + async) */
356
+ validateAll(): Promise<boolean>;
357
+
358
+ /** Validate all fields (sync only) */
359
+ validateAllSync(): boolean;
360
+ }
361
+
362
+ /**
363
+ * Create a field array for dynamic lists of fields.
364
+ *
365
+ * @example
366
+ * const tags = useFieldArray(['tag1', 'tag2'], [validators.required()]);
367
+ *
368
+ * tags.append('tag3');
369
+ * tags.remove(0);
370
+ *
371
+ * tags.fields.get().forEach((field, index) => {
372
+ * el('input', { value: field.value.get(), onInput: field.onChange });
373
+ * });
374
+ */
375
+ export declare function useFieldArray<T>(
376
+ initialValues?: T[],
377
+ itemRules?: AnyValidationRule[]
378
+ ): UseFieldArrayReturn<T>;