cogsbox-state 0.5.431 → 0.5.434

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
@@ -1,13 +1,14 @@
1
- import { create } from "zustand";
2
- import { ulid } from "ulid";
1
+ import { create } from 'zustand';
2
+ import { ulid } from 'ulid';
3
3
  import type {
4
4
  OptionsType,
5
5
  ReactivityType,
6
6
  StateKeys,
7
- SyncActionsType,
8
7
  SyncInfo,
9
8
  UpdateTypeDetail,
10
- } from "./CogsState.js";
9
+ } from './CogsState.js';
10
+
11
+ import type { ReactNode } from 'react';
11
12
 
12
13
  type StateUpdater<StateValue> =
13
14
  | StateValue
@@ -29,18 +30,7 @@ export type TrieNode = {
29
30
  subscribers: Set<string>;
30
31
  children: Map<string, TrieNode>;
31
32
  };
32
- export type ComponentsType = {
33
- components: Map<
34
- string,
35
- {
36
- forceUpdate: () => void;
37
- paths: Set<string>;
38
- deps?: any[];
39
- depsFunction?: (state: any) => any[] | true;
40
- reactiveType: ReactivityType[] | ReactivityType;
41
- }
42
- >;
43
- };
33
+
44
34
  export type FormRefStoreState = {
45
35
  formRefs: Map<string, React.RefObject<any>>;
46
36
  registerFormRef: (id: string, ref: React.RefObject<any>) => void;
@@ -74,7 +64,7 @@ export const formRefStore = create<FormRefStoreState>((set, get) => ({
74
64
  // Get all refs that start with the stateKey prefix
75
65
  getFormRefsByStateKey: (stateKey) => {
76
66
  const allRefs = get().formRefs;
77
- const stateKeyPrefix = stateKey + ".";
67
+ const stateKeyPrefix = stateKey + '.';
78
68
  const filteredRefs = new Map();
79
69
 
80
70
  allRefs.forEach((ref, id) => {
@@ -86,21 +76,95 @@ export const formRefStore = create<FormRefStoreState>((set, get) => ({
86
76
  return filteredRefs;
87
77
  },
88
78
  }));
89
-
79
+ export type ComponentsType = {
80
+ components?: Map<
81
+ string,
82
+ {
83
+ forceUpdate: () => void;
84
+ paths: Set<string>;
85
+ deps?: any[];
86
+ prevDeps?: any[];
87
+ depsFunction?: (state: any) => any[] | true;
88
+ reactiveType: ReactivityType[] | ReactivityType;
89
+ }
90
+ >;
91
+ };
90
92
  export type ShadowMetadata = {
91
- id: string;
93
+ id?: string;
94
+
95
+ stateSource?: 'default' | 'server' | 'localStorage';
96
+ lastServerSync?: number;
97
+ isDirty?: boolean;
98
+ baseServerState?: any;
99
+
92
100
  arrayKeys?: string[];
101
+
102
+ fields?: Record<string, any>;
93
103
  virtualizer?: {
94
104
  itemHeight?: number;
95
105
  domRef?: HTMLElement | null;
96
106
  };
97
107
  syncInfo?: { status: string };
98
108
  lastUpdated?: number;
99
- };
109
+ value?: any;
110
+ classSignals?: Array<{
111
+ // <-- ADD THIS BLOCK
112
+ id: string;
113
+ effect: string;
114
+ lastClasses: string;
115
+ deps: any[];
116
+ }>;
117
+ signals?: Array<{
118
+ instanceId: string;
119
+ parentId: string;
120
+ position: number;
121
+ effect?: string;
122
+ }>;
123
+ mapWrappers?: Array<{
124
+ instanceId: string;
125
+ path: string[];
126
+ componentId: string;
127
+ meta?: any;
128
+ mapFn: (
129
+ setter: any,
130
+ index: number,
131
+
132
+ arraySetter: any
133
+ ) => ReactNode;
134
+ containerRef: HTMLDivElement | null;
135
+ rebuildStateShape: any;
136
+ }>;
137
+ transformCaches?: Map<
138
+ string,
139
+ {
140
+ validIds: string[];
141
+ computedAt: number;
142
+ transforms: Array<{ type: 'filter' | 'sort'; fn: Function }>;
143
+ }
144
+ >;
145
+ pathComponents?: Set<string>;
146
+ streams?: Map<
147
+ string,
148
+ {
149
+ buffer: any[];
150
+ flushTimer: NodeJS.Timeout | null;
151
+ }
152
+ >;
153
+ } & ComponentsType;
154
+ export type CogsEvent =
155
+ | { type: 'INSERT'; path: string; itemKey: string; index: number }
156
+ | { type: 'REMOVE'; path: string; itemKey: string }
157
+ | { type: 'UPDATE'; path: string; newValue: any }
158
+ | { type: 'ITEMHEIGHT'; itemKey: string; height: number } // For full re-initializations (e.g., when a component is removed)
159
+ | { type: 'RELOAD'; path: string }; // For full re-initializations
100
160
  export type CogsGlobalState = {
101
161
  // --- Shadow State and Subscription System ---
102
162
  shadowStateStore: Map<string, ShadowMetadata>;
103
-
163
+ markAsDirty: (
164
+ key: string,
165
+ path: string[],
166
+ options: { bubble: boolean }
167
+ ) => void;
104
168
  // These method signatures stay the same
105
169
  initializeShadowState: (key: string, initialState: any) => void;
106
170
  updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
@@ -110,6 +174,12 @@ export type CogsGlobalState = {
110
174
  newItem: any
111
175
  ) => void;
112
176
  removeShadowArrayElement: (key: string, arrayPath: string[]) => void;
177
+ getShadowValue: (
178
+ key: string,
179
+
180
+ validArrayIds?: string[]
181
+ ) => any;
182
+
113
183
  getShadowMetadata: (
114
184
  key: string,
115
185
  path: string[]
@@ -117,60 +187,36 @@ export type CogsGlobalState = {
117
187
  setShadowMetadata: (
118
188
  key: string,
119
189
  path: string[],
120
- metadata: Omit<ShadowMetadata, "id">
190
+ metadata: Omit<ShadowMetadata, 'id'>
121
191
  ) => void;
122
-
123
- shadowStateSubscribers: Map<string, Set<() => void>>; // Stores subscribers for shadow state updates
124
- subscribeToShadowState: (key: string, callback: () => void) => () => void;
125
-
126
- selectedIndicesMap: Map<string, Map<string, number>>; // stateKey -> (parentPath -> selectedIndex)
127
- getSelectedIndex: (
128
- stateKey: string,
129
- parentPath: string
130
- ) => number | undefined;
131
- setSelectedIndex: (
132
- stateKey: string,
133
- parentPath: string,
134
- index: number | undefined
192
+ setTransformCache: (
193
+ key: string,
194
+ path: string[],
195
+ cacheKey: string,
196
+ cacheData: any
135
197
  ) => void;
136
- clearSelectedIndex: ({
137
- stateKey,
138
- path,
139
- }: {
140
- stateKey: string;
141
- path: string[];
142
- }) => void;
198
+
199
+ pathSubscribers: Map<string, Set<(newValue: any) => void>>;
200
+ subscribeToPath: (
201
+ path: string,
202
+ callback: (newValue: any) => void
203
+ ) => () => void;
204
+ notifyPathSubscribers: (updatedPath: string, newValue: any) => void;
205
+
206
+ selectedIndicesMap: Map<string, string>; // stateKey -> (parentPath -> selectedIndex)
207
+ getSelectedIndex: (stateKey: string, validArrayIds?: string[]) => number;
208
+ setSelectedIndex: (key: string, itemKey: string) => void;
209
+ clearSelectedIndex: ({ arrayKey }: { arrayKey: string }) => void;
143
210
  clearSelectedIndexesForState: (stateKey: string) => void;
144
211
 
145
212
  // --- Core State and Updaters ---
146
- updaterState: { [key: string]: any };
213
+
147
214
  initialStateOptions: { [key: string]: OptionsType };
148
- cogsStateStore: { [key: string]: StateValue };
149
- isLoadingGlobal: { [key: string]: boolean };
215
+
150
216
  initialStateGlobal: { [key: string]: StateValue };
151
- iniitialCreatedState: { [key: string]: StateValue };
152
- serverState: { [key: string]: StateValue };
153
-
154
- getUpdaterState: (key: string) => StateUpdater<StateValue>;
155
- setUpdaterState: (key: string, newUpdater: any) => void;
156
- getKeyState: <StateKey extends StateKeys>(key: StateKey) => StateValue;
157
- getNestedState: <StateKey extends StateKeys>(
158
- key: StateKey,
159
- path: string[]
160
- ) => StateValue;
161
- setState: <StateKey extends StateKeys>(
162
- key: StateKey,
163
- value: StateUpdater<StateValue>
164
- ) => void;
165
- setInitialStates: (initialState: StateValue) => void;
166
- setCreatedState: (initialState: StateValue) => void;
217
+
167
218
  updateInitialStateGlobal: (key: string, newState: StateValue) => void;
168
- updateInitialCreatedState: (key: string, newState: StateValue) => void;
169
- setIsLoadingGlobal: (key: string, value: boolean) => void;
170
- setServerState: <StateKey extends StateKeys>(
171
- key: StateKey,
172
- value: StateValue
173
- ) => void;
219
+
174
220
  getInitialOptions: (key: string) => OptionsType | undefined;
175
221
  setInitialStateOptions: (key: string, value: OptionsType) => void;
176
222
 
@@ -181,275 +227,432 @@ export type CogsGlobalState = {
181
227
  removeValidationError: (path: string) => void;
182
228
 
183
229
  // --- Server Sync and Logging ---
184
- serverSyncActions: { [key: string]: SyncActionsType<any> };
185
- serverSyncLog: { [key: string]: SyncLogType[] };
230
+
231
+ serverStateUpdates: Map<
232
+ string,
233
+ {
234
+ data: any;
235
+ status: 'loading' | 'success' | 'error';
236
+ timestamp: number;
237
+ }
238
+ >;
239
+
240
+ setServerStateUpdate: (key: string, serverState: any) => void;
241
+
186
242
  stateLog: { [key: string]: UpdateTypeDetail[] };
187
243
  syncInfoStore: Map<string, SyncInfo>;
188
- serverSideOrNot: { [key: string]: boolean };
189
- setServerSyncLog: (key: string, newValue: SyncLogType) => void;
190
- setServerSideOrNot: (key: string, value: boolean) => void;
191
- getServerSideOrNot: (key: string) => boolean | undefined;
192
- getThisLocalUpdate: (key: string) => UpdateTypeDetail[] | undefined;
193
- setServerSyncActions: (key: string, value: SyncActionsType<any>) => void;
244
+
194
245
  setStateLog: (
195
246
  key: string,
196
247
  updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
197
248
  ) => void;
198
249
  setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
199
250
  getSyncInfo: (key: string) => SyncInfo | null;
251
+ };
252
+ const isSimpleObject = (value: any): boolean => {
253
+ if (value === null || typeof value !== 'object') return false;
254
+
255
+ // Handle special cases that should be treated as primitives
256
+ if (
257
+ value instanceof Uint8Array ||
258
+ value instanceof Int8Array ||
259
+ value instanceof Uint16Array ||
260
+ value instanceof Int16Array ||
261
+ value instanceof Uint32Array ||
262
+ value instanceof Int32Array ||
263
+ value instanceof Float32Array ||
264
+ value instanceof Float64Array ||
265
+ value instanceof ArrayBuffer ||
266
+ value instanceof Date ||
267
+ value instanceof RegExp ||
268
+ value instanceof Map ||
269
+ value instanceof Set
270
+ ) {
271
+ return false; // Treat as primitive
272
+ }
273
+
274
+ // Arrays and plain objects are complex
275
+ return Array.isArray(value) || value.constructor === Object;
276
+ };
277
+ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
278
+ markAsDirty: (key: string, path: string[], options = { bubble: true }) => {
279
+ const newShadowStore = new Map(get().shadowStateStore);
280
+ let changed = false;
281
+
282
+ // This function marks a single path as dirty if it was previously synced.
283
+ const setDirty = (currentPath: string[]) => {
284
+ const fullKey = [key, ...currentPath].join('.');
285
+ const meta = newShadowStore.get(fullKey);
286
+
287
+ // We only mark something as dirty if it was previously synced from the server.
288
+ // We also check `isDirty !== true` to avoid redundant updates.
289
+ if (meta && meta.stateSource === 'server' && meta.isDirty !== true) {
290
+ newShadowStore.set(fullKey, { ...meta, isDirty: true });
291
+ changed = true;
292
+ }
293
+ };
200
294
 
201
- // --- Component and DOM Integration ---
202
- signalDomElements: Map<
203
- string,
204
- Set<{
205
- instanceId: string;
206
- parentId: string;
207
- position: number;
208
- effect?: string;
209
- map?: string;
210
- }>
211
- >;
212
- addSignalElement: (
213
- signalId: string,
214
- elementInfo: {
215
- instanceId: string;
216
- parentId: string;
217
- position: number;
218
- effect?: string;
219
- map?: string;
220
- }
221
- ) => void;
222
- removeSignalElement: (signalId: string, instanceId: string) => void;
223
- stateComponents: Map<string, ComponentsType>;
295
+ // 1. Mark the target path itself as dirty.
296
+ setDirty(path);
224
297
 
225
- // --- Deprecated/Legacy (Review for removal) ---
226
- reRenderTriggerPrevValue: Record<string, any>;
227
- reactiveDeps: Record<
228
- string,
229
- {
230
- deps: any[];
231
- updaters: Set<() => void>;
232
- depsFunction: ((state: any) => any[] | true) | null;
298
+ // 2. If `bubble` is true, walk up the path and mark all parents as dirty.
299
+ if (options.bubble) {
300
+ let parentPath = [...path];
301
+ while (parentPath.length > 0) {
302
+ parentPath.pop();
303
+ setDirty(parentPath);
304
+ }
233
305
  }
234
- >;
235
- setReactiveDeps: (
236
- key: string,
237
- record: {
238
- deps: any[];
239
- updaters: Set<() => void>;
240
- depsFunction: ((state: any) => any[] | true) | null;
306
+
307
+ // Only update the global state if something actually changed.
308
+ if (changed) {
309
+ set({ shadowStateStore: newShadowStore });
241
310
  }
242
- ) => void;
243
- deleteReactiveDeps: (key: string) => void;
244
- subscribe: (listener: () => void) => () => void;
245
- };
311
+ },
312
+ serverStateUpdates: new Map(),
313
+ setServerStateUpdate: (key, serverState) => {
314
+ set((state) => {
315
+ const newMap = new Map(state.serverStateUpdates);
316
+ newMap.set(key, serverState);
317
+ return { serverStateUpdates: newMap };
318
+ });
246
319
 
247
- export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
320
+ // Notify all subscribers for this key
321
+ get().notifyPathSubscribers(key, {
322
+ type: 'SERVER_STATE_UPDATE',
323
+ serverState,
324
+ });
325
+ },
248
326
  shadowStateStore: new Map(),
249
- shadowStateSubscribers: new Map(),
327
+ pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
250
328
 
251
- subscribeToShadowState: (key: string, callback: () => void) => {
252
- set((state) => {
253
- const newSubs = new Map(state.shadowStateSubscribers);
254
- const subsForKey = newSubs.get(key) || new Set();
255
- subsForKey.add(callback);
256
- newSubs.set(key, subsForKey);
257
- return { shadowStateSubscribers: newSubs };
258
- });
329
+ subscribeToPath: (path, callback) => {
330
+ const subscribers = get().pathSubscribers;
331
+ const subsForPath = subscribers.get(path) || new Set();
332
+ subsForPath.add(callback);
333
+ subscribers.set(path, subsForPath);
259
334
 
260
- // Return unsubscribe function
261
335
  return () => {
262
- set((state) => {
263
- const newSubs = new Map(state.shadowStateSubscribers);
264
- const subsForKey = newSubs.get(key);
265
- if (subsForKey) {
266
- subsForKey.delete(callback);
267
- if (subsForKey.size === 0) {
268
- newSubs.delete(key);
269
- }
336
+ const currentSubs = get().pathSubscribers.get(path);
337
+ if (currentSubs) {
338
+ currentSubs.delete(callback);
339
+ if (currentSubs.size === 0) {
340
+ get().pathSubscribers.delete(path);
270
341
  }
271
- return { shadowStateSubscribers: newSubs };
272
- });
342
+ }
273
343
  };
274
344
  },
345
+
346
+ notifyPathSubscribers: (updatedPath, newValue) => {
347
+ // <-- Now accepts newValue
348
+ const subscribers = get().pathSubscribers;
349
+ const subs = subscribers.get(updatedPath);
350
+
351
+ if (subs) {
352
+ // Pass the newValue to every callback
353
+ subs.forEach((callback) => callback(newValue));
354
+ }
355
+ },
275
356
  initializeShadowState: (key: string, initialState: any) => {
276
- const newShadowStore = new Map<string, ShadowMetadata>();
357
+ const existingShadowStore = new Map(get().shadowStateStore);
277
358
 
278
359
  const processValue = (value: any, path: string[]) => {
279
- const nodeKey = [key, ...path].join(".");
360
+ const nodeKey = [key, ...path].join('.');
280
361
 
281
362
  if (Array.isArray(value)) {
363
+ // Handle arrays as before
282
364
  const childIds: string[] = [];
283
365
 
284
366
  value.forEach((item) => {
285
- if (typeof item === "object" && item !== null && !item.id) {
286
- item.id = ulid();
287
- }
367
+ const itemId = `id:${ulid()}`;
368
+ childIds.push(nodeKey + '.' + itemId);
369
+ });
288
370
 
289
- const itemId = `id:${item.id}`;
290
- childIds.push(itemId);
371
+ existingShadowStore.set(nodeKey, { arrayKeys: childIds });
291
372
 
292
- const itemPath = [...path, itemId];
293
- processValue(item, itemPath);
373
+ value.forEach((item, index) => {
374
+ const itemId = childIds[index]!.split('.').pop();
375
+ processValue(item, [...path!, itemId!]);
294
376
  });
295
-
296
- const arrayContainerMetadata: ShadowMetadata = {
297
- id: ulid(),
298
- arrayKeys: childIds,
299
- };
300
- newShadowStore.set(nodeKey, arrayContainerMetadata);
301
- } else if (typeof value === "object" && value !== null) {
302
- newShadowStore.set(nodeKey, { id: ulid() });
377
+ } else if (isSimpleObject(value)) {
378
+ // Only create field mappings for simple objects
379
+ const fields = Object.fromEntries(
380
+ Object.keys(value).map((k) => [k, nodeKey + '.' + k])
381
+ );
382
+ existingShadowStore.set(nodeKey, { fields });
303
383
 
304
384
  Object.keys(value).forEach((k) => {
305
385
  processValue(value[k], [...path, k]);
306
386
  });
307
387
  } else {
308
- newShadowStore.set(nodeKey, { id: ulid() });
388
+ // Treat everything else (including Uint8Array) as primitive values
389
+ existingShadowStore.set(nodeKey, { value });
309
390
  }
310
391
  };
311
392
 
312
393
  processValue(initialState, []);
394
+ set({ shadowStateStore: existingShadowStore });
395
+ },
313
396
 
314
- set({ shadowStateStore: newShadowStore });
397
+ getShadowValue: (fullKey: string, validArrayIds?: string[]) => {
398
+ const shadowMeta = get().shadowStateStore.get(fullKey);
399
+
400
+ // If no metadata found, return undefined
401
+ if (!shadowMeta) {
402
+ return undefined;
403
+ }
404
+
405
+ // For primitive values, return the value
406
+ if (shadowMeta.value !== undefined) {
407
+ return shadowMeta.value;
408
+ }
409
+
410
+ // For arrays, reconstruct with possible validArrayIds
411
+ if (shadowMeta.arrayKeys) {
412
+ const arrayKeys = validArrayIds ?? shadowMeta.arrayKeys;
413
+ const items = arrayKeys.map((itemKey) => {
414
+ // RECURSIVELY call getShadowValue for each item
415
+ return get().getShadowValue(itemKey);
416
+ });
417
+ return items;
418
+ }
419
+
420
+ // For objects with fields, reconstruct object
421
+ if (shadowMeta.fields) {
422
+ const reconstructedObject: any = {};
423
+ Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
424
+ // RECURSIVELY call getShadowValue for each field
425
+ reconstructedObject[key] = get().getShadowValue(fieldPath as string);
426
+ });
427
+ return reconstructedObject;
428
+ }
429
+
430
+ return undefined;
315
431
  },
316
- getShadowMetadata: (key: string, path: string[]) => {
317
- const fullKey = [key, ...path].join(".");
432
+ getShadowMetadata: (
433
+ key: string,
434
+ path: string[],
435
+ validArrayIds?: string[]
436
+ ) => {
437
+ const fullKey = [key, ...path].join('.');
438
+ let data = get().shadowStateStore.get(fullKey);
439
+
318
440
  return get().shadowStateStore.get(fullKey);
319
441
  },
320
442
 
321
443
  setShadowMetadata: (key: string, path: string[], metadata: any) => {
322
- const fullKey = [key, ...path].join(".");
444
+ const fullKey = [key, ...path].join('.');
323
445
  const newShadowStore = new Map(get().shadowStateStore);
324
446
  const existing = newShadowStore.get(fullKey) || { id: ulid() };
325
447
  newShadowStore.set(fullKey, { ...existing, ...metadata });
326
448
  set({ shadowStateStore: newShadowStore });
449
+ },
450
+ setTransformCache: (
451
+ key: string,
452
+ path: string[],
453
+ cacheKey: string,
454
+ cacheData: any
455
+ ) => {
456
+ const fullKey = [key, ...path].join('.');
457
+ const newShadowStore = new Map(get().shadowStateStore);
458
+ const existing = newShadowStore.get(fullKey) || {};
327
459
 
328
- if (metadata.virtualizer?.itemHeight) {
329
- const subscribers = get().shadowStateSubscribers.get(key);
330
- subscribers?.forEach((cb) => cb());
460
+ // Initialize transformCaches if it doesn't exist
461
+ if (!existing.transformCaches) {
462
+ existing.transformCaches = new Map();
331
463
  }
332
- },
333
464
 
465
+ // Update just the specific cache entry
466
+ existing.transformCaches.set(cacheKey, cacheData);
467
+
468
+ // Update shadow store WITHOUT notifying path subscribers
469
+ newShadowStore.set(fullKey, existing);
470
+ set({ shadowStateStore: newShadowStore });
471
+
472
+ // Don't call notifyPathSubscribers here - cache updates shouldn't trigger renders
473
+ },
334
474
  insertShadowArrayElement: (
335
475
  key: string,
336
476
  arrayPath: string[],
337
477
  newItem: any
338
478
  ) => {
339
479
  const newShadowStore = new Map(get().shadowStateStore);
340
- const arrayKey = [key, ...arrayPath].join(".");
480
+ const arrayKey = [key, ...arrayPath].join('.');
341
481
  const parentMeta = newShadowStore.get(arrayKey);
342
- const newArrayState = get().getNestedState(key, arrayPath) as any[];
343
482
 
344
483
  if (!parentMeta || !parentMeta.arrayKeys) return;
345
484
 
346
- const newItemId = `id:${newItem.id}`;
347
- const newIndex = newArrayState.findIndex((item) => item.id === newItem.id);
348
-
349
- if (newIndex === -1) return;
485
+ const newItemId = `id:${ulid()}`;
486
+ const fullItemKey = arrayKey + '.' + newItemId;
350
487
 
488
+ // Just add to the end (or at a specific index if provided)
351
489
  const newArrayKeys = [...parentMeta.arrayKeys];
352
- newArrayKeys.splice(newIndex, 0, newItemId);
490
+ newArrayKeys.push(fullItemKey); // Or use splice if you have an index
353
491
  newShadowStore.set(arrayKey, { ...parentMeta, arrayKeys: newArrayKeys });
354
492
 
493
+ // Process the new item - but use the correct logic
355
494
  const processNewItem = (value: any, path: string[]) => {
356
- const nodeKey = [key, ...path].join(".");
357
- if (typeof value === "object" && value !== null) {
358
- newShadowStore.set(nodeKey, { id: ulid() });
359
- Object.keys(value).forEach((k) => {
360
- processNewItem(value[k], [...path, k]);
495
+ const nodeKey = [key, ...path].join('.');
496
+
497
+ if (Array.isArray(value)) {
498
+ // Handle arrays...
499
+ } else if (typeof value === 'object' && value !== null) {
500
+ // Create fields mapping
501
+ const fields = Object.fromEntries(
502
+ Object.keys(value).map((k) => [k, nodeKey + '.' + k])
503
+ );
504
+ newShadowStore.set(nodeKey, { fields });
505
+
506
+ // Process each field
507
+ Object.entries(value).forEach(([k, v]) => {
508
+ processNewItem(v, [...path, k]);
361
509
  });
362
510
  } else {
363
- newShadowStore.set(nodeKey, { id: ulid() });
511
+ // Primitive value
512
+ newShadowStore.set(nodeKey, { value });
364
513
  }
365
514
  };
366
515
 
367
516
  processNewItem(newItem, [...arrayPath, newItemId]);
368
-
369
517
  set({ shadowStateStore: newShadowStore });
370
- },
371
518
 
519
+ get().notifyPathSubscribers(arrayKey, {
520
+ type: 'INSERT',
521
+ path: arrayKey,
522
+ itemKey: fullItemKey,
523
+ });
524
+ },
372
525
  removeShadowArrayElement: (key: string, itemPath: string[]) => {
373
526
  const newShadowStore = new Map(get().shadowStateStore);
374
- const itemKey = [key, ...itemPath].join(".");
375
- const itemIdToRemove = itemPath[itemPath.length - 1];
376
527
 
528
+ // Get the full item key (e.g., "stateKey.products.id:xxx")
529
+ const itemKey = [key, ...itemPath].join('.');
530
+
531
+ // Extract parent path and item ID
377
532
  const parentPath = itemPath.slice(0, -1);
378
- const parentKey = [key, ...parentPath].join(".");
533
+ const parentKey = [key, ...parentPath].join('.');
534
+
535
+ // Get parent metadata
379
536
  const parentMeta = newShadowStore.get(parentKey);
380
537
 
381
538
  if (parentMeta && parentMeta.arrayKeys) {
382
- const newArrayKeys = parentMeta.arrayKeys.filter(
383
- (id) => id !== itemIdToRemove
539
+ // Find the index of the item to remove
540
+ const indexToRemove = parentMeta.arrayKeys.findIndex(
541
+ (arrayItemKey) => arrayItemKey === itemKey
384
542
  );
385
- newShadowStore.set(parentKey, { ...parentMeta, arrayKeys: newArrayKeys });
386
- }
387
543
 
388
- const prefixToDelete = itemKey + ".";
389
- for (const k of Array.from(newShadowStore.keys())) {
390
- if (k === itemKey || k.startsWith(prefixToDelete)) {
391
- newShadowStore.delete(k);
544
+ if (indexToRemove !== -1) {
545
+ // Create new array keys with the item removed
546
+ const newArrayKeys = parentMeta.arrayKeys.filter(
547
+ (arrayItemKey) => arrayItemKey !== itemKey
548
+ );
549
+
550
+ // Update parent with new array keys
551
+ newShadowStore.set(parentKey, {
552
+ ...parentMeta,
553
+ arrayKeys: newArrayKeys,
554
+ });
555
+
556
+ // Delete all data associated with the removed item
557
+ const prefixToDelete = itemKey + '.';
558
+ for (const k of Array.from(newShadowStore.keys())) {
559
+ if (k === itemKey || k.startsWith(prefixToDelete)) {
560
+ newShadowStore.delete(k);
561
+ }
562
+ }
392
563
  }
393
564
  }
394
565
 
395
566
  set({ shadowStateStore: newShadowStore });
567
+
568
+ get().notifyPathSubscribers(parentKey, {
569
+ type: 'REMOVE',
570
+ path: parentKey,
571
+ itemKey: itemKey, // The exact ID of the removed item
572
+ });
396
573
  },
397
- updateShadowAtPath: (key: string, path: string[], newValue: any) => {
398
- const fullKey = [key, ...path].join(".");
574
+ updateShadowAtPath: (key, path, newValue) => {
399
575
  const newShadowStore = new Map(get().shadowStateStore);
400
- const existing = newShadowStore.get(fullKey) || { id: ulid() };
401
- newShadowStore.set(fullKey, { ...existing, lastUpdated: Date.now() });
576
+ const fullKey = [key, ...path].join('.');
577
+
578
+ const updateValue = (currentKey: string, valueToSet: any) => {
579
+ const meta = newShadowStore.get(currentKey);
580
+
581
+ // If it's a simple object with fields, update recursively
582
+ if (isSimpleObject(valueToSet) && meta && meta.fields) {
583
+ for (const fieldKey in valueToSet) {
584
+ if (Object.prototype.hasOwnProperty.call(valueToSet, fieldKey)) {
585
+ const childPath = meta.fields[fieldKey];
586
+ const childValue = valueToSet[fieldKey];
587
+
588
+ if (childPath) {
589
+ updateValue(childPath as string, childValue);
590
+ }
591
+ }
592
+ }
593
+ } else {
594
+ // For primitives (including Uint8Array), just replace the value
595
+ // This gives you useState-like behavior
596
+ const existing = newShadowStore.get(currentKey) || {};
597
+ newShadowStore.set(currentKey, { ...existing, value: valueToSet });
598
+ }
599
+ };
600
+
601
+ updateValue(fullKey, newValue);
602
+ get().notifyPathSubscribers(fullKey, { type: 'UPDATE', newValue });
402
603
  set({ shadowStateStore: newShadowStore });
403
604
  },
605
+ selectedIndicesMap: new Map<string, string>(),
606
+ getSelectedIndex: (arrayKey: string, validIds?: string[]): number => {
607
+ const itemKey = get().selectedIndicesMap.get(arrayKey);
608
+
609
+ if (!itemKey) return -1;
404
610
 
405
- selectedIndicesMap: new Map<string, Map<string, number>>(),
611
+ // Use validIds if provided (for filtered views), otherwise use all arrayKeys
612
+ const arrayKeys =
613
+ validIds ||
614
+ getGlobalStore.getState().getShadowMetadata(arrayKey, [])?.arrayKeys;
406
615
 
407
- // Add the new methods
408
- getSelectedIndex: (stateKey: string, parentPath: string) => {
409
- const stateMap = get().selectedIndicesMap.get(stateKey);
410
- if (!stateMap) return undefined;
411
- return stateMap.get(parentPath);
616
+ if (!arrayKeys) return -1;
617
+
618
+ return arrayKeys.indexOf(itemKey);
412
619
  },
413
620
 
414
- setSelectedIndex: (
415
- stateKey: string,
416
- parentPath: string,
417
- index: number | undefined
418
- ) => {
621
+ setSelectedIndex: (arrayKey: string, itemKey: string | undefined) => {
419
622
  set((state) => {
420
- const newMap = new Map(state.selectedIndicesMap);
421
- let stateMap = newMap.get(stateKey);
422
-
423
- if (!stateMap) {
424
- stateMap = new Map<string, number>();
425
- newMap.set(stateKey, stateMap);
426
- }
623
+ const newMap = state.selectedIndicesMap;
427
624
 
428
- if (index === undefined) {
429
- stateMap.delete(parentPath);
625
+ if (itemKey === undefined) {
626
+ newMap.delete(arrayKey);
430
627
  } else {
431
- stateMap.set(parentPath, index);
432
- }
628
+ if (newMap.has(arrayKey)) {
629
+ get().notifyPathSubscribers(newMap.get(arrayKey)!, {
630
+ type: 'THIS_UNSELECTED',
631
+ });
632
+ }
633
+ newMap.set(arrayKey, itemKey);
433
634
 
635
+ get().notifyPathSubscribers(itemKey, {
636
+ type: 'THIS_SELECTED',
637
+ });
638
+ }
639
+ get().notifyPathSubscribers(arrayKey, {
640
+ type: 'GET_SELECTED',
641
+ });
434
642
  return {
435
643
  ...state,
436
644
  selectedIndicesMap: newMap,
437
645
  };
438
646
  });
439
647
  },
440
- clearSelectedIndex: ({
441
- stateKey,
442
- path,
443
- }: {
444
- stateKey: string;
445
- path: string[];
446
- }) => {
648
+ clearSelectedIndex: ({ arrayKey }: { arrayKey: string }): void => {
447
649
  set((state) => {
448
- const newMap = new Map(state.selectedIndicesMap);
449
- const stateMap = newMap.get(stateKey);
450
- if (!stateMap) return state;
451
- const parentPath = path.join(".");
452
- stateMap.delete(parentPath);
650
+ const newMap = state.selectedIndicesMap;
651
+
652
+ newMap.delete(arrayKey);
653
+ get().notifyPathSubscribers(arrayKey, {
654
+ type: 'CLEAR_SELECTION',
655
+ });
453
656
  return {
454
657
  ...state,
455
658
  selectedIndicesMap: newMap,
@@ -461,126 +664,23 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
461
664
  const newOuterMap = new Map(state.selectedIndicesMap);
462
665
  const changed = newOuterMap.delete(stateKey);
463
666
  if (changed) {
464
- console.log(
465
- `Cleared selected indices map entry for stateKey: ${stateKey}`
466
- );
467
667
  return { selectedIndicesMap: newOuterMap };
468
668
  } else {
469
669
  return {};
470
670
  }
471
671
  });
472
672
  },
473
- stateComponents: new Map(),
474
- subscribe: (listener: () => void) => {
475
- // zustand's subscribe returns an unsubscribe function
476
- return get().subscribe(listener);
477
- },
478
-
479
- reactiveDeps: {},
480
- setReactiveDeps: (key, record) =>
481
- set((state) => ({
482
- ...state,
483
- reactiveDeps: {
484
- ...state.reactiveDeps,
485
- [key]: record,
486
- },
487
- })),
488
- deleteReactiveDeps: (key) =>
489
- set((state) => {
490
- const { [key]: _, ...rest } = state.reactiveDeps;
491
- return {
492
- ...state,
493
- reactiveDeps: rest,
494
- };
495
- }),
496
-
497
- reRenderTriggerPrevValue: {},
498
- signalDomElements: new Map(),
499
- addSignalElement: (
500
- signalId: string,
501
- elementInfo: { instanceId: string; parentId: string; position: number }
502
- ) => {
503
- const current = get().signalDomElements;
504
- if (!current.has(signalId)) {
505
- current.set(signalId, new Set());
506
- }
507
- current.get(signalId)!.add(elementInfo);
508
673
 
509
- set({ signalDomElements: new Map(current) }); // Create new reference to trigger update
510
- },
511
- removeSignalElement: (signalId: string, instanceId: string) => {
512
- const current = get().signalDomElements;
513
- const elements = current.get(signalId);
514
- if (elements) {
515
- elements.forEach((el) => {
516
- if (el.instanceId === instanceId) {
517
- elements.delete(el);
518
- }
519
- });
520
- }
521
- set({ signalDomElements: new Map(current) });
522
- },
523
674
  initialStateOptions: {},
524
- updaterState: {},
675
+
525
676
  stateTimeline: {},
526
677
  cogsStateStore: {},
527
678
  stateLog: {},
528
- isLoadingGlobal: {},
529
679
 
530
680
  initialStateGlobal: {},
531
- iniitialCreatedState: {},
532
- updateInitialCreatedState: (key, newState) => {
533
- set((prev) => ({
534
- iniitialCreatedState: {
535
- ...prev.iniitialCreatedState,
536
- [key]: newState,
537
- },
538
- }));
539
- },
540
681
 
541
682
  validationErrors: new Map(),
542
683
 
543
- serverState: {},
544
-
545
- serverSyncActions: {},
546
-
547
- serverSyncLog: {},
548
- serverSideOrNot: {},
549
- setServerSyncLog: (key, newValue) => {
550
- set((state) => ({
551
- serverSyncLog: {
552
- ...state.serverSyncLog,
553
- [key]: [...(state.serverSyncLog[key] ?? []), newValue],
554
- },
555
- }));
556
- },
557
- setServerSideOrNot: (key, value) => {
558
- set((state) => ({
559
- serverSideOrNot: {
560
- ...state.serverSideOrNot,
561
- [key]: value,
562
- },
563
- }));
564
- },
565
- getServerSideOrNot: (key) => {
566
- return get().serverSideOrNot[key];
567
- },
568
-
569
- getThisLocalUpdate: (key: string) => {
570
- return get().stateLog[key];
571
- },
572
- setServerState: <StateKey extends StateKeys>(
573
- key: StateKey,
574
- value: StateValue
575
- ) => {
576
- set((prev) => ({
577
- serverState: {
578
- ...prev.serverState,
579
- [key]: value,
580
- },
581
- }));
582
- },
583
-
584
684
  setStateLog: (
585
685
  key: string,
586
686
  updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
@@ -596,28 +696,12 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
596
696
  };
597
697
  });
598
698
  },
599
- setIsLoadingGlobal: (key: string, value: boolean) => {
600
- set((prev) => ({
601
- isLoadingGlobal: {
602
- ...prev.isLoadingGlobal,
603
- [key]: value,
604
- },
605
- }));
606
- },
607
- setServerSyncActions: (key: string, value: SyncActionsType<any>) => {
608
- set((prev) => ({
609
- serverSyncActions: {
610
- ...prev.serverSyncActions,
611
- [key]: value,
612
- },
613
- }));
614
- },
699
+
615
700
  addValidationError: (path, message) => {
616
- console.log("addValidationError---");
617
701
  set((prev) => {
618
702
  const updatedErrors = new Map(prev.validationErrors);
619
703
  const existingMessages = updatedErrors.get(path) || [];
620
- console.log("addValidationError", path, message, existingMessages);
704
+ console.log('addValidationError', path, message, existingMessages);
621
705
  // Append the new message instead of replacing
622
706
  updatedErrors.set(path, [...existingMessages, message]);
623
707
  return { validationErrors: updatedErrors };
@@ -628,9 +712,9 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
628
712
  const updatedErrors = new Map(prev.validationErrors);
629
713
 
630
714
  let doSomething = false;
631
- const pathArray = path.split(".");
715
+ const pathArray = path.split('.');
632
716
  Array.from(updatedErrors.keys()).forEach((key) => {
633
- const keyArray = key.split(".");
717
+ const keyArray = key.split('.');
634
718
  if (keyArray.length >= pathArray.length) {
635
719
  let match = true;
636
720
  for (let i = 0; i < pathArray.length; i++) {
@@ -653,11 +737,11 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
653
737
  getValidationErrors: (path: string) => {
654
738
  const errors: string[] = [];
655
739
  const valErrors = get().validationErrors;
656
- const pathArray = path.split(".");
740
+ const pathArray = path.split('.');
657
741
 
658
742
  // Helper to check if an index matches either a wildcard or is in an array of indices
659
743
  const isIndexMatch = (pathSegment: string, keySegment: string) => {
660
- if (pathSegment === "[*]") return true;
744
+ if (pathSegment === '[*]') return true;
661
745
  if (Array.isArray(pathSegment)) {
662
746
  return pathSegment.includes(parseInt(keySegment));
663
747
  }
@@ -665,7 +749,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
665
749
  };
666
750
 
667
751
  Array.from(valErrors.keys()).forEach((key) => {
668
- const keyArray = key.split(".");
752
+ const keyArray = key.split('.');
669
753
  if (keyArray.length >= pathArray.length) {
670
754
  let match = true;
671
755
  for (let i = 0; i < pathArray.length; i++) {
@@ -673,7 +757,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
673
757
  const keySegment = keyArray[i]!;
674
758
 
675
759
  // If current path segment is a number or [*], we need special handling
676
- if (pathSegment === "[*]" || Array.isArray(pathSegment)) {
760
+ if (pathSegment === '[*]' || Array.isArray(pathSegment)) {
677
761
  // Key segment should be a number if we're using [*] or array indices
678
762
  const keyIndex = parseInt(keySegment);
679
763
  if (isNaN(keyIndex)) {
@@ -705,48 +789,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
705
789
  getInitialOptions: (key) => {
706
790
  return get().initialStateOptions[key];
707
791
  },
708
- getNestedState: (key: string, path: string[]) => {
709
- const rootState = get().cogsStateStore[key];
710
-
711
- const resolvePath = (obj: any, pathArray: string[]): any => {
712
- if (pathArray.length === 0 || obj === undefined) {
713
- return obj;
714
- }
715
-
716
- const currentSegment = pathArray[0];
717
- const remainingPath = pathArray.slice(1);
718
-
719
- // FIX: Handle ID-based array access like 'id:xyz'
720
- if (
721
- Array.isArray(obj) &&
722
- typeof currentSegment === "string" &&
723
- currentSegment.startsWith("id:")
724
- ) {
725
- const targetId = currentSegment.split(":")[1];
726
- const foundItem = obj.find(
727
- (item) => item && String(item.id) === targetId
728
- );
729
- return resolvePath(foundItem, remainingPath);
730
- }
731
-
732
- // Handle wildcard array access: '[*]'
733
- if (currentSegment === "[*]") {
734
- if (!Array.isArray(obj)) {
735
- console.warn("Asterisk notation used on non-array value");
736
- return undefined;
737
- }
738
- if (remainingPath.length === 0) return obj;
739
- const results = obj.map((item) => resolvePath(item, remainingPath));
740
- return Array.isArray(results[0]) ? results.flat() : results;
741
- }
742
-
743
- // Handle standard object property access and numeric array indices
744
- const nextObj = obj[currentSegment as keyof typeof obj];
745
- return resolvePath(nextObj, remainingPath);
746
- };
747
792
 
748
- return resolvePath(rootState, path);
749
- },
750
793
  setInitialStateOptions: (key, value) => {
751
794
  set((prev) => ({
752
795
  initialStateOptions: {
@@ -763,49 +806,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
763
806
  },
764
807
  }));
765
808
  },
766
- getUpdaterState: (key) => {
767
- return get().updaterState[key];
768
- },
769
- setUpdaterState: (key, newUpdater) => {
770
- const current = get().updaterState;
771
-
772
- if (!key || !newUpdater) return;
773
-
774
- set({ updaterState: { ...(current ?? {}), [key]: newUpdater } });
775
- },
776
- getKeyState: <StateKey extends StateKeys>(key: StateKey) => {
777
- return get().cogsStateStore[key];
778
- },
779
-
780
- setState: <StateKey extends StateKeys>(key: StateKey, value: StateValue) => {
781
- set((prev) => {
782
- return {
783
- cogsStateStore: {
784
- ...prev.cogsStateStore,
785
- [key]:
786
- typeof value === "function"
787
- ? value(prev.cogsStateStore[key])
788
- : value,
789
- },
790
- };
791
- });
792
- },
793
- setInitialStates: <StateKey extends StateKeys>(initialState: StateValue) => {
794
- set((prev) => ({
795
- cogsStateStore: {
796
- ...prev.cogsStateStore,
797
- ...initialState,
798
- },
799
- }));
800
- },
801
- setCreatedState: (initialState: StateValue) => {
802
- set((prev) => ({
803
- iniitialCreatedState: {
804
- ...prev.cogsStateStore,
805
- ...initialState,
806
- },
807
- }));
808
- },
809
809
 
810
810
  syncInfoStore: new Map<string, SyncInfo>(),
811
811
  setSyncInfo: (key: string, syncInfo: SyncInfo) =>