cogsbox-state 0.5.488 → 0.5.490
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/README.md +4 -4
- package/dist/CogsState.d.ts +2 -50
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.js +886 -1002
- package/dist/CogsState.js.map +1 -1
- package/dist/plugins.d.ts +4 -2
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/store.d.ts +1 -20
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +180 -218
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +267 -594
- package/src/plugins.ts +23 -14
- package/src/store.ts +2 -96
package/src/CogsState.tsx
CHANGED
|
@@ -58,31 +58,28 @@ 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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ref?: RefObject<any>;
|
|
84
|
-
value?: T extends boolean ? never : T;
|
|
85
|
-
onChange?: (
|
|
61
|
+
// Removed: SyncInfo is now owned by Shape Plugin
|
|
62
|
+
|
|
63
|
+
export type ValidationFieldSummary = {
|
|
64
|
+
status: ValidationStatus;
|
|
65
|
+
severity: ValidationSeverity;
|
|
66
|
+
hasErrors: boolean;
|
|
67
|
+
hasWarnings: boolean;
|
|
68
|
+
message: string;
|
|
69
|
+
errors: string[];
|
|
70
|
+
warnings: string[];
|
|
71
|
+
allErrors: Array<ValidationError & { path: string[] }>;
|
|
72
|
+
path: string[];
|
|
73
|
+
getData: () => unknown;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type ValidationSummaryArray = ValidationFieldSummary[];
|
|
77
|
+
|
|
78
|
+
export type FormElementParams<T> = StateObject<T> & {
|
|
79
|
+
$inputProps: {
|
|
80
|
+
ref?: RefObject<any>;
|
|
81
|
+
value?: T extends boolean ? never : T;
|
|
82
|
+
onChange?: (
|
|
86
83
|
event: ChangeEvent<
|
|
87
84
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
88
85
|
>
|
|
@@ -90,17 +87,17 @@ export type FormElementParams<T> = StateObject<T> & {
|
|
|
90
87
|
onBlur?: (
|
|
91
88
|
event: FocusEvent<
|
|
92
89
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
93
|
-
>
|
|
94
|
-
) => void;
|
|
95
|
-
};
|
|
96
|
-
status: ValidationStatus;
|
|
97
|
-
severity: ValidationSeverity;
|
|
98
|
-
hasErrors: boolean;
|
|
99
|
-
hasWarnings: boolean;
|
|
100
|
-
allErrors: ValidationError[];
|
|
101
|
-
message: string;
|
|
102
|
-
getData: () => T;
|
|
103
|
-
};
|
|
90
|
+
>
|
|
91
|
+
) => void;
|
|
92
|
+
};
|
|
93
|
+
status: ValidationStatus;
|
|
94
|
+
severity: ValidationSeverity;
|
|
95
|
+
hasErrors: boolean;
|
|
96
|
+
hasWarnings: boolean;
|
|
97
|
+
allErrors: ValidationError[];
|
|
98
|
+
message: string;
|
|
99
|
+
getData: () => T;
|
|
100
|
+
};
|
|
104
101
|
|
|
105
102
|
export type StateKeys = string;
|
|
106
103
|
|
|
@@ -206,16 +203,15 @@ export type EndType<
|
|
|
206
203
|
$get: () => T;
|
|
207
204
|
$$get: () => T;
|
|
208
205
|
$$derive: <R>(fn: EffectFunction<T, R>) => R;
|
|
209
|
-
$_status
|
|
210
|
-
$
|
|
211
|
-
$
|
|
212
|
-
|
|
213
|
-
(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
$setValidation: (ctx: string) => void;
|
|
206
|
+
// Removed: $_status and $getStatus are now owned by Shape Plugin
|
|
207
|
+
$showValidationErrors: () => string[];
|
|
208
|
+
$validationErrors: {
|
|
209
|
+
(): ValidationSummaryArray;
|
|
210
|
+
<K extends Extract<keyof NonNullable<T>, string>>(
|
|
211
|
+
keys: readonly K[]
|
|
212
|
+
): ValidationSummaryArray;
|
|
213
|
+
};
|
|
214
|
+
$setValidation: (ctx: string) => void;
|
|
219
215
|
$removeValidation: (ctx: string) => void;
|
|
220
216
|
$isSelected: boolean;
|
|
221
217
|
$setSelected: (value: boolean) => void;
|
|
@@ -230,7 +226,7 @@ export type EndType<
|
|
|
230
226
|
};
|
|
231
227
|
$removeStorage: () => void;
|
|
232
228
|
$setRaw: (value: T) => void;
|
|
233
|
-
$sync
|
|
229
|
+
// Removed: $sync is now owned by Shape Plugin
|
|
234
230
|
$validationWrapper: ({
|
|
235
231
|
children,
|
|
236
232
|
hideMessage,
|
|
@@ -238,7 +234,7 @@ export type EndType<
|
|
|
238
234
|
children: ReactNode;
|
|
239
235
|
hideMessage?: boolean;
|
|
240
236
|
}) => JSX.Element;
|
|
241
|
-
$lastSynced
|
|
237
|
+
// Removed: $lastSynced is now owned by Shape Plugin
|
|
242
238
|
};
|
|
243
239
|
|
|
244
240
|
// Helper type for element returned from array methods
|
|
@@ -383,21 +379,21 @@ export type StateObject<
|
|
|
383
379
|
: {}) & // Fallback to {} since we intersect EndType below anyway
|
|
384
380
|
EndType<T, TPlugins> & {
|
|
385
381
|
$toggle: NonNullable<T> extends boolean ? () => void : never;
|
|
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
|
-
};
|
|
382
|
+
$validate: {
|
|
383
|
+
(): { success: boolean; data?: T; error?: any };
|
|
384
|
+
<K extends Extract<keyof NonNullable<T>, string>>(
|
|
385
|
+
keys: readonly K[]
|
|
386
|
+
): {
|
|
387
|
+
success: boolean;
|
|
388
|
+
results: Array<{
|
|
389
|
+
key: K;
|
|
390
|
+
path: string[];
|
|
391
|
+
success: boolean;
|
|
392
|
+
data?: unknown;
|
|
393
|
+
error?: any;
|
|
394
|
+
}>;
|
|
395
|
+
};
|
|
396
|
+
};
|
|
401
397
|
$_componentId: string | null;
|
|
402
398
|
$getComponents: () => ComponentsType;
|
|
403
399
|
$_initialState: T;
|
|
@@ -405,8 +401,6 @@ export type StateObject<
|
|
|
405
401
|
fetchId: (field: keyof NonNullable<T>) => string | number;
|
|
406
402
|
};
|
|
407
403
|
$initializeAndMergeShadowState: (newState: any | null) => void;
|
|
408
|
-
$_isLoading: boolean;
|
|
409
|
-
$_serverState: T;
|
|
410
404
|
$revertToInitialState: (obj?: { validationKey?: string }) => T;
|
|
411
405
|
$middleware: (
|
|
412
406
|
middles: ({
|
|
@@ -526,34 +520,6 @@ export type OptionsType<
|
|
|
526
520
|
> = CreateStateOptionsType & {
|
|
527
521
|
log?: boolean;
|
|
528
522
|
componentId?: string;
|
|
529
|
-
syncOptions?: SyncOptionsType<TApiParams>;
|
|
530
|
-
|
|
531
|
-
serverState?: {
|
|
532
|
-
id?: string | number;
|
|
533
|
-
data?: T;
|
|
534
|
-
status?: 'pending' | 'error' | 'success' | 'loading';
|
|
535
|
-
timestamp?: number;
|
|
536
|
-
merge?:
|
|
537
|
-
| boolean
|
|
538
|
-
| {
|
|
539
|
-
strategy: 'append' | 'prepend' | 'diff';
|
|
540
|
-
key?: string;
|
|
541
|
-
};
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
sync?: {
|
|
545
|
-
action: (state: T) => Promise<{
|
|
546
|
-
success: boolean;
|
|
547
|
-
data?: any;
|
|
548
|
-
error?: any;
|
|
549
|
-
errors?: Array<{
|
|
550
|
-
path: (string | number)[];
|
|
551
|
-
message: string;
|
|
552
|
-
}>;
|
|
553
|
-
}>;
|
|
554
|
-
onSuccess?: (data: any) => void;
|
|
555
|
-
onError?: (error: any) => void;
|
|
556
|
-
};
|
|
557
523
|
middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
|
|
558
524
|
|
|
559
525
|
localStorage?: {
|
|
@@ -563,7 +529,6 @@ export type OptionsType<
|
|
|
563
529
|
|
|
564
530
|
reactiveDeps?: (state: T) => any[] | true;
|
|
565
531
|
reactiveType?: ReactivityType;
|
|
566
|
-
syncUpdate?: Partial<UpdateTypeDetail>;
|
|
567
532
|
|
|
568
533
|
defaultState?: T;
|
|
569
534
|
|
|
@@ -632,13 +597,10 @@ const {
|
|
|
632
597
|
insertManyShadowArrayElements,
|
|
633
598
|
removeShadowArrayElement,
|
|
634
599
|
setInitialStateOptions,
|
|
635
|
-
setServerStateUpdate,
|
|
636
|
-
markAsDirty,
|
|
637
600
|
addPathComponent,
|
|
638
601
|
clearSelectedIndexesForState,
|
|
639
602
|
addStateLog,
|
|
640
603
|
clearSelectedIndex,
|
|
641
|
-
getSyncInfo,
|
|
642
604
|
notifyPathSubscribers,
|
|
643
605
|
getPluginMetaDataMap,
|
|
644
606
|
setPluginMetaData,
|
|
@@ -914,7 +876,7 @@ type CreateCogsStateReturn<
|
|
|
914
876
|
PluginOptionsMap<TPlugins>,
|
|
915
877
|
StateKey
|
|
916
878
|
>
|
|
917
|
-
) => StateObject<CogsFullState<State, TPlugins>[StateKey]>;
|
|
879
|
+
) => StateObject<CogsFullState<State, TPlugins>[StateKey], TPlugins>;
|
|
918
880
|
setCogsOptionsByKey: <StateKey extends keyof CogsFullState<State, TPlugins>>(
|
|
919
881
|
stateKey: StateKey,
|
|
920
882
|
options: CreateStateOptionsType<
|
|
@@ -1030,7 +992,7 @@ export const createCogsState = <
|
|
|
1030
992
|
OptionsType<StateSlice<StateKey>, never> &
|
|
1031
993
|
PluginOptionsForState<PluginOptions, StateKey>
|
|
1032
994
|
>
|
|
1033
|
-
): StateObject<StateSlice<StateKey
|
|
995
|
+
): StateObject<StateSlice<StateKey>, TPlugins> => {
|
|
1034
996
|
const [componentId] = useState(options?.componentId ?? uuidv4());
|
|
1035
997
|
|
|
1036
998
|
const currentOptions = setOptions({
|
|
@@ -1048,7 +1010,6 @@ export const createCogsState = <
|
|
|
1048
1010
|
|
|
1049
1011
|
const updater = useCogsStateFn<StateSlice<StateKey>>(thiState, {
|
|
1050
1012
|
stateKey: stateKey as string,
|
|
1051
|
-
syncUpdate: options?.syncUpdate,
|
|
1052
1013
|
componentId,
|
|
1053
1014
|
localStorage: options?.localStorage,
|
|
1054
1015
|
middleware: options?.middleware,
|
|
@@ -1056,7 +1017,6 @@ export const createCogsState = <
|
|
|
1056
1017
|
reactiveDeps: options?.reactiveDeps,
|
|
1057
1018
|
defaultState: options?.defaultState as any,
|
|
1058
1019
|
dependencies: options?.dependencies,
|
|
1059
|
-
serverState: options?.serverState,
|
|
1060
1020
|
});
|
|
1061
1021
|
|
|
1062
1022
|
useLayoutEffect(() => {
|
|
@@ -1086,7 +1046,7 @@ export const createCogsState = <
|
|
|
1086
1046
|
}
|
|
1087
1047
|
});
|
|
1088
1048
|
|
|
1089
|
-
return updater as StateObject<StateSlice<StateKey
|
|
1049
|
+
return updater as StateObject<StateSlice<StateKey>, TPlugins>;
|
|
1090
1050
|
};
|
|
1091
1051
|
|
|
1092
1052
|
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
@@ -1183,8 +1143,6 @@ const saveToLocalStorage = <T,>(
|
|
|
1183
1143
|
state,
|
|
1184
1144
|
lastUpdated: Date.now(),
|
|
1185
1145
|
lastSyncedWithServer: lastSyncedWithServer ?? existingLastSynced,
|
|
1186
|
-
stateSource: shadowMeta?.stateSource,
|
|
1187
|
-
baseServerState: shadowMeta?.baseServerState,
|
|
1188
1146
|
};
|
|
1189
1147
|
|
|
1190
1148
|
// Use SuperJSON serialize to get the json part only
|
|
@@ -1251,8 +1209,6 @@ type LocalStorageData<T> = {
|
|
|
1251
1209
|
state: T;
|
|
1252
1210
|
lastUpdated: number;
|
|
1253
1211
|
lastSyncedWithServer?: number;
|
|
1254
|
-
baseServerState?: T; // Keep reference to what server state this is based on
|
|
1255
|
-
stateSource?: 'default' | 'server' | 'localStorage'; // Track origin
|
|
1256
1212
|
};
|
|
1257
1213
|
|
|
1258
1214
|
const notifyComponents = (thisKey: string) => {
|
|
@@ -1278,49 +1234,6 @@ const notifyComponents = (thisKey: string) => {
|
|
|
1278
1234
|
});
|
|
1279
1235
|
};
|
|
1280
1236
|
|
|
1281
|
-
function markEntireStateAsServerSynced(
|
|
1282
|
-
stateKey: string,
|
|
1283
|
-
path: string[],
|
|
1284
|
-
data: any,
|
|
1285
|
-
timestamp: number
|
|
1286
|
-
) {
|
|
1287
|
-
// Mark current path as synced
|
|
1288
|
-
const currentMeta = getShadowMetadata(stateKey, path);
|
|
1289
|
-
setShadowMetadata(stateKey, path, {
|
|
1290
|
-
...currentMeta,
|
|
1291
|
-
isDirty: false,
|
|
1292
|
-
stateSource: 'server',
|
|
1293
|
-
lastServerSync: timestamp || Date.now(),
|
|
1294
|
-
});
|
|
1295
|
-
|
|
1296
|
-
// If it's an array, mark each item as synced
|
|
1297
|
-
if (Array.isArray(data)) {
|
|
1298
|
-
const arrayMeta = getShadowMetadata(stateKey, path);
|
|
1299
|
-
if (arrayMeta?.arrayKeys) {
|
|
1300
|
-
arrayMeta.arrayKeys.forEach((itemKey, index) => {
|
|
1301
|
-
// Fix: Don't split the itemKey, just use it directly
|
|
1302
|
-
const itemPath = [...path, itemKey];
|
|
1303
|
-
const itemData = data[index];
|
|
1304
|
-
if (itemData !== undefined) {
|
|
1305
|
-
markEntireStateAsServerSynced(
|
|
1306
|
-
stateKey,
|
|
1307
|
-
itemPath,
|
|
1308
|
-
itemData,
|
|
1309
|
-
timestamp
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
// If it's an object, mark each field as synced
|
|
1316
|
-
else if (data && typeof data === 'object' && data.constructor === Object) {
|
|
1317
|
-
Object.keys(data).forEach((key) => {
|
|
1318
|
-
const fieldPath = [...path, key];
|
|
1319
|
-
const fieldData = data[key];
|
|
1320
|
-
markEntireStateAsServerSynced(stateKey, fieldPath, fieldData, timestamp);
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
1237
|
// 5. Batch queue
|
|
1325
1238
|
let updateBatchQueue: any[] = [];
|
|
1326
1239
|
let isFlushScheduled = false;
|
|
@@ -1508,12 +1421,8 @@ function handleUpdate(
|
|
|
1508
1421
|
return null; // <-- Abort the update
|
|
1509
1422
|
}
|
|
1510
1423
|
|
|
1511
|
-
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1512
|
-
// The manual loop has been removed.
|
|
1513
1424
|
updateShadowAtPath(stateKey, path, newValue);
|
|
1514
1425
|
|
|
1515
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1516
|
-
|
|
1517
1426
|
// Return the metadata of the node *after* the update.
|
|
1518
1427
|
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1519
1428
|
|
|
@@ -1534,10 +1443,8 @@ function handleInsertMany(
|
|
|
1534
1443
|
shadowMeta: any;
|
|
1535
1444
|
path: string[];
|
|
1536
1445
|
} {
|
|
1537
|
-
// Use the existing, optimized global store function to perform the state update
|
|
1538
1446
|
insertManyShadowArrayElements(stateKey, path, payload);
|
|
1539
1447
|
|
|
1540
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1541
1448
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1542
1449
|
|
|
1543
1450
|
return {
|
|
@@ -1578,10 +1485,6 @@ function handleInsert(
|
|
|
1578
1485
|
index,
|
|
1579
1486
|
itemId
|
|
1580
1487
|
);
|
|
1581
|
-
//console.timeEnd('insertShadowArrayElement');
|
|
1582
|
-
|
|
1583
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1584
|
-
|
|
1585
1488
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1586
1489
|
|
|
1587
1490
|
let insertAfterId: string | undefined;
|
|
@@ -1606,7 +1509,6 @@ function handleCut(
|
|
|
1606
1509
|
const parentArrayPath = path.slice(0, -1);
|
|
1607
1510
|
const oldValue = getShadowValue(stateKey, path);
|
|
1608
1511
|
removeShadowArrayElement(stateKey, path);
|
|
1609
|
-
markAsDirty(stateKey, parentArrayPath, { bubble: true });
|
|
1610
1512
|
return { type: 'cut', oldValue: oldValue, parentPath: parentArrayPath };
|
|
1611
1513
|
}
|
|
1612
1514
|
|
|
@@ -1759,12 +1661,10 @@ export function useCogsStateFn<
|
|
|
1759
1661
|
componentId,
|
|
1760
1662
|
defaultState,
|
|
1761
1663
|
dependencies,
|
|
1762
|
-
serverState,
|
|
1763
1664
|
}: {
|
|
1764
1665
|
stateKey?: string;
|
|
1765
1666
|
componentId?: string;
|
|
1766
1667
|
defaultState?: TStateObject;
|
|
1767
|
-
syncOptions?: SyncOptionsType<any>;
|
|
1768
1668
|
} & OptionsType<TStateObject> = {}
|
|
1769
1669
|
): StateObject<TStateObject, TPlugins> {
|
|
1770
1670
|
const [reactiveForce, forceUpdate] = useState({}); //this is the key to reactivity
|
|
@@ -1783,10 +1683,9 @@ export function useCogsStateFn<
|
|
|
1783
1683
|
overrideOptions?: OptionsType<TStateObject>
|
|
1784
1684
|
): {
|
|
1785
1685
|
value: TStateObject;
|
|
1786
|
-
source: 'default' | '
|
|
1686
|
+
source: 'default' | 'localStorage';
|
|
1787
1687
|
timestamp: number;
|
|
1788
1688
|
} => {
|
|
1789
|
-
// If we pass in options, use them. Otherwise, get from the global store.
|
|
1790
1689
|
const optionsToUse = overrideOptions
|
|
1791
1690
|
? { ...getInitialOptions(thisKey as string), ...overrideOptions }
|
|
1792
1691
|
: getInitialOptions(thisKey as string);
|
|
@@ -1795,19 +1694,7 @@ export function useCogsStateFn<
|
|
|
1795
1694
|
const finalDefaultState =
|
|
1796
1695
|
currentOptions?.defaultState || defaultState || stateObject;
|
|
1797
1696
|
|
|
1798
|
-
//
|
|
1799
|
-
const hasValidServerData =
|
|
1800
|
-
currentOptions?.serverState?.status === 'success' &&
|
|
1801
|
-
currentOptions?.serverState?.data !== undefined;
|
|
1802
|
-
|
|
1803
|
-
if (hasValidServerData) {
|
|
1804
|
-
return {
|
|
1805
|
-
value: currentOptions.serverState!.data! as any,
|
|
1806
|
-
source: 'server',
|
|
1807
|
-
timestamp: currentOptions.serverState!.timestamp || Date.now(),
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
// 2. Check localStorage
|
|
1697
|
+
// Check localStorage
|
|
1811
1698
|
if (currentOptions?.localStorage?.key && sessionId) {
|
|
1812
1699
|
const localKey = isFunction(currentOptions.localStorage.key)
|
|
1813
1700
|
? currentOptions.localStorage.key(finalDefaultState)
|
|
@@ -1817,10 +1704,7 @@ export function useCogsStateFn<
|
|
|
1817
1704
|
`${sessionId}-${thisKey}-${localKey}`
|
|
1818
1705
|
);
|
|
1819
1706
|
|
|
1820
|
-
if (
|
|
1821
|
-
localData &&
|
|
1822
|
-
localData.lastUpdated > (currentOptions?.serverState?.timestamp || 0)
|
|
1823
|
-
) {
|
|
1707
|
+
if (localData) {
|
|
1824
1708
|
return {
|
|
1825
1709
|
value: localData.state,
|
|
1826
1710
|
source: 'localStorage',
|
|
@@ -1829,7 +1713,7 @@ export function useCogsStateFn<
|
|
|
1829
1713
|
}
|
|
1830
1714
|
}
|
|
1831
1715
|
|
|
1832
|
-
//
|
|
1716
|
+
// Use default state
|
|
1833
1717
|
return {
|
|
1834
1718
|
value: finalDefaultState || (stateObject as any),
|
|
1835
1719
|
source: 'default',
|
|
@@ -1839,111 +1723,8 @@ export function useCogsStateFn<
|
|
|
1839
1723
|
[thisKey, defaultState, stateObject, sessionId]
|
|
1840
1724
|
);
|
|
1841
1725
|
|
|
1842
|
-
//
|
|
1843
|
-
useEffect(() => {
|
|
1844
|
-
if (!serverState) return;
|
|
1845
|
-
|
|
1846
|
-
// Only broadcast if we have valid server data
|
|
1847
|
-
if (serverState.status === 'success' && serverState.data !== undefined) {
|
|
1848
|
-
setServerStateUpdate(thisKey, serverState);
|
|
1849
|
-
}
|
|
1850
|
-
}, [serverState, thisKey]);
|
|
1851
|
-
// Effect 2: Listen for server state updates from ANY component
|
|
1726
|
+
// Removed: Server state effects are now owned by Shape Plugin
|
|
1852
1727
|
useEffect(() => {
|
|
1853
|
-
const unsubscribe = getGlobalStore
|
|
1854
|
-
.getState()
|
|
1855
|
-
.subscribeToPath(thisKey, (event) => {
|
|
1856
|
-
if (event?.type === 'SERVER_STATE_UPDATE') {
|
|
1857
|
-
const serverStateData = event.serverState;
|
|
1858
|
-
|
|
1859
|
-
if (
|
|
1860
|
-
serverStateData?.status !== 'success' ||
|
|
1861
|
-
serverStateData.data === undefined
|
|
1862
|
-
) {
|
|
1863
|
-
return; // Ignore if no valid data
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
// Store the server state in options for future reference
|
|
1867
|
-
setAndMergeOptions(thisKey, { serverState: serverStateData });
|
|
1868
|
-
|
|
1869
|
-
const mergeConfig =
|
|
1870
|
-
typeof serverStateData.merge === 'object'
|
|
1871
|
-
? serverStateData.merge
|
|
1872
|
-
: serverStateData.merge === true
|
|
1873
|
-
? { strategy: 'append' as const, key: 'id' }
|
|
1874
|
-
: null;
|
|
1875
|
-
|
|
1876
|
-
const currentState = getShadowValue(thisKey, []);
|
|
1877
|
-
const incomingData = serverStateData.data;
|
|
1878
|
-
|
|
1879
|
-
if (
|
|
1880
|
-
mergeConfig &&
|
|
1881
|
-
mergeConfig.strategy === 'append' &&
|
|
1882
|
-
'key' in mergeConfig &&
|
|
1883
|
-
Array.isArray(currentState) &&
|
|
1884
|
-
Array.isArray(incomingData)
|
|
1885
|
-
) {
|
|
1886
|
-
const keyField = mergeConfig.key;
|
|
1887
|
-
if (!keyField) {
|
|
1888
|
-
console.error(
|
|
1889
|
-
"CogsState: Merge strategy 'append' requires a 'key' field."
|
|
1890
|
-
);
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
// Get existing IDs to check for duplicates
|
|
1895
|
-
const existingIds = new Set(
|
|
1896
|
-
currentState.map((item: any) => item[keyField])
|
|
1897
|
-
);
|
|
1898
|
-
|
|
1899
|
-
// Filter out duplicates from incoming data
|
|
1900
|
-
const newUniqueItems = incomingData.filter(
|
|
1901
|
-
(item: any) => !existingIds.has(item[keyField])
|
|
1902
|
-
);
|
|
1903
|
-
|
|
1904
|
-
if (newUniqueItems.length > 0) {
|
|
1905
|
-
// Insert only the new unique items
|
|
1906
|
-
insertManyShadowArrayElements(thisKey, [], newUniqueItems);
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
// Mark the entire merged state as synced
|
|
1910
|
-
const finalState = getShadowValue(thisKey, []);
|
|
1911
|
-
markEntireStateAsServerSynced(
|
|
1912
|
-
thisKey,
|
|
1913
|
-
[],
|
|
1914
|
-
finalState,
|
|
1915
|
-
serverStateData.timestamp || Date.now()
|
|
1916
|
-
);
|
|
1917
|
-
} else {
|
|
1918
|
-
// Replace strategy (default) - completely replace the state
|
|
1919
|
-
initializeShadowState(thisKey, incomingData);
|
|
1920
|
-
|
|
1921
|
-
// Mark as synced from server
|
|
1922
|
-
markEntireStateAsServerSynced(
|
|
1923
|
-
thisKey,
|
|
1924
|
-
[],
|
|
1925
|
-
incomingData,
|
|
1926
|
-
serverStateData.timestamp || Date.now()
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
// Notify all components subscribed to this state
|
|
1931
|
-
notifyComponents(thisKey);
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
return unsubscribe;
|
|
1936
|
-
}, [thisKey]);
|
|
1937
|
-
useEffect(() => {
|
|
1938
|
-
const existingMeta = getGlobalStore
|
|
1939
|
-
.getState()
|
|
1940
|
-
.getShadowMetadata(thisKey, []);
|
|
1941
|
-
|
|
1942
|
-
// Skip if already initialized
|
|
1943
|
-
if (existingMeta && existingMeta.stateSource) {
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
1728
|
const options = getInitialOptions(thisKey as string);
|
|
1948
1729
|
|
|
1949
1730
|
const features = {
|
|
@@ -1951,7 +1732,6 @@ export function useCogsStateFn<
|
|
|
1951
1732
|
};
|
|
1952
1733
|
|
|
1953
1734
|
setShadowMetadata(thisKey, [], {
|
|
1954
|
-
...existingMeta,
|
|
1955
1735
|
features,
|
|
1956
1736
|
});
|
|
1957
1737
|
|
|
@@ -1964,7 +1744,7 @@ export function useCogsStateFn<
|
|
|
1964
1744
|
}
|
|
1965
1745
|
}
|
|
1966
1746
|
|
|
1967
|
-
const { value: resolvedState
|
|
1747
|
+
const { value: resolvedState } = resolveInitialState();
|
|
1968
1748
|
initializeShadowState(thisKey, resolvedState);
|
|
1969
1749
|
|
|
1970
1750
|
const validation = getInitialOptions(thisKey as string)?.validation;
|
|
@@ -1974,17 +1754,6 @@ export function useCogsStateFn<
|
|
|
1974
1754
|
updateShadowTypeInfo(thisKey as string, validation.zodSchemaV3, 'zod3');
|
|
1975
1755
|
}
|
|
1976
1756
|
|
|
1977
|
-
setShadowMetadata(thisKey, [], {
|
|
1978
|
-
stateSource: source,
|
|
1979
|
-
lastServerSync: source === 'server' ? timestamp : undefined,
|
|
1980
|
-
isDirty: source === 'server' ? false : undefined,
|
|
1981
|
-
baseServerState: source === 'server' ? resolvedState : undefined,
|
|
1982
|
-
});
|
|
1983
|
-
|
|
1984
|
-
if (source === 'server' && serverState) {
|
|
1985
|
-
setServerStateUpdate(thisKey, serverState);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
1757
|
notifyComponents(thisKey);
|
|
1989
1758
|
}, [thisKey, ...(dependencies || [])]);
|
|
1990
1759
|
|
|
@@ -2089,7 +1858,6 @@ type MetaData = {
|
|
|
2089
1858
|
fn: Function;
|
|
2090
1859
|
path: string[]; // Which array this transform applies to
|
|
2091
1860
|
}>;
|
|
2092
|
-
serverStateIsUpStream?: boolean;
|
|
2093
1861
|
};
|
|
2094
1862
|
|
|
2095
1863
|
const applyTransforms = (
|
|
@@ -2254,11 +2022,11 @@ function getFieldElementsForPath(stateKey: string, path: string[]) {
|
|
|
2254
2022
|
return refs.map((ref) => ref.current).filter(Boolean);
|
|
2255
2023
|
}
|
|
2256
2024
|
|
|
2257
|
-
function setFieldDisabledForPath(
|
|
2258
|
-
stateKey: string,
|
|
2259
|
-
path: string[],
|
|
2260
|
-
disabled: boolean
|
|
2261
|
-
) {
|
|
2025
|
+
function setFieldDisabledForPath(
|
|
2026
|
+
stateKey: string,
|
|
2027
|
+
path: string[],
|
|
2028
|
+
disabled: boolean
|
|
2029
|
+
) {
|
|
2262
2030
|
getFieldElementsForPath(stateKey, path).forEach((el: any) => {
|
|
2263
2031
|
if ('disabled' in el) {
|
|
2264
2032
|
el.disabled = disabled;
|
|
@@ -2266,11 +2034,36 @@ function setFieldDisabledForPath(
|
|
|
2266
2034
|
}
|
|
2267
2035
|
|
|
2268
2036
|
el.style.pointerEvents = disabled ? 'none' : '';
|
|
2269
|
-
el.setAttribute('aria-disabled', String(disabled));
|
|
2270
|
-
});
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
function
|
|
2037
|
+
el.setAttribute('aria-disabled', String(disabled));
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function pluginMetaDependencyPath(
|
|
2042
|
+
path: string[],
|
|
2043
|
+
pluginName: string,
|
|
2044
|
+
scope = 'default'
|
|
2045
|
+
) {
|
|
2046
|
+
return [...path, '$pluginMeta', pluginName, scope];
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function notifyPluginMetaDependency(
|
|
2050
|
+
stateKey: string,
|
|
2051
|
+
path: string[],
|
|
2052
|
+
pluginName: string,
|
|
2053
|
+
scope?: string
|
|
2054
|
+
) {
|
|
2055
|
+
const rootMeta = getShadowMetadata(stateKey, []);
|
|
2056
|
+
const dependencyMeta = getShadowMetadata(
|
|
2057
|
+
stateKey,
|
|
2058
|
+
pluginMetaDependencyPath(path, pluginName, scope)
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
dependencyMeta?.pathComponents?.forEach((componentId) => {
|
|
2062
|
+
rootMeta?.components?.get(componentId)?.forceUpdate();
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function createProxyHandler<
|
|
2274
2067
|
T,
|
|
2275
2068
|
const TPlugins extends readonly CogsPlugin<
|
|
2276
2069
|
any,
|
|
@@ -2395,9 +2188,19 @@ function createProxyHandler<
|
|
|
2395
2188
|
});
|
|
2396
2189
|
}
|
|
2397
2190
|
|
|
2191
|
+
const nextPath = [...path, prop];
|
|
2192
|
+
return rebuildStateShape({
|
|
2193
|
+
path: nextPath,
|
|
2194
|
+
componentId: componentId!,
|
|
2195
|
+
meta,
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
2199
|
+
const methodName = prop.slice(1);
|
|
2200
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
2398
2201
|
const registeredPlugins = pluginStore.getState().registeredPlugins;
|
|
2399
2202
|
for (const plugin of registeredPlugins) {
|
|
2400
|
-
const chainMethod = (plugin.chainMethods as any)?.[
|
|
2203
|
+
const chainMethod = (plugin.chainMethods as any)?.[methodName];
|
|
2401
2204
|
if (!chainMethod) continue;
|
|
2402
2205
|
if (!pathMatchesPattern(path, chainMethod.pathPattern)) continue;
|
|
2403
2206
|
if (!valueMatchesChainTarget(value, chainMethod.target)) continue;
|
|
@@ -2423,17 +2226,8 @@ function createProxyHandler<
|
|
|
2423
2226
|
});
|
|
2424
2227
|
|
|
2425
2228
|
return {
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
.getState()
|
|
2429
|
-
.getShadowMetadata(stateKey, path);
|
|
2430
|
-
|
|
2431
|
-
setShadowMetadata(stateKey, path, {
|
|
2432
|
-
...shadowMeta,
|
|
2433
|
-
isDirty: false,
|
|
2434
|
-
stateSource: 'server',
|
|
2435
|
-
lastServerSync: Date.now(),
|
|
2436
|
-
});
|
|
2229
|
+
syned: () => {
|
|
2230
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
2437
2231
|
},
|
|
2438
2232
|
};
|
|
2439
2233
|
},
|
|
@@ -2447,15 +2241,28 @@ function createProxyHandler<
|
|
|
2447
2241
|
metaData,
|
|
2448
2242
|
});
|
|
2449
2243
|
},
|
|
2450
|
-
getFieldMetaData: () =>
|
|
2451
|
-
getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
|
|
2452
|
-
setFieldMetaData: (data: Record<string, any>) =>
|
|
2453
|
-
setPluginMetaData(stateKey, path, plugin.name, data),
|
|
2454
|
-
removeFieldMetaData: () =>
|
|
2455
|
-
removePluginMetaData(stateKey, path, plugin.name),
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2244
|
+
getFieldMetaData: () =>
|
|
2245
|
+
getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
|
|
2246
|
+
setFieldMetaData: (data: Record<string, any>) =>
|
|
2247
|
+
setPluginMetaData(stateKey, path, plugin.name, data),
|
|
2248
|
+
removeFieldMetaData: () =>
|
|
2249
|
+
removePluginMetaData(stateKey, path, plugin.name),
|
|
2250
|
+
watchPluginMeta: (scope?: string) =>
|
|
2251
|
+
registerComponentDependency(
|
|
2252
|
+
stateKey,
|
|
2253
|
+
componentId,
|
|
2254
|
+
pluginMetaDependencyPath(path, plugin.name, scope)
|
|
2255
|
+
),
|
|
2256
|
+
notifyPluginMeta: (scope?: string) =>
|
|
2257
|
+
notifyPluginMetaDependency(
|
|
2258
|
+
stateKey,
|
|
2259
|
+
path,
|
|
2260
|
+
plugin.name,
|
|
2261
|
+
scope
|
|
2262
|
+
),
|
|
2263
|
+
getFieldRefs: () => getFieldRefsForPath(stateKey, path),
|
|
2264
|
+
getFieldElements: () =>
|
|
2265
|
+
getFieldElementsForPath(stateKey, path),
|
|
2459
2266
|
setFieldDisabled: (disabled: boolean) =>
|
|
2460
2267
|
setFieldDisabledForPath(stateKey, path, disabled),
|
|
2461
2268
|
},
|
|
@@ -2463,115 +2270,13 @@ function createProxyHandler<
|
|
|
2463
2270
|
);
|
|
2464
2271
|
};
|
|
2465
2272
|
}
|
|
2466
|
-
|
|
2467
|
-
const nextPath = [...path, prop];
|
|
2468
|
-
return rebuildStateShape({
|
|
2469
|
-
path: nextPath,
|
|
2470
|
-
componentId: componentId!,
|
|
2471
|
-
meta,
|
|
2472
|
-
});
|
|
2473
2273
|
}
|
|
2474
2274
|
if (prop === '$_rebuildStateShape') {
|
|
2475
2275
|
return rebuildStateShape;
|
|
2476
2276
|
}
|
|
2477
2277
|
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
const options = getGlobalStore
|
|
2481
|
-
.getState()
|
|
2482
|
-
.getInitialOptions(stateKey);
|
|
2483
|
-
const sync = options?.sync;
|
|
2484
|
-
|
|
2485
|
-
if (!sync) {
|
|
2486
|
-
console.error(`No mutation defined for state key "${stateKey}"`);
|
|
2487
|
-
return { success: false, error: `No mutation defined` };
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
const state = getGlobalStore
|
|
2491
|
-
.getState()
|
|
2492
|
-
.getShadowValue(stateKey, []);
|
|
2493
|
-
const validationKey = options?.validation?.key;
|
|
2494
|
-
|
|
2495
|
-
try {
|
|
2496
|
-
const response = await sync.action(state);
|
|
2497
|
-
if (
|
|
2498
|
-
response &&
|
|
2499
|
-
!response.success &&
|
|
2500
|
-
response.errors &&
|
|
2501
|
-
validationKey
|
|
2502
|
-
) {
|
|
2503
|
-
// getGlobalStore.getState().removeValidationError(validationKey);
|
|
2504
|
-
// response.errors.forEach((error) => {
|
|
2505
|
-
// const errorPath = [validationKey, ...error.path].join('.');
|
|
2506
|
-
// getGlobalStore
|
|
2507
|
-
// .getState()
|
|
2508
|
-
// .addValidationError(errorPath, error.message);
|
|
2509
|
-
// });
|
|
2510
|
-
// notifyComponents(stateKey);
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
if (response?.success) {
|
|
2514
|
-
// Mark as synced and not dirty
|
|
2515
|
-
const shadowMeta = getGlobalStore
|
|
2516
|
-
.getState()
|
|
2517
|
-
.getShadowMetadata(stateKey, []);
|
|
2518
|
-
setShadowMetadata(stateKey, [], {
|
|
2519
|
-
...shadowMeta,
|
|
2520
|
-
isDirty: false,
|
|
2521
|
-
lastServerSync: Date.now(),
|
|
2522
|
-
stateSource: 'server',
|
|
2523
|
-
baseServerState: state, // Update base server state
|
|
2524
|
-
});
|
|
2525
|
-
|
|
2526
|
-
if (sync.onSuccess) {
|
|
2527
|
-
sync.onSuccess(response.data);
|
|
2528
|
-
}
|
|
2529
|
-
} else if (!response?.success && sync.onError)
|
|
2530
|
-
sync.onError(response.error);
|
|
2531
|
-
|
|
2532
|
-
return response;
|
|
2533
|
-
} catch (error) {
|
|
2534
|
-
if (sync.onError) sync.onError(error);
|
|
2535
|
-
return { success: false, error };
|
|
2536
|
-
}
|
|
2537
|
-
};
|
|
2538
|
-
}
|
|
2539
|
-
// Fixed getStatus function in createProxyHandler
|
|
2540
|
-
if (prop === '$_status' || prop === '$getStatus') {
|
|
2541
|
-
const getStatusFunc = () => {
|
|
2542
|
-
// ✅ Use the optimized helper to get all data in one efficient call
|
|
2543
|
-
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
2544
|
-
|
|
2545
|
-
if (shadowMeta?.isDirty === true) {
|
|
2546
|
-
return 'dirty';
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
if (
|
|
2550
|
-
shadowMeta?.stateSource === 'server' ||
|
|
2551
|
-
shadowMeta?.isDirty === false
|
|
2552
|
-
) {
|
|
2553
|
-
return 'synced';
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
if (shadowMeta?.stateSource === 'localStorage') {
|
|
2557
|
-
return 'restored';
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
if (shadowMeta?.stateSource === 'default') {
|
|
2561
|
-
return 'fresh';
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
|
-
if (value !== undefined) {
|
|
2565
|
-
return 'fresh';
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
// Fallback if no other condition is met.
|
|
2569
|
-
return 'unknown';
|
|
2570
|
-
};
|
|
2571
|
-
|
|
2572
|
-
// This part remains the same
|
|
2573
|
-
return prop === '$_status' ? getStatusFunc() : getStatusFunc;
|
|
2574
|
-
}
|
|
2278
|
+
// Removed: $sync is now owned by Shape Plugin
|
|
2279
|
+
// Removed: $_status and $getStatus are now owned by Shape Plugin
|
|
2575
2280
|
if (prop === '$removeStorage') {
|
|
2576
2281
|
return () => {
|
|
2577
2282
|
const initialState =
|
|
@@ -2593,75 +2298,75 @@ function createProxyHandler<
|
|
|
2593
2298
|
setShadowMetadata(stateKey, path, { ...currentMeta, isRaw: true });
|
|
2594
2299
|
effectiveSetState(value, path, { updateType: 'update' });
|
|
2595
2300
|
};
|
|
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);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
if (prop === '$validate') {
|
|
2304
|
+
return (keys?: readonly string[]) => {
|
|
2305
|
+
const store = getGlobalStore.getState();
|
|
2306
|
+
|
|
2307
|
+
if (keys) {
|
|
2308
|
+
const results = keys.map((key) => {
|
|
2309
|
+
const targetPath = [...path, key];
|
|
2310
|
+
const targetValue = store.getShadowValue(stateKey, targetPath);
|
|
2311
|
+
const currentMeta =
|
|
2312
|
+
store.getShadowMetadata(stateKey, targetPath) || {};
|
|
2313
|
+
const fieldSchema = currentMeta.typeInfo?.schema;
|
|
2314
|
+
|
|
2315
|
+
if (!fieldSchema) {
|
|
2316
|
+
return {
|
|
2317
|
+
key,
|
|
2318
|
+
path: targetPath,
|
|
2319
|
+
success: true,
|
|
2320
|
+
data: targetValue,
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const result = (fieldSchema as any).safeParse(targetValue);
|
|
2325
|
+
const zodErrors =
|
|
2326
|
+
result.error?.issues || result.error?.errors || [];
|
|
2327
|
+
|
|
2328
|
+
store.setShadowMetadata(stateKey, targetPath, {
|
|
2329
|
+
...currentMeta,
|
|
2330
|
+
validation: {
|
|
2331
|
+
status: result.success ? 'VALID' : 'INVALID',
|
|
2332
|
+
errors: result.success
|
|
2333
|
+
? []
|
|
2334
|
+
: [
|
|
2335
|
+
{
|
|
2336
|
+
source: 'client',
|
|
2337
|
+
message:
|
|
2338
|
+
zodErrors[0]?.message || 'Invalid value',
|
|
2339
|
+
severity: 'error',
|
|
2340
|
+
code: zodErrors[0]?.code,
|
|
2341
|
+
},
|
|
2342
|
+
],
|
|
2343
|
+
lastValidated: Date.now(),
|
|
2344
|
+
validatedValue: targetValue,
|
|
2345
|
+
},
|
|
2346
|
+
});
|
|
2347
|
+
store.notifyPathSubscribers([stateKey, ...targetPath].join('.'), {
|
|
2348
|
+
type: 'VALIDATION_UPDATE',
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
return {
|
|
2352
|
+
key,
|
|
2353
|
+
path: targetPath,
|
|
2354
|
+
success: result.success,
|
|
2355
|
+
data: result.success ? result.data : undefined,
|
|
2356
|
+
error: result.success ? undefined : result.error,
|
|
2357
|
+
};
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
notifyComponents(stateKey);
|
|
2361
|
+
|
|
2362
|
+
return {
|
|
2363
|
+
success: results.every((result) => result.success),
|
|
2364
|
+
results,
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// 1. Get Data & Schema
|
|
2369
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
2665
2370
|
const opts = store.getInitialOptions(stateKey);
|
|
2666
2371
|
const schema =
|
|
2667
2372
|
opts?.validation?.zodSchemaV4 || opts?.validation?.zodSchemaV3;
|
|
@@ -2719,11 +2424,11 @@ function createProxyHandler<
|
|
|
2719
2424
|
return result;
|
|
2720
2425
|
};
|
|
2721
2426
|
}
|
|
2722
|
-
if (prop === '$showValidationErrors') {
|
|
2723
|
-
return () => {
|
|
2724
|
-
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2725
|
-
if (
|
|
2726
|
-
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2427
|
+
if (prop === '$showValidationErrors') {
|
|
2428
|
+
return () => {
|
|
2429
|
+
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2430
|
+
if (
|
|
2431
|
+
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2727
2432
|
shadowMeta.validation.errors.length > 0
|
|
2728
2433
|
) {
|
|
2729
2434
|
// Return only error-severity messages (not warnings)
|
|
@@ -2731,55 +2436,55 @@ function createProxyHandler<
|
|
|
2731
2436
|
.filter((err) => err.severity === 'error')
|
|
2732
2437
|
.map((err) => err.message);
|
|
2733
2438
|
}
|
|
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') {
|
|
2439
|
+
return [];
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
if (prop === '$validationErrors') {
|
|
2443
|
+
return (keys?: readonly string[]) => {
|
|
2444
|
+
const store = getGlobalStore.getState();
|
|
2445
|
+
const summarizePath = (targetPath: string[]) => {
|
|
2446
|
+
const validationState =
|
|
2447
|
+
store.getShadowMetadata(stateKey, targetPath)?.validation;
|
|
2448
|
+
const allErrors = (validationState?.errors || []).map((err) => ({
|
|
2449
|
+
...err,
|
|
2450
|
+
path: targetPath,
|
|
2451
|
+
}));
|
|
2452
|
+
const errors = allErrors.filter((err) => err.severity === 'error');
|
|
2453
|
+
const warnings = allErrors.filter(
|
|
2454
|
+
(err) => err.severity === 'warning'
|
|
2455
|
+
);
|
|
2456
|
+
const severity: ValidationSeverity =
|
|
2457
|
+
errors.length > 0
|
|
2458
|
+
? 'error'
|
|
2459
|
+
: warnings.length > 0
|
|
2460
|
+
? 'warning'
|
|
2461
|
+
: undefined;
|
|
2462
|
+
|
|
2463
|
+
return {
|
|
2464
|
+
status: validationState?.status || 'NOT_VALIDATED',
|
|
2465
|
+
severity,
|
|
2466
|
+
hasErrors: errors.length > 0,
|
|
2467
|
+
hasWarnings: warnings.length > 0,
|
|
2468
|
+
message: errors[0]?.message || warnings[0]?.message || '',
|
|
2469
|
+
errors: errors.map((err) => err.message),
|
|
2470
|
+
warnings: warnings.map((err) => err.message),
|
|
2471
|
+
allErrors,
|
|
2472
|
+
path: targetPath,
|
|
2473
|
+
getData: () => store.getShadowValue(stateKey, targetPath),
|
|
2474
|
+
} satisfies ValidationFieldSummary;
|
|
2475
|
+
};
|
|
2476
|
+
|
|
2477
|
+
const childKeys =
|
|
2478
|
+
keys ??
|
|
2479
|
+
Object.keys(store.getShadowNode(stateKey, path) ?? {}).filter(
|
|
2480
|
+
(key) => key !== '_meta'
|
|
2481
|
+
);
|
|
2482
|
+
|
|
2483
|
+
return childKeys.map((key) => summarizePath([...path, key]));
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
if (prop === '$getSelected') {
|
|
2783
2488
|
return () => {
|
|
2784
2489
|
const arrayKey = [stateKey, ...path].join('.');
|
|
2785
2490
|
registerComponentDependency(stateKey, componentId, [
|
|
@@ -3045,9 +2750,7 @@ function createProxyHandler<
|
|
|
3045
2750
|
e.type === 'INSERT' ||
|
|
3046
2751
|
e.type === 'INSERT_MANY' ||
|
|
3047
2752
|
e.type === 'REMOVE' ||
|
|
3048
|
-
e.type === 'CLEAR_SELECTION'
|
|
3049
|
-
(e.type === 'SERVER_STATE_UPDATE' &&
|
|
3050
|
-
!meta?.serverStateIsUpStream)
|
|
2753
|
+
e.type === 'CLEAR_SELECTION'
|
|
3051
2754
|
) {
|
|
3052
2755
|
forceUpdate({});
|
|
3053
2756
|
}
|
|
@@ -3384,10 +3087,6 @@ function createProxyHandler<
|
|
|
3384
3087
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3385
3088
|
}
|
|
3386
3089
|
|
|
3387
|
-
if (prop === '$lastSynced') {
|
|
3388
|
-
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3389
|
-
return getSyncInfo(syncKey);
|
|
3390
|
-
}
|
|
3391
3090
|
if (prop == 'getLocalStorage') {
|
|
3392
3091
|
return (key: string) =>
|
|
3393
3092
|
loadFromLocalStorage(sessionId + '-' + stateKey + '-' + key);
|
|
@@ -3659,8 +3358,6 @@ function createProxyHandler<
|
|
|
3659
3358
|
};
|
|
3660
3359
|
store.updateShadowAtPath(stateKey, relativePath, value);
|
|
3661
3360
|
|
|
3662
|
-
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3663
|
-
|
|
3664
3361
|
// Bubble up - notify components at this path and all parent paths
|
|
3665
3362
|
let currentPath = [...relativePath];
|
|
3666
3363
|
while (true) {
|
|
@@ -3690,7 +3387,6 @@ function createProxyHandler<
|
|
|
3690
3387
|
case 'remove': {
|
|
3691
3388
|
const parentPath = relativePath.slice(0, -1);
|
|
3692
3389
|
store.removeShadowArrayElement(stateKey, relativePath);
|
|
3693
|
-
store.markAsDirty(stateKey, parentPath, { bubble: true });
|
|
3694
3390
|
|
|
3695
3391
|
// Bubble up from parent path
|
|
3696
3392
|
let currentPath = [...parentPath];
|
|
@@ -3754,22 +3450,7 @@ function createProxyHandler<
|
|
|
3754
3450
|
|
|
3755
3451
|
return {
|
|
3756
3452
|
synced: () => {
|
|
3757
|
-
|
|
3758
|
-
.getState()
|
|
3759
|
-
.getShadowMetadata(stateKey, path);
|
|
3760
|
-
|
|
3761
|
-
setShadowMetadata(stateKey, path, {
|
|
3762
|
-
...shadowMeta,
|
|
3763
|
-
isDirty: false,
|
|
3764
|
-
stateSource: 'server',
|
|
3765
|
-
lastServerSync: Date.now(),
|
|
3766
|
-
});
|
|
3767
|
-
|
|
3768
|
-
const fullPath = [stateKey, ...path].join('.');
|
|
3769
|
-
notifyPathSubscribers(fullPath, {
|
|
3770
|
-
type: 'SYNC_STATUS_CHANGE',
|
|
3771
|
-
isDirty: false,
|
|
3772
|
-
});
|
|
3453
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
3773
3454
|
},
|
|
3774
3455
|
};
|
|
3775
3456
|
};
|
|
@@ -3852,16 +3533,8 @@ function createProxyHandler<
|
|
|
3852
3533
|
// ... (rest of the function: rootLevelMethods, returnShape, etc.)
|
|
3853
3534
|
const rootLevelMethods = {
|
|
3854
3535
|
$revertToInitialState: (obj?: { validationKey?: string }) => {
|
|
3855
|
-
const
|
|
3856
|
-
.getState()
|
|
3857
|
-
.getShadowMetadata(stateKey, []);
|
|
3858
|
-
let revertState;
|
|
3859
|
-
|
|
3860
|
-
if (shadowMeta?.stateSource === 'server' && shadowMeta.baseServerState) {
|
|
3861
|
-
revertState = shadowMeta.baseServerState;
|
|
3862
|
-
} else {
|
|
3863
|
-
revertState = getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3864
|
-
}
|
|
3536
|
+
const revertState =
|
|
3537
|
+
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3865
3538
|
|
|
3866
3539
|
clearSelectedIndexesForState(stateKey);
|
|
3867
3540
|
initializeShadowState(stateKey, revertState);
|