cogsbox-state 0.5.466 → 0.5.468

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/src/store.ts CHANGED
@@ -160,8 +160,6 @@ type ShadowNode = {
160
160
  };
161
161
 
162
162
  export type CogsGlobalState = {
163
- // NEW shadow store
164
- shadowStateStore: Map<string, ShadowNode>;
165
163
  setTransformCache: (
166
164
  key: string,
167
165
  path: string[],
@@ -289,9 +287,14 @@ export function buildShadowNode(value: any): ShadowNode {
289
287
 
290
288
  return { value };
291
289
  }
290
+ // store.ts - Replace the shadow store methods with mutable versions
291
+ // store.ts - Replace the shadow store methods with mutable versions
292
+
293
+ // Module-level mutable store
294
+ const shadowStateStore = new Map<string, ShadowNode>();
292
295
 
293
296
  export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
294
- shadowStateStore: new Map<string, ShadowNode>(),
297
+ // Remove shadowStateStore from Zustand state
295
298
 
296
299
  setTransformCache: (
297
300
  key: string,
@@ -310,47 +313,39 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
310
313
  },
311
314
 
312
315
  initializeShadowState: (key: string, initialState: any) => {
313
- set((state) => {
314
- const newShadowStore = new Map(state.shadowStateStore);
315
- const existingRoot =
316
- newShadowStore.get(key) || newShadowStore.get(`[${key}`);
317
- let preservedMetadata: Partial<ShadowMetadata> = {};
318
-
319
- if (existingRoot?._meta) {
320
- const {
321
- components,
322
- features,
323
- lastServerSync,
324
- stateSource,
325
- baseServerState,
326
- } = existingRoot._meta;
327
- if (components) preservedMetadata.components = components;
328
- if (features) preservedMetadata.features = features;
329
- if (lastServerSync) preservedMetadata.lastServerSync = lastServerSync;
330
- if (stateSource) preservedMetadata.stateSource = stateSource;
331
- if (baseServerState)
332
- preservedMetadata.baseServerState = baseServerState;
333
- }
334
-
335
- newShadowStore.delete(key);
336
- newShadowStore.delete(`[${key}`);
337
-
338
- const newRoot = buildShadowNode(initialState);
316
+ const existingRoot =
317
+ shadowStateStore.get(key) || shadowStateStore.get(`[${key}`);
318
+ let preservedMetadata: Partial<ShadowMetadata> = {};
319
+
320
+ if (existingRoot?._meta) {
321
+ const {
322
+ components,
323
+ features,
324
+ lastServerSync,
325
+ stateSource,
326
+ baseServerState,
327
+ } = existingRoot._meta;
328
+ if (components) preservedMetadata.components = components;
329
+ if (features) preservedMetadata.features = features;
330
+ if (lastServerSync) preservedMetadata.lastServerSync = lastServerSync;
331
+ if (stateSource) preservedMetadata.stateSource = stateSource;
332
+ if (baseServerState) preservedMetadata.baseServerState = baseServerState;
333
+ }
339
334
 
340
- if (!newRoot._meta) newRoot._meta = {};
341
- Object.assign(newRoot._meta, preservedMetadata);
335
+ shadowStateStore.delete(key);
336
+ shadowStateStore.delete(`[${key}`);
342
337
 
343
- const storageKey = Array.isArray(initialState) ? `[${key}` : key;
344
- newShadowStore.set(storageKey, newRoot);
338
+ const newRoot = buildShadowNode(initialState);
339
+ if (!newRoot._meta) newRoot._meta = {};
340
+ Object.assign(newRoot._meta, preservedMetadata);
345
341
 
346
- return { shadowStateStore: newShadowStore };
347
- });
342
+ const storageKey = Array.isArray(initialState) ? `[${key}` : key;
343
+ shadowStateStore.set(storageKey, newRoot);
348
344
  },
349
345
 
350
346
  getShadowNode: (key: string, path: string[]): ShadowNode | undefined => {
351
- const store = get().shadowStateStore;
352
- let current: any = store.get(key) || store.get(`[${key}`);
353
-
347
+ let current: any =
348
+ shadowStateStore.get(key) || shadowStateStore.get(`[${key}`);
354
349
  if (!current) return undefined;
355
350
  if (path.length === 0) return current;
356
351
 
@@ -375,31 +370,32 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
375
370
  path: string[],
376
371
  newMetadata: Partial<ShadowMetadata>
377
372
  ) => {
378
- set((state) => {
379
- const newStore = new Map(state.shadowStateStore);
380
- const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
381
- let root = newStore.get(rootKey);
382
-
383
- if (!root) {
384
- root = {};
385
- newStore.set(rootKey, root);
386
- }
373
+ // Direct mutation - no cloning!
374
+ const rootKey = shadowStateStore.has(`[${key}`) ? `[${key}` : key;
375
+ let root = shadowStateStore.get(rootKey);
387
376
 
388
- const clonedRoot: any = { ...root };
389
- newStore.set(rootKey, clonedRoot);
377
+ if (!root) {
378
+ root = { _meta: newMetadata };
379
+ shadowStateStore.set(rootKey, root);
380
+ return;
381
+ }
390
382
 
391
- let current = clonedRoot;
392
- for (const segment of path) {
393
- const nextNode = current[segment] || {};
394
- current[segment] = { ...nextNode };
395
- current = current[segment];
383
+ // Navigate to target without cloning
384
+ let current = root;
385
+ for (const segment of path) {
386
+ if (!current[segment]) {
387
+ current[segment] = {};
396
388
  }
389
+ current = current[segment];
390
+ }
397
391
 
398
- current._meta = { ...(current._meta || {}), ...newMetadata };
399
-
400
- return { shadowStateStore: newStore };
401
- });
392
+ // Mutate metadata directly
393
+ if (!current._meta) {
394
+ current._meta = {};
395
+ }
396
+ Object.assign(current._meta, newMetadata);
402
397
  },
398
+
403
399
  getShadowValue: (
404
400
  key: string,
405
401
  path: string[],
@@ -420,7 +416,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
420
416
  return node.value;
421
417
  }
422
418
 
423
- // Array Check (This part is correct)
424
419
  const isArrayNode =
425
420
  node._meta &&
426
421
  Object.prototype.hasOwnProperty.call(node._meta, 'arrayKeys');
@@ -443,101 +438,107 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
443
438
  }
444
439
  return result;
445
440
  },
441
+
446
442
  updateShadowAtPath: (key, path, newValue) => {
447
- set((state) => {
448
- const newStore = new Map(state.shadowStateStore);
449
- const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
450
- let root = newStore.get(rootKey);
451
- if (!root) return state;
452
- const clonedRoot: any = { ...root };
453
- newStore.set(rootKey, clonedRoot);
454
- let parentNode = clonedRoot;
455
- for (let i = 0; i < path.length - 1; i++) {
456
- parentNode[path[i]!] = { ...(parentNode[path[i]!] || {}) };
457
- parentNode = parentNode[path[i]!];
443
+ // NO MORE set() wrapper - direct mutation!
444
+ const rootKey = shadowStateStore.has(`[${key}`) ? `[${key}` : key;
445
+ let root = shadowStateStore.get(rootKey);
446
+ if (!root) return;
447
+
448
+ // Navigate to parent without cloning
449
+ let parentNode = root;
450
+ for (let i = 0; i < path.length - 1; i++) {
451
+ if (!parentNode[path[i]!]) {
452
+ parentNode[path[i]!] = {};
458
453
  }
459
- const targetNode =
460
- path.length === 0 ? parentNode : parentNode[path[path.length - 1]!];
454
+ parentNode = parentNode[path[i]!];
455
+ }
461
456
 
462
- if (!targetNode) {
463
- parentNode[path[path.length - 1]!] = buildShadowNode(newValue);
464
- return { shadowStateStore: newStore };
465
- }
457
+ const targetNode =
458
+ path.length === 0 ? parentNode : parentNode[path[path.length - 1]!];
466
459
 
467
- function intelligentMerge(nodeToUpdate: any, plainValue: any) {
468
- if (
469
- typeof plainValue !== 'object' ||
470
- plainValue === null ||
471
- Array.isArray(plainValue)
472
- ) {
473
- const oldMeta = nodeToUpdate._meta;
474
- const newNode = buildShadowNode(plainValue);
475
- if (oldMeta) {
476
- newNode._meta = { ...oldMeta, ...(newNode._meta || {}) };
477
- }
478
- Object.keys(nodeToUpdate).forEach((key) => delete nodeToUpdate[key]);
479
- Object.assign(nodeToUpdate, newNode);
480
- return;
460
+ if (!targetNode) {
461
+ parentNode[path[path.length - 1]!] = buildShadowNode(newValue);
462
+ get().notifyPathSubscribers([key, ...path].join('.'), {
463
+ type: 'UPDATE',
464
+ newValue,
465
+ });
466
+ return;
467
+ }
468
+
469
+ function intelligentMerge(nodeToUpdate: any, plainValue: any) {
470
+ if (
471
+ typeof plainValue !== 'object' ||
472
+ plainValue === null ||
473
+ Array.isArray(plainValue)
474
+ ) {
475
+ const oldMeta = nodeToUpdate._meta;
476
+ // Clear existing properties
477
+ for (const key in nodeToUpdate) {
478
+ if (key !== '_meta') delete nodeToUpdate[key];
481
479
  }
480
+ const newNode = buildShadowNode(plainValue);
481
+ Object.assign(nodeToUpdate, newNode);
482
+ if (oldMeta) {
483
+ nodeToUpdate._meta = { ...oldMeta, ...(nodeToUpdate._meta || {}) };
484
+ }
485
+ return;
486
+ }
482
487
 
483
- const plainValueKeys = new Set(Object.keys(plainValue));
488
+ const plainValueKeys = new Set(Object.keys(plainValue));
484
489
 
485
- for (const propKey of plainValueKeys) {
486
- const childValue = plainValue[propKey];
487
- if (nodeToUpdate[propKey]) {
488
- intelligentMerge(nodeToUpdate[propKey], childValue);
489
- } else {
490
- nodeToUpdate[propKey] = buildShadowNode(childValue);
491
- }
490
+ for (const propKey of plainValueKeys) {
491
+ const childValue = plainValue[propKey];
492
+ if (nodeToUpdate[propKey]) {
493
+ intelligentMerge(nodeToUpdate[propKey], childValue);
494
+ } else {
495
+ nodeToUpdate[propKey] = buildShadowNode(childValue);
492
496
  }
497
+ }
493
498
 
494
- for (const nodeKey in nodeToUpdate) {
495
- if (
496
- nodeKey === '_meta' ||
497
- !Object.prototype.hasOwnProperty.call(nodeToUpdate, nodeKey)
498
- )
499
- continue;
499
+ for (const nodeKey in nodeToUpdate) {
500
+ if (
501
+ nodeKey === '_meta' ||
502
+ !Object.prototype.hasOwnProperty.call(nodeToUpdate, nodeKey)
503
+ )
504
+ continue;
500
505
 
501
- if (!plainValueKeys.has(nodeKey)) {
502
- delete nodeToUpdate[nodeKey];
503
- }
506
+ if (!plainValueKeys.has(nodeKey)) {
507
+ delete nodeToUpdate[nodeKey];
504
508
  }
505
509
  }
510
+ }
506
511
 
507
- intelligentMerge(targetNode, newValue);
512
+ intelligentMerge(targetNode, newValue);
508
513
 
509
- get().notifyPathSubscribers([key, ...path].join('.'), {
510
- type: 'UPDATE',
511
- newValue,
512
- });
513
- return { shadowStateStore: newStore };
514
+ get().notifyPathSubscribers([key, ...path].join('.'), {
515
+ type: 'UPDATE',
516
+ newValue,
514
517
  });
515
518
  },
516
- addItemsToArrayNode: (key, arrayPath, newItems, newKeys) => {
517
- set((state) => {
518
- const newStore = new Map(state.shadowStateStore);
519
- const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
520
- let root = newStore.get(rootKey);
521
- if (!root) {
522
- console.error('Root not found for state key:', key);
523
- return state;
524
- }
525
519
 
526
- const clonedRoot = { ...root };
527
- newStore.set(rootKey, clonedRoot);
520
+ addItemsToArrayNode: (key, arrayPath, newItems, newKeys) => {
521
+ // Direct mutation - no cloning!
522
+ const rootKey = shadowStateStore.has(`[${key}`) ? `[${key}` : key;
523
+ let root = shadowStateStore.get(rootKey);
524
+ if (!root) {
525
+ console.error('Root not found for state key:', key);
526
+ return;
527
+ }
528
528
 
529
- let current = clonedRoot;
530
- for (const segment of arrayPath) {
531
- const nextNode = current[segment] || {};
532
- current[segment] = { ...nextNode };
533
- current = current[segment];
529
+ // Navigate without cloning
530
+ let current = root;
531
+ for (const segment of arrayPath) {
532
+ if (!current[segment]) {
533
+ current[segment] = {};
534
534
  }
535
+ current = current[segment];
536
+ }
535
537
 
536
- Object.assign(current, newItems);
537
- current._meta = { ...(current._meta || {}), arrayKeys: newKeys };
538
-
539
- return { shadowStateStore: newStore };
540
- });
538
+ // Mutate directly
539
+ Object.assign(current, newItems);
540
+ if (!current._meta) current._meta = {};
541
+ current._meta.arrayKeys = newKeys; // Direct assignment!
541
542
  },
542
543
 
543
544
  insertShadowArrayElement: (key, arrayPath, newItem, index) => {
@@ -552,15 +553,21 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
552
553
  const newItemId = `id:${ulid()}`;
553
554
  const itemsToAdd = { [newItemId]: buildShadowNode(newItem) };
554
555
 
556
+ // Mutate the array directly
555
557
  const currentKeys = arrayNode._meta.arrayKeys;
556
- const newKeys = [...currentKeys];
557
558
  const insertionPoint =
558
- index !== undefined && index >= 0 && index <= newKeys.length
559
+ index !== undefined && index >= 0 && index <= currentKeys.length
559
560
  ? index
560
- : newKeys.length;
561
- newKeys.splice(insertionPoint, 0, newItemId);
561
+ : currentKeys.length;
562
+
563
+ if (insertionPoint >= currentKeys.length) {
564
+ currentKeys.push(newItemId); // O(1)
565
+ } else {
566
+ currentKeys.splice(insertionPoint, 0, newItemId); // O(n) only for middle
567
+ }
562
568
 
563
- get().addItemsToArrayNode(key, arrayPath, itemsToAdd, newKeys);
569
+ // Pass the mutated array
570
+ get().addItemsToArrayNode(key, arrayPath, itemsToAdd, currentKeys);
564
571
 
565
572
  const arrayKey = [key, ...arrayPath].join('.');
566
573
  get().notifyPathSubscribers(arrayKey, {
@@ -570,6 +577,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
570
577
  index: insertionPoint,
571
578
  });
572
579
  },
580
+
573
581
  insertManyShadowArrayElements: (key, arrayPath, newItems, index) => {
574
582
  if (!newItems || newItems.length === 0) {
575
583
  return;
@@ -592,15 +600,20 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
592
600
  itemsToAdd[newItemId] = buildShadowNode(item);
593
601
  });
594
602
 
603
+ // Mutate directly
595
604
  const currentKeys = arrayNode._meta.arrayKeys;
596
- const finalKeys = [...currentKeys];
597
605
  const insertionPoint =
598
- index !== undefined && index >= 0 && index <= finalKeys.length
606
+ index !== undefined && index >= 0 && index <= currentKeys.length
599
607
  ? index
600
- : finalKeys.length;
601
- finalKeys.splice(insertionPoint, 0, ...newIds);
608
+ : currentKeys.length;
602
609
 
603
- get().addItemsToArrayNode(key, arrayPath, itemsToAdd, finalKeys);
610
+ if (insertionPoint >= currentKeys.length) {
611
+ currentKeys.push(...newIds); // O(k) where k is items being added
612
+ } else {
613
+ currentKeys.splice(insertionPoint, 0, ...newIds); // O(n + k)
614
+ }
615
+
616
+ get().addItemsToArrayNode(key, arrayPath, itemsToAdd, currentKeys);
604
617
 
605
618
  const arrayKey = [key, ...arrayPath].join('.');
606
619
  get().notifyPathSubscribers(arrayKey, {
@@ -621,10 +634,27 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
621
634
  const arrayNode = get().getShadowNode(key, arrayPath);
622
635
  if (!arrayNode?._meta?.arrayKeys) return;
623
636
 
624
- const newKeys = arrayNode._meta.arrayKeys.filter((k) => k !== itemId);
637
+ // Mutate directly
638
+ const currentKeys = arrayNode._meta.arrayKeys;
639
+ const indexToRemove = currentKeys.indexOf(itemId);
640
+
641
+ if (indexToRemove === -1) return;
642
+
643
+ // O(1) for removing from end
644
+ if (indexToRemove === currentKeys.length - 1) {
645
+ currentKeys.pop();
646
+ }
647
+ // O(n) for removing from beginning or middle
648
+ else if (indexToRemove === 0) {
649
+ currentKeys.shift();
650
+ } else {
651
+ currentKeys.splice(indexToRemove, 1);
652
+ }
653
+
654
+ // Delete the actual item
625
655
  delete arrayNode[itemId];
626
656
 
627
- get().setShadowMetadata(key, arrayPath, { arrayKeys: newKeys });
657
+ // No need to update metadata - already mutated!
628
658
 
629
659
  const arrayKey = [key, ...arrayPath].join('.');
630
660
  get().notifyPathSubscribers(arrayKey, {
@@ -634,6 +664,22 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
634
664
  });
635
665
  },
636
666
 
667
+ registerComponent: (stateKey, fullComponentId, registration) => {
668
+ const rootMeta = get().getShadowMetadata(stateKey, []) || {};
669
+ const components = new Map(rootMeta.components);
670
+ components.set(fullComponentId, registration);
671
+ get().setShadowMetadata(stateKey, [], { components });
672
+ },
673
+
674
+ unregisterComponent: (stateKey, fullComponentId) => {
675
+ const rootMeta = get().getShadowMetadata(stateKey, []);
676
+ if (!rootMeta?.components) return;
677
+ const components = new Map(rootMeta.components);
678
+ if (components.delete(fullComponentId)) {
679
+ get().setShadowMetadata(stateKey, [], { components });
680
+ }
681
+ },
682
+
637
683
  addPathComponent: (stateKey, dependencyPath, fullComponentId) => {
638
684
  const metadata = get().getShadowMetadata(stateKey, dependencyPath) || {};
639
685
  const newPathComponents = new Set(metadata.pathComponents);
@@ -657,22 +703,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
657
703
  }
658
704
  },
659
705
 
660
- registerComponent: (stateKey, fullComponentId, registration) => {
661
- const rootMeta = get().getShadowMetadata(stateKey, []) || {};
662
- const components = new Map(rootMeta.components);
663
- components.set(fullComponentId, registration);
664
- get().setShadowMetadata(stateKey, [], { components });
665
- },
666
-
667
- unregisterComponent: (stateKey, fullComponentId) => {
668
- const rootMeta = get().getShadowMetadata(stateKey, []);
669
- if (!rootMeta?.components) return;
670
- const components = new Map(rootMeta.components);
671
- if (components.delete(fullComponentId)) {
672
- get().setShadowMetadata(stateKey, [], { components });
673
- }
674
- },
675
-
676
706
  markAsDirty: (key, path, options = { bubble: true }) => {
677
707
  const setDirtyOnPath = (pathToMark: string[]) => {
678
708
  const node = get().getShadowNode(key, pathToMark);
@@ -696,6 +726,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
696
726
  }
697
727
  },
698
728
 
729
+ // Keep these in Zustand as they need React reactivity
699
730
  serverStateUpdates: new Map(),
700
731
  setServerStateUpdate: (key, serverState) => {
701
732
  set((state) => ({
@@ -727,6 +758,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
727
758
  }
728
759
  };
729
760
  },
761
+
730
762
  notifyPathSubscribers: (updatedPath, newValue) => {
731
763
  const subscribers = get().pathSubscribers;
732
764
  const subs = subscribers.get(updatedPath);
@@ -734,6 +766,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
734
766
  subs.forEach((callback) => callback(newValue));
735
767
  }
736
768
  },
769
+
737
770
  selectedIndicesMap: new Map<string, string>(),
738
771
  getSelectedIndex: (arrayKey, validIds) => {
739
772
  const itemKey = get().selectedIndicesMap.get(arrayKey);
@@ -750,7 +783,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
750
783
 
751
784
  setSelectedIndex: (arrayKey: string, itemKey: string | undefined) => {
752
785
  set((state) => {
753
- const newMap = new Map(state.selectedIndicesMap); // CREATE A NEW MAP!
786
+ const newMap = new Map(state.selectedIndicesMap);
754
787
 
755
788
  if (itemKey === undefined) {
756
789
  newMap.delete(arrayKey);
@@ -775,7 +808,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
775
808
 
776
809
  clearSelectedIndex: ({ arrayKey }: { arrayKey: string }): void => {
777
810
  set((state) => {
778
- const newMap = new Map(state.selectedIndicesMap); // CREATE A NEW MAP!
811
+ const newMap = new Map(state.selectedIndicesMap);
779
812
  const actualKey = newMap.get(arrayKey);
780
813
  if (actualKey) {
781
814
  get().notifyPathSubscribers(actualKey, {
@@ -793,6 +826,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
793
826
  };
794
827
  });
795
828
  },
829
+
796
830
  clearSelectedIndexesForState: (stateKey) => {
797
831
  set((state) => {
798
832
  const newMap = new Map(state.selectedIndicesMap);
@@ -838,6 +872,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
838
872
  initialStateOptions: { ...prev.initialStateOptions, [key]: value },
839
873
  }));
840
874
  },
875
+
841
876
  updateInitialStateGlobal: (key, newState) => {
842
877
  set((prev) => ({
843
878
  initialStateGlobal: { ...prev.initialStateGlobal, [key]: newState },