cogsbox-state 0.5.486 → 0.5.488

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-state",
3
- "version": "0.5.486",
3
+ "version": "0.5.488",
4
4
  "description": "React state management library with form controls and server sync",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/CogsState.tsx CHANGED
@@ -58,11 +58,26 @@ import { ZodType } from 'zod/v4';
58
58
 
59
59
  export type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
60
60
 
61
- export type SyncInfo = {
62
- timeStamp: number;
63
- userId: number;
64
- };
65
-
61
+ export type SyncInfo = {
62
+ timeStamp: number;
63
+ userId: number;
64
+ };
65
+
66
+ export type ValidationFieldSummary = {
67
+ status: ValidationStatus;
68
+ severity: ValidationSeverity;
69
+ hasErrors: boolean;
70
+ hasWarnings: boolean;
71
+ message: string;
72
+ errors: string[];
73
+ warnings: string[];
74
+ allErrors: Array<ValidationError & { path: string[] }>;
75
+ path: string[];
76
+ getData: () => unknown;
77
+ };
78
+
79
+ type ValidationSummaryArray = ValidationFieldSummary[];
80
+
66
81
  export type FormElementParams<T> = StateObject<T> & {
67
82
  $inputProps: {
68
83
  ref?: RefObject<any>;
@@ -193,8 +208,14 @@ export type EndType<
193
208
  $$derive: <R>(fn: EffectFunction<T, R>) => R;
194
209
  $_status: 'fresh' | 'dirty' | 'synced' | 'restored' | 'unknown';
195
210
  $getStatus: () => 'fresh' | 'dirty' | 'synced' | 'restored' | 'unknown';
196
- $showValidationErrors: () => string[];
197
- $setValidation: (ctx: string) => void;
211
+ $showValidationErrors: () => string[];
212
+ $validationErrors: {
213
+ (): ValidationSummaryArray;
214
+ <K extends Extract<keyof NonNullable<T>, string>>(
215
+ keys: readonly K[]
216
+ ): ValidationSummaryArray;
217
+ };
218
+ $setValidation: (ctx: string) => void;
198
219
  $removeValidation: (ctx: string) => void;
199
220
  $isSelected: boolean;
200
221
  $setSelected: (value: boolean) => void;
@@ -362,7 +383,21 @@ export type StateObject<
362
383
  : {}) & // Fallback to {} since we intersect EndType below anyway
363
384
  EndType<T, TPlugins> & {
364
385
  $toggle: NonNullable<T> extends boolean ? () => void : never;
365
- $validate: () => { success: boolean; data?: T; error?: any };
386
+ $validate: {
387
+ (): { success: boolean; data?: T; error?: any };
388
+ <K extends Extract<keyof NonNullable<T>, string>>(
389
+ keys: readonly K[]
390
+ ): {
391
+ success: boolean;
392
+ results: Array<{
393
+ key: K;
394
+ path: string[];
395
+ success: boolean;
396
+ data?: unknown;
397
+ error?: any;
398
+ }>;
399
+ };
400
+ };
366
401
  $_componentId: string | null;
367
402
  $getComponents: () => ComponentsType;
368
403
  $_initialState: T;
@@ -2558,14 +2593,75 @@ function createProxyHandler<
2558
2593
  setShadowMetadata(stateKey, path, { ...currentMeta, isRaw: true });
2559
2594
  effectiveSetState(value, path, { updateType: 'update' });
2560
2595
  };
2561
- }
2562
-
2563
- if (prop === '$validate') {
2564
- return () => {
2565
- const store = getGlobalStore.getState();
2566
-
2567
- // 1. Get Data & Schema
2568
- const { value } = getScopedData(stateKey, path, meta);
2596
+ }
2597
+
2598
+ if (prop === '$validate') {
2599
+ return (keys?: readonly string[]) => {
2600
+ const store = getGlobalStore.getState();
2601
+
2602
+ if (keys) {
2603
+ const results = keys.map((key) => {
2604
+ const targetPath = [...path, key];
2605
+ const targetValue = store.getShadowValue(stateKey, targetPath);
2606
+ const currentMeta =
2607
+ store.getShadowMetadata(stateKey, targetPath) || {};
2608
+ const fieldSchema = currentMeta.typeInfo?.schema;
2609
+
2610
+ if (!fieldSchema) {
2611
+ return {
2612
+ key,
2613
+ path: targetPath,
2614
+ success: true,
2615
+ data: targetValue,
2616
+ };
2617
+ }
2618
+
2619
+ const result = (fieldSchema as any).safeParse(targetValue);
2620
+ const zodErrors =
2621
+ result.error?.issues || result.error?.errors || [];
2622
+
2623
+ store.setShadowMetadata(stateKey, targetPath, {
2624
+ ...currentMeta,
2625
+ validation: {
2626
+ status: result.success ? 'VALID' : 'INVALID',
2627
+ errors: result.success
2628
+ ? []
2629
+ : [
2630
+ {
2631
+ source: 'client',
2632
+ message:
2633
+ zodErrors[0]?.message || 'Invalid value',
2634
+ severity: 'error',
2635
+ code: zodErrors[0]?.code,
2636
+ },
2637
+ ],
2638
+ lastValidated: Date.now(),
2639
+ validatedValue: targetValue,
2640
+ },
2641
+ });
2642
+ store.notifyPathSubscribers([stateKey, ...targetPath].join('.'), {
2643
+ type: 'VALIDATION_UPDATE',
2644
+ });
2645
+
2646
+ return {
2647
+ key,
2648
+ path: targetPath,
2649
+ success: result.success,
2650
+ data: result.success ? result.data : undefined,
2651
+ error: result.success ? undefined : result.error,
2652
+ };
2653
+ });
2654
+
2655
+ notifyComponents(stateKey);
2656
+
2657
+ return {
2658
+ success: results.every((result) => result.success),
2659
+ results,
2660
+ };
2661
+ }
2662
+
2663
+ // 1. Get Data & Schema
2664
+ const { value } = getScopedData(stateKey, path, meta);
2569
2665
  const opts = store.getInitialOptions(stateKey);
2570
2666
  const schema =
2571
2667
  opts?.validation?.zodSchemaV4 || opts?.validation?.zodSchemaV3;
@@ -2623,11 +2719,11 @@ function createProxyHandler<
2623
2719
  return result;
2624
2720
  };
2625
2721
  }
2626
- if (prop === '$showValidationErrors') {
2627
- return () => {
2628
- const { shadowMeta } = getScopedData(stateKey, path, meta);
2629
- if (
2630
- shadowMeta?.validation?.status === 'INVALID' &&
2722
+ if (prop === '$showValidationErrors') {
2723
+ return () => {
2724
+ const { shadowMeta } = getScopedData(stateKey, path, meta);
2725
+ if (
2726
+ shadowMeta?.validation?.status === 'INVALID' &&
2631
2727
  shadowMeta.validation.errors.length > 0
2632
2728
  ) {
2633
2729
  // Return only error-severity messages (not warnings)
@@ -2635,11 +2731,55 @@ function createProxyHandler<
2635
2731
  .filter((err) => err.severity === 'error')
2636
2732
  .map((err) => err.message);
2637
2733
  }
2638
- return [];
2639
- };
2640
- }
2641
-
2642
- if (prop === '$getSelected') {
2734
+ return [];
2735
+ };
2736
+ }
2737
+ if (prop === '$validationErrors') {
2738
+ return (keys?: readonly string[]) => {
2739
+ const store = getGlobalStore.getState();
2740
+ const summarizePath = (targetPath: string[]) => {
2741
+ const validationState =
2742
+ store.getShadowMetadata(stateKey, targetPath)?.validation;
2743
+ const allErrors = (validationState?.errors || []).map((err) => ({
2744
+ ...err,
2745
+ path: targetPath,
2746
+ }));
2747
+ const errors = allErrors.filter((err) => err.severity === 'error');
2748
+ const warnings = allErrors.filter(
2749
+ (err) => err.severity === 'warning'
2750
+ );
2751
+ const severity: ValidationSeverity =
2752
+ errors.length > 0
2753
+ ? 'error'
2754
+ : warnings.length > 0
2755
+ ? 'warning'
2756
+ : undefined;
2757
+
2758
+ return {
2759
+ status: validationState?.status || 'NOT_VALIDATED',
2760
+ severity,
2761
+ hasErrors: errors.length > 0,
2762
+ hasWarnings: warnings.length > 0,
2763
+ message: errors[0]?.message || warnings[0]?.message || '',
2764
+ errors: errors.map((err) => err.message),
2765
+ warnings: warnings.map((err) => err.message),
2766
+ allErrors,
2767
+ path: targetPath,
2768
+ getData: () => store.getShadowValue(stateKey, targetPath),
2769
+ } satisfies ValidationFieldSummary;
2770
+ };
2771
+
2772
+ const childKeys =
2773
+ keys ??
2774
+ Object.keys(store.getShadowNode(stateKey, path) ?? {}).filter(
2775
+ (key) => key !== '_meta'
2776
+ );
2777
+
2778
+ return childKeys.map((key) => summarizePath([...path, key]));
2779
+ };
2780
+ }
2781
+
2782
+ if (prop === '$getSelected') {
2643
2783
  return () => {
2644
2784
  const arrayKey = [stateKey, ...path].join('.');
2645
2785
  registerComponentDependency(stateKey, componentId, [