cogsbox-state 0.5.432 → 0.5.435

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,441 @@ 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
+ const setDirty = (currentPath: string[]) => {
283
+ const fullKey = [key, ...currentPath].join('.');
284
+ const meta = newShadowStore.get(fullKey);
285
+
286
+ // We mark something as dirty if it isn't already.
287
+ // The original data source doesn't matter.
288
+ if (meta && meta.isDirty !== true) {
289
+ newShadowStore.set(fullKey, { ...meta, isDirty: true });
290
+ changed = true;
291
+ } else if (!meta) {
292
+ // If there's no metadata, create it and mark it as dirty.
293
+ // This handles newly created fields within an object.
294
+ newShadowStore.set(fullKey, { isDirty: true });
295
+ changed = true;
296
+ }
297
+ };
200
298
 
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>;
299
+ // 1. Mark the target path itself as dirty.
300
+ setDirty(path);
224
301
 
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;
302
+ // 2. If `bubble` is true, walk up the path and mark all parents as dirty.
303
+ if (options.bubble) {
304
+ let parentPath = [...path];
305
+ while (parentPath.length > 0) {
306
+ parentPath.pop();
307
+ setDirty(parentPath);
308
+ }
233
309
  }
234
- >;
235
- setReactiveDeps: (
236
- key: string,
237
- record: {
238
- deps: any[];
239
- updaters: Set<() => void>;
240
- depsFunction: ((state: any) => any[] | true) | null;
310
+
311
+ if (changed) {
312
+ set({ shadowStateStore: newShadowStore });
241
313
  }
242
- ) => void;
243
- deleteReactiveDeps: (key: string) => void;
244
- subscribe: (listener: () => void) => () => void;
245
- };
314
+ },
315
+ serverStateUpdates: new Map(),
316
+ setServerStateUpdate: (key, serverState) => {
317
+ set((state) => {
318
+ const newMap = new Map(state.serverStateUpdates);
319
+ newMap.set(key, serverState);
320
+ return { serverStateUpdates: newMap };
321
+ });
246
322
 
247
- export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
323
+ // Notify all subscribers for this key
324
+ get().notifyPathSubscribers(key, {
325
+ type: 'SERVER_STATE_UPDATE',
326
+ serverState,
327
+ });
328
+ },
248
329
  shadowStateStore: new Map(),
249
- shadowStateSubscribers: new Map(),
330
+ pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
250
331
 
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
- });
332
+ subscribeToPath: (path, callback) => {
333
+ const subscribers = get().pathSubscribers;
334
+ const subsForPath = subscribers.get(path) || new Set();
335
+ subsForPath.add(callback);
336
+ subscribers.set(path, subsForPath);
259
337
 
260
- // Return unsubscribe function
261
338
  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
- }
339
+ const currentSubs = get().pathSubscribers.get(path);
340
+ if (currentSubs) {
341
+ currentSubs.delete(callback);
342
+ if (currentSubs.size === 0) {
343
+ get().pathSubscribers.delete(path);
270
344
  }
271
- return { shadowStateSubscribers: newSubs };
272
- });
345
+ }
273
346
  };
274
347
  },
