cogsbox-state 0.5.460 → 0.5.462

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.
@@ -12,7 +12,6 @@ export const useCogsTrpcValidationLink = <
12
12
  log?: boolean;
13
13
  }) => {
14
14
  const addValidationError = getGlobalStore.getState().addValidationError;
15
-
16
15
  const TrpcValidationLink = (): TRPCLink<TRouter> => {
17
16
  return (opts) => {
18
17
  return ({ next, op }: { next: any; op: Operation }) => {
package/src/store.ts CHANGED
@@ -8,7 +8,7 @@ import type {
8
8
  UpdateTypeDetail,
9
9
  } from './CogsState.js';
10
10
 
11
- import type { ReactNode } from 'react';
11
+ import { startTransition, type ReactNode } from 'react';
12
12
 
13
13
  type StateUpdater<StateValue> =
14
14
  | StateValue
@@ -177,6 +177,11 @@ export type CogsEvent =
177
177
  | { type: 'ITEMHEIGHT'; itemKey: string; height: number } // For full re-initializations (e.g., when a component is removed)
178
178
  | { type: 'RELOAD'; path: string }; // For full re-initializations
179
179
  export type CogsGlobalState = {
180
+ updateQueue: Set<() => void>;
181
+ isFlushScheduled: boolean;
182
+
183
+ flushUpdates: () => void;
184
+
180
185
  // --- Shadow State and Subscription System ---
181
186
  registerComponent: (
182
187
  stateKey: string,
@@ -190,6 +195,7 @@ export type CogsGlobalState = {
190
195
  fullComponentId: string
191
196
  ) => void;
192
197
  shadowStateStore: Map<string, ShadowMetadata>;
198
+
193
199
  markAsDirty: (
194
200
  key: string,
195
201
  path: string[],
@@ -265,13 +271,10 @@ export type CogsGlobalState = {
265
271
 
266
272
  setServerStateUpdate: (key: string, serverState: any) => void;
267
273
 
268
- stateLog: { [key: string]: UpdateTypeDetail[] };
274
+ stateLog: Map<string, Map<string, UpdateTypeDetail>>;
269
275
  syncInfoStore: Map<string, SyncInfo>;
276
+ addStateLog: (key: string, update: UpdateTypeDetail) => void;
270
277
 
271
- setStateLog: (
272
- key: string,
273
- updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
274
- ) => void;
275
278
  setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
276
279
  getSyncInfo: (key: string) => SyncInfo | null;
277
280
  };
@@ -301,6 +304,23 @@ const isSimpleObject = (value: any): boolean => {
301
304
  return Array.isArray(value) || value.constructor === Object;
302
305
  };
303
306
  export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
307
+ updateQueue: new Set<() => void>(),
308
+ // A flag to ensure we only schedule the flush once per event-loop tick.
309
+ isFlushScheduled: false,
310
+
311
+ // This function is called by queueMicrotask to execute all queued updates.
312
+ flushUpdates: () => {
313
+ const { updateQueue } = get();
314
+
315
+ if (updateQueue.size > 0) {
316
+ startTransition(() => {
317
+ updateQueue.forEach((updateFn) => updateFn());
318
+ });
319
+ }
320
+
321
+ // Clear the queue and reset the flag for the next event loop tick.
322
+ set({ updateQueue: new Set(), isFlushScheduled: false });
323
+ },
304
324
  addPathComponent: (stateKey, dependencyPath, fullComponentId) => {
305
325
  set((state) => {
306
326
  const newShadowStore = new Map(state.shadowStateStore);
@@ -432,6 +452,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
432
452
  });
433
453
  },
434
454
  shadowStateStore: new Map(),
455
+ getShadowNode: (key: string) => get().shadowStateStore.get(key),
435
456
  pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
436
457
 
437
458
  subscribeToPath: (path, callback) => {
@@ -521,39 +542,57 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
521
542
  },
522
543
 
523
544
  getShadowValue: (fullKey: string, validArrayIds?: string[]) => {
524
- const shadowMeta = get().shadowStateStore.get(fullKey);
545
+ // The cache is created here. It's temporary and exists only for this one top-level call.
546
+ const memo = new Map<string, any>();
547
+
548
+ // This is the inner recursive function that does the real work.
549
+ const reconstruct = (keyToBuild: string, ids?: string[]): any => {
550
+ // --- STEP 1: Check the cache first ---
551
+ // If we have already built the object for this path *during this call*, return it instantly.
552
+ if (memo.has(keyToBuild)) {
553
+ return memo.get(keyToBuild);
554
+ }
525
555
 
526
- // If no metadata found, return undefined
527
- if (!shadowMeta) {
528
- return undefined;
529
- }
556
+ const shadowMeta = get().shadowStateStore.get(keyToBuild);
530
557
 
531
- // For primitive values, return the value
532
- if (shadowMeta.value !== undefined) {
533
- return shadowMeta.value;
534
- }
558
+ if (!shadowMeta) {
559
+ return undefined;
560
+ }
535
561
 
536
- // For arrays, reconstruct with possible validArrayIds
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
- }
562
+ if (shadowMeta.value !== undefined) {
563
+ return shadowMeta.value;
564
+ }
545
565
 
546
- // For objects with fields, reconstruct object
547
- if (shadowMeta.fields) {
548
- const reconstructedObject: any = {};
549
- Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
550
- // RECURSIVELY call getShadowValue for each field
551
- reconstructedObject[key] = get().getShadowValue(fieldPath as string);
552
- });
553
- return reconstructedObject;
554
- }
566
+ let result: any; // The value we are about to build.
567
+
568
+ if (shadowMeta.arrayKeys) {
569
+ const keys = ids ?? shadowMeta.arrayKeys;
570
+ // --- IMPORTANT: Set the cache BEFORE the recursive calls ---
571
+ // This handles circular references gracefully. We put an empty array in the cache now.
572
+ result = [];
573
+ memo.set(keyToBuild, result);
574
+ // Now, fill the array with the recursively constructed items.
575
+ keys.forEach((itemKey) => {
576
+ result.push(reconstruct(itemKey)); // Pass the memo cache along implicitly via closure
577
+ });
578
+ } else if (shadowMeta.fields) {
579
+ // --- IMPORTANT: Set the cache BEFORE the recursive calls ---
580
+ result = {};
581
+ memo.set(keyToBuild, result);
582
+ // Now, fill the object with the recursively constructed items.
583
+ Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
584
+ result[key] = reconstruct(fieldPath as string);
585
+ });
586
+ } else {
587
+ result = undefined;
588
+ }
589
+
590
+ // Return the final, fully populated result.
591
+ return result;
592
+ };
555
593
 
556
- return undefined;
594
+ // Start the process by calling the inner function on the root key.
595
+ return reconstruct(fullKey, validArrayIds);
557
596
  },
558
597
  getShadowMetadata: (
559
598
  key: string,
@@ -837,25 +876,29 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
837
876
 
838
877
  stateTimeline: {},
839
878
  cogsStateStore: {},
840
- stateLog: {},
879
+ stateLog: new Map(),
841
880
 
842
881
  initialStateGlobal: {},
843
882
 
844
883
  validationErrors: new Map(),
884
+ addStateLog: (key, update) => {
885
+ set((state) => {
886
+ const newLog = new Map(state.stateLog);
887
+ const stateLogForKey = new Map(newLog.get(key));
888
+ const uniquePathKey = JSON.stringify(update.path);
889
+
890
+ const existing = stateLogForKey.get(uniquePathKey);
891
+ if (existing) {
892
+ // If an update for this path already exists, just modify it. (Fast)
893
+ existing.newValue = update.newValue;
894
+ existing.timeStamp = update.timeStamp;
895
+ } else {
896
+ // Otherwise, add the new update. (Fast)
897
+ stateLogForKey.set(uniquePathKey, { ...update });
898
+ }
845
899
 
846
- setStateLog: (
847
- key: string,
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
- };
900
+ newLog.set(key, stateLogForKey);
901
+ return { stateLog: newLog };
859
902
  });
860
903
  },
861
904