cogsbox-state 0.5.464 → 0.5.466
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 +11 -30
- package/dist/CogsState.d.ts.map +1 -1
- package/dist/CogsState.jsx +1269 -1552
- 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 +23 -24
- package/dist/store.d.ts +36 -47
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +299 -293
- package/dist/store.js.map +1 -1
- package/dist/utility.d.ts +0 -1
- package/dist/utility.d.ts.map +1 -1
- package/dist/utility.js +121 -158
- package/dist/utility.js.map +1 -1
- package/package.json +5 -4
- package/src/CogsState.tsx +1095 -1996
- package/src/Components.tsx +541 -0
- package/src/store.ts +502 -569
- package/src/utility.ts +0 -65
- package/dist/Functions.d.ts +0 -11
- package/dist/Functions.d.ts.map +0 -1
- package/dist/Functions.jsx +0 -22
- package/dist/Functions.jsx.map +0 -1
- package/src/Functions.tsx +0 -46
package/src/store.ts
CHANGED
|
@@ -3,16 +3,11 @@ import { ulid } from 'ulid';
|
|
|
3
3
|
import type {
|
|
4
4
|
OptionsType,
|
|
5
5
|
ReactivityType,
|
|
6
|
-
StateKeys,
|
|
7
6
|
SyncInfo,
|
|
8
7
|
UpdateTypeDetail,
|
|
9
8
|
} from './CogsState.js';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
type StateUpdater<StateValue> =
|
|
14
|
-
| StateValue
|
|
15
|
-
| ((prevValue: StateValue) => StateValue);
|
|
10
|
+
import { type ReactNode } from 'react';
|
|
16
11
|
|
|
17
12
|
export type FreshValuesObject = {
|
|
18
13
|
pathsToValues?: string[];
|
|
@@ -21,9 +16,6 @@ export type FreshValuesObject = {
|
|
|
21
16
|
timeStamp: number;
|
|
22
17
|
};
|
|
23
18
|
|
|
24
|
-
type SyncLogType = {
|
|
25
|
-
timeStamp: number;
|
|
26
|
-
};
|
|
27
19
|
type StateValue = any;
|
|
28
20
|
|
|
29
21
|
export type TrieNode = {
|
|
@@ -36,7 +28,6 @@ export type FormRefStoreState = {
|
|
|
36
28
|
registerFormRef: (id: string, ref: React.RefObject<any>) => void;
|
|
37
29
|
getFormRef: (id: string) => React.RefObject<any> | undefined;
|
|
38
30
|
removeFormRef: (id: string) => void;
|
|
39
|
-
// New method to get all refs for a stateKey
|
|
40
31
|
getFormRefsByStateKey: (
|
|
41
32
|
stateKey: string
|
|
42
33
|
) => Map<string, React.RefObject<any>>;
|
|
@@ -61,7 +52,6 @@ export const formRefStore = create<FormRefStoreState>((set, get) => ({
|
|
|
61
52
|
return { formRefs: newRefs };
|
|
62
53
|
}),
|
|
63
54
|
|
|
64
|
-
// Get all refs that start with the stateKey prefix
|
|
65
55
|
getFormRefsByStateKey: (stateKey) => {
|
|
66
56
|
const allRefs = get().formRefs;
|
|
67
57
|
const stateKeyPrefix = stateKey + '.';
|
|
@@ -89,16 +79,34 @@ export type ComponentsType = {
|
|
|
89
79
|
}
|
|
90
80
|
>;
|
|
91
81
|
};
|
|
82
|
+
|
|
83
|
+
export type ValidationStatus =
|
|
84
|
+
| 'NOT_VALIDATED'
|
|
85
|
+
| 'VALIDATING'
|
|
86
|
+
| 'VALID'
|
|
87
|
+
| 'INVALID';
|
|
88
|
+
|
|
89
|
+
export type ValidationError = {
|
|
90
|
+
source: 'client' | 'sync_engine' | 'api';
|
|
91
|
+
message: string;
|
|
92
|
+
severity: 'warning' | 'error';
|
|
93
|
+
code?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type ValidationState = {
|
|
97
|
+
status: ValidationStatus;
|
|
98
|
+
errors: ValidationError[];
|
|
99
|
+
lastValidated?: number;
|
|
100
|
+
validatedValue?: any;
|
|
101
|
+
};
|
|
102
|
+
|
|
92
103
|
export type ShadowMetadata = {
|
|
93
104
|
id?: string;
|
|
94
|
-
|
|
95
105
|
stateSource?: 'default' | 'server' | 'localStorage';
|
|
96
106
|
lastServerSync?: number;
|
|
97
107
|
isDirty?: boolean;
|
|
98
108
|
baseServerState?: any;
|
|
99
|
-
|
|
100
109
|
arrayKeys?: string[];
|
|
101
|
-
|
|
102
110
|
fields?: Record<string, any>;
|
|
103
111
|
virtualizer?: {
|
|
104
112
|
itemHeight?: number;
|
|
@@ -106,14 +114,12 @@ export type ShadowMetadata = {
|
|
|
106
114
|
};
|
|
107
115
|
syncInfo?: { status: string };
|
|
108
116
|
validation?: ValidationState;
|
|
117
|
+
features?: {
|
|
118
|
+
syncEnabled: boolean;
|
|
119
|
+
validationEnabled: boolean;
|
|
120
|
+
localStorageEnabled: boolean;
|
|
121
|
+
};
|
|
109
122
|
lastUpdated?: number;
|
|
110
|
-
value?: any;
|
|
111
|
-
classSignals?: Array<{
|
|
112
|
-
id: string;
|
|
113
|
-
effect: string;
|
|
114
|
-
lastClasses: string;
|
|
115
|
-
deps: any[];
|
|
116
|
-
}>;
|
|
117
123
|
signals?: Array<{
|
|
118
124
|
instanceId: string;
|
|
119
125
|
parentId: string;
|
|
@@ -125,12 +131,7 @@ export type ShadowMetadata = {
|
|
|
125
131
|
path: string[];
|
|
126
132
|
componentId: string;
|
|
127
133
|
meta?: any;
|
|
128
|
-
mapFn: (
|
|
129
|
-
setter: any,
|
|
130
|
-
index: number,
|
|
131
|
-
|
|
132
|
-
arraySetter: any
|
|
133
|
-
) => ReactNode;
|
|
134
|
+
mapFn: (setter: any, index: number, arraySetter: any) => ReactNode;
|
|
134
135
|
containerRef: HTMLDivElement | null;
|
|
135
136
|
rebuildStateShape: any;
|
|
136
137
|
}>;
|
|
@@ -152,36 +153,55 @@ export type ShadowMetadata = {
|
|
|
152
153
|
>;
|
|
153
154
|
} & ComponentsType;
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
| 'INVALID_LIVE' // Gentle error during typing.
|
|
160
|
-
| 'VALIDATION_FAILED' // Hard error on blur/submit.
|
|
161
|
-
| 'VALID_PENDING_SYNC' // Passed validation, ready for sync.
|
|
162
|
-
| 'SYNCING' // Actively being sent to the server.
|
|
163
|
-
| 'SYNCED' // Server confirmed success.
|
|
164
|
-
| 'SYNC_FAILED'; // Server rejected the data.
|
|
165
|
-
|
|
166
|
-
export type ValidationState = {
|
|
167
|
-
status: ValidationStatus;
|
|
168
|
-
message?: string;
|
|
169
|
-
lastValidated?: number;
|
|
170
|
-
validatedValue?: any;
|
|
156
|
+
type ShadowNode = {
|
|
157
|
+
value?: any;
|
|
158
|
+
_meta?: ShadowMetadata;
|
|
159
|
+
[key: string]: any;
|
|
171
160
|
};
|
|
172
|
-
export type CogsEvent =
|
|
173
|
-
| { type: 'INSERT'; path: string; itemKey: string; index: number }
|
|
174
|
-
| { type: 'REMOVE'; path: string; itemKey: string }
|
|
175
|
-
| { type: 'UPDATE'; path: string; newValue: any }
|
|
176
|
-
| { type: 'ITEMHEIGHT'; itemKey: string; height: number }
|
|
177
|
-
| { type: 'RELOAD'; path: string };
|
|
178
|
-
export type CogsGlobalState = {
|
|
179
|
-
updateQueue: Set<() => void>;
|
|
180
|
-
isFlushScheduled: boolean;
|
|
181
161
|
|
|
182
|
-
|
|
162
|
+
export type CogsGlobalState = {
|
|
163
|
+
// NEW shadow store
|
|
164
|
+
shadowStateStore: Map<string, ShadowNode>;
|
|
165
|
+
setTransformCache: (
|
|
166
|
+
key: string,
|
|
167
|
+
path: string[],
|
|
168
|
+
cacheKey: string,
|
|
169
|
+
cacheData: any
|
|
170
|
+
) => void;
|
|
171
|
+
initializeShadowState: (key: string, initialState: any) => void;
|
|
172
|
+
getShadowNode: (key: string, path: string[]) => ShadowNode | undefined;
|
|
173
|
+
getShadowMetadata: (
|
|
174
|
+
key: string,
|
|
175
|
+
path: string[]
|
|
176
|
+
) => ShadowMetadata | undefined;
|
|
183
177
|
|
|
184
|
-
|
|
178
|
+
setShadowMetadata: (key: string, path: string[], metadata: any) => void;
|
|
179
|
+
getShadowValue: (
|
|
180
|
+
key: string,
|
|
181
|
+
path: string[],
|
|
182
|
+
validArrayIds?: string[],
|
|
183
|
+
log?: boolean
|
|
184
|
+
) => any;
|
|
185
|
+
updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
|
|
186
|
+
insertManyShadowArrayElements: (
|
|
187
|
+
key: string,
|
|
188
|
+
arrayPath: string[],
|
|
189
|
+
newItems: any[],
|
|
190
|
+
index?: number
|
|
191
|
+
) => void;
|
|
192
|
+
addItemsToArrayNode: (
|
|
193
|
+
key: string,
|
|
194
|
+
arrayPath: string[],
|
|
195
|
+
newItems: any,
|
|
196
|
+
newKeys: string[]
|
|
197
|
+
) => void;
|
|
198
|
+
insertShadowArrayElement: (
|
|
199
|
+
key: string,
|
|
200
|
+
arrayPath: string[],
|
|
201
|
+
newItem: any,
|
|
202
|
+
index?: number
|
|
203
|
+
) => void;
|
|
204
|
+
removeShadowArrayElement: (key: string, itemPath: string[]) => void;
|
|
185
205
|
registerComponent: (
|
|
186
206
|
stateKey: string,
|
|
187
207
|
componentId: string,
|
|
@@ -193,43 +213,11 @@ export type CogsGlobalState = {
|
|
|
193
213
|
dependencyPath: string[],
|
|
194
214
|
fullComponentId: string
|
|
195
215
|
) => void;
|
|
196
|
-
shadowStateStore: Map<string, ShadowMetadata>;
|
|
197
|
-
|
|
198
216
|
markAsDirty: (
|
|
199
217
|
key: string,
|
|
200
218
|
path: string[],
|
|
201
219
|
options: { bubble: boolean }
|
|
202
220
|
) => void;
|
|
203
|
-
// These method signatures stay the same
|
|
204
|
-
initializeShadowState: (key: string, initialState: any) => void;
|
|
205
|
-
updateShadowAtPath: (key: string, path: string[], newValue: any) => void;
|
|
206
|
-
insertShadowArrayElement: (
|
|
207
|
-
key: string,
|
|
208
|
-
arrayPath: string[],
|
|
209
|
-
newItem: any
|
|
210
|
-
) => void;
|
|
211
|
-
removeShadowArrayElement: (key: string, arrayPath: string[]) => void;
|
|
212
|
-
getShadowValue: (
|
|
213
|
-
key: string,
|
|
214
|
-
|
|
215
|
-
validArrayIds?: string[]
|
|
216
|
-
) => any;
|
|
217
|
-
|
|
218
|
-
getShadowMetadata: (
|
|
219
|
-
key: string,
|
|
220
|
-
path: string[]
|
|
221
|
-
) => ShadowMetadata | undefined;
|
|
222
|
-
setShadowMetadata: (
|
|
223
|
-
key: string,
|
|
224
|
-
path: string[],
|
|
225
|
-
metadata: Omit<ShadowMetadata, 'id'>
|
|
226
|
-
) => void;
|
|
227
|
-
setTransformCache: (
|
|
228
|
-
key: string,
|
|
229
|
-
path: string[],
|
|
230
|
-
cacheKey: string,
|
|
231
|
-
cacheData: any
|
|
232
|
-
) => void;
|
|
233
221
|
|
|
234
222
|
pathSubscribers: Map<string, Set<(newValue: any) => void>>;
|
|
235
223
|
subscribeToPath: (
|
|
@@ -244,12 +232,8 @@ export type CogsGlobalState = {
|
|
|
244
232
|
clearSelectedIndex: ({ arrayKey }: { arrayKey: string }) => void;
|
|
245
233
|
clearSelectedIndexesForState: (stateKey: string) => void;
|
|
246
234
|
|
|
247
|
-
// --- Core State and Updaters ---
|
|
248
|
-
|
|
249
235
|
initialStateOptions: { [key: string]: OptionsType };
|
|
250
|
-
|
|
251
236
|
initialStateGlobal: { [key: string]: StateValue };
|
|
252
|
-
|
|
253
237
|
updateInitialStateGlobal: (key: string, newState: StateValue) => void;
|
|
254
238
|
|
|
255
239
|
getInitialOptions: (key: string) => OptionsType | undefined;
|
|
@@ -268,542 +252,505 @@ export type CogsGlobalState = {
|
|
|
268
252
|
|
|
269
253
|
stateLog: Map<string, Map<string, UpdateTypeDetail>>;
|
|
270
254
|
syncInfoStore: Map<string, SyncInfo>;
|
|
271
|
-
addStateLog: (
|
|
255
|
+
addStateLog: (updates: UpdateTypeDetail[]) => void;
|
|
272
256
|
|
|
273
257
|
setSyncInfo: (key: string, syncInfo: SyncInfo) => void;
|
|
274
258
|
getSyncInfo: (key: string) => SyncInfo | null;
|
|
275
259
|
};
|
|
276
|
-
const isSimpleObject = (value: any): boolean => {
|
|
277
|
-
// Most common cases first
|
|
278
|
-
if (value === null || typeof value !== 'object') return false;
|
|
279
260
|
|
|
280
|
-
|
|
281
|
-
if (
|
|
261
|
+
export function buildShadowNode(value: any): ShadowNode {
|
|
262
|
+
if (value === null || typeof value !== 'object') {
|
|
263
|
+
return { value };
|
|
264
|
+
}
|
|
282
265
|
|
|
283
|
-
|
|
284
|
-
|
|
266
|
+
if (Array.isArray(value)) {
|
|
267
|
+
const arrayNode: ShadowNode = { _meta: { arrayKeys: [] } };
|
|
268
|
+
const idKeys: string[] = [];
|
|
285
269
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// A flag to ensure we only schedule the flush once per event-loop tick.
|
|
292
|
-
isFlushScheduled: false,
|
|
270
|
+
value.forEach((item) => {
|
|
271
|
+
const itemId = `id:${ulid()}`;
|
|
272
|
+
arrayNode[itemId] = buildShadowNode(item);
|
|
273
|
+
idKeys.push(itemId);
|
|
274
|
+
});
|
|
293
275
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
arrayNode._meta!.arrayKeys = idKeys;
|
|
277
|
+
return arrayNode;
|
|
278
|
+
}
|
|
297
279
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
280
|
+
if (value.constructor === Object) {
|
|
281
|
+
const objectNode: ShadowNode = { _meta: {} };
|
|
282
|
+
for (const key in value) {
|
|
283
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
284
|
+
objectNode[key] = buildShadowNode(value[key]);
|
|
285
|
+
}
|
|
302
286
|
}
|
|
287
|
+
return objectNode;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { value };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
294
|
+
shadowStateStore: new Map<string, ShadowNode>(),
|
|
303
295
|
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
setTransformCache: (
|
|
297
|
+
key: string,
|
|
298
|
+
path: string[],
|
|
299
|
+
cacheKey: string,
|
|
300
|
+
cacheData: any
|
|
301
|
+
) => {
|
|
302
|
+
const metadata = get().getShadowMetadata(key, path) || {};
|
|
303
|
+
if (!metadata.transformCaches) {
|
|
304
|
+
metadata.transformCaches = new Map();
|
|
305
|
+
}
|
|
306
|
+
metadata.transformCaches.set(cacheKey, cacheData);
|
|
307
|
+
get().setShadowMetadata(key, path, {
|
|
308
|
+
transformCaches: metadata.transformCaches,
|
|
309
|
+
});
|
|
306
310
|
},
|
|
307
|
-
|
|
311
|
+
|
|
312
|
+
initializeShadowState: (key: string, initialState: any) => {
|
|
308
313
|
set((state) => {
|
|
309
314
|
const newShadowStore = new Map(state.shadowStateStore);
|
|
310
|
-
const
|
|
315
|
+
const existingRoot =
|
|
316
|
+
newShadowStore.get(key) || newShadowStore.get(`[${key}`);
|
|
317
|
+
let preservedMetadata: Partial<ShadowMetadata> = {};
|
|
318
|
+
|
|
319
|
+
if (existingRoot?._meta) {
|
|
320
|
+
const {
|
|
321
|
+
components,
|
|
322
|
+
features,
|
|
323
|
+
lastServerSync,
|
|
324
|
+
stateSource,
|
|
325
|
+
baseServerState,
|
|
326
|
+
} = existingRoot._meta;
|
|
327
|
+
if (components) preservedMetadata.components = components;
|
|
328
|
+
if (features) preservedMetadata.features = features;
|
|
329
|
+
if (lastServerSync) preservedMetadata.lastServerSync = lastServerSync;
|
|
330
|
+
if (stateSource) preservedMetadata.stateSource = stateSource;
|
|
331
|
+
if (baseServerState)
|
|
332
|
+
preservedMetadata.baseServerState = baseServerState;
|
|
333
|
+
}
|
|
311
334
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// Create a *new* Set to ensure immutability
|
|
315
|
-
const pathComponents = new Set(pathMeta.pathComponents);
|
|
316
|
-
pathComponents.add(fullComponentId);
|
|
317
|
-
// Update the metadata for the specific path
|
|
318
|
-
newShadowStore.set(dependencyKey, { ...pathMeta, pathComponents });
|
|
335
|
+
newShadowStore.delete(key);
|
|
336
|
+
newShadowStore.delete(`[${key}`);
|
|
319
337
|
|
|
320
|
-
|
|
321
|
-
const rootMeta = newShadowStore.get(stateKey) || {};
|
|
322
|
-
const component = rootMeta.components?.get(fullComponentId);
|
|
338
|
+
const newRoot = buildShadowNode(initialState);
|
|
323
339
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const newPaths = new Set(component.paths);
|
|
327
|
-
newPaths.add(dependencyKey);
|
|
340
|
+
if (!newRoot._meta) newRoot._meta = {};
|
|
341
|
+
Object.assign(newRoot._meta, preservedMetadata);
|
|
328
342
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
newComponentsMap.set(fullComponentId, newComponentRegistration);
|
|
332
|
-
|
|
333
|
-
// Update the root metadata with the new components map
|
|
334
|
-
newShadowStore.set(stateKey, {
|
|
335
|
-
...rootMeta,
|
|
336
|
-
components: newComponentsMap,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
343
|
+
const storageKey = Array.isArray(initialState) ? `[${key}` : key;
|
|
344
|
+
newShadowStore.set(storageKey, newRoot);
|
|
339
345
|
|
|
340
|
-
// Return the final, updated state
|
|
341
346
|
return { shadowStateStore: newShadowStore };
|
|
342
347
|
});
|
|
343
348
|
},
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
|
|
350
|
+
getShadowNode: (key: string, path: string[]): ShadowNode | undefined => {
|
|
351
|
+
const store = get().shadowStateStore;
|
|
352
|
+
let current: any = store.get(key) || store.get(`[${key}`);
|
|
353
|
+
|
|
354
|
+
if (!current) return undefined;
|
|
355
|
+
if (path.length === 0) return current;
|
|
356
|
+
|
|
357
|
+
for (const segment of path) {
|
|
358
|
+
if (typeof current !== 'object' || current === null) return undefined;
|
|
359
|
+
current = current[segment];
|
|
360
|
+
if (current === undefined) return undefined;
|
|
361
|
+
}
|
|
362
|
+
return current;
|
|
353
363
|
},
|
|
354
364
|
|
|
355
|
-
|
|
365
|
+
getShadowMetadata: (
|
|
366
|
+
key: string,
|
|
367
|
+
path: string[]
|
|
368
|
+
): ShadowMetadata | undefined => {
|
|
369
|
+
const node = get().getShadowNode(key, path);
|
|
370
|
+
return node?._meta;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
setShadowMetadata: (
|
|
374
|
+
key: string,
|
|
375
|
+
path: string[],
|
|
376
|
+
newMetadata: Partial<ShadowMetadata>
|
|
377
|
+
) => {
|
|
356
378
|
set((state) => {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
379
|
+
const newStore = new Map(state.shadowStateStore);
|
|
380
|
+
const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
|
|
381
|
+
let root = newStore.get(rootKey);
|
|
382
|
+
|
|
383
|
+
if (!root) {
|
|
384
|
+
root = {};
|
|
385
|
+
newStore.set(rootKey, root);
|
|
361
386
|
}
|
|
362
387
|
|
|
363
|
-
const
|
|
364
|
-
|
|
388
|
+
const clonedRoot: any = { ...root };
|
|
389
|
+
newStore.set(rootKey, clonedRoot);
|
|
365
390
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
391
|
+
let current = clonedRoot;
|
|
392
|
+
for (const segment of path) {
|
|
393
|
+
const nextNode = current[segment] || {};
|
|
394
|
+
current[segment] = { ...nextNode };
|
|
395
|
+
current = current[segment];
|
|
370
396
|
}
|
|
371
397
|
|
|
372
|
-
|
|
398
|
+
current._meta = { ...(current._meta || {}), ...newMetadata };
|
|
399
|
+
|
|
400
|
+
return { shadowStateStore: newStore };
|
|
373
401
|
});
|
|
374
402
|
},
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
403
|
+
getShadowValue: (
|
|
404
|
+
key: string,
|
|
405
|
+
path: string[],
|
|
406
|
+
validArrayIds?: string[],
|
|
407
|
+
log?: boolean
|
|
408
|
+
) => {
|
|
409
|
+
const node = get().getShadowNode(key, path);
|
|
378
410
|
|
|
379
|
-
|
|
380
|
-
const fullKey = [key, ...currentPath].join('.');
|
|
381
|
-
const meta = shadowStateStore.get(fullKey) || {};
|
|
411
|
+
if (node === null || node === undefined) return undefined;
|
|
382
412
|
|
|
383
|
-
|
|
384
|
-
if (meta.isDirty === true) {
|
|
385
|
-
return true; // Return true to indicate parent is dirty
|
|
386
|
-
}
|
|
413
|
+
const nodeKeys = Object.keys(node);
|
|
387
414
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
415
|
+
const isPrimitiveWrapper =
|
|
416
|
+
Object.prototype.hasOwnProperty.call(node, 'value') &&
|
|
417
|
+
nodeKeys.every((k) => k === 'value' || k === '_meta');
|
|
391
418
|
|
|
392
|
-
|
|
393
|
-
|
|
419
|
+
if (isPrimitiveWrapper) {
|
|
420
|
+
return node.value;
|
|
421
|
+
}
|
|
394
422
|
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
423
|
+
// Array Check (This part is correct)
|
|
424
|
+
const isArrayNode =
|
|
425
|
+
node._meta &&
|
|
426
|
+
Object.prototype.hasOwnProperty.call(node._meta, 'arrayKeys');
|
|
427
|
+
if (isArrayNode) {
|
|
428
|
+
const keysToIterate =
|
|
429
|
+
validArrayIds !== undefined && validArrayIds.length > 0
|
|
430
|
+
? validArrayIds
|
|
431
|
+
: node._meta!.arrayKeys!;
|
|
432
|
+
|
|
433
|
+
return keysToIterate.map((itemKey: string) =>
|
|
434
|
+
get().getShadowValue(key, [...path, itemKey])
|
|
435
|
+
);
|
|
405
436
|
}
|
|
406
437
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
});
|
|
413
|
-
return state;
|
|
414
|
-
});
|
|
438
|
+
const result: any = {};
|
|
439
|
+
for (const propKey of nodeKeys) {
|
|
440
|
+
if (propKey !== '_meta' && !propKey.startsWith('id:')) {
|
|
441
|
+
result[propKey] = get().getShadowValue(key, [...path, propKey]);
|
|
442
|
+
}
|
|
415
443
|
}
|
|
444
|
+
return result;
|
|
416
445
|
},
|
|
417
|
-
|
|
418
|
-
setServerStateUpdate: (key, serverState) => {
|
|
446
|
+
updateShadowAtPath: (key, path, newValue) => {
|
|
419
447
|
set((state) => {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
|
|
434
|
-
|
|
435
|
-
subscribeToPath: (path, callback) => {
|
|
436
|
-
const subscribers = get().pathSubscribers;
|
|
437
|
-
const subsForPath = subscribers.get(path) || new Set();
|
|
438
|
-
subsForPath.add(callback);
|
|
439
|
-
subscribers.set(path, subsForPath);
|
|
448
|
+
const newStore = new Map(state.shadowStateStore);
|
|
449
|
+
const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
|
|
450
|
+
let root = newStore.get(rootKey);
|
|
451
|
+
if (!root) return state;
|
|
452
|
+
const clonedRoot: any = { ...root };
|
|
453
|
+
newStore.set(rootKey, clonedRoot);
|
|
454
|
+
let parentNode = clonedRoot;
|
|
455
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
456
|
+
parentNode[path[i]!] = { ...(parentNode[path[i]!] || {}) };
|
|
457
|
+
parentNode = parentNode[path[i]!];
|
|
458
|
+
}
|
|
459
|
+
const targetNode =
|
|
460
|
+
path.length === 0 ? parentNode : parentNode[path[path.length - 1]!];
|
|
440
461
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
currentSubs.delete(callback);
|
|
445
|
-
if (currentSubs.size === 0) {
|
|
446
|
-
get().pathSubscribers.delete(path);
|
|
447
|
-
}
|
|
462
|
+
if (!targetNode) {
|
|
463
|
+
parentNode[path[path.length - 1]!] = buildShadowNode(newValue);
|
|
464
|
+
return { shadowStateStore: newStore };
|
|
448
465
|
}
|
|
449
|
-
};
|
|
450
|
-
},
|
|
451
466
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
467
|
+
function intelligentMerge(nodeToUpdate: any, plainValue: any) {
|
|
468
|
+
if (
|
|
469
|
+
typeof plainValue !== 'object' ||
|
|
470
|
+
plainValue === null ||
|
|
471
|
+
Array.isArray(plainValue)
|
|
472
|
+
) {
|
|
473
|
+
const oldMeta = nodeToUpdate._meta;
|
|
474
|
+
const newNode = buildShadowNode(plainValue);
|
|
475
|
+
if (oldMeta) {
|
|
476
|
+
newNode._meta = { ...oldMeta, ...(newNode._meta || {}) };
|
|
477
|
+
}
|
|
478
|
+
Object.keys(nodeToUpdate).forEach((key) => delete nodeToUpdate[key]);
|
|
479
|
+
Object.assign(nodeToUpdate, newNode);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
455
482
|
|
|
456
|
-
|
|
457
|
-
subs.forEach((callback) => callback(newValue));
|
|
458
|
-
}
|
|
459
|
-
},
|
|
483
|
+
const plainValueKeys = new Set(Object.keys(plainValue));
|
|
460
484
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const preservedComponents = existingRootMeta?.components;
|
|
469
|
-
|
|
470
|
-
// 3. Wipe all old shadow entries for this state key
|
|
471
|
-
const prefixToDelete = key + '.';
|
|
472
|
-
for (const k of Array.from(newShadowStore.keys())) {
|
|
473
|
-
if (k === key || k.startsWith(prefixToDelete)) {
|
|
474
|
-
newShadowStore.delete(k);
|
|
485
|
+
for (const propKey of plainValueKeys) {
|
|
486
|
+
const childValue = plainValue[propKey];
|
|
487
|
+
if (nodeToUpdate[propKey]) {
|
|
488
|
+
intelligentMerge(nodeToUpdate[propKey], childValue);
|
|
489
|
+
} else {
|
|
490
|
+
nodeToUpdate[propKey] = buildShadowNode(childValue);
|
|
491
|
+
}
|
|
475
492
|
}
|
|
476
|
-
}
|
|
477
493
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
494
|
+
for (const nodeKey in nodeToUpdate) {
|
|
495
|
+
if (
|
|
496
|
+
nodeKey === '_meta' ||
|
|
497
|
+
!Object.prototype.hasOwnProperty.call(nodeToUpdate, nodeKey)
|
|
498
|
+
)
|
|
499
|
+
continue;
|
|
481
500
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const itemId = `id:${ulid()}`;
|
|
486
|
-
childIds.push(nodeKey + '.' + itemId);
|
|
487
|
-
});
|
|
488
|
-
newShadowStore.set(nodeKey, { arrayKeys: childIds });
|
|
489
|
-
value.forEach((item, index) => {
|
|
490
|
-
const itemId = childIds[index]!.split('.').pop();
|
|
491
|
-
processValue(item, [...path!, itemId!]);
|
|
492
|
-
});
|
|
493
|
-
} else if (isSimpleObject(value)) {
|
|
494
|
-
const fields = Object.fromEntries(
|
|
495
|
-
Object.keys(value).map((k) => [k, nodeKey + '.' + k])
|
|
496
|
-
);
|
|
497
|
-
newShadowStore.set(nodeKey, { fields });
|
|
498
|
-
Object.keys(value).forEach((k) => {
|
|
499
|
-
processValue(value[k], [...path, k]);
|
|
500
|
-
});
|
|
501
|
-
} else {
|
|
502
|
-
newShadowStore.set(nodeKey, { value });
|
|
501
|
+
if (!plainValueKeys.has(nodeKey)) {
|
|
502
|
+
delete nodeToUpdate[nodeKey];
|
|
503
|
+
}
|
|
503
504
|
}
|
|
504
|
-
};
|
|
505
|
-
processValue(initialState, []);
|
|
506
|
-
|
|
507
|
-
// 5. RESTORE the preserved components map onto the new root metadata
|
|
508
|
-
if (preservedComponents) {
|
|
509
|
-
const newRootMeta = newShadowStore.get(key) || {};
|
|
510
|
-
newShadowStore.set(key, {
|
|
511
|
-
...newRootMeta,
|
|
512
|
-
components: preservedComponents,
|
|
513
|
-
});
|
|
514
505
|
}
|
|
515
506
|
|
|
516
|
-
|
|
517
|
-
|
|
507
|
+
intelligentMerge(targetNode, newValue);
|
|
508
|
+
|
|
509
|
+
get().notifyPathSubscribers([key, ...path].join('.'), {
|
|
510
|
+
type: 'UPDATE',
|
|
511
|
+
newValue,
|
|
512
|
+
});
|
|
513
|
+
return { shadowStateStore: newStore };
|
|
518
514
|
});
|
|
519
515
|
},
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
516
|
+
addItemsToArrayNode: (key, arrayPath, newItems, newKeys) => {
|
|
517
|
+
set((state) => {
|
|
518
|
+
const newStore = new Map(state.shadowStateStore);
|
|
519
|
+
const rootKey = newStore.has(`[${key}`) ? `[${key}` : key;
|
|
520
|
+
let root = newStore.get(rootKey);
|
|
521
|
+
if (!root) {
|
|
522
|
+
console.error('Root not found for state key:', key);
|
|
523
|
+
return state;
|
|
526
524
|
}
|
|
527
525
|
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
return undefined;
|
|
531
|
-
}
|
|
526
|
+
const clonedRoot = { ...root };
|
|
527
|
+
newStore.set(rootKey, clonedRoot);
|
|
532
528
|
|
|
533
|
-
|
|
534
|
-
|
|
529
|
+
let current = clonedRoot;
|
|
530
|
+
for (const segment of arrayPath) {
|
|
531
|
+
const nextNode = current[segment] || {};
|
|
532
|
+
current[segment] = { ...nextNode };
|
|
533
|
+
current = current[segment];
|
|
535
534
|
}
|
|
536
535
|
|
|
537
|
-
|
|
536
|
+
Object.assign(current, newItems);
|
|
537
|
+
current._meta = { ...(current._meta || {}), arrayKeys: newKeys };
|
|
538
538
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
memo.set(keyToBuild, result);
|
|
543
|
-
keys.forEach((itemKey) => {
|
|
544
|
-
result.push(reconstruct(itemKey));
|
|
545
|
-
});
|
|
546
|
-
} else if (shadowMeta.fields) {
|
|
547
|
-
result = {};
|
|
548
|
-
memo.set(keyToBuild, result);
|
|
549
|
-
Object.entries(shadowMeta.fields).forEach(([key, fieldPath]) => {
|
|
550
|
-
result[key] = reconstruct(fieldPath as string);
|
|
551
|
-
});
|
|
552
|
-
} else {
|
|
553
|
-
result = undefined;
|
|
554
|
-
}
|
|
539
|
+
return { shadowStateStore: newStore };
|
|
540
|
+
});
|
|
541
|
+
},
|
|
555
542
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
543
|
+
insertShadowArrayElement: (key, arrayPath, newItem, index) => {
|
|
544
|
+
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
545
|
+
if (!arrayNode?._meta?.arrayKeys) {
|
|
546
|
+
console.error(
|
|
547
|
+
`Array not found at path: ${[key, ...arrayPath].join('.')}`
|
|
548
|
+
);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
559
551
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const
|
|
552
|
+
const newItemId = `id:${ulid()}`;
|
|
553
|
+
const itemsToAdd = { [newItemId]: buildShadowNode(newItem) };
|
|
554
|
+
|
|
555
|
+
const currentKeys = arrayNode._meta.arrayKeys;
|
|
556
|
+
const newKeys = [...currentKeys];
|
|
557
|
+
const insertionPoint =
|
|
558
|
+
index !== undefined && index >= 0 && index <= newKeys.length
|
|
559
|
+
? index
|
|
560
|
+
: newKeys.length;
|
|
561
|
+
newKeys.splice(insertionPoint, 0, newItemId);
|
|
562
|
+
|
|
563
|
+
get().addItemsToArrayNode(key, arrayPath, itemsToAdd, newKeys);
|
|
565
564
|
|
|
566
|
-
|
|
565
|
+
const arrayKey = [key, ...arrayPath].join('.');
|
|
566
|
+
get().notifyPathSubscribers(arrayKey, {
|
|
567
|
+
type: 'INSERT',
|
|
568
|
+
path: arrayKey,
|
|
569
|
+
itemKey: `${arrayKey}.${newItemId}`,
|
|
570
|
+
index: insertionPoint,
|
|
571
|
+
});
|
|
567
572
|
},
|
|
573
|
+
insertManyShadowArrayElements: (key, arrayPath, newItems, index) => {
|
|
574
|
+
if (!newItems || newItems.length === 0) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
568
577
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const existingMeta = get().shadowStateStore.get(fullKey);
|
|
572
|
-
|
|
573
|
-
// --- THIS IS THE TRAP ---
|
|
574
|
-
// If the existing metadata HAS a components map, but the NEW metadata DOES NOT,
|
|
575
|
-
// it means we are about to wipe it out. This is the bug.
|
|
576
|
-
if (existingMeta?.components && !metadata.components) {
|
|
577
|
-
console.group(
|
|
578
|
-
'%c🚨 RACE CONDITION DETECTED! 🚨',
|
|
579
|
-
'color: red; font-size: 18px; font-weight: bold;'
|
|
580
|
-
);
|
|
578
|
+
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
579
|
+
if (!arrayNode?._meta?.arrayKeys) {
|
|
581
580
|
console.error(
|
|
582
|
-
`
|
|
583
|
-
);
|
|
584
|
-
console.log(
|
|
585
|
-
'The EXISTING metadata had a components map:',
|
|
586
|
-
existingMeta.components
|
|
581
|
+
`Array not found at path: ${[key, ...arrayPath].join('.')}`
|
|
587
582
|
);
|
|
588
|
-
|
|
589
|
-
'The NEW metadata is trying to save WITHOUT a components map:',
|
|
590
|
-
metadata
|
|
591
|
-
);
|
|
592
|
-
console.log(
|
|
593
|
-
'%cStack trace to the function that caused this overwrite:',
|
|
594
|
-
'font-weight: bold;'
|
|
595
|
-
);
|
|
596
|
-
console.trace(); // This prints the call stack, leading you to the bad code.
|
|
597
|
-
console.groupEnd();
|
|
583
|
+
return;
|
|
598
584
|
}
|
|
599
|
-
// --- END OF TRAP ---
|
|
600
585
|
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
newShadowStore.set(fullKey, finalMeta);
|
|
604
|
-
set({ shadowStateStore: newShadowStore });
|
|
605
|
-
},
|
|
606
|
-
setTransformCache: (
|
|
607
|
-
key: string,
|
|
608
|
-
path: string[],
|
|
609
|
-
cacheKey: string,
|
|
610
|
-
cacheData: any
|
|
611
|
-
) => {
|
|
612
|
-
const fullKey = [key, ...path].join('.');
|
|
613
|
-
const newShadowStore = new Map(get().shadowStateStore);
|
|
614
|
-
const existing = newShadowStore.get(fullKey) || {};
|
|
586
|
+
const itemsToAdd: Record<string, any> = {};
|
|
587
|
+
const newIds: string[] = [];
|
|
615
588
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
589
|
+
newItems.forEach((item) => {
|
|
590
|
+
const newItemId = `id:${ulid()}`;
|
|
591
|
+
newIds.push(newItemId);
|
|
592
|
+
itemsToAdd[newItemId] = buildShadowNode(item);
|
|
593
|
+
});
|
|
620
594
|
|
|
621
|
-
|
|
622
|
-
|
|
595
|
+
const currentKeys = arrayNode._meta.arrayKeys;
|
|
596
|
+
const finalKeys = [...currentKeys];
|
|
597
|
+
const insertionPoint =
|
|
598
|
+
index !== undefined && index >= 0 && index <= finalKeys.length
|
|
599
|
+
? index
|
|
600
|
+
: finalKeys.length;
|
|
601
|
+
finalKeys.splice(insertionPoint, 0, ...newIds);
|
|
623
602
|
|
|
624
|
-
|
|
625
|
-
newShadowStore.set(fullKey, existing);
|
|
626
|
-
set({ shadowStateStore: newShadowStore });
|
|
603
|
+
get().addItemsToArrayNode(key, arrayPath, itemsToAdd, finalKeys);
|
|
627
604
|
|
|
628
|
-
// Don't call notifyPathSubscribers here - cache updates shouldn't trigger renders
|
|
629
|
-
},
|
|
630
|
-
insertShadowArrayElement: (
|
|
631
|
-
key: string,
|
|
632
|
-
arrayPath: string[],
|
|
633
|
-
newItem: any
|
|
634
|
-
) => {
|
|
635
|
-
const newShadowStore = new Map(get().shadowStateStore);
|
|
636
605
|
const arrayKey = [key, ...arrayPath].join('.');
|
|
637
|
-
|
|
606
|
+
get().notifyPathSubscribers(arrayKey, {
|
|
607
|
+
type: 'INSERT_MANY',
|
|
608
|
+
path: arrayKey,
|
|
609
|
+
count: newItems.length,
|
|
610
|
+
index: insertionPoint,
|
|
611
|
+
});
|
|
612
|
+
},
|
|
638
613
|
|
|
639
|
-
|
|
614
|
+
removeShadowArrayElement: (key, itemPath) => {
|
|
615
|
+
if (itemPath.length === 0) return;
|
|
640
616
|
|
|
641
|
-
const
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const processNewItem = (value: any, path: string[]) => {
|
|
651
|
-
const nodeKey = [key, ...path].join('.');
|
|
652
|
-
|
|
653
|
-
if (Array.isArray(value)) {
|
|
654
|
-
// Handle arrays...
|
|
655
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
656
|
-
// Create fields mapping
|
|
657
|
-
const fields = Object.fromEntries(
|
|
658
|
-
Object.keys(value).map((k) => [k, nodeKey + '.' + k])
|
|
659
|
-
);
|
|
660
|
-
newShadowStore.set(nodeKey, { fields });
|
|
661
|
-
|
|
662
|
-
// Process each field
|
|
663
|
-
Object.entries(value).forEach(([k, v]) => {
|
|
664
|
-
processNewItem(v, [...path, k]);
|
|
665
|
-
});
|
|
666
|
-
} else {
|
|
667
|
-
// Primitive value
|
|
668
|
-
newShadowStore.set(nodeKey, { value });
|
|
669
|
-
}
|
|
670
|
-
};
|
|
617
|
+
const arrayPath = itemPath.slice(0, -1);
|
|
618
|
+
const itemId = itemPath[itemPath.length - 1];
|
|
619
|
+
if (!itemId?.startsWith('id:')) return;
|
|
620
|
+
|
|
621
|
+
const arrayNode = get().getShadowNode(key, arrayPath);
|
|
622
|
+
if (!arrayNode?._meta?.arrayKeys) return;
|
|
623
|
+
|
|
624
|
+
const newKeys = arrayNode._meta.arrayKeys.filter((k) => k !== itemId);
|
|
625
|
+
delete arrayNode[itemId];
|
|
671
626
|
|
|
672
|
-
|
|
673
|
-
set({ shadowStateStore: newShadowStore });
|
|
627
|
+
get().setShadowMetadata(key, arrayPath, { arrayKeys: newKeys });
|
|
674
628
|
|
|
629
|
+
const arrayKey = [key, ...arrayPath].join('.');
|
|
675
630
|
get().notifyPathSubscribers(arrayKey, {
|
|
676
|
-
type: '
|
|
631
|
+
type: 'REMOVE',
|
|
677
632
|
path: arrayKey,
|
|
678
|
-
itemKey:
|
|
633
|
+
itemKey: `${arrayKey}.${itemId}`,
|
|
679
634
|
});
|
|
680
635
|
},
|
|
681
|
-
removeShadowArrayElement: (key: string, itemPath: string[]) => {
|
|
682
|
-
const newShadowStore = new Map(get().shadowStateStore);
|
|
683
636
|
|
|
684
|
-
|
|
685
|
-
const
|
|
637
|
+
addPathComponent: (stateKey, dependencyPath, fullComponentId) => {
|
|
638
|
+
const metadata = get().getShadowMetadata(stateKey, dependencyPath) || {};
|
|
639
|
+
const newPathComponents = new Set(metadata.pathComponents);
|
|
640
|
+
newPathComponents.add(fullComponentId);
|
|
641
|
+
get().setShadowMetadata(stateKey, dependencyPath, {
|
|
642
|
+
pathComponents: newPathComponents,
|
|
643
|
+
});
|
|
686
644
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
645
|
+
const rootMeta = get().getShadowMetadata(stateKey, []);
|
|
646
|
+
if (rootMeta?.components) {
|
|
647
|
+
const component = rootMeta.components.get(fullComponentId);
|
|
648
|
+
if (component) {
|
|
649
|
+
const fullPathKey = [stateKey, ...dependencyPath].join('.');
|
|
650
|
+
const newPaths = new Set(component.paths);
|
|
651
|
+
newPaths.add(fullPathKey);
|
|
652
|
+
const newComponentRegistration = { ...component, paths: newPaths };
|
|
653
|
+
const newComponentsMap = new Map(rootMeta.components);
|
|
654
|
+
newComponentsMap.set(fullComponentId, newComponentRegistration);
|
|
655
|
+
get().setShadowMetadata(stateKey, [], { components: newComponentsMap });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
},
|
|
690
659
|
|
|
691
|
-
|
|
692
|
-
const
|
|
660
|
+
registerComponent: (stateKey, fullComponentId, registration) => {
|
|
661
|
+
const rootMeta = get().getShadowMetadata(stateKey, []) || {};
|
|
662
|
+
const components = new Map(rootMeta.components);
|
|
663
|
+
components.set(fullComponentId, registration);
|
|
664
|
+
get().setShadowMetadata(stateKey, [], { components });
|
|
665
|
+
},
|
|
693
666
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
667
|
+
unregisterComponent: (stateKey, fullComponentId) => {
|
|
668
|
+
const rootMeta = get().getShadowMetadata(stateKey, []);
|
|
669
|
+
if (!rootMeta?.components) return;
|
|
670
|
+
const components = new Map(rootMeta.components);
|
|
671
|
+
if (components.delete(fullComponentId)) {
|
|
672
|
+
get().setShadowMetadata(stateKey, [], { components });
|
|
673
|
+
}
|
|
674
|
+
},
|
|
699
675
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
676
|
+
markAsDirty: (key, path, options = { bubble: true }) => {
|
|
677
|
+
const setDirtyOnPath = (pathToMark: string[]) => {
|
|
678
|
+
const node = get().getShadowNode(key, pathToMark);
|
|
679
|
+
if (node?._meta?.isDirty) {
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
get().setShadowMetadata(key, pathToMark, { isDirty: true });
|
|
683
|
+
return false;
|
|
684
|
+
};
|
|
705
685
|
|
|
706
|
-
|
|
707
|
-
newShadowStore.set(parentKey, {
|
|
708
|
-
...parentMeta,
|
|
709
|
-
arrayKeys: newArrayKeys,
|
|
710
|
-
});
|
|
686
|
+
setDirtyOnPath(path);
|
|
711
687
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
688
|
+
if (options.bubble) {
|
|
689
|
+
let parentPath = [...path];
|
|
690
|
+
while (parentPath.length > 0) {
|
|
691
|
+
parentPath.pop();
|
|
692
|
+
if (setDirtyOnPath(parentPath)) {
|
|
693
|
+
break;
|
|
718
694
|
}
|
|
719
695
|
}
|
|
720
696
|
}
|
|
697
|
+
},
|
|
721
698
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
699
|
+
serverStateUpdates: new Map(),
|
|
700
|
+
setServerStateUpdate: (key, serverState) => {
|
|
701
|
+
set((state) => ({
|
|
702
|
+
serverStateUpdates: new Map(state.serverStateUpdates).set(
|
|
703
|
+
key,
|
|
704
|
+
serverState
|
|
705
|
+
),
|
|
706
|
+
}));
|
|
707
|
+
get().notifyPathSubscribers(key, {
|
|
708
|
+
type: 'SERVER_STATE_UPDATE',
|
|
709
|
+
serverState,
|
|
728
710
|
});
|
|
729
711
|
},
|
|
730
712
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return; // Skip update for unchanged primitives
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// CHANGE: Don't clone the entire Map, just update in place
|
|
741
|
-
set((state) => {
|
|
742
|
-
const store = state.shadowStateStore;
|
|
743
|
-
|
|
744
|
-
if (!isSimpleObject(newValue)) {
|
|
745
|
-
const meta = store.get(fullKey) || {};
|
|
746
|
-
store.set(fullKey, { ...meta, value: newValue });
|
|
747
|
-
} else {
|
|
748
|
-
// Handle objects by iterating
|
|
749
|
-
const processObject = (currentPath: string[], objectToSet: any) => {
|
|
750
|
-
const currentFullKey = [key, ...currentPath].join('.');
|
|
751
|
-
const meta = store.get(currentFullKey);
|
|
752
|
-
|
|
753
|
-
if (meta && meta.fields) {
|
|
754
|
-
for (const fieldKey in objectToSet) {
|
|
755
|
-
if (Object.prototype.hasOwnProperty.call(objectToSet, fieldKey)) {
|
|
756
|
-
const childValue = objectToSet[fieldKey];
|
|
757
|
-
const childFullPath = meta.fields[fieldKey];
|
|
758
|
-
|
|
759
|
-
if (childFullPath) {
|
|
760
|
-
if (isSimpleObject(childValue)) {
|
|
761
|
-
processObject(
|
|
762
|
-
childFullPath.split('.').slice(1),
|
|
763
|
-
childValue
|
|
764
|
-
);
|
|
765
|
-
} else {
|
|
766
|
-
const existingChildMeta = store.get(childFullPath) || {};
|
|
767
|
-
store.set(childFullPath, {
|
|
768
|
-
...existingChildMeta,
|
|
769
|
-
value: childValue,
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
};
|
|
713
|
+
pathSubscribers: new Map<string, Set<(newValue: any) => void>>(),
|
|
714
|
+
subscribeToPath: (path, callback) => {
|
|
715
|
+
const subscribers = get().pathSubscribers;
|
|
716
|
+
const subsForPath = subscribers.get(path) || new Set();
|
|
717
|
+
subsForPath.add(callback);
|
|
718
|
+
subscribers.set(path, subsForPath);
|
|
777
719
|
|
|
778
|
-
|
|
720
|
+
return () => {
|
|
721
|
+
const currentSubs = get().pathSubscribers.get(path);
|
|
722
|
+
if (currentSubs) {
|
|
723
|
+
currentSubs.delete(callback);
|
|
724
|
+
if (currentSubs.size === 0) {
|
|
725
|
+
get().pathSubscribers.delete(path);
|
|
726
|
+
}
|
|
779
727
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
728
|
+
};
|
|
729
|
+
},
|
|
730
|
+
notifyPathSubscribers: (updatedPath, newValue) => {
|
|
731
|
+
const subscribers = get().pathSubscribers;
|
|
732
|
+
const subs = subscribers.get(updatedPath);
|
|
733
|
+
if (subs) {
|
|
734
|
+
subs.forEach((callback) => callback(newValue));
|
|
735
|
+
}
|
|
787
736
|
},
|
|
788
737
|
selectedIndicesMap: new Map<string, string>(),
|
|
789
|
-
getSelectedIndex: (arrayKey
|
|
738
|
+
getSelectedIndex: (arrayKey, validIds) => {
|
|
790
739
|
const itemKey = get().selectedIndicesMap.get(arrayKey);
|
|
791
|
-
|
|
792
740
|
if (!itemKey) return -1;
|
|
793
741
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
742
|
+
const arrayMeta = get().getShadowMetadata(
|
|
743
|
+
arrayKey.split('.')[0]!,
|
|
744
|
+
arrayKey.split('.').slice(1)
|
|
745
|
+
);
|
|
746
|
+
const arrayKeys = validIds || arrayMeta?.arrayKeys;
|
|
798
747
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
return arrayKeys.indexOf(itemKey);
|
|
748
|
+
return arrayKeys ? arrayKeys.indexOf(itemKey) : -1;
|
|
802
749
|
},
|
|
803
750
|
|
|
804
751
|
setSelectedIndex: (arrayKey: string, itemKey: string | undefined) => {
|
|
805
752
|
set((state) => {
|
|
806
|
-
const newMap = state.selectedIndicesMap;
|
|
753
|
+
const newMap = new Map(state.selectedIndicesMap); // CREATE A NEW MAP!
|
|
807
754
|
|
|
808
755
|
if (itemKey === undefined) {
|
|
809
756
|
newMap.delete(arrayKey);
|
|
@@ -814,26 +761,24 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
814
761
|
});
|
|
815
762
|
}
|
|
816
763
|
newMap.set(arrayKey, itemKey);
|
|
817
|
-
|
|
818
|
-
get().notifyPathSubscribers(itemKey, {
|
|
819
|
-
type: 'THIS_SELECTED',
|
|
820
|
-
});
|
|
764
|
+
get().notifyPathSubscribers(itemKey, { type: 'THIS_SELECTED' });
|
|
821
765
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
766
|
+
|
|
767
|
+
get().notifyPathSubscribers(arrayKey, { type: 'GET_SELECTED' });
|
|
768
|
+
|
|
825
769
|
return {
|
|
826
770
|
...state,
|
|
827
771
|
selectedIndicesMap: newMap,
|
|
828
772
|
};
|
|
829
773
|
});
|
|
830
774
|
},
|
|
775
|
+
|
|
831
776
|
clearSelectedIndex: ({ arrayKey }: { arrayKey: string }): void => {
|
|
832
777
|
set((state) => {
|
|
833
|
-
const newMap = state.selectedIndicesMap;
|
|
834
|
-
const
|
|
835
|
-
if (
|
|
836
|
-
get().notifyPathSubscribers(
|
|
778
|
+
const newMap = new Map(state.selectedIndicesMap); // CREATE A NEW MAP!
|
|
779
|
+
const actualKey = newMap.get(arrayKey);
|
|
780
|
+
if (actualKey) {
|
|
781
|
+
get().notifyPathSubscribers(actualKey, {
|
|
837
782
|
type: 'CLEAR_SELECTION',
|
|
838
783
|
});
|
|
839
784
|
}
|
|
@@ -848,75 +793,63 @@ export const getGlobalStore = create<CogsGlobalState>((set, get) => ({
|
|
|
848
793
|
};
|
|
849
794
|
});
|
|
850
795
|
},
|
|
851
|
-
clearSelectedIndexesForState: (stateKey
|
|
796
|
+
clearSelectedIndexesForState: (stateKey) => {
|
|
852
797
|
set((state) => {
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
798
|
+
const newMap = new Map(state.selectedIndicesMap);
|
|
799
|
+
let changed = false;
|
|
800
|
+
for (const key of newMap.keys()) {
|
|
801
|
+
if (key === stateKey || key.startsWith(stateKey + '.')) {
|
|
802
|
+
newMap.delete(key);
|
|
803
|
+
changed = true;
|
|
804
|
+
}
|
|
859
805
|
}
|
|
806
|
+
return changed ? { selectedIndicesMap: newMap } : {};
|
|
860
807
|
});
|
|
861
808
|
},
|
|
862
809
|
|
|
863
810
|
initialStateOptions: {},
|
|
864
|
-
|
|
865
|
-
stateTimeline: {},
|
|
866
|
-
cogsStateStore: {},
|
|
867
811
|
stateLog: new Map(),
|
|
868
|
-
|
|
869
812
|
initialStateGlobal: {},
|
|
870
813
|
|
|
871
|
-
|
|
872
|
-
|
|
814
|
+
addStateLog: (updates) => {
|
|
815
|
+
if (!updates || updates.length === 0) return;
|
|
873
816
|
set((state) => {
|
|
874
817
|
const newLog = new Map(state.stateLog);
|
|
875
|
-
const
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
818
|
+
const logsGroupedByKey = new Map<string, UpdateTypeDetail[]>();
|
|
819
|
+
for (const update of updates) {
|
|
820
|
+
const group = logsGroupedByKey.get(update.stateKey) || [];
|
|
821
|
+
group.push(update);
|
|
822
|
+
logsGroupedByKey.set(update.stateKey, group);
|
|
823
|
+
}
|
|
824
|
+
for (const [key, batchOfUpdates] of logsGroupedByKey.entries()) {
|
|
825
|
+
const newStateLogForKey = new Map(newLog.get(key));
|
|
826
|
+
for (const update of batchOfUpdates) {
|
|
827
|
+
newStateLogForKey.set(JSON.stringify(update.path), { ...update });
|
|
828
|
+
}
|
|
829
|
+
newLog.set(key, newStateLogForKey);
|
|
886
830
|
}
|
|
887
|
-
|
|
888
|
-
newLog.set(key, stateLogForKey);
|
|
889
831
|
return { stateLog: newLog };
|
|
890
832
|
});
|
|
891
833
|
},
|
|
892
834
|
|
|
893
|
-
getInitialOptions: (key) =>
|
|
894
|
-
return get().initialStateOptions[key];
|
|
895
|
-
},
|
|
896
|
-
|
|
835
|
+
getInitialOptions: (key) => get().initialStateOptions[key],
|
|
897
836
|
setInitialStateOptions: (key, value) => {
|
|
898
837
|
set((prev) => ({
|
|
899
|
-
initialStateOptions: {
|
|
900
|
-
...prev.initialStateOptions,
|
|
901
|
-
[key]: value,
|
|
902
|
-
},
|
|
838
|
+
initialStateOptions: { ...prev.initialStateOptions, [key]: value },
|
|
903
839
|
}));
|
|
904
840
|
},
|
|
905
841
|
updateInitialStateGlobal: (key, newState) => {
|
|
906
842
|
set((prev) => ({
|
|
907
|
-
initialStateGlobal: {
|
|
908
|
-
...prev.initialStateGlobal,
|
|
909
|
-
[key]: newState,
|
|
910
|
-
},
|
|
843
|
+
initialStateGlobal: { ...prev.initialStateGlobal, [key]: newState },
|
|
911
844
|
}));
|
|
912
845
|
},
|
|
913
846
|
|
|
914
847
|
syncInfoStore: new Map<string, SyncInfo>(),
|
|
915
|
-
setSyncInfo: (key
|
|
848
|
+
setSyncInfo: (key, syncInfo) =>
|
|
916
849
|
set((state) => {
|
|
917
850
|
const newMap = new Map(state.syncInfoStore);
|
|
918
851
|
newMap.set(key, syncInfo);
|
|
919
|
-
return {
|
|
852
|
+
return { syncInfoStore: newMap };
|
|
920
853
|
}),
|
|
921
|
-
getSyncInfo: (key
|
|
854
|
+
getSyncInfo: (key) => get().syncInfoStore.get(key) || null,
|
|
922
855
|
}));
|