348
+
349
+ notifyPathSubscribers: (updatedPath, newValue) => {
350
+ // <-- Now accepts newValue
351
+ const subscribers = get().pathSubscribers;
352
+ const subs = subscribers.get(updatedPath);
353
+
354
+ if (subs) {
355
+ // Pass the newValue to every callback
356
+ subs.forEach((callback) => callback(newValue));
357
+ }
358
+ },
275
359
  initializeShadowState: (key: string, initialState: any) => {
276
- const newShadowStore = new Map<string, ShadowMetadata>();
360
+ const existingShadowStore = new Map(get().shadowStateStore);
277
361
 
278
362
  const processValue = (value: any, path: string[]) => {
279
- const nodeKey = [key, ...path].join(".");
363
+ const nodeKey = [key, ...path].join('.');
280
364
 
281
365
  if (Array.isArray(value)) {
366
+ // Handle arrays as before
282
367
  const childIds: string[] = [];
283
368
 
284
369
  value.forEach((item) => {
285
- if (typeof item === "object" && item !== null && !item.id) {
286
- item.id = ulid();
287
- }
370
+ const itemId = `id:${ulid()}`;
371
+ childIds.push(nodeKey + '.' + itemId);
372
+ });
288
373
 
289
- const itemId = `id:${item.id}`;
290
- childIds.push(itemId);
374
+ existingShadowStore.set(nodeKey, { arrayKeys: childIds });
291
375
 
292
- const itemPath = [...path, itemId];
293
- processValue(item, itemPath);
376
+ value.forEach((item, index) => {
377
+ const itemId = childIds[index]!.split('.').pop();
378
+ processValue(item, [...path!, itemId!]);
294
379
  });
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() });
380
+ } else if (isSimpleObject(value)) {
381
+ // Only create field mappings for simple objects
382
+ const fields = Object.fromEntries(
383
+ Object.keys(value).map((k) => [k, nodeKey + '.' + k])
384
+ );
385
+ existingShadowStore.set(nodeKey, { fields });
303
386
 
304
387
  Object.keys(value).forEach((k) => {
305
388
  processValue(value[k], [...path, k]);
306
389
  });
307
390
  } else {
308
- newShadowStore.set(nodeKey, { id: ulid() });
391
+ // Treat everything else (including Uint8Array) as primitive values
392
+ existingShadowStore.set(nodeKey, { value });
309
393
  }
310
394
  };
311
395
 
312
396
  processValue(initialState, []);
397
+ set({ shadowStateStore: existingShadowStore });
398
+ },
313
399
 
314
- set({ shadowStateStore: newShadowStore });
400
+ getShadowValue: (fullKey: string, validArrayIds?: string[]) => {
401
+ const shadowMeta = get().shadowStateStore.get(fullKey);
402
+
403
+ // If no metadata found, return undefined
404
+ if (!shadowMeta) {
405
+ return undefined;
406
+ }
407
+
408
+ // For primitive values, return the value
409
+ if (shadowMeta.value !== undefined) {
410
+ return shadowMeta.value;
411
+ }
412
+
413
+ // For arrays, reconstruct with possible validArrayIds
414
+ if (shadowMeta.arrayKeys) {
415
+ const arrayKeys = validArrayIds ?? shadowMeta.arrayKeys;
416
+ const items = arrayKeys.map((itemKey) => {
417
+ // RECURSIVELY call getShadowValue for each item
418
+ return get().getShadowValue(itemKey);
419
+ });
420
+ return items;
421
+ }
422
+
423
+ // For objects with fields, reconstruct object
424
+ if (shadowMeta.fields) {
425
+ const reconstructedObject: any = {};
426
+ Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
427
+ // RECURSIVELY call getShadowValue for each field
428
+ reconstructedObject[key] = get().getShadowValue(fieldPath as string);
429
+ });
430
+ return reconstructedObject;
431
+ }
432
+
433
+ return undefined;
315
434
  },
