cogsbox-state 0.5.489 → 0.5.491
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 +3 -50
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.js +931 -1049
- package/dist/CogsState.js.map +1 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.js +85 -87
- package/dist/Components.js.map +1 -1
- package/dist/plugins.d.ts +4 -1
- 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 +321 -645
- package/src/Components.tsx +45 -46
- package/src/plugins.ts +21 -15
- package/src/store.ts +2 -96
package/src/CogsState.tsx
CHANGED
|
@@ -57,32 +57,30 @@ import { runValidation } from './validation';
|
|
|
57
57
|
import { ZodType } from 'zod/v4';
|
|
58
58
|
|
|
59
59
|
export type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
|
|
60
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
61
|
+
|
|
62
|
+
// Removed: SyncInfo is now owned by Shape Plugin
|
|
63
|
+
|
|
64
|
+
export type ValidationFieldSummary = {
|
|
65
|
+
status: ValidationStatus;
|
|
66
|
+
severity: ValidationSeverity;
|
|
67
|
+
hasErrors: boolean;
|
|
68
|
+
hasWarnings: boolean;
|
|
69
|
+
message: string;
|
|
70
|
+
errors: string[];
|
|
71
|
+
warnings: string[];
|
|
72
|
+
allErrors: Array<ValidationError & { path: string[] }>;
|
|
73
|
+
path: string[];
|
|
74
|
+
getData: () => unknown;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type ValidationSummaryArray = ValidationFieldSummary[];
|
|
60
78
|
|
|
61
|
-
export type
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export type ValidationFieldSummary = {
|
|
67
|
-
status: ValidationStatus;
|
|
68
|
-
severity: ValidationSeverity;
|
|
69
|
-
hasErrors: boolean;
|
|
70
|
-
hasWarnings: boolean;
|
|
71
|
-
message: string;
|
|
72
|
-
errors: string[];
|
|
73
|
-
warnings: string[];
|
|
74
|
-
allErrors: Array<ValidationError & { path: string[] }>;
|
|
75
|
-
path: string[];
|
|
76
|
-
getData: () => unknown;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
type ValidationSummaryArray = ValidationFieldSummary[];
|
|
80
|
-
|
|
81
|
-
export type FormElementParams<T> = StateObject<T> & {
|
|
82
|
-
$inputProps: {
|
|
83
|
-
ref?: RefObject<any>;
|
|
84
|
-
value?: T extends boolean ? never : T;
|
|
85
|
-
onChange?: (
|
|
79
|
+
export type FormElementParams<T> = StateObject<T> & {
|
|
80
|
+
$inputProps: {
|
|
81
|
+
ref?: RefObject<any>;
|
|
82
|
+
value?: T extends boolean ? never : T;
|
|
83
|
+
onChange?: (
|
|
86
84
|
event: ChangeEvent<
|
|
87
85
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
88
86
|
>
|
|
@@ -90,17 +88,17 @@ export type FormElementParams<T> = StateObject<T> & {
|
|
|
90
88
|
onBlur?: (
|
|
91
89
|
event: FocusEvent<
|
|
92
90
|
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
|
-
};
|
|
91
|
+
>
|
|
92
|
+
) => void;
|
|
93
|
+
};
|
|
94
|
+
status: ValidationStatus;
|
|
95
|
+
severity: ValidationSeverity;
|
|
96
|
+
hasErrors: boolean;
|
|
97
|
+
hasWarnings: boolean;
|
|
98
|
+
allErrors: ValidationError[];
|
|
99
|
+
message: string;
|
|
100
|
+
getData: () => T;
|
|
101
|
+
};
|
|
104
102
|
|
|
105
103
|
export type StateKeys = string;
|
|
106
104
|
|
|
@@ -206,16 +204,15 @@ export type EndType<
|
|
|
206
204
|
$get: () => T;
|
|
207
205
|
$$get: () => T;
|
|
208
206
|
$$derive: <R>(fn: EffectFunction<T, R>) => R;
|
|
209
|
-
$_status
|
|
210
|
-
$
|
|
211
|
-
$
|
|
212
|
-
|
|
213
|
-
(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
$setValidation: (ctx: string) => void;
|
|
207
|
+
// Removed: $_status and $getStatus are now owned by Shape Plugin
|
|
208
|
+
$showValidationErrors: () => string[];
|
|
209
|
+
$validationErrors: {
|
|
210
|
+
(): ValidationSummaryArray;
|
|
211
|
+
<K extends Extract<keyof NonNullable<T>, string>>(
|
|
212
|
+
keys: readonly K[]
|
|
213
|
+
): ValidationSummaryArray;
|
|
214
|
+
};
|
|
215
|
+
$setValidation: (ctx: string) => void;
|
|
219
216
|
$removeValidation: (ctx: string) => void;
|
|
220
217
|
$isSelected: boolean;
|
|
221
218
|
$setSelected: (value: boolean) => void;
|
|
@@ -230,7 +227,7 @@ export type EndType<
|
|
|
230
227
|
};
|
|
231
228
|
$removeStorage: () => void;
|
|
232
229
|
$setRaw: (value: T) => void;
|
|
233
|
-
$sync
|
|
230
|
+
// Removed: $sync is now owned by Shape Plugin
|
|
234
231
|
$validationWrapper: ({
|
|
235
232
|
children,
|
|
236
233
|
hideMessage,
|
|
@@ -238,7 +235,7 @@ export type EndType<
|
|
|
238
235
|
children: ReactNode;
|
|
239
236
|
hideMessage?: boolean;
|
|
240
237
|
}) => JSX.Element;
|
|
241
|
-
$lastSynced
|
|
238
|
+
// Removed: $lastSynced is now owned by Shape Plugin
|
|
242
239
|
};
|
|
243
240
|
|
|
244
241
|
// Helper type for element returned from array methods
|
|
@@ -374,30 +371,35 @@ export type StateObject<
|
|
|
374
371
|
> = {
|
|
375
372
|
(): T;
|
|
376
373
|
(newValue: T | ((prev: T) => T)): void;
|
|
377
|
-
} & (
|
|
378
|
-
?
|
|
379
|
-
: [NonNullable<T>] extends [
|
|
380
|
-
?
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
} & (IsAny<T> extends true
|
|
375
|
+
? {}
|
|
376
|
+
: [NonNullable<T>] extends [any[]] // <-- Wrap in brackets
|
|
377
|
+
? ArrayEndType<T, TPlugins>
|
|
378
|
+
: [NonNullable<T>] extends [Record<string, unknown> | object] // <-- Wrap in brackets
|
|
379
|
+
? {
|
|
380
|
+
[K in keyof NonNullable<T>]-?: StateObject<
|
|
381
|
+
NonNullable<T>[K],
|
|
382
|
+
TPlugins
|
|
383
|
+
>;
|
|
384
|
+
}
|
|
385
|
+
: {}) & // Fallback to {} since we intersect EndType below anyway
|
|
384
386
|
EndType<T, TPlugins> & {
|
|
385
387
|
$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
|
-
};
|
|
388
|
+
$validate: {
|
|
389
|
+
(): { success: boolean; data?: T; error?: any };
|
|
390
|
+
<K extends Extract<keyof NonNullable<T>, string>>(
|
|
391
|
+
keys: readonly K[]
|
|
392
|
+
): {
|
|
393
|
+
success: boolean;
|
|
394
|
+
results: Array<{
|
|
395
|
+
key: K;
|
|
396
|
+
path: string[];
|
|
397
|
+
success: boolean;
|
|
398
|
+
data?: unknown;
|
|
399
|
+
error?: any;
|
|
400
|
+
}>;
|
|
401
|
+
};
|
|
402
|
+
};
|
|
401
403
|
$_componentId: string | null;
|
|
402
404
|
$getComponents: () => ComponentsType;
|
|
403
405
|
$_initialState: T;
|
|
@@ -405,8 +407,6 @@ export type StateObject<
|
|
|
405
407
|
fetchId: (field: keyof NonNullable<T>) => string | number;
|
|
406
408
|
};
|
|
407
409
|
$initializeAndMergeShadowState: (newState: any | null) => void;
|
|
408
|
-
$_isLoading: boolean;
|
|
409
|
-
$_serverState: T;
|
|
410
410
|
$revertToInitialState: (obj?: { validationKey?: string }) => T;
|
|
411
411
|
$middleware: (
|
|
412
412
|
middles: ({
|
|
@@ -526,34 +526,6 @@ export type OptionsType<
|
|
|
526
526
|
> = CreateStateOptionsType & {
|
|
527
527
|
log?: boolean;
|
|
528
528
|
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
529
|
middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
|
|
558
530
|
|
|
559
531
|
localStorage?: {
|
|
@@ -563,7 +535,6 @@ export type OptionsType<
|
|
|
563
535
|
|
|
564
536
|
reactiveDeps?: (state: T) => any[] | true;
|
|
565
537
|
reactiveType?: ReactivityType;
|
|
566
|
-
syncUpdate?: Partial<UpdateTypeDetail>;
|
|
567
538
|
|
|
568
539
|
defaultState?: T;
|
|
569
540
|
|
|
@@ -632,13 +603,10 @@ const {
|
|
|
632
603
|
insertManyShadowArrayElements,
|
|
633
604
|
removeShadowArrayElement,
|
|
634
605
|
setInitialStateOptions,
|
|
635
|
-
setServerStateUpdate,
|
|
636
|
-
markAsDirty,
|
|
637
606
|
addPathComponent,
|
|
638
607
|
clearSelectedIndexesForState,
|
|
639
608
|
addStateLog,
|
|
640
609
|
clearSelectedIndex,
|
|
641
|
-
getSyncInfo,
|
|
642
610
|
notifyPathSubscribers,
|
|
643
611
|
getPluginMetaDataMap,
|
|
644
612
|
setPluginMetaData,
|
|
@@ -914,7 +882,7 @@ type CreateCogsStateReturn<
|
|
|
914
882
|
PluginOptionsMap<TPlugins>,
|
|
915
883
|
StateKey
|
|
916
884
|
>
|
|
917
|
-
) => StateObject<CogsFullState<State, TPlugins>[StateKey], TPlugins>;
|
|
885
|
+
) => StateObject<CogsFullState<State, TPlugins>[StateKey], TPlugins>;
|
|
918
886
|
setCogsOptionsByKey: <StateKey extends keyof CogsFullState<State, TPlugins>>(
|
|
919
887
|
stateKey: StateKey,
|
|
920
888
|
options: CreateStateOptionsType<
|
|
@@ -1030,7 +998,7 @@ export const createCogsState = <
|
|
|
1030
998
|
OptionsType<StateSlice<StateKey>, never> &
|
|
1031
999
|
PluginOptionsForState<PluginOptions, StateKey>
|
|
1032
1000
|
>
|
|
1033
|
-
): StateObject<StateSlice<StateKey>, TPlugins> => {
|
|
1001
|
+
): StateObject<StateSlice<StateKey>, TPlugins> => {
|
|
1034
1002
|
const [componentId] = useState(options?.componentId ?? uuidv4());
|
|
1035
1003
|
|
|
1036
1004
|
const currentOptions = setOptions({
|
|
@@ -1048,7 +1016,6 @@ export const createCogsState = <
|
|
|
1048
1016
|
|
|
1049
1017
|
const updater = useCogsStateFn<StateSlice<StateKey>>(thiState, {
|
|
1050
1018
|
stateKey: stateKey as string,
|
|
1051
|
-
syncUpdate: options?.syncUpdate,
|
|
1052
1019
|
componentId,
|
|
1053
1020
|
localStorage: options?.localStorage,
|
|
1054
1021
|
middleware: options?.middleware,
|
|
@@ -1056,7 +1023,6 @@ export const createCogsState = <
|
|
|
1056
1023
|
reactiveDeps: options?.reactiveDeps,
|
|
1057
1024
|
defaultState: options?.defaultState as any,
|
|
1058
1025
|
dependencies: options?.dependencies,
|
|
1059
|
-
serverState: options?.serverState,
|
|
1060
1026
|
});
|
|
1061
1027
|
|
|
1062
1028
|
useLayoutEffect(() => {
|
|
@@ -1086,8 +1052,8 @@ export const createCogsState = <
|
|
|
1086
1052
|
}
|
|
1087
1053
|
});
|
|
1088
1054
|
|
|
1089
|
-
return updater as StateObject<StateSlice<StateKey>, TPlugins>;
|
|
1090
|
-
};
|
|
1055
|
+
return updater as StateObject<StateSlice<StateKey>, TPlugins>;
|
|
1056
|
+
};
|
|
1091
1057
|
|
|
1092
1058
|
function setCogsOptionsByKey<StateKey extends StateKeys>(
|
|
1093
1059
|
stateKey: StateKey,
|
|
@@ -1183,8 +1149,6 @@ const saveToLocalStorage = <T,>(
|
|
|
1183
1149
|
state,
|
|
1184
1150
|
lastUpdated: Date.now(),
|
|
1185
1151
|
lastSyncedWithServer: lastSyncedWithServer ?? existingLastSynced,
|
|
1186
|
-
stateSource: shadowMeta?.stateSource,
|
|
1187
|
-
baseServerState: shadowMeta?.baseServerState,
|
|
1188
1152
|
};
|
|
1189
1153
|
|
|
1190
1154
|
// Use SuperJSON serialize to get the json part only
|
|
@@ -1251,8 +1215,6 @@ type LocalStorageData<T> = {
|
|
|
1251
1215
|
state: T;
|
|
1252
1216
|
lastUpdated: number;
|
|
1253
1217
|
lastSyncedWithServer?: number;
|
|
1254
|
-
baseServerState?: T; // Keep reference to what server state this is based on
|
|
1255
|
-
stateSource?: 'default' | 'server' | 'localStorage'; // Track origin
|
|
1256
1218
|
};
|
|
1257
1219
|
|
|
1258
1220
|
const notifyComponents = (thisKey: string) => {
|
|
@@ -1278,49 +1240,6 @@ const notifyComponents = (thisKey: string) => {
|
|
|
1278
1240
|
});
|
|
1279
1241
|
};
|
|
1280
1242
|
|
|
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
1243
|
// 5. Batch queue
|
|
1325
1244
|
let updateBatchQueue: any[] = [];
|
|
1326
1245
|
let isFlushScheduled = false;
|
|
@@ -1508,12 +1427,8 @@ function handleUpdate(
|
|
|
1508
1427
|
return null; // <-- Abort the update
|
|
1509
1428
|
}
|
|
1510
1429
|
|
|
1511
|
-
// ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
|
|
1512
|
-
// The manual loop has been removed.
|
|
1513
1430
|
updateShadowAtPath(stateKey, path, newValue);
|
|
1514
1431
|
|
|
1515
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1516
|
-
|
|
1517
1432
|
// Return the metadata of the node *after* the update.
|
|
1518
1433
|
const newShadowMeta = getShadowMetadata(stateKey, path);
|
|
1519
1434
|
|
|
@@ -1534,10 +1449,8 @@ function handleInsertMany(
|
|
|
1534
1449
|
shadowMeta: any;
|
|
1535
1450
|
path: string[];
|
|
1536
1451
|
} {
|
|
1537
|
-
// Use the existing, optimized global store function to perform the state update
|
|
1538
1452
|
insertManyShadowArrayElements(stateKey, path, payload);
|
|
1539
1453
|
|
|
1540
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1541
1454
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1542
1455
|
|
|
1543
1456
|
return {
|
|
@@ -1578,10 +1491,6 @@ function handleInsert(
|
|
|
1578
1491
|
index,
|
|
1579
1492
|
itemId
|
|
1580
1493
|
);
|
|
1581
|
-
//console.timeEnd('insertShadowArrayElement');
|
|
1582
|
-
|
|
1583
|
-
markAsDirty(stateKey, path, { bubble: true });
|
|
1584
|
-
|
|
1585
1494
|
const updatedMeta = getShadowMetadata(stateKey, path);
|
|
1586
1495
|
|
|
1587
1496
|
let insertAfterId: string | undefined;
|
|
@@ -1606,7 +1515,6 @@ function handleCut(
|
|
|
1606
1515
|
const parentArrayPath = path.slice(0, -1);
|
|
1607
1516
|
const oldValue = getShadowValue(stateKey, path);
|
|
1608
1517
|
removeShadowArrayElement(stateKey, path);
|
|
1609
|
-
markAsDirty(stateKey, parentArrayPath, { bubble: true });
|
|
1610
1518
|
return { type: 'cut', oldValue: oldValue, parentPath: parentArrayPath };
|
|
1611
1519
|
}
|
|
1612
1520
|
|
|
@@ -1759,12 +1667,10 @@ export function useCogsStateFn<
|
|
|
1759
1667
|
componentId,
|
|
1760
1668
|
defaultState,
|
|
1761
1669
|
dependencies,
|
|
1762
|
-
serverState,
|
|
1763
1670
|
}: {
|
|
1764
1671
|
stateKey?: string;
|
|
1765
1672
|
componentId?: string;
|
|
1766
1673
|
defaultState?: TStateObject;
|
|
1767
|
-
syncOptions?: SyncOptionsType<any>;
|
|
1768
1674
|
} & OptionsType<TStateObject> = {}
|
|
1769
1675
|
): StateObject<TStateObject, TPlugins> {
|
|
1770
1676
|
const [reactiveForce, forceUpdate] = useState({}); //this is the key to reactivity
|
|
@@ -1783,10 +1689,9 @@ export function useCogsStateFn<
|
|
|
1783
1689
|
overrideOptions?: OptionsType<TStateObject>
|
|
1784
1690
|
): {
|
|
1785
1691
|
value: TStateObject;
|
|
1786
|
-
source: 'default' | '
|
|
1692
|
+
source: 'default' | 'localStorage';
|
|
1787
1693
|
timestamp: number;
|
|
1788
1694
|
} => {
|
|
1789
|
-
// If we pass in options, use them. Otherwise, get from the global store.
|
|
1790
1695
|
const optionsToUse = overrideOptions
|
|
1791
1696
|
? { ...getInitialOptions(thisKey as string), ...overrideOptions }
|
|
1792
1697
|
: getInitialOptions(thisKey as string);
|
|
@@ -1795,19 +1700,7 @@ export function useCogsStateFn<
|
|
|
1795
1700
|
const finalDefaultState =
|
|
1796
1701
|
currentOptions?.defaultState || defaultState || stateObject;
|
|
1797
1702
|
|
|
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
|
|
1703
|
+
// Check localStorage
|
|
1811
1704
|
if (currentOptions?.localStorage?.key && sessionId) {
|
|
1812
1705
|
const localKey = isFunction(currentOptions.localStorage.key)
|
|
1813
1706
|
? currentOptions.localStorage.key(finalDefaultState)
|
|
@@ -1817,10 +1710,7 @@ export function useCogsStateFn<
|
|
|
1817
1710
|
`${sessionId}-${thisKey}-${localKey}`
|
|
1818
1711
|
);
|
|
1819
1712
|
|
|
1820
|
-
if (
|
|
1821
|
-
localData &&
|
|
1822
|
-
localData.lastUpdated > (currentOptions?.serverState?.timestamp || 0)
|
|
1823
|
-
) {
|
|
1713
|
+
if (localData) {
|
|
1824
1714
|
return {
|
|
1825
1715
|
value: localData.state,
|
|
1826
1716
|
source: 'localStorage',
|
|
@@ -1829,7 +1719,7 @@ export function useCogsStateFn<
|
|
|
1829
1719
|
}
|
|
1830
1720
|
}
|
|
1831
1721
|
|
|
1832
|
-
//
|
|
1722
|
+
// Use default state
|
|
1833
1723
|
return {
|
|
1834
1724
|
value: finalDefaultState || (stateObject as any),
|
|
1835
1725
|
source: 'default',
|
|
@@ -1839,111 +1729,8 @@ export function useCogsStateFn<
|
|
|
1839
1729
|
[thisKey, defaultState, stateObject, sessionId]
|
|
1840
1730
|
);
|
|
1841
1731
|
|
|
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
|
|
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]);
|
|
1732
|
+
// Removed: Server state effects are now owned by Shape Plugin
|
|
1937
1733
|
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
1734
|
const options = getInitialOptions(thisKey as string);
|
|
1948
1735
|
|
|
1949
1736
|
const features = {
|
|
@@ -1951,7 +1738,6 @@ export function useCogsStateFn<
|
|
|
1951
1738
|
};
|
|
1952
1739
|
|
|
1953
1740
|
setShadowMetadata(thisKey, [], {
|
|
1954
|
-
...existingMeta,
|
|
1955
1741
|
features,
|
|
1956
1742
|
});
|
|
1957
1743
|
|
|
@@ -1964,7 +1750,7 @@ export function useCogsStateFn<
|
|
|
1964
1750
|
}
|
|
1965
1751
|
}
|
|
1966
1752
|
|
|
1967
|
-
const { value: resolvedState
|
|
1753
|
+
const { value: resolvedState } = resolveInitialState();
|
|
1968
1754
|
initializeShadowState(thisKey, resolvedState);
|
|
1969
1755
|
|
|
1970
1756
|
const validation = getInitialOptions(thisKey as string)?.validation;
|
|
@@ -1974,17 +1760,6 @@ export function useCogsStateFn<
|
|
|
1974
1760
|
updateShadowTypeInfo(thisKey as string, validation.zodSchemaV3, 'zod3');
|
|
1975
1761
|
}
|
|
1976
1762
|
|
|
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
1763
|
notifyComponents(thisKey);
|
|
1989
1764
|
}, [thisKey, ...(dependencies || [])]);
|
|
1990
1765
|
|
|
@@ -2089,7 +1864,6 @@ type MetaData = {
|
|
|
2089
1864
|
fn: Function;
|
|
2090
1865
|
path: string[]; // Which array this transform applies to
|
|
2091
1866
|
}>;
|
|
2092
|
-
serverStateIsUpStream?: boolean;
|
|
2093
1867
|
};
|
|
2094
1868
|
|
|
2095
1869
|
const applyTransforms = (
|
|
@@ -2270,6 +2044,31 @@ function setFieldDisabledForPath(
|
|
|
2270
2044
|
});
|
|
2271
2045
|
}
|
|
2272
2046
|
|
|
2047
|
+
function pluginMetaDependencyPath(
|
|
2048
|
+
path: string[],
|
|
2049
|
+
pluginName: string,
|
|
2050
|
+
scope = 'default'
|
|
2051
|
+
) {
|
|
2052
|
+
return [...path, '$pluginMeta', pluginName, scope];
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function notifyPluginMetaDependency(
|
|
2056
|
+
stateKey: string,
|
|
2057
|
+
path: string[],
|
|
2058
|
+
pluginName: string,
|
|
2059
|
+
scope?: string
|
|
2060
|
+
) {
|
|
2061
|
+
const rootMeta = getShadowMetadata(stateKey, []);
|
|
2062
|
+
const dependencyMeta = getShadowMetadata(
|
|
2063
|
+
stateKey,
|
|
2064
|
+
pluginMetaDependencyPath(path, pluginName, scope)
|
|
2065
|
+
);
|
|
2066
|
+
|
|
2067
|
+
dependencyMeta?.pathComponents?.forEach((componentId) => {
|
|
2068
|
+
rootMeta?.components?.get(componentId)?.forceUpdate();
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2273
2072
|
function createProxyHandler<
|
|
2274
2073
|
T,
|
|
2275
2074
|
const TPlugins extends readonly CogsPlugin<
|
|
@@ -2395,186 +2194,95 @@ function createProxyHandler<
|
|
|
2395
2194
|
});
|
|
2396
2195
|
}
|
|
2397
2196
|
|
|
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
|
-
synced: () => {
|
|
2437
|
-
const shadowMeta = getGlobalStore
|
|
2438
|
-
.getState()
|
|
2439
|
-
.getShadowMetadata(stateKey, path);
|
|
2440
|
-
|
|
2441
|
-
setShadowMetadata(stateKey, path, {
|
|
2442
|
-
...shadowMeta,
|
|
2443
|
-
isDirty: false,
|
|
2444
|
-
stateSource: 'server',
|
|
2445
|
-
lastServerSync: Date.now(),
|
|
2446
|
-
});
|
|
2447
|
-
},
|
|
2448
|
-
};
|
|
2449
|
-
},
|
|
2450
|
-
$applyOperation: (
|
|
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
|
-
},
|
|
2460
|
-
getFieldMetaData: () =>
|
|
2461
|
-
getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
|
|
2462
|
-
setFieldMetaData: (data: Record<string, any>) =>
|
|
2463
|
-
setPluginMetaData(stateKey, path, plugin.name, data),
|
|
2464
|
-
removeFieldMetaData: () =>
|
|
2465
|
-
removePluginMetaData(stateKey, path, plugin.name),
|
|
2466
|
-
getFieldRefs: () => getFieldRefsForPath(stateKey, path),
|
|
2467
|
-
getFieldElements: () =>
|
|
2468
|
-
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
|
-
};
|
|
2197
|
+
const nextPath = [...path, prop];
|
|
2198
|
+
return rebuildStateShape({
|
|
2199
|
+
path: nextPath,
|
|
2200
|
+
componentId: componentId!,
|
|
2201
|
+
meta,
|
|
2202
|
+
});
|
|
2541
2203
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
if (
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2204
|
+
if (typeof prop === 'string' && prop.startsWith('$')) {
|
|
2205
|
+
const methodName = prop.slice(1);
|
|
2206
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
2207
|
+
const registeredPlugins = pluginStore.getState().registeredPlugins;
|
|
2208
|
+
for (const plugin of registeredPlugins) {
|
|
2209
|
+
const chainMethod = (plugin.chainMethods as any)?.[methodName];
|
|
2210
|
+
if (!chainMethod) continue;
|
|
2211
|
+
if (!pathMatchesPattern(path, chainMethod.pathPattern)) continue;
|
|
2212
|
+
if (!valueMatchesChainTarget(value, chainMethod.target)) continue;
|
|
2213
|
+
|
|
2214
|
+
return (...args: any[]) => {
|
|
2215
|
+
const store = pluginStore.getState();
|
|
2216
|
+
const options = store.pluginOptions
|
|
2217
|
+
.get(stateKey)
|
|
2218
|
+
?.get(plugin.name);
|
|
2219
|
+
const hookData = store.getHookResult(stateKey, plugin.name);
|
|
2220
|
+
|
|
2221
|
+
return chainMethod.handler(
|
|
2222
|
+
{
|
|
2223
|
+
stateKey,
|
|
2224
|
+
path,
|
|
2225
|
+
pluginName: plugin.name,
|
|
2226
|
+
options,
|
|
2227
|
+
hookData,
|
|
2228
|
+
$get: () => getScopedData(stateKey, path, meta).value,
|
|
2229
|
+
$update: (payload: any) => {
|
|
2230
|
+
effectiveSetState(payload, path, {
|
|
2231
|
+
updateType: 'update',
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
return {
|
|
2235
|
+
syned: () => {
|
|
2236
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
2237
|
+
},
|
|
2238
|
+
};
|
|
2239
|
+
},
|
|
2240
|
+
$applyOperation: (
|
|
2241
|
+
operation: UpdateTypeDetail,
|
|
2242
|
+
metaData?: Record<string, any>
|
|
2243
|
+
) => {
|
|
2244
|
+
effectiveSetState(operation.newValue, operation.path, {
|
|
2245
|
+
updateType: operation.updateType,
|
|
2246
|
+
itemId: operation.itemId,
|
|
2247
|
+
metaData,
|
|
2248
|
+
});
|
|
2249
|
+
},
|
|
2250
|
+
getFieldMetaData: () =>
|
|
2251
|
+
getPluginMetaDataMap(stateKey, path)?.get(plugin.name),
|
|
2252
|
+
setFieldMetaData: (data: Record<string, any>) =>
|
|
2253
|
+
setPluginMetaData(stateKey, path, plugin.name, data),
|
|
2254
|
+
removeFieldMetaData: () =>
|
|
2255
|
+
removePluginMetaData(stateKey, path, plugin.name),
|
|
2256
|
+
watchPluginMeta: (scope?: string) =>
|
|
2257
|
+
registerComponentDependency(
|
|
2258
|
+
stateKey,
|
|
2259
|
+
componentId,
|
|
2260
|
+
pluginMetaDependencyPath(path, plugin.name, scope)
|
|
2261
|
+
),
|
|
2262
|
+
notifyPluginMeta: (scope?: string) =>
|
|
2263
|
+
notifyPluginMetaDependency(
|
|
2264
|
+
stateKey,
|
|
2265
|
+
path,
|
|
2266
|
+
plugin.name,
|
|
2267
|
+
scope
|
|
2268
|
+
),
|
|
2269
|
+
getFieldRefs: () => getFieldRefsForPath(stateKey, path),
|
|
2270
|
+
getFieldElements: () =>
|
|
2271
|
+
getFieldElementsForPath(stateKey, path),
|
|
2272
|
+
setFieldDisabled: (disabled: boolean) =>
|
|
2273
|
+
setFieldDisabledForPath(stateKey, path, disabled),
|
|
2274
|
+
},
|
|
2275
|
+
...args
|
|
2276
|
+
);
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (prop === '$_rebuildStateShape') {
|
|
2281
|
+
return rebuildStateShape;
|
|
2577
2282
|
}
|
|
2283
|
+
|
|
2284
|
+
// Removed: $sync is now owned by Shape Plugin
|
|
2285
|
+
// Removed: $_status and $getStatus are now owned by Shape Plugin
|
|
2578
2286
|
if (prop === '$removeStorage') {
|
|
2579
2287
|
return () => {
|
|
2580
2288
|
const initialState =
|
|
@@ -2596,75 +2304,75 @@ function createProxyHandler<
|
|
|
2596
2304
|
setShadowMetadata(stateKey, path, { ...currentMeta, isRaw: true });
|
|
2597
2305
|
effectiveSetState(value, path, { updateType: 'update' });
|
|
2598
2306
|
};
|
|
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);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
if (prop === '$validate') {
|
|
2310
|
+
return (keys?: readonly string[]) => {
|
|
2311
|
+
const store = getGlobalStore.getState();
|
|
2312
|
+
|
|
2313
|
+
if (keys) {
|
|
2314
|
+
const results = keys.map((key) => {
|
|
2315
|
+
const targetPath = [...path, key];
|
|
2316
|
+
const targetValue = store.getShadowValue(stateKey, targetPath);
|
|
2317
|
+
const currentMeta =
|
|
2318
|
+
store.getShadowMetadata(stateKey, targetPath) || {};
|
|
2319
|
+
const fieldSchema = currentMeta.typeInfo?.schema;
|
|
2320
|
+
|
|
2321
|
+
if (!fieldSchema) {
|
|
2322
|
+
return {
|
|
2323
|
+
key,
|
|
2324
|
+
path: targetPath,
|
|
2325
|
+
success: true,
|
|
2326
|
+
data: targetValue,
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
const result = (fieldSchema as any).safeParse(targetValue);
|
|
2331
|
+
const zodErrors =
|
|
2332
|
+
result.error?.issues || result.error?.errors || [];
|
|
2333
|
+
|
|
2334
|
+
store.setShadowMetadata(stateKey, targetPath, {
|
|
2335
|
+
...currentMeta,
|
|
2336
|
+
validation: {
|
|
2337
|
+
status: result.success ? 'VALID' : 'INVALID',
|
|
2338
|
+
errors: result.success
|
|
2339
|
+
? []
|
|
2340
|
+
: [
|
|
2341
|
+
{
|
|
2342
|
+
source: 'client',
|
|
2343
|
+
message:
|
|
2344
|
+
zodErrors[0]?.message || 'Invalid value',
|
|
2345
|
+
severity: 'error',
|
|
2346
|
+
code: zodErrors[0]?.code,
|
|
2347
|
+
},
|
|
2348
|
+
],
|
|
2349
|
+
lastValidated: Date.now(),
|
|
2350
|
+
validatedValue: targetValue,
|
|
2351
|
+
},
|
|
2352
|
+
});
|
|
2353
|
+
store.notifyPathSubscribers([stateKey, ...targetPath].join('.'), {
|
|
2354
|
+
type: 'VALIDATION_UPDATE',
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
return {
|
|
2358
|
+
key,
|
|
2359
|
+
path: targetPath,
|
|
2360
|
+
success: result.success,
|
|
2361
|
+
data: result.success ? result.data : undefined,
|
|
2362
|
+
error: result.success ? undefined : result.error,
|
|
2363
|
+
};
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
notifyComponents(stateKey);
|
|
2367
|
+
|
|
2368
|
+
return {
|
|
2369
|
+
success: results.every((result) => result.success),
|
|
2370
|
+
results,
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// 1. Get Data & Schema
|
|
2375
|
+
const { value } = getScopedData(stateKey, path, meta);
|
|
2668
2376
|
const opts = store.getInitialOptions(stateKey);
|
|
2669
2377
|
const schema =
|
|
2670
2378
|
opts?.validation?.zodSchemaV4 || opts?.validation?.zodSchemaV3;
|
|
@@ -2722,11 +2430,11 @@ function createProxyHandler<
|
|
|
2722
2430
|
return result;
|
|
2723
2431
|
};
|
|
2724
2432
|
}
|
|
2725
|
-
if (prop === '$showValidationErrors') {
|
|
2726
|
-
return () => {
|
|
2727
|
-
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2728
|
-
if (
|
|
2729
|
-
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2433
|
+
if (prop === '$showValidationErrors') {
|
|
2434
|
+
return () => {
|
|
2435
|
+
const { shadowMeta } = getScopedData(stateKey, path, meta);
|
|
2436
|
+
if (
|
|
2437
|
+
shadowMeta?.validation?.status === 'INVALID' &&
|
|
2730
2438
|
shadowMeta.validation.errors.length > 0
|
|
2731
2439
|
) {
|
|
2732
2440
|
// Return only error-severity messages (not warnings)
|
|
@@ -2734,55 +2442,55 @@ function createProxyHandler<
|
|
|
2734
2442
|
.filter((err) => err.severity === 'error')
|
|
2735
2443
|
.map((err) => err.message);
|
|
2736
2444
|
}
|
|
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') {
|
|
2445
|
+
return [];
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
if (prop === '$validationErrors') {
|
|
2449
|
+
return (keys?: readonly string[]) => {
|
|
2450
|
+
const store = getGlobalStore.getState();
|
|
2451
|
+
const summarizePath = (targetPath: string[]) => {
|
|
2452
|
+
const validationState =
|
|
2453
|
+
store.getShadowMetadata(stateKey, targetPath)?.validation;
|
|
2454
|
+
const allErrors = (validationState?.errors || []).map((err) => ({
|
|
2455
|
+
...err,
|
|
2456
|
+
path: targetPath,
|
|
2457
|
+
}));
|
|
2458
|
+
const errors = allErrors.filter((err) => err.severity === 'error');
|
|
2459
|
+
const warnings = allErrors.filter(
|
|
2460
|
+
(err) => err.severity === 'warning'
|
|
2461
|
+
);
|
|
2462
|
+
const severity: ValidationSeverity =
|
|
2463
|
+
errors.length > 0
|
|
2464
|
+
? 'error'
|
|
2465
|
+
: warnings.length > 0
|
|
2466
|
+
? 'warning'
|
|
2467
|
+
: undefined;
|
|
2468
|
+
|
|
2469
|
+
return {
|
|
2470
|
+
status: validationState?.status || 'NOT_VALIDATED',
|
|
2471
|
+
severity,
|
|
2472
|
+
hasErrors: errors.length > 0,
|
|
2473
|
+
hasWarnings: warnings.length > 0,
|
|
2474
|
+
message: errors[0]?.message || warnings[0]?.message || '',
|
|
2475
|
+
errors: errors.map((err) => err.message),
|
|
2476
|
+
warnings: warnings.map((err) => err.message),
|
|
2477
|
+
allErrors,
|
|
2478
|
+
path: targetPath,
|
|
2479
|
+
getData: () => store.getShadowValue(stateKey, targetPath),
|
|
2480
|
+
} satisfies ValidationFieldSummary;
|
|
2481
|
+
};
|
|
2482
|
+
|
|
2483
|
+
const childKeys =
|
|
2484
|
+
keys ??
|
|
2485
|
+
Object.keys(store.getShadowNode(stateKey, path) ?? {}).filter(
|
|
2486
|
+
(key) => key !== '_meta'
|
|
2487
|
+
);
|
|
2488
|
+
|
|
2489
|
+
return childKeys.map((key) => summarizePath([...path, key]));
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
if (prop === '$getSelected') {
|
|
2786
2494
|
return () => {
|
|
2787
2495
|
const arrayKey = [stateKey, ...path].join('.');
|
|
2788
2496
|
registerComponentDependency(stateKey, componentId, [
|
|
@@ -3048,9 +2756,7 @@ function createProxyHandler<
|
|
|
3048
2756
|
e.type === 'INSERT' ||
|
|
3049
2757
|
e.type === 'INSERT_MANY' ||
|
|
3050
2758
|
e.type === 'REMOVE' ||
|
|
3051
|
-
e.type === 'CLEAR_SELECTION'
|
|
3052
|
-
(e.type === 'SERVER_STATE_UPDATE' &&
|
|
3053
|
-
!meta?.serverStateIsUpStream)
|
|
2759
|
+
e.type === 'CLEAR_SELECTION'
|
|
3054
2760
|
) {
|
|
3055
2761
|
forceUpdate({});
|
|
3056
2762
|
}
|
|
@@ -3387,10 +3093,6 @@ function createProxyHandler<
|
|
|
3387
3093
|
$cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
|
|
3388
3094
|
}
|
|
3389
3095
|
|
|
3390
|
-
if (prop === '$lastSynced') {
|
|
3391
|
-
const syncKey = `${stateKey}:${path.join('.')}`;
|
|
3392
|
-
return getSyncInfo(syncKey);
|
|
3393
|
-
}
|
|
3394
3096
|
if (prop == 'getLocalStorage') {
|
|
3395
3097
|
return (key: string) =>
|
|
3396
3098
|
loadFromLocalStorage(sessionId + '-' + stateKey + '-' + key);
|
|
@@ -3662,8 +3364,6 @@ function createProxyHandler<
|
|
|
3662
3364
|
};
|
|
3663
3365
|
store.updateShadowAtPath(stateKey, relativePath, value);
|
|
3664
3366
|
|
|
3665
|
-
store.markAsDirty(stateKey, relativePath, { bubble: true });
|
|
3666
|
-
|
|
3667
3367
|
// Bubble up - notify components at this path and all parent paths
|
|
3668
3368
|
let currentPath = [...relativePath];
|
|
3669
3369
|
while (true) {
|
|
@@ -3693,7 +3393,6 @@ function createProxyHandler<
|
|
|
3693
3393
|
case 'remove': {
|
|
3694
3394
|
const parentPath = relativePath.slice(0, -1);
|
|
3695
3395
|
store.removeShadowArrayElement(stateKey, relativePath);
|
|
3696
|
-
store.markAsDirty(stateKey, parentPath, { bubble: true });
|
|
3697
3396
|
|
|
3698
3397
|
// Bubble up from parent path
|
|
3699
3398
|
let currentPath = [...parentPath];
|
|
@@ -3757,22 +3456,7 @@ function createProxyHandler<
|
|
|
3757
3456
|
|
|
3758
3457
|
return {
|
|
3759
3458
|
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
|
-
});
|
|
3459
|
+
// Removed: sync status tracking is now owned by Shape Plugin
|
|
3776
3460
|
},
|
|
3777
3461
|
};
|
|
3778
3462
|
};
|
|
@@ -3855,16 +3539,8 @@ function createProxyHandler<
|
|
|
3855
3539
|
// ... (rest of the function: rootLevelMethods, returnShape, etc.)
|
|
3856
3540
|
const rootLevelMethods = {
|
|
3857
3541
|
$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
|
-
}
|
|
3542
|
+
const revertState =
|
|
3543
|
+
getGlobalStore.getState().initialStateGlobal[stateKey];
|
|
3868
3544
|
|
|
3869
3545
|
clearSelectedIndexesForState(stateKey);
|
|
3870
3546
|
initializeShadowState(stateKey, revertState);
|