cogsbox-state 0.5.465 → 0.5.467
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/README.md +3 -3
- package/dist/CogsState.d.ts +1 -0
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1008 -1273
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Components.d.ts +39 -0
- package/dist/Components.d.ts.map +1 -0
- package/dist/Components.jsx +281 -0
- package/dist/Components.jsx.map +1 -0
- package/dist/index.js +11 -12
- package/dist/store.d.ts +2 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +274 -236
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
- package/src/CogsState.tsx +144 -709
- package/src/Components.tsx +541 -0
- package/src/store.ts +293 -221
- package/dist/Functions.d.ts +0 -11
- package/dist/Functions.d.ts.map +0 -1
- package/dist/Functions.jsx +0 -29
- package/dist/Functions.jsx.map +0 -1
- package/src/Functions.tsx +0 -66
package/src/store.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
UpdateTypeDetail,
|
|
8
8
|
} from './CogsState.js';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { type ReactNode } from 'react';
|
|
11
11
|
|
|
12
12
|
export type FreshValuesObject = {
|
|
13
13
|
pathsToValues?: string[];
|
|
@@ -28,7 +28,6 @@ export type FormRefStoreState = {
|
|
|
28
28
|
registerFormRef: (id: string, ref: React.RefObject<any>) => void;
|
|
29
29
|
getFormRef: (id: string) => React.RefObject<any> | undefined;
|
|
30
30
|
removeFormRef: (id: string) => void;
|
|
31
|
-
// New method to get all refs for a stateKey
|
|
32
31
|
getFormRefsByStateKey: (
|
|
33
32
|
stateKey: string
|
|
34
33
|
) => Map<string, React.RefObject<any>>;
|
|
@@ -53,7 +52,6 @@ export const formRefStore = create<FormRefStoreState>((set, get) => ({
|
|
|
53
52
|
return { formRefs: newRefs };
|
|
54
53
|
}),
|
|
55
54
|
|
|
56
|
-
// Get all refs that start with the stateKey prefix
|
|
57
55
|
getFormRefsByStateKey: (stateKey) => {
|
|
58
56
|
const allRefs = get().formRefs;
|
|
59
57
|
const stateKeyPrefix = stateKey + '.';
|
|
@@ -83,26 +81,25 @@ export type ComponentsType = {
|
|
|
83
81
|
};
|
|
84
82
|
|
|
85
83
|
export type ValidationStatus =
|
|
86
|
-
| 'NOT_VALIDATED'
|
|
87
|
-
| 'VALIDATING'
|
|
88
|
-
| 'VALID'
|
|
89
|
-
| 'INVALID';
|
|
84
|
+
| 'NOT_VALIDATED'
|
|
85
|
+
| 'VALIDATING'
|
|
86
|
+
| 'VALID'
|
|
87
|
+
| 'INVALID';
|
|
90
88
|
|
|
91
89
|
export type ValidationError = {
|
|
92
90
|
source: 'client' | 'sync_engine' | 'api';
|
|
93
91
|
message: string;
|
|
94
|
-
severity: 'warning' | 'error';
|
|
95
|
-
code?: string;
|
|
92
|
+
severity: 'warning' | 'error';
|
|
93
|
+
code?: string;
|
|
96
94
|
};
|
|
97
95
|
|
|
98
96
|
export type ValidationState = {
|
|
99
97
|
status: ValidationStatus;
|
|
100
98
|
errors: ValidationError[];
|
|
101
99
|
lastValidated?: number;
|
|
102
|
-
validatedValue?: any;
|
|
100
|
+
validatedValue?: any;
|
|
103
101
|
};
|
|
104
102
|
|
|
105
|
-
// This is the new definition for the metadata object
|
|
106
103
|
export type ShadowMetadata = {
|
|
107
104
|
id?: string;
|
|
108
105
|
stateSource?: 'default' | 'server' | 'localStorage';
|
|
@@ -156,28 +153,21 @@ export type ShadowMetadata = {
|
|
|
156
153
|
>;
|
|
157
154
|
} & ComponentsType;
|
|
158
155
|
|
|
159
|
-
// The shadow node itself can have a value and the metadata object.
|
|
160
156
|
type ShadowNode = {
|
|
161
157
|
value?: any;
|
|
162
158
|
_meta?: ShadowMetadata;
|
|
163
|
-
[key: string]: any;
|
|
159
|
+
[key: string]: any;
|
|
164
160
|
};
|
|
165
161
|
|
|
166
162
|
export type CogsGlobalState = {
|
|
167
|
-
// NEW shadow store
|
|
168
|
-
shadowStateStore: Map<string, ShadowNode>; // Changed ShadowMetadata to ShadowNode
|
|
169
163
|
setTransformCache: (
|
|
170
164
|
key: string,
|
|
171
165
|
path: string[],
|
|
172
166
|
cacheKey: string,
|
|
173
167
|
cacheData: any
|
|
174
168
|
) => void;
|
|
175
|
-
// NEW functions
|
|
176
169
|
initializeShadowState: (key: string, initialState: any) => void;
|
|
177
|
-
|
|
178
|
-
// REFACTORED: getShadowNode gets the whole object (data + _meta)
|
|
179
170
|
getShadowNode: (key: string, path: string[]) => ShadowNode | undefined;
|
|
180
|
-
// REFACTORED: getShadowMetadata now returns just the _meta field
|
|
181
171
|
getShadowMetadata: (
|
|
182
172
|
key: string,
|
|
183
173
|
path: string[]
|
|
@@ -191,6 +181,18 @@ export type CogsGlobalState = {
|
|
|
191
181
|
log?: boolean
|
|
192
182
|
) => any;
|
|
193
183
|
updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
|
|
184
|
+
insertManyShadowArrayElements: (
|
|
185
|
+
key: string,
|
|
186
|
+
arrayPath: string[],
|
|
187
|
+
newItems: any[],
|
|
188
|
+
index?: number
|
|
189
|
+
) => void;
|
|
190
|
+
addItemsToArrayNode: (
|
|
191
|
+
key: string,
|
|
192
|
+
arrayPath: string[],
|
|
193
|
+
newItems: any,
|
|
194
|
+
newKeys: string[]
|
|
195
|
+
) => void;
|
|
194
196
|
insertShadowArrayElement: (
|
|
195
197
|
key: string,
|
|
196
198
|
arrayPath: string[],
|
|
@@ -209,13 +211,11 @@ export type CogsGlobalState = {
|
|
|
209
211
|
dependencyPath: string[],
|
|
210
212
|
fullComponentId: string
|
|
211
213
|
) => void;
|
|
212
|
-
|
|
213
214
|
markAsDirty: (
|
|
214
215
|
key: string,
|
|
215
216
|
path: string[],
|
|
216
217
|
options: { bubble: boolean }
|
|
217
218
|
) => void;
|
|
218
|
-
// These method signatures stay the same
|
|
219
219
|
|
|
220
220
|
pathSubscribers: Map<string, Set<(newValue: any) => void>>;
|
|
221
221
|
subscribeToPath: (
|
|
@@ -230,12 +230,8 @@ export type CogsGlobalState = {
|
|
|
230
230
|
clearSelectedIndex: ({ arrayKey }: { arrayKey: string }) => void;
|
|
231
231
|
clearSelectedIndexesForState: (stateKey: string) => void;
|
|
232
232
|
|
|
233
|
-
// --- Core State and Updaters ---
|
|
234
|
-
|
|
235
233
|
initialStateOptions: { [key: string]: OptionsType };
|
|
236
|
-
|
|
237
234
|
initialStateGlobal: { [key: string]: StateValue };
|
|
238
|
-
|
|
239
235
|
updateInitialStateGlobal: (key: string, newState: StateValue) => void;
|
|
240
236
|
|
|
241
237
|
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
@@ -260,51 +256,45 @@ export type CogsGlobalState = {
|
|
|
260
256
|
getSyncInfo: (key: string) => SyncInfo | null;
|
|
261
257
|
};
|
|
262
258
|
|
|
263
|
-
// ✅ CHANGE 1: `METADATA_KEYS` now only contains `_meta` and `value`.
|
|
264
|
-
// The other keys are now properties of the `ShadowMetadata` type.
|
|
265
|
-
export const METADATA_KEYS = new Set(['_meta', 'value']);
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* The single source of truth for converting a regular JS value/object
|
|
269
|
-
* into the shadow state tree format with the new `_meta` structure.
|
|
270
|
-
*/
|
|
271
|
-
// ✅ CHANGE 2: `buildShadowNode` now creates the `_meta` field.
|
|
272
259
|
export function buildShadowNode(value: any): ShadowNode {
|
|
273
|
-
// Primitives and null are wrapped.
|
|
274
260
|
if (value === null || typeof value !== 'object') {
|
|
275
261
|
return { value };
|
|
276
262
|
}
|
|
277
263
|
|
|
278
|
-
// Arrays are converted to an object with id-keyed children and metadata in `_meta`.
|
|
279
264
|
if (Array.isArray(value)) {
|
|
280
|
-
const arrayNode: ShadowNode = { _meta: { arrayKeys: [] } };
|
|
265
|
+
const arrayNode: ShadowNode = { _meta: { arrayKeys: [] } };
|
|
281
266
|
const idKeys: string[] = [];
|
|
267
|
+
|
|
282
268
|
value.forEach((item) => {
|
|
283
269
|
const itemId = `id:${ulid()}`;
|
|
284
|
-
arrayNode[itemId] = buildShadowNode(item);
|
|
270
|
+
arrayNode[itemId] = buildShadowNode(item);
|
|
285
271
|
idKeys.push(itemId);
|
|
286
272
|
});
|
|
287
|
-
|
|
273
|
+
|
|
274
|
+
arrayNode._meta!.arrayKeys = idKeys;
|
|
288
275
|
return arrayNode;
|
|
289
276
|
}
|
|
290
277
|
|
|
291
|
-
// Plain objects are recursively processed.
|
|
292
278
|
if (value.constructor === Object) {
|
|
293
|
-
const objectNode: ShadowNode = { _meta: {} };
|
|
279
|
+
const objectNode: ShadowNode = { _meta: {} };
|
|
294
280
|
for (const key in value) {
|
|
295
281
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
296
|
-
objectNode[key] = buildShadowNode(value[key]);
|
|
282
|
+
objectNode[key] = buildShadowNode(value[key]);
|
|
297
283
|
}
|
|
298
284
|
}
|
|
299
285
|
return objectNode;
|
|
300
286
|
}
|
|
301
287
|
|
|
302
|
-
// Fallback for other object types (Date, etc.) - treat them as primitives.
|
|
303
288
|
return { value };
|
|
304
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>();
|
|
305
295
|
|
|
306
296
|
export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
307
|
-
shadowStateStore
|
|
297
|
+
// Remove shadowStateStore from Zustand state
|
|
308
298
|
|
|
309
299
|
setTransformCache: (
|
|
310
300
|
key: string,
|
|
@@ -312,7 +302,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
312
302
|
cacheKey: string,
|
|
313
303
|
cacheData: any
|
|
314
304
|
) => {
|
|
315
|
-
// This function now uses setShadowMetadata which correctly places the data.
|
|
316
305
|
const metadata = get().getShadowMetadata(key, path) || {};
|
|
317
306
|
if (!metadata.transformCaches) {
|
|
318
307
|
metadata.transformCaches = new Map();
|
|
@@ -324,48 +313,39 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
324
313
|
},
|
|
325
314
|
|
|
326
315
|
initializeShadowState: (key: string, initialState: any) => {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (baseServerState)
|
|
346
|
-
preservedMetadata.baseServerState = baseServerState;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
newShadowStore.delete(key);
|
|
350
|
-
newShadowStore.delete(`[${key}`);
|
|
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
|
+
}
|
|
351
334
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (!newRoot._meta) newRoot._meta = {};
|
|
355
|
-
Object.assign(newRoot._meta, preservedMetadata);
|
|
335
|
+
shadowStateStore.delete(key);
|
|
336
|
+
shadowStateStore.delete(`[${key}`);
|
|
356
337
|
|
|
357
|
-
|
|
358
|
-
|
|
338
|
+
const newRoot = buildShadowNode(initialState);
|
|
339
|
+
if (!newRoot._meta) newRoot._meta = {};
|
|
340
|
+
Object.assign(newRoot._meta, preservedMetadata);
|
|
359
341
|
|
|
360
|
-
|
|
361
|
-
|
|
342
|
+
const storageKey = Array.isArray(initialState) ? `[${key}` : key;
|
|
343
|
+
shadowStateStore.set(storageKey, newRoot);
|
|
362
344
|
},
|
|
363
345
|
|
|
364
|
-
// ✅ NEW HELPER: Gets the entire node (data and metadata).
|
|
365
346
|
getShadowNode: (key: string, path: string[]): ShadowNode | undefined => {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
347
|
+
let current: any =
|
|
348
|
+
shadowStateStore.get(key) || shadowStateStore.get(`[${key}`);
|
|
369
349
|
if (!current) return undefined;
|
|
370
350
|
if (path.length === 0) return current;
|
|
371
351
|
|
|
@@ -377,7 +357,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
377
357
|
return current;
|
|
378
358
|
},
|
|
379
359
|
|
|
380
|
-
// ✅ REFACTORED: Returns only the `_meta` part of a node.
|
|
381
360
|
getShadowMetadata: (
|
|
382
361
|
key: string,
|
|
383
362
|
path: string[]
|
|
@@ -386,38 +365,37 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
386
365
|
return node?._meta;
|
|
387
366
|
},
|
|
388
367
|
|
|
389
|
-
// ✅ REFACTORED: Sets data within the `_meta` object.
|
|
390
368
|
setShadowMetadata: (
|
|
391
369
|
key: string,
|
|
392
370
|
path: string[],
|
|
393
371
|
newMetadata: Partial<ShadowMetadata>
|
|
394
372
|
) => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
let root = newStore.get(rootKey);
|
|
373
|
+
// Direct mutation - no cloning!
|
|
374
|
+
const rootKey = shadowStateStore.has(`[${key}`) ? `[${key}` : key;
|
|
375
|
+
let root = shadowStateStore.get(rootKey);
|
|
399
376
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const clonedRoot: any = { ...root };
|
|
406
|
-
newStore.set(rootKey, clonedRoot);
|
|
377
|
+
if (!root) {
|
|
378
|
+
root = { _meta: newMetadata };
|
|
379
|
+
shadowStateStore.set(rootKey, root);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
407
382
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
current =
|
|
383
|
+
// Navigate to target without cloning
|
|
384
|
+
let current = root;
|
|
385
|
+
for (const segment of path) {
|
|
386
|
+
if (!current[segment]) {
|
|
387
|
+
current[segment] = {};
|
|
413
388
|
}
|
|
389
|
+
current = current[segment];
|
|
390
|
+
}
|
|
414
391
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
392
|
+
// Mutate metadata directly
|
|
393
|
+
if (!current._meta) {
|
|
394
|
+
current._meta = {};
|
|
395
|
+
}
|
|
396
|
+
Object.assign(current._meta, newMetadata);
|
|
420
397
|
},
|
|
398
|
+
|
|
421
399
|
getShadowValue: (
|
|
422
400
|
key: string,
|
|
423
401
|
path: string[],
|
|
@@ -430,9 +408,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
430
408
|
|
|
431
409
|
const nodeKeys = Object.keys(node);
|
|
432
410
|
|
|
433
|
-
// ✅ FIX: A node is a primitive wrapper ONLY if its keys are 'value' and/or '_meta'.
|
|
434
|
-
// This prevents objects in your data that happen to have a "value" property from being
|
|
435
|
-
// incorrectly treated as wrappers.
|
|
436
411
|
const isPrimitiveWrapper =
|
|
437
412
|
Object.prototype.hasOwnProperty.call(node, 'value') &&
|
|
438
413
|
nodeKeys.every((k) => k === 'value' || k === '_meta');
|
|
@@ -441,7 +416,6 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
441
416
|
return node.value;
|
|
442
417
|
}
|
|
443
418
|
|
|
444
|
-
// Array Check (This part is correct)
|
|
445
419
|
const isArrayNode =
|
|
446
420
|
node._meta &&
|
|
447
421
|
Object.prototype.hasOwnProperty.call(node._meta, 'arrayKeys');
|
|
@@ -456,10 +430,8 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
456
430
|
);
|
|
457
431
|
}
|
|
458
432
|
|
|
459
|
-
// Object Reconstruction (This part is also correct)
|
|
460
433
|
const result: any = {};
|
|
461
434
|
for (const propKey of nodeKeys) {
|
|
462
|
-
// We correctly ignore metadata and array item keys here.
|
|
463
435
|
if (propKey !== '_meta' && !propKey.startsWith('id:')) {
|
|
464
436
|
result[propKey] = get().getShadowValue(key, [...path, propKey]);
|
|
465
437
|
}
|
|
@@ -467,59 +439,108 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
467
439
|
return result;
|
|
468
440
|
},
|
|
469
441
|
|
|
470
|
-
// ✅ REFACTORED: Correctly preserves `_meta` on updates.
|
|
471
442
|
updateShadowAtPath: (key, path, newValue) => {
|
|
472
|
-
set(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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]!] = {};
|
|
453
|
+
}
|
|
454
|
+
parentNode = parentNode[path[i]!];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const targetNode =
|
|
458
|
+
path.length === 0 ? parentNode : parentNode[path[path.length - 1]!];
|
|
459
|
+
|
|
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];
|
|
490
479
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
for (const segment of parentPath) {
|
|
496
|
-
current[segment] = { ...current[segment] };
|
|
497
|
-
current = current[segment];
|
|
480
|
+
const newNode = buildShadowNode(plainValue);
|
|
481
|
+
Object.assign(nodeToUpdate, newNode);
|
|
482
|
+
if (oldMeta) {
|
|
483
|
+
nodeToUpdate._meta = { ...oldMeta, ...(nodeToUpdate._meta || {}) };
|
|
498
484
|
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
499
487
|
|
|
500
|
-
|
|
501
|
-
const existingNode = current[lastSegment] || {};
|
|
502
|
-
const newNodeStructure = buildShadowNode(newValue);
|
|
488
|
+
const plainValueKeys = new Set(Object.keys(plainValue));
|
|
503
489
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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);
|
|
510
496
|
}
|
|
511
|
-
current[lastSegment] = newNodeStructure;
|
|
512
497
|
}
|
|
513
498
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
499
|
+
for (const nodeKey in nodeToUpdate) {
|
|
500
|
+
if (
|
|
501
|
+
nodeKey === '_meta' ||
|
|
502
|
+
!Object.prototype.hasOwnProperty.call(nodeToUpdate, nodeKey)
|
|
503
|
+
)
|
|
504
|
+
continue;
|
|
505
|
+
|
|
506
|
+
if (!plainValueKeys.has(nodeKey)) {
|
|
507
|
+
delete nodeToUpdate[nodeKey];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
intelligentMerge(targetNode, newValue);
|
|
513
|
+
|
|
514
|
+
get().notifyPathSubscribers([key, ...path].join('.'), {
|
|
515
|
+
type: 'UPDATE',
|
|
516
|
+
newValue,
|
|
519
517
|
});
|
|
520
518
|
},
|
|
521
519
|
|
|
522
|
-
|
|
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
|
+
|
|
529
|
+
// Navigate without cloning
|
|
530
|
+
let current = root;
|
|
531
|
+
for (const segment of arrayPath) {
|
|
532
|
+
if (!current[segment]) {
|
|
533
|
+
current[segment] = {};
|
|
534
|
+
}
|
|
535
|
+
current = current[segment];
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Mutate directly
|
|
539
|
+
Object.assign(current, newItems);
|
|
540
|
+
if (!current._meta) current._meta = {};
|
|
541
|
+
current._meta.arrayKeys = newKeys; // Direct assignment!
|
|
542
|
+
},
|
|
543
|
+
|
|
523
544
|
insertShadowArrayElement: (key, arrayPath, newItem, index) => {
|
|
524
545
|
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
525
546
|
if (!arrayNode?._meta?.arrayKeys) {
|
|
@@ -530,53 +551,79 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
530
551
|
}
|
|
531
552
|
|
|
532
553
|
const newItemId = `id:${ulid()}`;
|
|
533
|
-
const
|
|
554
|
+
const itemsToAdd = { [newItemId]: buildShadowNode(newItem) };
|
|
534
555
|
|
|
535
|
-
//
|
|
556
|
+
// Mutate the array directly
|
|
536
557
|
const currentKeys = arrayNode._meta.arrayKeys;
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
558
|
+
const insertionPoint =
|
|
559
|
+
index !== undefined && index >= 0 && index <= currentKeys.length
|
|
560
|
+
? index
|
|
561
|
+
: currentKeys.length;
|
|
562
|
+
|
|
563
|
+
if (insertionPoint >= currentKeys.length) {
|
|
564
|
+
currentKeys.push(newItemId); // O(1)
|
|
540
565
|
} else {
|
|
541
|
-
|
|
566
|
+
currentKeys.splice(insertionPoint, 0, newItemId); // O(n) only for middle
|
|
542
567
|
}
|
|
543
568
|
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
});
|
|
569
|
+
// Pass the mutated array
|
|
570
|
+
get().addItemsToArrayNode(key, arrayPath, itemsToAdd, currentKeys);
|
|
571
|
+
|
|
572
|
+
const arrayKey = [key, ...arrayPath].join('.');
|
|
573
|
+
get().notifyPathSubscribers(arrayKey, {
|
|
574
|
+
type: 'INSERT',
|
|
575
|
+
path: arrayKey,
|
|
576
|
+
itemKey: `${arrayKey}.${newItemId}`,
|
|
577
|
+
index: insertionPoint,
|
|
578
|
+
});
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
insertManyShadowArrayElements: (key, arrayPath, newItems, index) => {
|
|
582
|
+
if (!newItems || newItems.length === 0) {
|
|
583
|
+
return;
|
|
561
584
|
}
|
|
562
585
|
|
|
563
|
-
|
|
564
|
-
arrayNode
|
|
565
|
-
|
|
586
|
+
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
587
|
+
if (!arrayNode?._meta?.arrayKeys) {
|
|
588
|
+
console.error(
|
|
589
|
+
`Array not found at path: ${[key, ...arrayPath].join('.')}`
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
566
593
|
|
|
567
|
-
|
|
594
|
+
const itemsToAdd: Record<string, any> = {};
|
|
595
|
+
const newIds: string[] = [];
|
|
596
|
+
|
|
597
|
+
newItems.forEach((item) => {
|
|
598
|
+
const newItemId = `id:${ulid()}`;
|
|
599
|
+
newIds.push(newItemId);
|
|
600
|
+
itemsToAdd[newItemId] = buildShadowNode(item);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// Mutate directly
|
|
604
|
+
const currentKeys = arrayNode._meta.arrayKeys;
|
|
605
|
+
const insertionPoint =
|
|
606
|
+
index !== undefined && index >= 0 && index <= currentKeys.length
|
|
607
|
+
? index
|
|
608
|
+
: currentKeys.length;
|
|
609
|
+
|
|
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);
|
|
568
617
|
|
|
569
|
-
// Trigger notifications
|
|
570
618
|
const arrayKey = [key, ...arrayPath].join('.');
|
|
571
619
|
get().notifyPathSubscribers(arrayKey, {
|
|
572
|
-
type: '
|
|
620
|
+
type: 'INSERT_MANY',
|
|
573
621
|
path: arrayKey,
|
|
574
|
-
|
|
575
|
-
index:
|
|
622
|
+
count: newItems.length,
|
|
623
|
+
index: insertionPoint,
|
|
576
624
|
});
|
|
577
625
|
},
|
|
578
626
|
|
|
579
|
-
// ✅ REFACTORED: Works with `_meta.arrayKeys`.
|
|
580
627
|
removeShadowArrayElement: (key, itemPath) => {
|
|
581
628
|
if (itemPath.length === 0) return;
|
|
582
629
|
|
|
@@ -587,14 +634,27 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
587
634
|
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
588
635
|
if (!arrayNode?._meta?.arrayKeys) return;
|
|
589
636
|
|
|
590
|
-
//
|
|
591
|
-
const
|
|
637
|
+
// Mutate directly
|
|
638
|
+
const currentKeys = arrayNode._meta.arrayKeys;
|
|
639
|
+
const indexToRemove = currentKeys.indexOf(itemId);
|
|
592
640
|
|
|
593
|
-
|
|
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
|
|
594
655
|
delete arrayNode[itemId];
|
|
595
656
|
|
|
596
|
-
//
|
|
597
|
-
get().setShadowMetadata(key, arrayPath, { arrayKeys: newKeys });
|
|
657
|
+
// No need to update metadata - already mutated!
|
|
598
658
|
|
|
599
659
|
const arrayKey = [key, ...arrayPath].join('.');
|
|
600
660
|
get().notifyPathSubscribers(arrayKey, {
|
|
@@ -604,8 +664,21 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
604
664
|
});
|
|
605
665
|
},
|
|
606
666
|
|
|
607
|
-
|
|
608
|
-
|
|
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
|
+
},
|
|
609
682
|
|
|
610
683
|
addPathComponent: (stateKey, dependencyPath, fullComponentId) => {
|
|
611
684
|
const metadata = get().getShadowMetadata(stateKey, dependencyPath) || {};
|
|
@@ -630,31 +703,14 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
630
703
|
}
|
|
631
704
|
},
|
|
632
705
|
|
|
633
|
-
registerComponent: (stateKey, fullComponentId, registration) => {
|
|
634
|
-
const rootMeta = get().getShadowMetadata(stateKey, []) || {};
|
|
635
|
-
const components = new Map(rootMeta.components);
|
|
636
|
-
components.set(fullComponentId, registration);
|
|
637
|
-
get().setShadowMetadata(stateKey, [], { components });
|
|
638
|
-
},
|
|
639
|
-
|
|
640
|
-
unregisterComponent: (stateKey, fullComponentId) => {
|
|
641
|
-
const rootMeta = get().getShadowMetadata(stateKey, []);
|
|
642
|
-
if (!rootMeta?.components) return;
|
|
643
|
-
const components = new Map(rootMeta.components);
|
|
644
|
-
if (components.delete(fullComponentId)) {
|
|
645
|
-
get().setShadowMetadata(stateKey, [], { components });
|
|
646
|
-
}
|
|
647
|
-
},
|
|
648
|
-
|
|
649
|
-
// ✅ REFACTORED: `markAsDirty` now correctly writes to `_meta.isDirty`.
|
|
650
706
|
markAsDirty: (key, path, options = { bubble: true }) => {
|
|
651
707
|
const setDirtyOnPath = (pathToMark: string[]) => {
|
|
652
708
|
const node = get().getShadowNode(key, pathToMark);
|
|
653
709
|
if (node?._meta?.isDirty) {
|
|
654
|
-
return true;
|
|
710
|
+
return true;
|
|
655
711
|
}
|
|
656
712
|
get().setShadowMetadata(key, pathToMark, { isDirty: true });
|
|
657
|
-
return false;
|
|
713
|
+
return false;
|
|
658
714
|
};
|
|
659
715
|
|
|
660
716
|
setDirtyOnPath(path);
|
|
@@ -664,12 +720,13 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
664
720
|
while (parentPath.length > 0) {
|
|
665
721
|
parentPath.pop();
|
|
666
722
|
if (setDirtyOnPath(parentPath)) {
|
|
667
|
-
break;
|
|
723
|
+
break;
|
|
668
724
|
}
|
|
669
725
|
}
|
|
670
726
|
}
|
|
671
727
|
},
|
|
672
728
|
|
|
729
|
+
// Keep these in Zustand as they need React reactivity
|
|
673
730
|
serverStateUpdates: new Map(),
|
|
674
731
|
setServerStateUpdate: (key, serverState) => {
|
|
675
732
|
set((state) => ({
|
|
@@ -701,6 +758,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
701
758
|
}
|
|
702
759
|
};
|
|
703
760
|
},
|
|
761
|
+
|
|
704
762
|
notifyPathSubscribers: (updatedPath, newValue) => {
|
|
705
763
|
const subscribers = get().pathSubscribers;
|
|
706
764
|
const subs = subscribers.get(updatedPath);
|
|
@@ -723,36 +781,49 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
723
781
|
return arrayKeys ? arrayKeys.indexOf(itemKey) : -1;
|
|
724
782
|
},
|
|
725
783
|
|
|
726
|
-
setSelectedIndex: (arrayKey, itemKey) => {
|
|
784
|
+
setSelectedIndex: (arrayKey: string, itemKey: string | undefined) => {
|
|
727
785
|
set((state) => {
|
|
728
786
|
const newMap = new Map(state.selectedIndicesMap);
|
|
729
|
-
const oldSelection = newMap.get(arrayKey);
|
|
730
|
-
if (oldSelection) {
|
|
731
|
-
get().notifyPathSubscribers(oldSelection, { type: 'THIS_UNSELECTED' });
|
|
732
|
-
}
|
|
733
787
|
|
|
734
788
|
if (itemKey === undefined) {
|
|
735
789
|
newMap.delete(arrayKey);
|
|
736
790
|
} else {
|
|
791
|
+
if (newMap.has(arrayKey)) {
|
|
792
|
+
get().notifyPathSubscribers(newMap.get(arrayKey)!, {
|
|
793
|
+
type: 'THIS_UNSELECTED',
|
|
794
|
+
});
|
|
795
|
+
}
|
|
737
796
|
newMap.set(arrayKey, itemKey);
|
|
738
797
|
get().notifyPathSubscribers(itemKey, { type: 'THIS_SELECTED' });
|
|
739
798
|
}
|
|
740
799
|
|
|
741
800
|
get().notifyPathSubscribers(arrayKey, { type: 'GET_SELECTED' });
|
|
742
|
-
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
...state,
|
|
804
|
+
selectedIndicesMap: newMap,
|
|
805
|
+
};
|
|
743
806
|
});
|
|
744
807
|
},
|
|
745
808
|
|
|
746
|
-
clearSelectedIndex: ({ arrayKey }) => {
|
|
809
|
+
clearSelectedIndex: ({ arrayKey }: { arrayKey: string }): void => {
|
|
747
810
|
set((state) => {
|
|
748
811
|
const newMap = new Map(state.selectedIndicesMap);
|
|
749
812
|
const actualKey = newMap.get(arrayKey);
|
|
750
813
|
if (actualKey) {
|
|
751
|
-
get().notifyPathSubscribers(actualKey, {
|
|
814
|
+
get().notifyPathSubscribers(actualKey, {
|
|
815
|
+
type: 'CLEAR_SELECTION',
|
|
816
|
+
});
|
|
752
817
|
}
|
|
818
|
+
|
|
753
819
|
newMap.delete(arrayKey);
|
|
754
|
-
get().notifyPathSubscribers(arrayKey, {
|
|
755
|
-
|
|
820
|
+
get().notifyPathSubscribers(arrayKey, {
|
|
821
|
+
type: 'CLEAR_SELECTION',
|
|
822
|
+
});
|
|
823
|
+
return {
|
|
824
|
+
...state,
|
|
825
|
+
selectedIndicesMap: newMap,
|
|
826
|
+
};
|
|
756
827
|
});
|
|
757
828
|
},
|
|
758
829
|
|
|
@@ -801,6 +872,7 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
801
872
|
initialStateOptions: { ...prev.initialStateOptions, [key]: value },
|
|
802
873
|
}));
|
|
803
874
|
},
|
|
875
|
+
|
|
804
876
|
updateInitialStateGlobal: (key, newState) => {
|
|
805
877
|
set((prev) => ({
|
|
806
878
|
initialStateGlobal: { ...prev.initialStateGlobal, [key]: newState },
|