316
- getShadowMetadata: (key: string, path: string[]) => {
317
- const fullKey = [key, ...path].join(".");
435
+ getShadowMetadata: (
436
+ key: string,
437
+ path: string[],
438
+ validArrayIds?: string[]
439
+ ) => {
440
+ const fullKey = [key, ...path].join('.');
441
+ let data = get().shadowStateStore.get(fullKey);
442
+
318
443
  return get().shadowStateStore.get(fullKey);
319
444
  },
320
445
 
321
446
  setShadowMetadata: (key: string, path: string[], metadata: any) => {
322
- const fullKey = [key, ...path].join(".");
447
+ const fullKey = [key, ...path].join('.');
323
448
  const newShadowStore = new Map(get().shadowStateStore);
324
449
  const existing = newShadowStore.get(fullKey) || { id: ulid() };
325
450
  newShadowStore.set(fullKey, { ...existing, ...metadata });
326
451
  set({ shadowStateStore: newShadowStore });
452
+ },
453
+ setTransformCache: (
454
+ key: string,
455
+ path: string[],
456
+ cacheKey: string,
457
+ cacheData: any
458
+ ) => {
459
+ const fullKey = [key, ...path].join('.');
460
+ const newShadowStore = new Map(get().shadowStateStore);
461
+ const existing = newShadowStore.get(fullKey) || {};
327
462
 
328
- if (metadata.virtualizer?.itemHeight) {
329
- const subscribers = get().shadowStateSubscribers.get(key);
330
- subscribers?.forEach((cb) => cb());
463
+ // Initialize transformCaches if it doesn't exist
464
+ if (!existing.transformCaches) {
465
+ existing.transformCaches = new Map();
331
466
  }
332
- },
333
467
 
468
+ // Update just the specific cache entry
469
+ existing.transformCaches.set(cacheKey, cacheData);
470
+
471
+ // Update shadow store WITHOUT notifying path subscribers
472
+ newShadowStore.set(fullKey, existing);
473
+ set({ shadowStateStore: newShadowStore });
474
+
475
+ // Don't call notifyPathSubscribers here - cache updates shouldn't trigger renders
476
+ },
334
477
  insertShadowArrayElement: (
335
478
  key: string,
336
479
  arrayPath: string[],
337
480
  newItem: any
338
481
  ) => {
339
482
  const newShadowStore = new Map(get().shadowStateStore);
340
- const arrayKey = [key, ...arrayPath].join(".");
483
+ const arrayKey = [key, ...arrayPath].join('.');
341
484
  const parentMeta = newShadowStore.get(arrayKey);
342
- const newArrayState = get().getNestedState(key, arrayPath) as any[];
343
485
 
344
486
  if (!parentMeta || !parentMeta.arrayKeys) return;
345
487
 
346
- const newItemId = `id:${newItem.id}`;
347
- const newIndex = newArrayState.findIndex((item) => item.id === newItem.id);
348
-
349
- if (newIndex === -1) return;
488
+ const newItemId = `id:${ulid()}`;
489
+ const fullItemKey = arrayKey + '.' + newItemId;
350
490
 
491
+ // Just add to the end (or at a specific index if provided)
351
492
  const newArrayKeys = [...parentMeta.arrayKeys];
352
- newArrayKeys.splice(newIndex, 0, newItemId);
493
+ newArrayKeys.push(fullItemKey); // Or use splice if you have an index
353
494
  newShadowStore.set(arrayKey, { ...parentMeta, arrayKeys: newArrayKeys });
354
495
 
496
+ // Process the new item - but use the correct logic
355
497
  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]);
498
+ const nodeKey = [key, ...path].join('.');
499
+
500
+ if (Array.isArray(value)) {
501
+ // Handle arrays...
502
+ } else if (typeof value === 'object' && value !== null) {
503
+ // Create fields mapping
504
+ const fields = Object.fromEntries(
505
+ Object.keys(value).map((k) => [k, nodeKey + '.' + k])
506
+ );
507
+ newShadowStore.set(nodeKey, { fields });
508
+
509
+ // Process each field
510
+ Object.entries(value).forEach(([k, v]) => {
511
+ processNewItem(v, [...path, k]);
361
512
  });
362
513
  } else {
363
- newShadowStore.set(nodeKey, { id: ulid() });
514
+ // Primitive value
515
+ newShadowStore.set(nodeKey, { value });
364
516
  }
365
517
  };
366
518
 
367
519
  processNewItem(newItem, [...arrayPath, newItemId]);
368
-
369
520
  set({ shadowStateStore: newShadowStore });
370
- },
371
521
 
