cogsbox-state 0.5.489 → 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/dist/CogsState.d.ts +1 -49
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.js +931 -1049
- package/dist/CogsState.js.map +1 -1
- package/dist/plugins.d.ts +2 -0
- 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 +309 -639
- package/src/plugins.ts +8 -6
- 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], TPlugins>;
|
|
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>, TPlugins> => {
|
|
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,8 +1046,8 @@ export const createCogsState = <
|
|
|
1086
1046
|
}
|
|
1087
1047
|
});
|
|
1088
1048
|
|
|
1089
|
-
return updater as StateObject<StateSlice<StateKey>, TPlugins>;
|
|
1090
|
-
};
|
|
1049
|
+
return updater as StateObject<StateSlice<StateKey>, TPlugins>;
|
|
1050
|
+
};
|
|
1091
1051
|
|
|
1092
1052
|
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
1093
1053
|
stateKey: StateKey,
|
|
@@ -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
|
-
//
|
|
1726
|
+
// Removed: Server state effects are now owned by Shape Plugin
|
|
1843
1727
|
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
|
|
1852
|
-
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,186 +2188,95 @@ function createProxyHandler<
|
|
|
2395
2188
|
});
|
|
2396
2189
|
}
|
|
2397
2190
|
|
|
2398
|
-
const nextPath = [...path, prop];
|
|
2399
|
-
return rebuildStateShape({
|
|
2400
|
-
path: nextPath,
|
|
2401
|
-
componentId: componentId!,
|
|
2402
|
-
meta,
|
|
2403
|
-
});
|
|
2404
|
-
}
|
|
2405
|
-
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
2406
|
-
const methodName = prop.slice(1);
|
|
2407
|
-
const { value } = getScopedData(stateKey, path, meta);
|
|
2408
|
-
const registeredPlugins = pluginStore.getState().registeredPlugins;
|
|
2409
|
-
for (const plugin of registeredPlugins) {
|
|
2410
|
-
const chainMethod = (plugin.chainMethods as any)?.[methodName];
|
|
2411
|
-
if (!chainMethod) continue;
|
|
2412
|
-
if (!pathMatchesPattern(path, chainMethod.pathPattern)) continue;
|
|
2413
|
-
if (!valueMatchesChainTarget(value, chainMethod.target)) continue;
|
|
2414
|
-
|
|
2415
|
-
return (...args: any[]) => {
|
|
2416
|
-
const store = pluginStore.getState();
|
|
2417
|
-
const options = store.pluginOptions
|
|
2418
|
-
.get(stateKey)
|
|
2419
|
-
?.get(plugin.name);
|
|
2420
|
-
const hookData = store.getHookResult(stateKey, plugin.name);
|
|
2421
|
-
|
|
2422
|
-
return chainMethod.handler(
|
|
2423
|
-
{
|
|
2424
|
-
stateKey,
|
|
2425
|
-
path,
|
|
2426
|
-
pluginName: plugin.name,
|
|
2427
|
-
options,
|
|
2428
|
-
hookData,
|
|
2429
|
-
$get: () => getScopedData(stateKey, path, meta).value,
|
|
2430
|
-
$update: (payload: any) => {
|
|
2431
|
-
effectiveSetState(payload, path, {
|
|
2432
|
-
updateType: 'update',
|
|
2433
|
-
});
|
|
2434
|
-
|
|
2435
|
-
return {
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
operation: UpdateTypeDetail,
|
|
2452
|
-
metaData?: Record<string, any>
|
|
2453
|
-
) => {
|
|
2454
|
-
effectiveSetState(operation.newValue, operation.path, {
|
|
2455
|
-
updateType: operation.updateType,
|
|
2456
|
-
itemId: operation.itemId,
|
|
2457
|
-
metaData,
|
|
2458
|
-
});
|
|
2459
|
-
},
|
|
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);
|
|
2201
|
+
const registeredPlugins = pluginStore.getState().registeredPlugins;
|
|
2202
|
+
for (const plugin of registeredPlugins) {
|
|
2203
|
+
const chainMethod = (plugin.chainMethods as any)?.[methodName];
|
|
2204
|
+
if (!chainMethod) continue;
|
|
2205
|
+
if (!pathMatchesPattern(path, chainMethod.pathPattern)) continue;
|
|
2206
|
+
if (!valueMatchesChainTarget(value, chainMethod.target)) continue;
|
|
2207
|
+
|
|
2208
|
+
return (...args: any[]) => {
|
|
2209
|
+
const store = pluginStore.getState();
|
|
2210
|
+
const options = store.pluginOptions
|
|
2211
|
+
.get(stateKey)
|
|
2212
|
+
?.get(plugin.name);
|
|
2213
|
+
const hookData = store.getHookResult(stateKey, plugin.name);
|
|
2214
|
+
|
|
2215
|
+
return chainMethod.handler(
|
|
2216
|
+
{
|
|
2217
|
+
stateKey,
|
|
2218
|
+
path,
|
|
2219
|
+
pluginName: plugin.name,
|
|
2220
|
+
options,
|
|
2221
|
+
hookData,
|
|
2222
|
+
$get: () => getScopedData(stateKey, path, meta).value,
|
|
2223
|
+
$update: (payload: any) => {
|
|
2224
|
+
effectiveSetState(payload, path, {
|
|
2225
|
+
updateType: 'update',
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
return {
|
|
2229
|
+
syned: () => {
|
|
2230
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
2231
|
+
},
|
|
2232
|
+
};
|
|
2233
|
+
},
|
|
2234
|
+
$applyOperation: (
|
|
2235
|
+
operation: UpdateTypeDetail,
|
|
2236
|
+
metaData?: Record<string, any>
|
|
2237
|
+
) => {
|
|
2238
|
+
effectiveSetState(operation.newValue, operation.path, {
|
|
2239
|
+
updateType: operation.updateType,
|
|
2240
|
+
itemId: operation.itemId,
|
|
2241
|
+
metaData,
|
|
2242
|
+
});
|
|
2243
|
+
},
|
|
2460
2244
|
getFieldMetaData: () =>
|
|
2461
2245
|
getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
|
|
2462
2246
|
setFieldMetaData: (data: Record<string, any>) =>
|
|
2463
2247
|
setPluginMetaData(stateKey, path, plugin.name, data),
|
|
2464
2248
|
removeFieldMetaData: () =>
|
|
2465
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
|
+
),
|
|
2466
2263
|
getFieldRefs: () => getFieldRefsForPath(stateKey, path),
|
|
2467
2264
|
getFieldElements: () =>
|
|
2468
2265
|
getFieldElementsForPath(stateKey, path),
|
|
2469
|
-
setFieldDisabled: (disabled: boolean) =>
|
|
2470
|
-
setFieldDisabledForPath(stateKey, path, disabled),
|
|
2471
|
-
},
|
|
2472
|
-
...args
|
|
2473
|
-
);
|
|
2474
|
-
};
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
if (prop === '$_rebuildStateShape') {
|
|
2478
|
-
return rebuildStateShape;
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
if (prop === '$sync' && path.length === 0) {
|
|
2482
|
-
return async function () {
|
|
2483
|
-
const options = getGlobalStore
|
|
2484
|
-
.getState()
|
|
2485
|
-
.getInitialOptions(stateKey);
|
|
2486
|
-
const sync = options?.sync;
|
|
2487
|
-
|
|
2488
|
-
if (!sync) {
|
|
2489
|
-
console.error(`No mutation defined for state key "${stateKey}"`);
|
|
2490
|
-
return { success: false, error: `No mutation defined` };
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
const state = getGlobalStore
|
|
2494
|
-
.getState()
|
|
2495
|
-
.getShadowValue(stateKey, []);
|
|
2496
|
-
const validationKey = options?.validation?.key;
|
|
2497
|
-
|
|
2498
|
-
try {
|
|
2499
|
-
const response = await sync.action(state);
|
|
2500
|
-
if (
|
|
2501
|
-
response &&
|
|
2502
|
-
!response.success &&
|
|
2503
|
-
response.errors &&
|
|
2504
|
-
validationKey
|
|
2505
|
-
) {
|
|
2506
|
-
// getGlobalStore.getState().removeValidationError(validationKey);
|
|
2507
|
-
// response.errors.forEach((error) => {
|
|
2508
|
-
// const errorPath = [validationKey, ...error.path].join('.');
|
|
2509
|
-
// getGlobalStore
|
|
2510
|
-
// .getState()
|
|
2511
|
-
// .addValidationError(errorPath, error.message);
|
|
2512
|
-
// });
|
|
2513
|
-
// notifyComponents(stateKey);
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
if (response?.success) {
|
|
2517
|
-
// Mark as synced and not dirty
|
|
2518
|
-
const shadowMeta = getGlobalStore
|
|
2519
|
-
.getState()
|
|
2520
|
-
.getShadowMetadata(stateKey, []);
|
|
2521
|
-
setShadowMetadata(stateKey, [], {
|
|
2522
|
-
...shadowMeta,
|
|
2523
|
-
isDirty: false,
|
|
2524
|
-
lastServerSync: Date.now(),
|
|
2525
|
-
stateSource: 'server',
|
|
2526
|
-
baseServerState: state, // Update base server state
|
|
2527
|
-
});
|
|
2528
|
-
|
|
2529
|
-
if (sync.onSuccess) {
|
|
2530
|
-
sync.onSuccess(response.data);
|
|
2531
|
-
}
|
|
2532
|
-
} else if (!response?.success && sync.onError)
|
|
2533
|
-
sync.onError(response.error);
|
|
2534
|
-
|
|
2535
|
-
return response;
|
|
2536
|
-
} catch (error) {
|
|
2537
|
-
if (sync.onError) sync.onError(error);
|
|
2538
|
-
return { success: false, error };
|
|
2539
|
-
}
|
|
2540
|
-
};
|
|
2266
|
+
setFieldDisabled: (disabled: boolean) =>
|
|
2267
|
+
setFieldDisabledForPath(stateKey, path, disabled),
|
|
2268
|
+
},
|
|
2269
|
+
...args
|
|
2270
|
+
);
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2541
2273
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
const getStatusFunc = () => {
|
|
2545
|
-
// ✅ Use the optimized helper to get all data in one efficient call
|
|
2546
|
-
const { shadowMeta, value } = getScopedData(stateKey, path, meta);
|
|
2547
|
-
|
|
2548
|
-
if (shadowMeta?.isDirty === true) {
|
|
2549
|
-
return 'dirty';
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
if (
|
|
2553
|
-
shadowMeta?.stateSource === 'server' ||
|
|
2554
|
-
shadowMeta?.isDirty === false
|
|
2555
|
-
) {
|
|
2556
|
-
return 'synced';
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
if (shadowMeta?.stateSource === 'localStorage') {
|
|
2560
|
-
return 'restored';
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
if (shadowMeta?.stateSource === 'default') {
|
|
2564
|
-
return 'fresh';
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
if (value !== undefined) {
|
|
2568
|
-
return 'fresh';
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
// Fallback if no other condition is met.
|
|
2572
|
-
return 'unknown';
|
|
2573
|
-
};
|
|
2574
|
-
|
|
2575
|
-
// This part remains the same
|
|
2576
|
-
return prop === '$_status' ? getStatusFunc() : getStatusFunc;
|
|
2274
|
+
if (prop === '$_rebuildStateShape') {
|
|
2275
|
+
return rebuildStateShape;
|
|
2577
2276
|
}
|
|
2277
|
+
|
|
2278
|
+
// Removed: $sync is now owned by Shape Plugin
|
|
2279
|
+
// Removed: $_status and $getStatus are now owned by Shape Plugin
|
|
2578
2280
|
if (prop === '$removeStorage') {
|
|
2579
2281
|
return () => {
|
|
2580
2282
|
const initialState =
|
|
@@ -2596,75 +2298,75 @@ function createProxyHandler<
|
|
|
2596
2298
|
setShadowMetadata(stateKey, path, { ...currentMeta, isRaw: true });
|
|
2597
2299
|
effectiveSetState(value, path, { updateType: 'update' });
|
|
2598
2300
|
};
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
if (prop === '$validate') {
|
|
2602
|
-
return (keys?: readonly string[]) => {
|
|
2603
|
-
const store = getGlobalStore.getState();
|
|
2604
|
-
|
|
2605
|
-
if (keys) {
|
|
2606
|
-
const results = keys.map((key) => {
|
|
2607
|
-
const targetPath = [...path, key];
|
|
2608
|
-
const targetValue = store.getShadowValue(stateKey, targetPath);
|
|
2609
|
-
const currentMeta =
|
|
2610
|
-
store.getShadowMetadata(stateKey, targetPath) || {};
|
|
2611
|
-
const fieldSchema = currentMeta.typeInfo?.schema;
|
|
2612
|
-
|
|
2613
|
-
if (!fieldSchema) {
|
|
2614
|
-
return {
|
|
2615
|
-
key,
|
|
2616
|
-
path: targetPath,
|
|
2617
|
-
success: true,
|
|
2618
|
-
data: targetValue,
|
|
2619
|
-
};
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
const result = (fieldSchema as any).safeParse(targetValue);
|
|
2623
|
-
const zodErrors =
|
|
2624
|
-
result.error?.issues || result.error?.errors || [];
|
|
2625
|
-
|
|
2626
|
-
store.setShadowMetadata(stateKey, targetPath, {
|
|
2627
|
-
...currentMeta,
|
|
2628
|
-
validation: {
|
|
2629
|
-
status: result.success ? 'VALID' : 'INVALID',
|
|
2630
|
-
errors: result.success
|
|
2631
|
-
? []
|
|
2632
|
-
: [
|
|
2633
|
-
{
|
|
2634
|
-
source: 'client',
|
|
2635
|
-
message:
|
|
2636
|
-
zodErrors[0]?.message || 'Invalid value',
|
|
2637
|
-
severity: 'error',
|
|
2638
|
-
code: zodErrors[0]?.code,
|
|
2639
|
-
},
|
|
2640
|
-
],
|
|
2641
|
-
lastValidated: Date.now(),
|
|
2642
|
-
validatedValue: targetValue,
|
|
2643
|
-
},
|
|
2644
|
-
});
|
|
2645
|
-
store.notifyPathSubscribers([stateKey, ...targetPath].join('.'), {
|
|
2646
|
-
type: 'VALIDATION_UPDATE',
|
|
2647
|
-
});
|
|
2648
|
-
|
|
2649
|
-
return {
|
|
2650
|
-
key,
|
|
2651
|
-
path: targetPath,
|
|
2652
|
-
success: result.success,
|
|
2653
|
-
data: result.success ? result.data : undefined,
|
|
2654
|
-
error: result.success ? undefined : result.error,
|
|
2655
|
-
};
|
|
2656
|
-
});
|
|
2657
|
-
|
|
2658
|
-
notifyComponents(stateKey);
|
|
2659
|
-
|
|
2660
|
-
return {
|
|
2661
|
-
success: results.every((result) => result.success),
|
|
2662
|
-
results,
|
|
2663
|
-
};
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
// 1. Get Data & Schema
|
|
2667
|
-
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);
|
|
2668
2370
|
const opts = store.getInitialOptions(stateKey);
|
|
2669
2371
|
const schema =
|
|
2670
2372
|
opts?.validation?.zodSchemaV4 || opts?.validation?.zodSchemaV3;
|
|
@@ -2722,11 +2424,11 @@ function createProxyHandler<
|
|
|
2722
2424
|
return result;
|
|
2723
2425
|
};
|
|
2724
2426
|
}
|
|
2725
|
-
if (prop === '$showValidationErrors') {
|
|
2726
|
-
return () => {
|
|
2727
|
-
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2728
|
-
if (
|
|
2729
|
-
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2427
|
+
if (prop === '$showValidationErrors') {
|
|
2428
|
+
return () => {
|
|
2429
|
+
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2430
|
+
if (
|
|
2431
|
+
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2730
2432
|
shadowMeta.validation.errors.length > 0
|
|
2731
2433
|
) {
|
|
2732
2434
|
// Return only error-severity messages (not warnings)
|
|
@@ -2734,55 +2436,55 @@ function createProxyHandler<
|
|
|
2734
2436
|
.filter((err) => err.severity === 'error')
|
|
2735
2437
|
.map((err) => err.message);
|
|
2736
2438
|
}
|
|
2737
|
-
return [];
|
|
2738
|
-
};
|
|
2739
|
-
}
|
|
2740
|
-
if (prop === '$validationErrors') {
|
|
2741
|
-
return (keys?: readonly string[]) => {
|
|
2742
|
-
const store = getGlobalStore.getState();
|
|
2743
|
-
const summarizePath = (targetPath: string[]) => {
|
|
2744
|
-
const validationState =
|
|
2745
|
-
store.getShadowMetadata(stateKey, targetPath)?.validation;
|
|
2746
|
-
const allErrors = (validationState?.errors || []).map((err) => ({
|
|
2747
|
-
...err,
|
|
2748
|
-
path: targetPath,
|
|
2749
|
-
}));
|
|
2750
|
-
const errors = allErrors.filter((err) => err.severity === 'error');
|
|
2751
|
-
const warnings = allErrors.filter(
|
|
2752
|
-
(err) => err.severity === 'warning'
|
|
2753
|
-
);
|
|
2754
|
-
const severity: ValidationSeverity =
|
|
2755
|
-
errors.length > 0
|
|
2756
|
-
? 'error'
|
|
2757
|
-
: warnings.length > 0
|
|
2758
|
-
? 'warning'
|
|
2759
|
-
: undefined;
|
|
2760
|
-
|
|
2761
|
-
return {
|
|
2762
|
-
status: validationState?.status || 'NOT_VALIDATED',
|
|
2763
|
-
severity,
|
|
2764
|
-
hasErrors: errors.length > 0,
|
|
2765
|
-
hasWarnings: warnings.length > 0,
|
|
2766
|
-
message: errors[0]?.message || warnings[0]?.message || '',
|
|
2767
|
-
errors: errors.map((err) => err.message),
|
|
2768
|
-
warnings: warnings.map((err) => err.message),
|
|
2769
|
-
allErrors,
|
|
2770
|
-
path: targetPath,
|
|
2771
|
-
getData: () => store.getShadowValue(stateKey, targetPath),
|
|
2772
|
-
} satisfies ValidationFieldSummary;
|
|
2773
|
-
};
|
|
2774
|
-
|
|
2775
|
-
const childKeys =
|
|
2776
|
-
keys ??
|
|
2777
|
-
Object.keys(store.getShadowNode(stateKey, path) ?? {}).filter(
|
|
2778
|
-
(key) => key !== '_meta'
|
|
2779
|
-
);
|
|
2780
|
-
|
|
2781
|
-
return childKeys.map((key) => summarizePath([...path, key]));
|
|
2782
|
-
};
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
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') {
|
|
2786
2488
|
return () => {
|
|
2787
2489
|
const arrayKey = [stateKey, ...path].join('.');
|
|
2788
2490
|
registerComponentDependency(stateKey, componentId, [
|
|
@@ -3048,9 +2750,7 @@ function createProxyHandler<
|
|
|
3048
2750
|
e.type === 'INSERT' ||
|
|
3049
2751
|
e.type === 'INSERT_MANY' ||
|
|
3050
2752
|
e.type === 'REMOVE' ||
|
|
3051
|
-
e.type === 'CLEAR_SELECTION'
|
|
3052
|
-
(e.type === 'SERVER_STATE_UPDATE' &&
|
|
3053
|
-
!meta?.serverStateIsUpStream)
|
|
2753
|
+
e.type === 'CLEAR_SELECTION'
|
|
3054
2754
|
) {
|
|
3055
2755
|
forceUpdate({});
|
|
3056
2756
|
}
|
|
@@ -3387,10 +3087,6 @@ function createProxyHandler<
|
|
|
3387
3087
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3388
3088
|
}
|
|
3389
3089
|
|
|
3390
|
-
if (prop === '$lastSynced') {
|
|
3391
|
-
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3392
|
-
return getSyncInfo(syncKey);
|
|
3393
|
-
}
|
|
3394
3090
|
if (prop == 'getLocalStorage') {
|
|
3395
3091
|
return (key: string) =>
|
|
3396
3092
|
loadFromLocalStorage(sessionId + '-' + stateKey + '-' + key);
|
|
@@ -3662,8 +3358,6 @@ function createProxyHandler<
|
|
|
3662
3358
|
};
|
|
3663
3359
|
store.updateShadowAtPath(stateKey, relativePath, value);
|
|
3664
3360
|
|
|
3665
|
-
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3666
|
-
|
|
3667
3361
|
// Bubble up - notify components at this path and all parent paths
|
|
3668
3362
|
let currentPath = [...relativePath];
|
|
3669
3363
|
while (true) {
|
|
@@ -3693,7 +3387,6 @@ function createProxyHandler<
|
|
|
3693
3387
|
case 'remove': {
|
|
3694
3388
|
const parentPath = relativePath.slice(0, -1);
|
|
3695
3389
|
store.removeShadowArrayElement(stateKey, relativePath);
|
|
3696
|
-
store.markAsDirty(stateKey, parentPath, { bubble: true });
|
|
3697
3390
|
|
|
3698
3391
|
// Bubble up from parent path
|
|
3699
3392
|
let currentPath = [...parentPath];
|
|
@@ -3757,22 +3450,7 @@ function createProxyHandler<
|
|
|
3757
3450
|
|
|
3758
3451
|
return {
|
|
3759
3452
|
synced: () => {
|
|
3760
|
-
|
|
3761
|
-
.getState()
|
|
3762
|
-
.getShadowMetadata(stateKey, path);
|
|
3763
|
-
|
|
3764
|
-
setShadowMetadata(stateKey, path, {
|
|
3765
|
-
...shadowMeta,
|
|
3766
|
-
isDirty: false,
|
|
3767
|
-
stateSource: 'server',
|
|
3768
|
-
lastServerSync: Date.now(),
|
|
3769
|
-
});
|
|
3770
|
-
|
|
3771
|
-
const fullPath = [stateKey, ...path].join('.');
|
|
3772
|
-
notifyPathSubscribers(fullPath, {
|
|
3773
|
-
type: 'SYNC_STATUS_CHANGE',
|
|
3774
|
-
isDirty: false,
|
|
3775
|
-
});
|
|
3453
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
3776
3454
|
},
|
|
3777
3455
|
};
|
|
3778
3456
|
};
|
|
@@ -3855,16 +3533,8 @@ function createProxyHandler<
|
|
|
3855
3533
|
// ... (rest of the function: rootLevelMethods, returnShape, etc.)
|
|
3856
3534
|
const rootLevelMethods = {
|
|
3857
3535
|
$revertToInitialState: (obj?: { validationKey?: string }) => {
|
|
3858
|
-
const
|
|
3859
|
-
.getState()
|
|
3860
|
-
.getShadowMetadata(stateKey, []);
|
|
3861
|
-
let revertState;
|
|
3862
|
-
|
|
3863
|
-
if (shadowMeta?.stateSource === 'server' && shadowMeta.baseServerState) {
|
|
3864
|
-
revertState = shadowMeta.baseServerState;
|
|
3865
|
-
} else {
|
|
3866
|
-
revertState = getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3867
|
-
}
|
|
3536
|
+
const revertState =
|
|
3537
|
+
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3868
3538
|
|
|
3869
3539
|
clearSelectedIndexesForState(stateKey);
|
|
3870
3540
|
initializeShadowState(stateKey, revertState);
|