cogsbox-state 0.5.461 → 0.5.463
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 -4
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1489 -1505
- package/dist/CogsState.jsx.map +1 -1
- package/dist/store.d.ts +5 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +231 -196
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +1136 -1138
- package/src/store.ts +165 -134
package/src/store.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
UpdateTypeDetail,
|
|
9
9
|
} from './CogsState.js';
|
|
10
10
|
|
|
11
|
-
import type
|
|
11
|
+
import { startTransition, type ReactNode } from 'react';
|
|
12
12
|
|
|
13
13
|
type StateUpdater<StateValue> =
|
|
14
14
|
| StateValue
|
|
@@ -109,7 +109,6 @@ export type ShadowMetadata = {
|
|
|
109
109
|
lastUpdated?: number;
|
|
110
110
|
value?: any;
|
|
111
111
|
classSignals?: Array<{
|
|
112
|
-
// <-- ADD THIS BLOCK
|
|
113
112
|
id: string;
|
|
114
113
|
effect: string;
|
|
115
114
|
lastClasses: string;
|
|
@@ -174,9 +173,14 @@ export type CogsEvent =
|
|
|
174
173
|
| { type: 'INSERT'; path: string; itemKey: string; index: number }
|
|
175
174
|
| { type: 'REMOVE'; path: string; itemKey: string }
|
|
176
175
|
| { type: 'UPDATE'; path: string; newValue: any }
|
|
177
|
-
| { type: 'ITEMHEIGHT'; itemKey: string; height: number }
|
|
178
|
-
| { type: 'RELOAD'; path: string };
|
|
176
|
+
| { type: 'ITEMHEIGHT'; itemKey: string; height: number }
|
|
177
|
+
| { type: 'RELOAD'; path: string };
|
|
179
178
|
export type CogsGlobalState = {
|
|
179
|
+
updateQueue: Set<() => void>;
|
|
180
|
+
isFlushScheduled: boolean;
|
|
181
|
+
|
|
182
|
+
flushUpdates: () => void;
|
|
183
|
+
|
|
180
184
|
// --- Shadow State and Subscription System ---
|
|
181
185
|
registerComponent: (
|
|
182
186
|
stateKey: string,
|
|
@@ -190,6 +194,7 @@ export type CogsGlobalState = {
|
|
|
190
194
|
fullComponentId: string
|
|
191
195
|
) => void;
|
|
192
196
|
shadowStateStore: Map<string, ShadowMetadata>;
|
|
197
|
+
|
|
193
198
|
markAsDirty: (
|
|
194
199
|
key: string,
|
|
195
200
|
path: string[],
|
|
@@ -250,10 +255,6 @@ export type CogsGlobalState = {
|
|
|
250
255
|
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
251
256
|
setInitialStateOptions: (key: string, value: OptionsType) => void;
|
|
252
257
|
|
|
253
|
-
// --- Validation ---
|
|
254
|
-
|
|
255
|
-
// --- Server Sync and Logging ---
|
|
256
|
-
|
|
257
258
|
serverStateUpdates: Map<
|
|
258
259
|
string,
|
|
259
260
|
{
|
|
@@ -265,42 +266,44 @@ export type CogsGlobalState = {
|
|
|
265
266
|
|
|
266
267
|
setServerStateUpdate: (key: string, serverState: any) => void;
|
|
267
268
|
|
|
268
|
-
stateLog:
|
|
269
|
+
stateLog: Map<string, Map<string, UpdateTypeDetail>>;
|
|
269
270
|
syncInfoStore: Map<string, SyncInfo>;
|
|
271
|
+
addStateLog: (key: string, update: UpdateTypeDetail) => void;
|
|
270
272
|
|
|
271
|
-
setStateLog: (
|
|
272
|
-
key: string,
|
|
273
|
-
updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
|
|
274
|
-
) => void;
|
|
275
273
|
setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
|
|
276
274
|
getSyncInfo: (key: string) => SyncInfo | null;
|
|
277
275
|
};
|
|
278
276
|
const isSimpleObject = (value: any): boolean => {
|
|
277
|
+
// Most common cases first
|
|
279
278
|
if (value === null || typeof value !== 'object') return false;
|
|
280
279
|
|
|
281
|
-
//
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
value instanceof Float32Array ||
|
|
290
|
-
value instanceof Float64Array ||
|
|
291
|
-
value instanceof ArrayBuffer ||
|
|
292
|
-
value instanceof Date ||
|
|
293
|
-
value instanceof RegExp ||
|
|
294
|
-
value instanceof Map ||
|
|
295
|
-
value instanceof Set
|
|
296
|
-
) {
|
|
297
|
-
return false; // Treat as primitive
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Arrays and plain objects are complex
|
|
301
|
-
return Array.isArray(value) || value.constructor === Object;
|
|
280
|
+
// Arrays are simple objects
|
|
281
|
+
if (Array.isArray(value)) return true;
|
|
282
|
+
|
|
283
|
+
// Plain objects second most common
|
|
284
|
+
if (value.constructor === Object) return true;
|
|
285
|
+
|
|
286
|
+
// Everything else is not simple
|
|
287
|
+
return false;
|
|
302
288
|
};
|
|
303
289
|
export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
290
|
+
updateQueue: new Set<() => void>(),
|
|
291
|
+
// A flag to ensure we only schedule the flush once per event-loop tick.
|
|
292
|
+
isFlushScheduled: false,
|
|
293
|
+
|
|
294
|
+
// This function is called by queueMicrotask to execute all queued updates.
|
|
295
|
+
flushUpdates: () => {
|
|
296
|
+
const { updateQueue } = get();
|
|
297
|
+
|
|
298
|
+
if (updateQueue.size > 0) {
|
|
299
|
+
startTransition(() => {
|
|
300
|
+
updateQueue.forEach((updateFn) => updateFn());
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Clear the queue and reset the flag for the next event loop tick.
|
|
305
|
+
set({ updateQueue: new Set(), isFlushScheduled: false });
|
|
306
|
+
},
|
|
304
307
|
addPathComponent: (stateKey, dependencyPath, fullComponentId) => {
|
|
305
308
|
set((state) => {
|
|
306
309
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
@@ -340,20 +343,11 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
340
343
|
},
|
|
341
344
|
registerComponent: (stateKey, fullComponentId, registration) => {
|
|
342
345
|
set((state) => {
|
|
343
|
-
// Create a new Map to ensure Zustand detects the change
|
|
344
346
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
345
|
-
|
|
346
|
-
// Get the metadata for the ROOT of the state (where the components map lives)
|
|
347
347
|
const rootMeta = newShadowStore.get(stateKey) || {};
|
|
348
|
-
|
|
349
|
-
// Also clone the components map to avoid direct mutation
|
|
350
348
|
const components = new Map(rootMeta.components);
|
|
351
349
|
components.set(fullComponentId, registration);
|
|
352
|
-
|
|
353
|
-
// Update the root metadata with the new components map
|
|
354
350
|
newShadowStore.set(stateKey, { ...rootMeta, components });
|
|
355
|
-
|
|
356
|
-
// Return the updated state
|
|
357
351
|
return { shadowStateStore: newShadowStore };
|
|
358
352
|
});
|
|
359
353
|
},
|
|
@@ -362,8 +356,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
362
356
|
set((state) => {
|
|
363
357
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
364
358
|
const rootMeta = newShadowStore.get(stateKey);
|
|
365
|
-
|
|
366
|
-
// If there's no metadata or no components map, do nothing
|
|
367
359
|
if (!rootMeta?.components) {
|
|
368
360
|
return state; // Return original state, no change needed
|
|
369
361
|
}
|
|
@@ -381,40 +373,45 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
381
373
|
});
|
|
382
374
|
},
|
|
383
375
|
markAsDirty: (key: string, path: string[], options = { bubble: true }) => {
|
|
384
|
-
const
|
|
385
|
-
|
|
376
|
+
const { shadowStateStore } = get();
|
|
377
|
+
const updates = new Map<string, ShadowMetadata>();
|
|
386
378
|
|
|
387
379
|
const setDirty = (currentPath: string[]) => {
|
|
388
380
|
const fullKey = [key, ...currentPath].join('.');
|
|
389
|
-
const meta =
|
|
390
|
-
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
newShadowStore.set(fullKey, { ...meta, isDirty: true });
|
|
395
|
-
changed = true;
|
|
396
|
-
} else if (!meta) {
|
|
397
|
-
// If there's no metadata, create it and mark it as dirty.
|
|
398
|
-
// This handles newly created fields within an object.
|
|
399
|
-
newShadowStore.set(fullKey, { isDirty: true });
|
|
400
|
-
changed = true;
|
|
381
|
+
const meta = shadowStateStore.get(fullKey) || {};
|
|
382
|
+
|
|
383
|
+
// If already dirty, no need to update
|
|
384
|
+
if (meta.isDirty === true) {
|
|
385
|
+
return true; // Return true to indicate parent is dirty
|
|
401
386
|
}
|
|
387
|
+
|
|
388
|
+
updates.set(fullKey, { ...meta, isDirty: true });
|
|
389
|
+
return false; // Not previously dirty
|
|
402
390
|
};
|
|
403
391
|
|
|
404
|
-
//
|
|
392
|
+
// Mark the target path
|
|
405
393
|
setDirty(path);
|
|
406
394
|
|
|
407
|
-
//
|
|
395
|
+
// Bubble up if requested
|
|
408
396
|
if (options.bubble) {
|
|
409
397
|
let parentPath = [...path];
|
|
410
398
|
while (parentPath.length > 0) {
|
|
411
399
|
parentPath.pop();
|
|
412
|
-
setDirty(parentPath);
|
|
400
|
+
const wasDirty = setDirty(parentPath);
|
|
401
|
+
if (wasDirty) {
|
|
402
|
+
break; // Stop bubbling if parent was already dirty
|
|
403
|
+
}
|
|
413
404
|
}
|
|
414
405
|
}
|
|
415
406
|
|
|
416
|
-
|
|
417
|
-
|
|
407
|
+
// Apply all updates at once
|
|
408
|
+
if (updates.size > 0) {
|
|
409
|
+
set((state) => {
|
|
410
|
+
updates.forEach((meta, key) => {
|
|
411
|
+
state.shadowStateStore.set(key, meta);
|
|
412
|
+
});
|
|
413
|
+
return state;
|
|
414
|
+
});
|
|
418
415
|
}
|
|
419
416
|
},
|
|
420
417
|
serverStateUpdates: new Map(),
|
|
@@ -432,6 +429,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
432
429
|
});
|
|
433
430
|
},
|
|
434
431
|
shadowStateStore: new Map(),
|
|
432
|
+
getShadowNode: (key: string) => get().shadowStateStore.get(key),
|
|
435
433
|
pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
|
|
436
434
|
|
|
437
435
|
subscribeToPath: (path, callback) => {
|
|
@@ -464,7 +462,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
464
462
|
set((state) => {
|
|
465
463
|
// 1. Make a copy of the current store to modify it
|
|
466
464
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
467
|
-
|
|
465
|
+
console.log('initializeShadowState');
|
|
468
466
|
// 2. PRESERVE the existing components map before doing anything else
|
|
469
467
|
const existingRootMeta = newShadowStore.get(key);
|
|
470
468
|
const preservedComponents = existingRootMeta?.components;
|
|
@@ -521,47 +519,49 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
521
519
|
},
|
|
522
520
|
|
|
523
521
|
getShadowValue: (fullKey: string, validArrayIds?: string[]) => {
|
|
524
|
-
const
|
|
522
|
+
const memo = new Map<string, any>();
|
|
523
|
+
const reconstruct = (keyToBuild: string, ids?: string[]): any => {
|
|
524
|
+
if (memo.has(keyToBuild)) {
|
|
525
|
+
return memo.get(keyToBuild);
|
|
526
|
+
}
|
|
525
527
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
528
|
+
const shadowMeta = get().shadowStateStore.get(keyToBuild);
|
|
529
|
+
if (!shadowMeta) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
530
532
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
533
|
+
if (shadowMeta.value !== undefined) {
|
|
534
|
+
return shadowMeta.value;
|
|
535
|
+
}
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
if (shadowMeta.arrayKeys) {
|
|
538
|
-
const arrayKeys = validArrayIds ?? shadowMeta.arrayKeys;
|
|
539
|
-
const items = arrayKeys.map((itemKey) => {
|
|
540
|
-
// RECURSIVELY call getShadowValue for each item
|
|
541
|
-
return get().getShadowValue(itemKey);
|
|
542
|
-
});
|
|
543
|
-
return items;
|
|
544
|
-
}
|
|
537
|
+
let result: any; // The value we are about to build.
|
|
545
538
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
539
|
+
if (shadowMeta.arrayKeys) {
|
|
540
|
+
const keys = ids ?? shadowMeta.arrayKeys;
|
|
541
|
+
result = [];
|
|
542
|
+
memo.set(keyToBuild, result);
|
|
543
|
+
keys.forEach((itemKey) => {
|
|
544
|
+
result.push(reconstruct(itemKey));
|
|
545
|
+
});
|
|
546
|
+
} else if (shadowMeta.fields) {
|
|
547
|
+
result = {};
|
|
548
|
+
memo.set(keyToBuild, result);
|
|
549
|
+
Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
|
|
550
|
+
result[key] = reconstruct(fieldPath as string);
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
result = undefined;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Return the final, fully populated result.
|
|
557
|
+
return result;
|
|
558
|
+
};
|
|
555
559
|
|
|
556
|
-
|
|
560
|
+
// Start the process by calling the inner function on the root key.
|
|
561
|
+
return reconstruct(fullKey, validArrayIds);
|
|
557
562
|
},
|
|
558
|
-
getShadowMetadata: (
|
|
559
|
-
key: string,
|
|
560
|
-
path: string[],
|
|
561
|
-
validArrayIds?: string[]
|
|
562
|
-
) => {
|
|
563
|
+
getShadowMetadata: (key: string, path: string[]) => {
|
|
563
564
|
const fullKey = [key, ...path].join('.');
|
|
564
|
-
let data = get().shadowStateStore.get(fullKey);
|
|
565
565
|
|
|
566
566
|
return get().shadowStateStore.get(fullKey);
|
|
567
567
|
},
|
|
@@ -727,36 +727,63 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
727
727
|
itemKey: itemKey, // The exact ID of the removed item
|
|
728
728
|
});
|
|
729
729
|
},
|
|
730
|
+
|
|
730
731
|
updateShadowAtPath: (key, path, newValue) => {
|
|
731
|
-
const newShadowStore = new Map(get().shadowStateStore);
|
|
732
732
|
const fullKey = [key, ...path].join('.');
|
|
733
733
|
|
|
734
|
-
|
|
735
|
-
|
|
734
|
+
// Optimization: Only update if value actually changed
|
|
735
|
+
const existingMeta = get().shadowStateStore.get(fullKey);
|
|
736
|
+
if (existingMeta?.value === newValue && !isSimpleObject(newValue)) {
|
|
737
|
+
return; // Skip update for unchanged primitives
|
|
738
|
+
}
|
|
736
739
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (Object.prototype.hasOwnProperty.call(valueToSet, fieldKey)) {
|
|
741
|
-
const childPath = meta.fields[fieldKey];
|
|
742
|
-
const childValue = valueToSet[fieldKey];
|
|
740
|
+
// CHANGE: Don't clone the entire Map, just update in place
|
|
741
|
+
set((state) => {
|
|
742
|
+
const store = state.shadowStateStore;
|
|
743
743
|
|
|
744
|
-
|
|
745
|
-
|
|
744
|
+
if (!isSimpleObject(newValue)) {
|
|
745
|
+
const meta = store.get(fullKey) || {};
|
|
746
|
+
store.set(fullKey, { ...meta, value: newValue });
|
|
747
|
+
} else {
|
|
748
|
+
// Handle objects by iterating
|
|
749
|
+
const processObject = (currentPath: string[], objectToSet: any) => {
|
|
750
|
+
const currentFullKey = [key, ...currentPath].join('.');
|
|
751
|
+
const meta = store.get(currentFullKey);
|
|
752
|
+
|
|
753
|
+
if (meta && meta.fields) {
|
|
754
|
+
for (const fieldKey in objectToSet) {
|
|
755
|
+
if (Object.prototype.hasOwnProperty.call(objectToSet, fieldKey)) {
|
|
756
|
+
const childValue = objectToSet[fieldKey];
|
|
757
|
+
const childFullPath = meta.fields[fieldKey];
|
|
758
|
+
|
|
759
|
+
if (childFullPath) {
|
|
760
|
+
if (isSimpleObject(childValue)) {
|
|
761
|
+
processObject(
|
|
762
|
+
childFullPath.split('.').slice(1),
|
|
763
|
+
childValue
|
|
764
|
+
);
|
|
765
|
+
} else {
|
|
766
|
+
const existingChildMeta = store.get(childFullPath) || {};
|
|
767
|
+
store.set(childFullPath, {
|
|
768
|
+
...existingChildMeta,
|
|
769
|
+
value: childValue,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
746
774
|
}
|
|
747
775
|
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
// This gives you useState-like behavior
|
|
752
|
-
const existing = newShadowStore.get(currentKey) || {};
|
|
753
|
-
newShadowStore.set(currentKey, { ...existing, value: valueToSet });
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
processObject(path, newValue);
|
|
754
779
|
}
|
|
755
|
-
};
|
|
756
780
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
781
|
+
// Only notify after all changes are made
|
|
782
|
+
get().notifyPathSubscribers(fullKey, { type: 'UPDATE', newValue });
|
|
783
|
+
|
|
784
|
+
// Return same reference if using Zustand's immer middleware
|
|
785
|
+
return state;
|
|
786
|
+
});
|
|
760
787
|
},
|
|
761
788
|
selectedIndicesMap: new Map<string, string>(),
|
|
762
789
|
getSelectedIndex: (arrayKey: string, validIds?: string[]): number => {
|
|
@@ -837,25 +864,29 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
837
864
|
|
|
838
865
|
stateTimeline: {},
|
|
839
866
|
cogsStateStore: {},
|
|
840
|
-
stateLog:
|
|
867
|
+
stateLog: new Map(),
|
|
841
868
|
|
|
842
869
|
initialStateGlobal: {},
|
|
843
870
|
|
|
844
871
|
validationErrors: new Map(),
|
|
872
|
+
addStateLog: (key, update) => {
|
|
873
|
+
set((state) => {
|
|
874
|
+
const newLog = new Map(state.stateLog);
|
|
875
|
+
const stateLogForKey = new Map(newLog.get(key));
|
|
876
|
+
const uniquePathKey = JSON.stringify(update.path);
|
|
877
|
+
|
|
878
|
+
const existing = stateLogForKey.get(uniquePathKey);
|
|
879
|
+
if (existing) {
|
|
880
|
+
// If an update for this path already exists, just modify it. (Fast)
|
|
881
|
+
existing.newValue = update.newValue;
|
|
882
|
+
existing.timeStamp = update.timeStamp;
|
|
883
|
+
} else {
|
|
884
|
+
// Otherwise, add the new update. (Fast)
|
|
885
|
+
stateLogForKey.set(uniquePathKey, { ...update });
|
|
886
|
+
}
|
|
845
887
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
|
|
849
|
-
) => {
|
|
850
|
-
set((prev) => {
|
|
851
|
-
const currentUpdates = prev.stateLog[key] ?? [];
|
|
852
|
-
const newUpdates = updater(currentUpdates);
|
|
853
|
-
return {
|
|
854
|
-
stateLog: {
|
|
855
|
-
...prev.stateLog,
|
|
856
|
-
[key]: newUpdates,
|
|
857
|
-
},
|
|
858
|
-
};
|
|
888
|
+
newLog.set(key, stateLogForKey);
|
|
889
|
+
return { stateLog: newLog };
|
|
859
890
|
});
|
|
860
891
|
},
|
|
861
892
|
|