522
+ get().notifyPathSubscribers(arrayKey, {
523
+ type: 'INSERT',
524
+ path: arrayKey,
525
+ itemKey: fullItemKey,
526
+ });
527
+ },
372
528
  removeShadowArrayElement: (key: string, itemPath: string[]) => {
373
529
  const newShadowStore = new Map(get().shadowStateStore);
374
- const itemKey = [key, ...itemPath].join(".");
375
- const itemIdToRemove = itemPath[itemPath.length - 1];
376
530
 
531
+ // Get the full item key (e.g., "stateKey.products.id:xxx")
532
+ const itemKey = [key, ...itemPath].join('.');
533
+
534
+ // Extract parent path and item ID
377
535
  const parentPath = itemPath.slice(0, -1);
378
- const parentKey = [key, ...parentPath].join(".");
536
+ const parentKey = [key, ...parentPath].join('.');
537
+
538
+ // Get parent metadata
379
539
  const parentMeta = newShadowStore.get(parentKey);
380
540
 
381
541
  if (parentMeta && parentMeta.arrayKeys) {
382
- const newArrayKeys = parentMeta.arrayKeys.filter(
383
- (id) => id !== itemIdToRemove
542
+ // Find the index of the item to remove
543
+ const indexToRemove = parentMeta.arrayKeys.findIndex(
544
+ (arrayItemKey) => arrayItemKey === itemKey
384
545
  );
385
- newShadowStore.set(parentKey, { ...parentMeta, arrayKeys: newArrayKeys });
386
- }
387
546
 
388
- const prefixToDelete = itemKey + ".";
389
- for (const k of Array.from(newShadowStore.keys())) {
390
- if (k === itemKey || k.startsWith(prefixToDelete)) {
391
- newShadowStore.delete(k);
547
+ if (indexToRemove !== -1) {
548
+ // Create new array keys with the item removed
549
+ const newArrayKeys = parentMeta.arrayKeys.filter(
550
+ (arrayItemKey) => arrayItemKey !== itemKey
551
+ );
552
+
553
+ // Update parent with new array keys
554
+ newShadowStore.set(parentKey, {
555
+ ...parentMeta,
556
+ arrayKeys: newArrayKeys,
557
+ });
558
+
559
+ // Delete all data associated with the removed item
560
+ const prefixToDelete = itemKey + '.';
561
+ for (const k of Array.from(newShadowStore.keys())) {
562
+ if (k === itemKey || k.startsWith(prefixToDelete)) {
563
+ newShadowStore.delete(k);
564
+ }
565
+ }
392
566
  }
393
567
  }
394
568
 
395
569
  set({ shadowStateStore: newShadowStore });
570
+
571
+ get().notifyPathSubscribers(parentKey, {
572
+ type: 'REMOVE',
573
+ path: parentKey,
574
+ itemKey: itemKey, // The exact ID of the removed item
575
+ });
396
576
  },
397
- updateShadowAtPath: (key: string, path: string[], newValue: any) => {
398
- const fullKey = [key, ...path].join(".");
577
+ updateShadowAtPath: (key, path, newValue) => {
399
578
  const newShadowStore = new Map(get().shadowStateStore);
400
- const existing = newShadowStore.get(fullKey) || { id: ulid() };
401
- newShadowStore.set(fullKey, { ...existing, lastUpdated: Date.now() });
579
+ const fullKey = [key, ...path].join('.');
580
+
581
+ const updateValue = (currentKey: string, valueToSet: any) => {
582
+ const meta = newShadowStore.get(currentKey);
583
+
584
+ // If it's a simple object with fields, update recursively
585
+ if (isSimpleObject(valueToSet) && meta && meta.fields) {
586
+ for (const fieldKey in valueToSet) {
587
+ if (Object.prototype.hasOwnProperty.call(valueToSet, fieldKey)) {
588
+ const childPath = meta.fields[fieldKey];
589
+ const childValue = valueToSet[fieldKey];
590
+
591
+ if (childPath) {
592
+ updateValue(childPath as string, childValue);
593
+ }
594
+ }
595
+ }
596
+ } else {
597
+ // For primitives (including Uint8Array), just replace the value
598
+ // This gives you useState-like behavior
599
+ const existing = newShadowStore.get(currentKey) || {};
600
+ newShadowStore.set(currentKey, { ...existing, value: valueToSet });
601
+ }
602
+ };
603
+
604
+ updateValue(fullKey, newValue);
605
+ get().notifyPathSubscribers(fullKey, { type: 'UPDATE', newValue });
402
606
  set({ shadowStateStore: newShadowStore });
403
607
  },
608
+ selectedIndicesMap: new Map<string, string>(),
609
+ getSelectedIndex: (arrayKey: string, validIds?: string[]): number => {
610
+ const itemKey = get().selectedIndicesMap.get(arrayKey);
611
+
612
+ if (!itemKey) return -1;
613
+
614
+ // Use validIds if provided (for filtered views), otherwise use all arrayKeys
615
+ const arrayKeys =
616
+ validIds ||
617
+ getGlobalStore.getState().getShadowMetadata(arrayKey, [])?.arrayKeys;
404
618
 
405
- selectedIndicesMap: new Map<string, Map<string, number>>(),
619
+ if (!arrayKeys) return -1;
406
620
 
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);
621
+ return arrayKeys.indexOf(itemKey);
412
622
  },
413
623
 
414
- setSelectedIndex: (
415
- stateKey: string,
416
- parentPath: string,
417
- index: number | undefined
418
- ) => {
624
+ setSelectedIndex: (arrayKey: string, itemKey: string | undefined) => {
419
625
  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
- }
626
+ const newMap = state.selectedIndicesMap;
427
627
 
428
- if (index === undefined) {
429
- stateMap.delete(parentPath);
628
+ if (itemKey === undefined) {
629
+ newMap.delete(arrayKey);
430
630
  } else {
431
- stateMap.set(parentPath, index);
432
- }
631
+ if (newMap.has(arrayKey)) {
632
+ get().notifyPathSubscribers(newMap.get(arrayKey)!, {
633
+ type: 'THIS_UNSELECTED',
634
+ });
635
+ }
636
+ newMap.set(arrayKey, itemKey);
433
637
 
638
+ get().notifyPathSubscribers(itemKey, {
639
+ type: 'THIS_SELECTED',
640
+ });
641
+ }
642
+ get().notifyPathSubscribers(arrayKey, {
643
+ type: 'GET_SELECTED',
644
+ });
434
645
  return {
435
646
  ...state,
436
647
  selectedIndicesMap: newMap,
437
648
  };
438
649
  });
439
650
  },
440
- clearSelectedIndex: ({
441
- stateKey,
442
- path,
443
- }: {
444
- stateKey: string;
445
- path: string[];
446
- }) => {
651
+ clearSelectedIndex: ({ arrayKey }: { arrayKey: string }): void => {
447
652
  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);
653
+ const newMap = state.selectedIndicesMap;
654
+ const acutalKey = newMap.get(arrayKey);
655
+ if (acutalKey) {
656
+ get().notifyPathSubscribers(acutalKey, {
657
+ type: 'CLEAR_SELECTION',
658
+ });
659
+ }
660
+
661
+ newMap.delete(arrayKey);
662
+ get().notifyPathSubscribers(arrayKey, {
663
+ type: 'CLEAR_SELECTION',
664
+ });
453
665
  return {
454
666
  ...state,
455
667
  selectedIndicesMap: newMap,
@@ -461,126 +673,23 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
461
673
  const newOuterMap = new Map(state.selectedIndicesMap);
462
674
  const changed = newOuterMap.delete(stateKey);
463
675
  if (changed) {
464
- console.log(
465
- `Cleared selected indices map entry for stateKey: ${stateKey}`
466
- );
467
676
  return { selectedIndicesMap: newOuterMap };
468
677
  } else {
469
678
  return {};
470
679
  }
471
680
  });
472
681
  },
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
682
 
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
683
  initialStateOptions: {},
524
- updaterState: {},
684
+
525
685
  stateTimeline: {},
526
686
  cogsStateStore: {},
527
687
  stateLog: {},
528
- isLoadingGlobal: {},
529
688
 
530
689
  initialStateGlobal: {},
531
- iniitialCreatedState: {},
532
- updateInitialCreatedState: (key, newState) => {
533
- set((prev) => ({
534
- iniitialCreatedState: {
535
- ...prev.iniitialCreatedState,
536
- [key]: newState,
537
- },
538
- }));
539
- },
540
690
 
541
691
  validationErrors: new Map(),
542
692
 
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
693
  setStateLog: (
585
694
  key: string,
586
695
  updater: (prevUpdates: UpdateTypeDetail[]) => UpdateTypeDetail[]
@@ -596,28 +705,12 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
596
705
  };
597
706
  });
598
707
  },
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
- },
708
+
615
709
  addValidationError: (path, message) => {
616
- console.log("addValidationError---");
617
710
  set((prev) => {
618
711
  const updatedErrors = new Map(prev.validationErrors);
619
712
  const existingMessages = updatedErrors.get(path) || [];
620
- console.log("addValidationError", path, message, existingMessages);
713
+ console.log('addValidationError', path, message, existingMessages);
621
714
  // Append the new message instead of replacing
622
715
  updatedErrors.set(path, [...existingMessages, message]);
623
716
  return { validationErrors: updatedErrors };
@@ -628,9 +721,9 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
628
721
  const updatedErrors = new Map(prev.validationErrors);
629
722
 
630
723
  let doSomething = false;
631
- const pathArray = path.split(".");
724
+ const pathArray = path.split('.');
632
725
  Array.from(updatedErrors.keys()).forEach((key) => {
633
- const keyArray = key.split(".");
726
+ const keyArray = key.split('.');
634
727
  if (keyArray.length >= pathArray.length) {
635
728
  let match = true;
636
729
  for (let i = 0; i < pathArray.length; i++) {
@@ -653,11 +746,11 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
653
746
  getValidationErrors: (path: string) => {
654
747
  const errors: string[] = [];
655
748
  const valErrors = get().validationErrors;
656
- const pathArray = path.split(".");
749
+ const pathArray = path.split('.');
657
750
 
658
751
  // Helper to check if an index matches either a wildcard or is in an array of indices
659
752
  const isIndexMatch = (pathSegment: string, keySegment: string) => {
660
- if (pathSegment === "[*]") return true;
753
+ if (pathSegment === '[*]') return true;
661
754
  if (Array.isArray(pathSegment)) {
662
755
  return pathSegment.includes(parseInt(keySegment));
663
756
  }
@@ -665,7 +758,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
665
758
  };
666
759
 
667
760
  Array.from(valErrors.keys()).forEach((key) => {
668
- const keyArray = key.split(".");
761
+ const keyArray = key.split('.');
669
762
  if (keyArray.length >= pathArray.length) {
670
763
  let match = true;
671
764
  for (let i = 0; i < pathArray.length; i++) {
@@ -673,7 +766,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
673
766
  const keySegment = keyArray[i]!;
674
767
 
675
768
  // If current path segment is a number or [*], we need special handling
676
- if (pathSegment === "[*]" || Array.isArray(pathSegment)) {
769
+ if (pathSegment === '[*]' || Array.isArray(pathSegment)) {
677
770
  // Key segment should be a number if we're using [*] or array indices
678
771
  const keyIndex = parseInt(keySegment);
679
772
  if (isNaN(keyIndex)) {
@@ -705,48 +798,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
705
798
  getInitialOptions: (key) => {
706
799
  return get().initialStateOptions[key];
707
800
  },
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
801
 
748
- return resolvePath(rootState, path);
749
- },
750
802
  setInitialStateOptions: (key, value) => {
751
803
  set((prev) => ({
752
804
  initialStateOptions: {
@@ -763,49 +815,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
763
815
  },
764
816
  }));
765
817
  },
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
818
 
810
819
  syncInfoStore: new Map<string, SyncInfo>(),
811
820
  setSyncInfo: (key: string, syncInfo: SyncInfo) =>