cogsbox-state 0.5.472 → 0.5.473

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.
Files changed (45) hide show
  1. package/dist/CogsState.d.ts +98 -82
  2. package/dist/CogsState.d.ts.map +1 -1
  3. package/dist/CogsState.jsx +1020 -950
  4. package/dist/CogsState.jsx.map +1 -1
  5. package/dist/Components.d.ts.map +1 -1
  6. package/dist/Components.jsx +276 -217
  7. package/dist/Components.jsx.map +1 -1
  8. package/dist/PluginRunner.d.ts +10 -0
  9. package/dist/PluginRunner.d.ts.map +1 -0
  10. package/dist/PluginRunner.jsx +128 -0
  11. package/dist/PluginRunner.jsx.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +33 -26
  15. package/dist/index.js.map +1 -1
  16. package/dist/pluginStore.d.ts +43 -0
  17. package/dist/pluginStore.d.ts.map +1 -0
  18. package/dist/pluginStore.js +52 -0
  19. package/dist/pluginStore.js.map +1 -0
  20. package/dist/plugins.d.ts +1326 -0
  21. package/dist/plugins.d.ts.map +1 -0
  22. package/dist/plugins.js +76 -0
  23. package/dist/plugins.js.map +1 -0
  24. package/dist/store.d.ts +50 -15
  25. package/dist/store.d.ts.map +1 -1
  26. package/dist/store.js +509 -470
  27. package/dist/store.js.map +1 -1
  28. package/dist/utility.d.ts +1 -1
  29. package/dist/utility.d.ts.map +1 -1
  30. package/dist/utility.js +12 -12
  31. package/dist/utility.js.map +1 -1
  32. package/dist/validation.d.ts +7 -0
  33. package/dist/validation.d.ts.map +1 -0
  34. package/dist/validation.js +39 -0
  35. package/dist/validation.js.map +1 -0
  36. package/package.json +13 -3
  37. package/src/CogsState.tsx +658 -457
  38. package/src/Components.tsx +272 -194
  39. package/src/PluginRunner.tsx +208 -0
  40. package/src/index.ts +2 -0
  41. package/src/pluginStore.ts +159 -0
  42. package/src/plugins.ts +548 -0
  43. package/src/store.ts +748 -493
  44. package/src/utility.ts +31 -31
  45. package/src/validation.ts +84 -0
package/src/CogsState.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  'use client';
2
-
2
+ import { pluginStore } from './pluginStore';
3
+ import { FormWrapperParams, type CogsPlugin } from './plugins';
3
4
  import {
4
5
  createElement,
5
6
  startTransition,
@@ -15,10 +16,11 @@ import {
15
16
  } from 'react';
16
17
 
17
18
  import {
18
- getDifferences,
19
- isArray,
19
+ transformStateFunc,
20
20
  isFunction,
21
21
  type GenericObject,
22
+ isArray,
23
+ getDifferences,
22
24
  } from './utility.js';
23
25
  import {
24
26
  FormElementWrapper,
@@ -26,13 +28,13 @@ import {
26
28
  MemoizedCogsItemWrapper,
27
29
  ValidationWrapper,
28
30
  } from './Components.js';
29
- import { isDeepEqual, transformStateFunc } from './utility.js';
31
+ import { isDeepEqual } from './utility.js';
30
32
  import superjson from 'superjson';
31
33
  import { v4 as uuidv4 } from 'uuid';
32
34
 
33
35
  import {
34
- formRefStore,
35
36
  getGlobalStore,
37
+ updateShadowTypeInfo,
36
38
  ValidationError,
37
39
  ValidationSeverity,
38
40
  ValidationStatus,
@@ -44,7 +46,9 @@ import { Operation } from 'fast-json-patch';
44
46
  import * as z3 from 'zod/v3';
45
47
  import * as z4 from 'zod/v4';
46
48
 
47
- type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
49
+ import { runValidation } from './validation';
50
+
51
+ export type Prettify<T> = T extends any ? { [K in keyof T]: T[K] } : never;
48
52
 
49
53
  export type VirtualViewOptions = {
50
54
  itemHeight?: number;
@@ -101,37 +105,7 @@ type CutFunctionType<T> = (
101
105
  ) => StateObject<T>;
102
106
 
103
107
  export type InferArrayElement<T> = T extends (infer U)[] ? U : never;
104
- type ArraySpecificPrototypeKeys =
105
- | 'concat'
106
- | 'copyWithin'
107
- | 'fill'
108
- | 'find'
109
- | 'findIndex'
110
- | 'flat'
111
- | 'flatMap'
112
- | 'includes'
113
- | 'indexOf'
114
- | 'join'
115
- | 'keys'
116
- | 'lastIndexOf'
117
- | 'map'
118
- | 'pop'
119
- | 'push'
120
- | 'reduce'
121
- | 'reduceRight'
122
- | 'reverse'
123
- | 'shift'
124
- | 'slice'
125
- | 'some'
126
- | 'sort'
127
- | 'splice'
128
- | 'unshift'
129
- | 'values'
130
- | 'entries'
131
- | 'every'
132
- | 'filter'
133
- | 'forEach'
134
- | 'with';
108
+
135
109
  export type StreamOptions<T, R = T> = {
136
110
  bufferSize?: number;
137
111
  flushInterval?: number;
@@ -162,6 +136,7 @@ export type ArrayEndType<TShape extends unknown> = {
162
136
  $_index: number;
163
137
  } & EndType<Prettify<InferArrayElement<TShape>>>;
164
138
  $insert: InsertType<Prettify<InferArrayElement<TShape>>>;
139
+ $insertMany: (payload: InferArrayElement<TShape>[]) => void;
165
140
  $cut: CutFunctionType<TShape>;
166
141
  $cutSelected: () => void;
167
142
  $cutByValue: (value: string | number | boolean) => void;
@@ -247,12 +222,20 @@ export type InsertTypeObj<T> = (payload: InsertParams<T>) => void;
247
222
 
248
223
  type EffectFunction<T, R> = (state: T, deps: any[]) => R;
249
224
  export type EndType<T, IsArrayElement = false> = {
225
+ $getPluginMetaData: (pluginName: string) => Record<string, any>;
226
+ $addPluginMetaData: (key: string, data: Record<string, any>) => void;
227
+ $removePluginMetaData: (key: string) => void;
228
+ $setOptions: (options: OptionsType<T>) => void;
229
+
250
230
  $addZodValidation: (
251
231
  errors: ValidationError[],
252
232
  source?: 'client' | 'sync_engine' | 'api'
253
233
  ) => void;
254
234
  $clearZodValidation: (paths?: string[]) => void;
255
- $applyOperation: (operation: UpdateTypeDetail) => void;
235
+ $applyOperation: (
236
+ operation: UpdateTypeDetail,
237
+ metaData?: Record<string, any>
238
+ ) => void;
256
239
  $applyJsonPatch: (patches: any[]) => void;
257
240
  $update: UpdateType<T>;
258
241
  $_path: string[];
@@ -273,7 +256,14 @@ export type EndType<T, IsArrayElement = false> = {
273
256
  $isSelected: boolean;
274
257
  $setSelected: (value: boolean) => void;
275
258
  $toggleSelected: () => void;
276
- $getFormRef: () => React.RefObject<any> | undefined;
259
+ $formInput: {
260
+ setDisabled: (isDisabled: boolean) => void;
261
+ focus: () => void;
262
+ blur: () => void;
263
+ scrollIntoView: (options?: ScrollIntoViewOptions) => void;
264
+ click: () => void;
265
+ selectText: () => void;
266
+ };
277
267
  $removeStorage: () => void;
278
268
  $sync: () => void;
279
269
  $validationWrapper: ({
@@ -295,7 +285,7 @@ export type StateObject<T> = (T extends any[]
295
285
  : never) &
296
286
  EndType<T, true> & {
297
287
  $toggle: T extends boolean ? () => void : never;
298
- $getAllFormRefs: () => Map<string, React.RefObject<any>>;
288
+
299
289
  $_componentId: string | null;
300
290
  $getComponents: () => ComponentsType;
301
291
 
@@ -303,6 +293,7 @@ export type StateObject<T> = (T extends any[]
303
293
  $updateInitialState: (newState: T | null) => {
304
294
  fetchId: (field: keyof T) => string | number;
305
295
  };
296
+ $initializeAndMergeShadowState: (newState: any | null) => void;
306
297
  $_isLoading: boolean;
307
298
  $_serverState: T;
308
299
  $revertToInitialState: (obj?: { validationKey?: string }) => T;
@@ -329,10 +320,13 @@ type EffectiveSetStateArg<
329
320
  ? InsertParams<InferArrayElement<T>>
330
321
  : never
331
322
  : UpdateArg<T>;
332
- type UpdateOptions = {
333
- updateType: 'insert' | 'cut' | 'update';
334
323
 
335
- sync?: boolean;
324
+ type UpdateOptions = {
325
+ updateType: 'insert' | 'cut' | 'update' | 'insert_many';
326
+ validationTrigger?: 'onBlur' | 'onChange' | 'programmatic';
327
+ itemId?: string;
328
+ index?: number;
329
+ metaData?: Record<string, any>;
336
330
  };
337
331
  type EffectiveSetState<TStateObject> = (
338
332
  newStateOrFunction:
@@ -340,21 +334,23 @@ type EffectiveSetState<TStateObject> = (
340
334
  | EffectiveSetStateArg<TStateObject, 'insert'>
341
335
  | null,
342
336
  path: string[],
343
- updateObj: UpdateOptions,
337
+ updateObj: UpdateOptions, // Now includes itemId
344
338
  validationKey?: string
345
339
  ) => void;
346
340
 
347
341
  export type UpdateTypeDetail = {
348
342
  timeStamp: number;
349
343
  stateKey: string;
350
- updateType: 'update' | 'insert' | 'cut';
344
+ updateType: 'update' | 'insert' | 'cut' | 'insert_many';
351
345
  path: string[];
352
346
  status: 'new' | 'sent' | 'synced';
353
347
  oldValue: any;
354
348
  newValue: any;
355
349
  userId?: number;
356
- itemId?: string; // For insert: the new item's ID
357
- insertAfterId?: string; // For insert: ID to insert after (null = beginning)
350
+
351
+ itemId?: string;
352
+ insertAfterId?: string;
353
+ metaData?: Record<string, any>;
358
354
  };
359
355
  export type ReactivityUnion = 'none' | 'component' | 'deps' | 'all';
360
356
  export type ReactivityType =
@@ -365,12 +361,7 @@ export type ReactivityType =
365
361
  | Array<Prettify<'none' | 'component' | 'deps' | 'all'>>;
366
362
 
367
363
  // Define the return type of the sync hook locally
368
- type SyncApi = {
369
- updateState: (data: { operation: any }) => void;
370
- connected: boolean;
371
- clientId: string | null;
372
- subscribers: string[];
373
- };
364
+
374
365
  type ValidationOptionsType = {
375
366
  key?: string;
376
367
  zodSchemaV3?: z3.ZodType<any, any, any>;
@@ -379,7 +370,7 @@ type ValidationOptionsType = {
379
370
  onChange?: 'error' | 'warning';
380
371
  blockSync?: boolean;
381
372
  };
382
- type UseSyncType<T> = (state: T, a: SyncOptionsType<any>) => SyncApi;
373
+
383
374
  type SyncOptionsType<TApiParams> = {
384
375
  apiParams: TApiParams;
385
376
  stateKey?: string;
@@ -390,12 +381,23 @@ type SyncOptionsType<TApiParams> = {
390
381
  connect?: boolean;
391
382
  inMemoryState?: boolean;
392
383
  };
393
- export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
384
+
385
+ export type CreateStateOptionsType<
386
+ T extends unknown = unknown,
387
+ TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
388
+ > = {
389
+ formElements?: FormsElementsType<T, TPlugins>;
390
+ validation?: ValidationOptionsType;
391
+ plugins?: TPlugins;
392
+ };
393
+ export type OptionsType<
394
+ T extends unknown = unknown,
395
+ TApiParams = never,
396
+ > = CreateStateOptionsType & {
394
397
  log?: boolean;
395
398
  componentId?: string;
396
399
  syncOptions?: SyncOptionsType<TApiParams>;
397
400
 
398
- validation?: ValidationOptionsType;
399
401
  serverState?: {
400
402
  id?: string | number;
401
403
  data?: T;
@@ -424,12 +426,10 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
424
426
  };
425
427
  middleware?: ({ update }: { update: UpdateTypeDetail }) => void;
426
428
 
427
- modifyState?: (state: T) => T;
428
429
  localStorage?: {
429
430
  key: string | ((state: T) => string);
430
431
  onChange?: (state: T) => void;
431
432
  };
432
- formElements?: FormsElementsType<T>;
433
433
 
434
434
  reactiveDeps?: (state: T) => any[] | true;
435
435
  reactiveType?: ReactivityType;
@@ -440,14 +440,11 @@ export type OptionsType<T extends unknown = unknown, TApiParams = never> = {
440
440
  dependencies?: any[];
441
441
  };
442
442
 
443
- export type SyncRenderOptions<T extends unknown = unknown> = {
444
- children: React.ReactNode;
445
- time: number;
446
- data?: T;
447
- key?: string;
448
- };
449
-
450
- type FormsElementsType<T> = {
443
+ export type FormsElementsType<
444
+ TState,
445
+ TPlugins extends readonly CogsPlugin<any, any, any, any, any>[] = [],
446
+ > = {
447
+ // These optional, built-in wrappers are unchanged.
451
448
  validation?: (options: {
452
449
  children: React.ReactNode;
453
450
  status: ValidationStatus;
@@ -455,28 +452,32 @@ type FormsElementsType<T> = {
455
452
  hasErrors: boolean;
456
453
  hasWarnings: boolean;
457
454
  allErrors: ValidationError[];
458
-
459
455
  path: string[];
460
456
  message?: string;
461
- getData?: () => T;
457
+ getData?: () => TState;
462
458
  }) => React.ReactNode;
463
- syncRender?: (options: SyncRenderOptions<T>) => React.ReactNode;
464
- };
465
-
466
- export type InitialStateInnerType<T extends unknown = unknown> = {
467
- initialState: T;
468
- } & OptionsType<T>;
469
-
470
- export type InitialStateType<T> = {
471
- [key: string]: InitialStateInnerType<T>;
472
- };
473
-
474
- export type AllStateTypes<T extends unknown> = Record<string, T>;
475
-
476
- export type CogsInitialState<T> = {
477
- initialState: T;
478
- formElements?: FormsElementsType<T>;
459
+ syncRender?: (options: {
460
+ children: React.ReactNode;
461
+ time: number;
462
+ data?: TState;
463
+ key?: string;
464
+ }) => React.ReactNode;
465
+ } & {
466
+ // For each plugin `P` in the TPlugins array...
467
+ [P in TPlugins[number] as P['name']]?: P['formWrapper'] extends (
468
+ // ...check if its `formWrapper` property is a function...
469
+ arg: any
470
+ ) => any
471
+ ? // ...if it is, infer the type of its FIRST PARAMETER. This is the key.
472
+ Parameters<P['formWrapper']>[0]
473
+ : // Otherwise, the type is invalid.
474
+ never;
479
475
  };
476
+ export type CogsInitialState<T> =
477
+ | {
478
+ initialState: T;
479
+ }
480
+ | CreateStateOptionsType<T>;
480
481
 
481
482
  export type TransformedStateType<T> = {
482
483
  [P in keyof T]: T[P] extends CogsInitialState<infer U> ? U : T[P];
@@ -489,6 +490,7 @@ const {
489
490
  setShadowMetadata,
490
491
  getShadowValue,
491
492
  initializeShadowState,
493
+ initializeAndMergeShadowState,
492
494
  updateShadowAtPath,
493
495
  insertShadowArrayElement,
494
496
  insertManyShadowArrayElements,
@@ -503,8 +505,14 @@ const {
503
505
  clearSelectedIndex,
504
506
  getSyncInfo,
505
507
  notifyPathSubscribers,
508
+ getPluginMetaDataMap,
509
+ setPluginMetaData,
510
+ removePluginMetaData,
506
511
  // Note: The old functions are no longer imported under their original names
507
512
  } = getGlobalStore.getState();
513
+
514
+ const { notifyUpdate } = pluginStore.getState();
515
+
508
516
  function getArrayData(stateKey: string, path: string[], meta?: MetaData) {
509
517
  const shadowMeta = getShadowMetadata(stateKey, path);
510
518
  const isArray = !!shadowMeta?.arrayKeys;
@@ -547,10 +555,21 @@ function findArrayItem(
547
555
  function setAndMergeOptions(stateKey: string, newOptions: OptionsType<any>) {
548
556
  const initialOptions = getInitialOptions(stateKey as string) || {};
549
557
 
550
- setInitialStateOptions(stateKey as string, {
558
+ const mergedOptions = {
551
559
  ...initialOptions,
552
560
  ...newOptions,
553
- });
561
+ };
562
+
563
+ // FIX: Apply the same default onBlur logic here too
564
+ if (
565
+ (mergedOptions.validation?.zodSchemaV4 ||
566
+ mergedOptions.validation?.zodSchemaV3) &&
567
+ !mergedOptions.validation?.onBlur
568
+ ) {
569
+ mergedOptions.validation.onBlur = 'error';
570
+ }
571
+
572
+ setInitialStateOptions(stateKey as string, mergedOptions);
554
573
  }
555
574
 
556
575
  function setOptions<StateKey, Opt>({
@@ -565,28 +584,23 @@ function setOptions<StateKey, Opt>({
565
584
  const initialOptions = getInitialOptions(stateKey as string) || {};
566
585
  const initialOptionsPartState = initialOptionsPart[stateKey as string] || {};
567
586
 
568
- // Start with the base options
569
587
  let mergedOptions = { ...initialOptionsPartState, ...initialOptions };
570
588
  let needToAdd = false;
571
589
 
572
590
  if (options) {
573
- // A function to recursively merge properties
574
591
  const deepMerge = (target: any, source: any) => {
575
592
  for (const key in source) {
576
593
  if (source.hasOwnProperty(key)) {
577
- // If the property is an object (and not an array), recurse
578
594
  if (
579
595
  source[key] instanceof Object &&
580
596
  !Array.isArray(source[key]) &&
581
597
  target[key] instanceof Object
582
598
  ) {
583
- // Check for changes before merging to set `needToAdd`
584
599
  if (!isDeepEqual(target[key], source[key])) {
585
600
  deepMerge(target[key], source[key]);
586
601
  needToAdd = true;
587
602
  }
588
603
  } else {
589
- // Overwrite if the value is different
590
604
  if (target[key] !== source[key]) {
591
605
  target[key] = source[key];
592
606
  needToAdd = true;
@@ -597,78 +611,107 @@ function setOptions<StateKey, Opt>({
597
611
  return target;
598
612
  };
599
613
 
600
- // Perform a deep merge
601
614
  mergedOptions = deepMerge(mergedOptions, options);
602
615
  }
603
616
 
604
- // Your existing logic for defaults and preservation can follow
605
- if (
606
- mergedOptions.syncOptions &&
607
- (!options || !options.hasOwnProperty('syncOptions'))
608
- ) {
609
- needToAdd = true;
610
- }
611
- if (
612
- (mergedOptions.validation && mergedOptions?.validation?.zodSchemaV4) ||
613
- mergedOptions?.validation?.zodSchemaV3
614
- ) {
615
- // Only set default if onBlur wasn't explicitly provided
616
- const wasOnBlurProvided =
617
+ // Set default onBlur
618
+ if (mergedOptions.validation) {
619
+ const onBlurProvided =
617
620
  options?.validation?.hasOwnProperty('onBlur') ||
618
- initialOptions?.validation?.hasOwnProperty('onBlur');
621
+ initialOptions?.validation?.hasOwnProperty('onBlur') ||
622
+ initialOptionsPartState?.validation?.hasOwnProperty('onBlur');
619
623
 
620
- if (!wasOnBlurProvided) {
621
- mergedOptions.validation.onBlur = 'error'; // Default to error on blur
624
+ if (!onBlurProvided) {
625
+ mergedOptions.validation.onBlur = 'error';
626
+ needToAdd = true;
622
627
  }
623
628
  }
629
+
624
630
  if (needToAdd) {
625
631
  setInitialStateOptions(stateKey as string, mergedOptions);
632
+
633
+ // NEW: Update type info if validation schema was added
634
+ const hadSchema =
635
+ initialOptions?.validation?.zodSchemaV4 ||
636
+ initialOptions?.validation?.zodSchemaV3;
637
+ const hasNewSchemaV4 =
638
+ mergedOptions.validation?.zodSchemaV4 &&
639
+ !initialOptions?.validation?.zodSchemaV4;
640
+ const hasNewSchemaV3 =
641
+ mergedOptions.validation?.zodSchemaV3 &&
642
+ !initialOptions?.validation?.zodSchemaV3;
643
+
644
+ if (!hadSchema && (hasNewSchemaV4 || hasNewSchemaV3)) {
645
+ if (hasNewSchemaV4) {
646
+ updateShadowTypeInfo(
647
+ stateKey as string,
648
+ mergedOptions.validation.zodSchemaV4,
649
+ 'zod4'
650
+ );
651
+ } else if (hasNewSchemaV3) {
652
+ updateShadowTypeInfo(
653
+ stateKey as string,
654
+ mergedOptions.validation.zodSchemaV3,
655
+ 'zod3'
656
+ );
657
+ }
658
+
659
+ // Notify components to re-render with updated validation
660
+ notifyComponents(stateKey as string);
661
+ }
626
662
  }
627
- }
628
663
 
629
- export function addStateOptions<T extends unknown>(
664
+ return mergedOptions;
665
+ }
666
+ export function addStateOptions<T>(
630
667
  initialState: T,
631
- { formElements, validation }: OptionsType<T>
668
+ options: CreateStateOptionsType<T>
632
669
  ) {
633
- return { initialState: initialState, formElements, validation } as T;
670
+ return {
671
+ ...options,
672
+ initialState,
673
+ _addStateOptions: true,
674
+ };
634
675
  }
676
+ export type PluginData = {
677
+ plugin: CogsPlugin<any, any, any, any, any>;
678
+ options: any;
679
+ hookData?: any;
680
+ };
635
681
 
636
- // Define the type for the options setter using the Transformed state
637
- type SetCogsOptionsFunc<T extends Record<string, any>> = <
638
- StateKey extends keyof TransformedStateType<T>,
639
- >(
640
- stateKey: StateKey,
641
- options: OptionsType<TransformedStateType<T>[StateKey]>
642
- ) => void;
682
+ ////////////////////////////////
643
683
 
644
- export const createCogsState = <State extends Record<StateKeys, unknown>>(
684
+ export const createCogsState = <
685
+ State extends Record<string, unknown>,
686
+ const TPlugins extends readonly CogsPlugin<string, any, any, any, any>[] = [],
687
+ >(
645
688
  initialState: State,
646
689
  opt?: {
647
- formElements?: FormsElementsType<State>;
690
+ plugins?: TPlugins;
691
+ formElements?: FormsElementsType<State, TPlugins>;
648
692
  validation?: ValidationOptionsType;
649
- __fromSyncSchema?: boolean;
650
- __syncNotifications?: Record<string, Function>;
651
- __apiParamsMap?: Record<string, any>;
652
- __useSync?: UseSyncType<State>;
653
- __syncSchemas?: Record<string, any>;
654
693
  }
655
694
  ) => {
656
- let newInitialState = initialState;
657
- const [statePart, initialOptionsPart] =
658
- transformStateFunc<State>(newInitialState);
695
+ type PluginOptions = {
696
+ [K in TPlugins[number] as K['name']]?: K extends CogsPlugin<
697
+ string,
698
+ infer O,
699
+ any,
700
+ any,
701
+ any
702
+ >
703
+ ? O
704
+ : never;
705
+ };
659
706
 
660
- if (opt?.__fromSyncSchema && opt?.__syncNotifications) {
661
- getGlobalStore
662
- .getState()
663
- .setInitialStateOptions('__notifications', opt.__syncNotifications);
707
+ if (opt?.plugins) {
708
+ pluginStore.getState().setRegisteredPlugins(opt.plugins as any);
664
709
  }
665
710
 
666
- if (opt?.__fromSyncSchema && opt?.__apiParamsMap) {
667
- getGlobalStore
668
- .getState()
669
- .setInitialStateOptions('__apiParamsMap', opt.__apiParamsMap);
670
- }
711
+ const [statePart, initialOptionsPart] =
712
+ transformStateFunc<State>(initialState);
671
713
 
714
+ // FIX: Store options INCLUDING validation for each state key
672
715
  Object.keys(statePart).forEach((key) => {
673
716
  let existingOptions = initialOptionsPart[key] || {};
674
717
 
@@ -683,61 +726,77 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
683
726
  };
684
727
  }
685
728
 
686
- if (opt?.validation) {
687
- mergedOptions.validation = {
688
- ...opt.validation,
689
- ...(existingOptions.validation || {}),
690
- };
729
+ mergedOptions.validation = {
730
+ onBlur: 'error',
731
+ ...opt?.validation,
732
+ ...(existingOptions.validation || {}),
733
+ };
691
734
 
692
- if (opt.validation.key && !existingOptions.validation?.key) {
693
- mergedOptions.validation.key = `${opt.validation.key}.${key}`;
694
- }
695
- }
696
- if (opt?.__syncSchemas?.[key]?.schemas?.validation) {
697
- mergedOptions.validation = {
698
- zodSchemaV4: opt.__syncSchemas[key].schemas.validation,
699
- ...existingOptions.validation,
700
- };
735
+ if (opt?.validation?.key && !existingOptions.validation?.key) {
736
+ mergedOptions.validation.key = `${opt.validation.key}.${key}`;
701
737
  }
702
- if (Object.keys(mergedOptions).length > 0) {
703
- const existingGlobalOptions = getInitialOptions(key);
704
738
 
705
- if (!existingGlobalOptions) {
706
- setInitialStateOptions(key, mergedOptions);
707
- } else {
708
- // Merge with existing global options
709
- setInitialStateOptions(key, {
739
+ const existingGlobalOptions = getInitialOptions(key);
740
+
741
+ const finalOptions = existingGlobalOptions
742
+ ? {
710
743
  ...existingGlobalOptions,
711
744
  ...mergedOptions,
712
- });
713
- }
714
- }
745
+ formElements: {
746
+ ...existingGlobalOptions.formElements,
747
+ ...mergedOptions.formElements,
748
+ },
749
+ validation: {
750
+ ...existingGlobalOptions.validation,
751
+ ...mergedOptions.validation,
752
+ },
753
+ }
754
+ : mergedOptions;
755
+
756
+ setInitialStateOptions(key, finalOptions);
715
757
  });
716
758
 
717
759
  Object.keys(statePart).forEach((key) => {
718
760
  initializeShadowState(key, statePart[key]);
719
761
  });
720
-
762
+ type Prettify<T> = {
763
+ [K in keyof T]: T[K];
764
+ } & {};
721
765
  type StateKeys = keyof typeof statePart;
722
766
 
723
767
  const useCogsState = <StateKey extends StateKeys>(
724
768
  stateKey: StateKey,
725
- options?: Prettify<OptionsType<(typeof statePart)[StateKey]>>
769
+ options?: Prettify<
770
+ OptionsType<(typeof statePart)[StateKey], never> & {
771
+ [PName in keyof PluginOptions]?: PluginOptions[PName] extends infer P
772
+ ? P extends Record<string, any>
773
+ ? {
774
+ [K in keyof P]: P[K] extends { __key: 'keyed'; map: infer TMap }
775
+ ? StateKey extends keyof TMap
776
+ ? TMap[StateKey]
777
+ : never
778
+ : P[K];
779
+ }
780
+ : P
781
+ : never;
782
+ }
783
+ >
726
784
  ) => {
727
785
  const [componentId] = useState(options?.componentId ?? uuidv4());
728
786
 
729
- setOptions({
787
+ const currentOptions = setOptions({
730
788
  stateKey,
731
789
  options,
732
790
  initialOptionsPart,
733
791
  });
792
+
793
+ const optionsRef = useRef(currentOptions);
794
+ optionsRef.current = currentOptions;
795
+
734
796
  const thiState =
735
797
  getShadowValue(stateKey as string, []) || statePart[stateKey as string];
736
- const partialState = options?.modifyState
737
- ? options.modifyState(thiState)
738
- : thiState;
739
798
 
740
- const updater = useCogsStateFn<(typeof statePart)[StateKey]>(partialState, {
799
+ const updater = useCogsStateFn<(typeof statePart)[StateKey]>(thiState, {
741
800
  stateKey: stateKey as string,
742
801
  syncUpdate: options?.syncUpdate,
743
802
  componentId,
@@ -748,14 +807,30 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
748
807
  defaultState: options?.defaultState as any,
749
808
  dependencies: options?.dependencies,
750
809
  serverState: options?.serverState,
751
- syncOptions: options?.syncOptions,
752
- __useSync: opt?.__useSync as UseSyncType<(typeof statePart)[StateKey]>,
753
810
  });
754
811
 
812
+ useEffect(() => {
813
+ if (options) {
814
+ pluginStore
815
+ .getState()
816
+ .setPluginOptionsForState(stateKey as string, options);
817
+ }
818
+ }, [stateKey, options]);
819
+ useEffect(() => {
820
+ console.log('adding handler 1', stateKey, updater);
821
+ pluginStore
822
+ .getState()
823
+ .stateHandlers.set(stateKey as string, updater as any);
824
+
825
+ return () => {
826
+ pluginStore.getState().stateHandlers.delete(stateKey as string);
827
+ };
828
+ }, [stateKey, updater]);
829
+
755
830
  return updater;
756
831
  };
757
832
 
758
- function setCogsOptions<StateKey extends StateKeys>(
833
+ function setCogsOptionsByKey<StateKey extends StateKeys>(
759
834
  stateKey: StateKey,
760
835
  options: OptionsType<(typeof statePart)[StateKey]>
761
836
  ) {
@@ -768,109 +843,53 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
768
843
  notifyComponents(stateKey as string);
769
844
  }
770
845
 
771
- return { useCogsState, setCogsOptions } as CogsApi<State, never>;
772
- };
773
- type UseCogsStateHook<
774
- T extends Record<string, any>,
775
- TApiParamsMap extends Record<string, any> = never,
776
- > = <StateKey extends keyof TransformedStateType<T> & string>(
777
- stateKey: StateKey,
778
- options?: [TApiParamsMap] extends [never]
779
- ? // When TApiParamsMap is never (no sync)
780
- Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
781
- : // When TApiParamsMap exists (sync enabled)
782
- StateKey extends keyof TApiParamsMap
783
- ? Prettify<
784
- OptionsType<
785
- TransformedStateType<T>[StateKey],
786
- TApiParamsMap[StateKey]
787
- > & {
788
- syncOptions: Prettify<SyncOptionsType<TApiParamsMap[StateKey]>>;
789
- }
790
- >
791
- : Prettify<OptionsType<TransformedStateType<T>[StateKey]>>
792
- ) => StateObject<TransformedStateType<T>[StateKey]>;
793
-
794
- // Update CogsApi to default to never instead of Record<string, never>
795
- type CogsApi<
796
- T extends Record<string, any>,
797
- TApiParamsMap extends Record<string, any> = never,
798
- > = {
799
- useCogsState: UseCogsStateHook<T, TApiParamsMap>;
800
- setCogsOptions: SetCogsOptionsFunc<T>;
801
- };
802
- type GetParamType<SchemaEntry> = SchemaEntry extends {
803
- api?: { queryData?: { _paramType?: infer P } };
804
- }
805
- ? P
806
- : never;
807
-
808
- export function createCogsStateFromSync<
809
- TSyncSchema extends {
810
- schemas: Record<
811
- string,
812
- {
813
- schemas: { defaults: any };
814
- relations?: any;
815
- api?: {
816
- queryData?: any;
846
+ function setCogsFormElements(
847
+ formElements: FormsElementsType<State, TPlugins>
848
+ ) {
849
+ // Get the current list of registered plugins from the store.
850
+ const currentPlugins = pluginStore.getState().registeredPlugins;
851
+
852
+ // Create a new array by mapping over the current plugins.
853
+ // This is crucial for immutability and ensuring Zustand detects the change.
854
+ const updatedPlugins = currentPlugins.map((plugin) => {
855
+ // Check if the formElements object has a wrapper for this specific plugin by name.
856
+ if (formElements.hasOwnProperty(plugin.name)) {
857
+ // If it does, return a *new* plugin object.
858
+ // Spread the existing plugin properties and add/overwrite the formWrapper.
859
+ return {
860
+ ...plugin,
861
+ formWrapper: formElements[plugin.name as keyof typeof formElements],
817
862
  };
818
- [key: string]: any;
819
863
  }
820
- >;
821
- notifications: Record<string, any>;
822
- },
823
- >(
824
- syncSchema: TSyncSchema,
825
- useSync: UseSyncType<any>
826
- ): CogsApi<
827
- {
828
- [K in keyof TSyncSchema['schemas']]: TSyncSchema['schemas'][K]['relations'] extends object
829
- ? TSyncSchema['schemas'][K] extends {
830
- schemas: { defaults: infer D };
831
- }
832
- ? D
833
- : TSyncSchema['schemas'][K]['schemas']['defaults']
834
- : TSyncSchema['schemas'][K]['schemas']['defaults'];
835
- },
836
- {
837
- [K in keyof TSyncSchema['schemas']]: GetParamType<
838
- TSyncSchema['schemas'][K]
839
- >;
840
- }
841
- > {
842
- const schemas = syncSchema.schemas;
843
- const initialState: any = {};
844
- const apiParamsMap: any = {};
845
-
846
- // Extract defaultValues AND apiParams from each entry
847
- for (const key in schemas) {
848
- const entry = schemas[key];
849
-
850
- // Check if we have relations and thus view defaults
851
- if (entry?.relations && entry?.schemas?.defaults) {
852
- // Use the view defaults when relations are present
853
- initialState[key] = entry.schemas.defaults;
854
- } else {
855
- // Fall back to regular defaultValues
856
- initialState[key] = entry?.schemas?.defaults || {};
857
- }
858
- console.log('initialState', initialState);
864
+ // If there's no new wrapper for this plugin, return the original object.
865
+ return plugin;
866
+ });
859
867
 
860
- // Extract apiParams from the api.queryData._paramType
861
- if (entry?.api?.queryData?._paramType) {
862
- apiParamsMap[key] = entry.api.queryData._paramType;
863
- }
868
+ // Use the store's dedicated setter function to update the registered plugins list.
869
+ // This will trigger a state update that components listening to the store will react to.
870
+ pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
871
+
872
+ // For good measure and consistency, we should still update the formElements
873
+ // in the initial options, in case any other part of the system relies on it.
874
+ const allStateKeys = Object.keys(statePart);
875
+ allStateKeys.forEach((key) => {
876
+ const existingOptions = getInitialOptions(key) || {};
877
+ const finalOptions = {
878
+ ...existingOptions,
879
+ formElements: {
880
+ ...(existingOptions.formElements || {}),
881
+ ...formElements,
882
+ },
883
+ };
884
+ setInitialStateOptions(key, finalOptions);
885
+ });
864
886
  }
865
-
866
- return createCogsState(initialState, {
867
- __fromSyncSchema: true,
868
- __syncNotifications: syncSchema.notifications,
869
- __apiParamsMap: apiParamsMap,
870
- __useSync: useSync,
871
- __syncSchemas: schemas,
872
- }) as any;
873
- }
887
+ return {
888
+ useCogsState,
889
+ setCogsOptionsByKey,
890
+ setCogsFormElements,
891
+ };
892
+ };
874
893
 
875
894
  const saveToLocalStorage = <T,>(
876
895
  state: T,
@@ -1042,35 +1061,14 @@ let isFlushScheduled = false;
1042
1061
  function scheduleFlush() {
1043
1062
  if (!isFlushScheduled) {
1044
1063
  isFlushScheduled = true;
1045
- queueMicrotask(flushQueue);
1064
+ console.log('Scheduling flush');
1065
+ queueMicrotask(() => {
1066
+ console.log('Actually flushing');
1067
+ flushQueue();
1068
+ });
1046
1069
  }
1047
1070
  }
1048
- function handleUpdate(
1049
- stateKey: string,
1050
- path: string[],
1051
- payload: any
1052
- ): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
1053
- // ✅ FIX: Get the old value before the update.
1054
- const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
1055
1071
 
1056
- const newValue = isFunction(payload) ? payload(oldValue) : payload;
1057
-
1058
- // ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
1059
- // The manual loop has been removed.
1060
- updateShadowAtPath(stateKey, path, newValue);
1061
-
1062
- markAsDirty(stateKey, path, { bubble: true });
1063
-
1064
- // Return the metadata of the node *after* the update.
1065
- const newShadowMeta = getShadowMetadata(stateKey, path);
1066
-
1067
- return {
1068
- type: 'update',
1069
- oldValue: oldValue,
1070
- newValue,
1071
- shadowMeta: newShadowMeta,
1072
- };
1073
- }
1074
1072
  // 2. Update signals
1075
1073
  function updateSignals(shadowMeta: any, displayValue: any) {
1076
1074
  if (!shadowMeta?.signals?.length) return;
@@ -1114,72 +1112,179 @@ function getComponentNotifications(
1114
1112
 
1115
1113
  const componentsToNotify = new Set<any>();
1116
1114
 
1117
- // For insert operations, use the array path not the item path
1118
- let notificationPath = path;
1119
- if (result.type === 'insert' && result.itemId) {
1120
- // We have the new structure with itemId separate
1121
- notificationPath = path; // Already the array path
1122
- }
1123
-
1124
- // BUBBLE UP: Notify components at this path and all parent paths
1125
- let currentPath = [...notificationPath];
1126
- while (true) {
1127
- const pathMeta = getShadowMetadata(stateKey, currentPath);
1128
-
1129
- if (pathMeta?.pathComponents) {
1130
- pathMeta.pathComponents.forEach((componentId: string) => {
1131
- const component = rootMeta.components?.get(componentId);
1132
- if (component) {
1133
- const reactiveTypes = Array.isArray(component.reactiveType)
1134
- ? component.reactiveType
1135
- : [component.reactiveType || 'component'];
1136
- if (!reactiveTypes.includes('none')) {
1137
- componentsToNotify.add(component);
1115
+ // --- PASS 1: Notify specific subscribers based on update type ---
1116
+
1117
+ if (result.type === 'update') {
1118
+ // --- Bubble-up Notification ---
1119
+ // An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
1120
+ let currentPath = [...path];
1121
+ while (true) {
1122
+ const pathMeta = getShadowMetadata(stateKey, currentPath);
1123
+
1124
+ if (pathMeta?.pathComponents) {
1125
+ pathMeta.pathComponents.forEach((componentId: string) => {
1126
+ const component = rootMeta.components?.get(componentId);
1127
+ // NEW: Add component to the set instead of calling forceUpdate()
1128
+ if (component) {
1129
+ const reactiveTypes = Array.isArray(component.reactiveType)
1130
+ ? component.reactiveType
1131
+ : [component.reactiveType || 'component'];
1132
+ if (!reactiveTypes.includes('none')) {
1133
+ componentsToNotify.add(component);
1134
+ }
1138
1135
  }
1136
+ });
1137
+ }
1138
+
1139
+ if (currentPath.length === 0) break;
1140
+ currentPath.pop(); // Go up one level
1141
+ }
1142
+
1143
+ // --- Deep Object Change Notification ---
1144
+ // If the new value is an object, notify components subscribed to sub-paths that changed.
1145
+ if (
1146
+ result.newValue &&
1147
+ typeof result.newValue === 'object' &&
1148
+ !isArray(result.newValue)
1149
+ ) {
1150
+ const changedSubPaths = getDifferences(result.newValue, result.oldValue);
1151
+
1152
+ changedSubPaths.forEach((subPathString: string) => {
1153
+ const subPath = subPathString.split('.');
1154
+ const fullSubPath = [...path, ...subPath];
1155
+ const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
1156
+
1157
+ if (subPathMeta?.pathComponents) {
1158
+ subPathMeta.pathComponents.forEach((componentId: string) => {
1159
+ const component = rootMeta.components?.get(componentId);
1160
+ // NEW: Add component to the set
1161
+ if (component) {
1162
+ const reactiveTypes = Array.isArray(component.reactiveType)
1163
+ ? component.reactiveType
1164
+ : [component.reactiveType || 'component'];
1165
+ if (!reactiveTypes.includes('none')) {
1166
+ componentsToNotify.add(component);
1167
+ }
1168
+ }
1169
+ });
1139
1170
  }
1140
1171
  });
1141
1172
  }
1173
+ } else if (
1174
+ result.type === 'insert' ||
1175
+ result.type === 'cut' ||
1176
+ result.type === 'insert_many'
1177
+ ) {
1178
+ // This path is the array itself (e.g., ['pets'])
1179
+ const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
1180
+
1181
+ // --- FIX: ADD BUBBLE-UP LOGIC HERE ---
1182
+ // Start from the array's path and go up to the root
1183
+ let currentPath = [...parentArrayPath];
1184
+ while (true) {
1185
+ const pathMeta = getShadowMetadata(stateKey, currentPath);
1186
+
1187
+ if (pathMeta?.pathComponents) {
1188
+ pathMeta.pathComponents.forEach((componentId: string) => {
1189
+ const component = rootMeta.components?.get(componentId);
1190
+ if (component) {
1191
+ componentsToNotify.add(component);
1192
+ }
1193
+ });
1194
+ }
1142
1195
 
1143
- if (currentPath.length === 0) break;
1144
- currentPath.pop(); // Go up one level
1196
+ if (currentPath.length === 0) break;
1197
+ currentPath.pop(); // Go up one level
1198
+ }
1199
+ // --- END FIX ---
1145
1200
  }
1146
1201
 
1147
1202
  // --- PASS 2: Handle 'all' and 'deps' reactivity types ---
1148
1203
  // Iterate over all components for this stateKey that haven't been notified yet.
1149
- rootMeta.components.forEach((component, componentId) => {
1150
- // If we've already added this component, skip it.
1151
- if (componentsToNotify.has(component)) {
1152
- return;
1153
- }
1154
-
1155
- const reactiveTypes = Array.isArray(component.reactiveType)
1156
- ? component.reactiveType
1157
- : [component.reactiveType || 'component'];
1158
-
1159
- if (reactiveTypes.includes('all')) {
1160
- componentsToNotify.add(component);
1161
- } else if (reactiveTypes.includes('deps') && component.depsFunction) {
1162
- const currentState = getShadowValue(stateKey, []);
1163
- const newDeps = component.depsFunction(currentState);
1164
-
1165
- if (
1166
- newDeps === true ||
1167
- (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
1168
- ) {
1169
- component.prevDeps = newDeps as any; // Update the dependencies for the next check
1170
- componentsToNotify.add(component);
1171
- }
1172
- }
1173
- });
1204
+ // rootMeta.components.forEach((component, componentId) => {
1205
+ // // If we've already added this component, skip it.
1206
+ // if (componentsToNotify.has(component)) {
1207
+ // return;
1208
+ // }
1209
+
1210
+ // const reactiveTypes = Array.isArray(component.reactiveType)
1211
+ // ? component.reactiveType
1212
+ // : [component.reactiveType || 'component'];
1213
+
1214
+ // if (reactiveTypes.includes('all')) {
1215
+ // componentsToNotify.add(component);
1216
+ // } else if (reactiveTypes.includes('deps') && component.depsFunction) {
1217
+ // const currentState = getShadowValue(stateKey, []);
1218
+ // const newDeps = component.depsFunction(currentState);
1219
+
1220
+ // if (
1221
+ // newDeps === true ||
1222
+ // (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
1223
+ // ) {
1224
+ // component.prevDeps = newDeps as any; // Update the dependencies for the next check
1225
+ // componentsToNotify.add(component);
1226
+ // }
1227
+ // }
1228
+ // });
1174
1229
 
1175
1230
  return componentsToNotify;
1176
1231
  }
1177
1232
 
1233
+ function handleUpdate(
1234
+ stateKey: string,
1235
+ path: string[],
1236
+ payload: any
1237
+ ): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
1238
+ // ✅ FIX: Get the old value before the update.
1239
+ const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
1240
+
1241
+ const newValue = isFunction(payload) ? payload(oldValue) : payload;
1242
+
1243
+ // ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
1244
+ // The manual loop has been removed.
1245
+ updateShadowAtPath(stateKey, path, newValue);
1246
+
1247
+ markAsDirty(stateKey, path, { bubble: true });
1248
+
1249
+ // Return the metadata of the node *after* the update.
1250
+ const newShadowMeta = getShadowMetadata(stateKey, path);
1251
+
1252
+ return {
1253
+ type: 'update',
1254
+ oldValue: oldValue,
1255
+ newValue,
1256
+ shadowMeta: newShadowMeta,
1257
+ };
1258
+ }
1259
+ function handleInsertMany(
1260
+ stateKey: string,
1261
+ path: string[],
1262
+ payload: any[]
1263
+ ): {
1264
+ type: 'insert_many';
1265
+ count: number;
1266
+ shadowMeta: any;
1267
+ path: string[];
1268
+ } {
1269
+ // Use the existing, optimized global store function to perform the state update
1270
+ insertManyShadowArrayElements(stateKey, path, payload);
1271
+
1272
+ markAsDirty(stateKey, path, { bubble: true });
1273
+ const updatedMeta = getShadowMetadata(stateKey, path);
1274
+
1275
+ return {
1276
+ type: 'insert_many',
1277
+ count: payload.length,
1278
+ shadowMeta: updatedMeta,
1279
+ path: path,
1280
+ };
1281
+ }
1178
1282
  function handleInsert(
1179
1283
  stateKey: string,
1180
1284
  path: string[],
1181
1285
  payload: any,
1182
- index?: number
1286
+ index?: number,
1287
+ itemId?: string
1183
1288
  ): {
1184
1289
  type: 'insert';
1185
1290
  newValue: any;
@@ -1189,19 +1294,28 @@ function handleInsert(
1189
1294
  insertAfterId?: string;
1190
1295
  } {
1191
1296
  let newValue;
1297
+
1192
1298
  if (isFunction(payload)) {
1193
1299
  const { value: currentValue } = getScopedData(stateKey, path);
1194
- newValue = payload({ state: currentValue, uuid: uuidv4() });
1300
+ newValue = payload({ state: currentValue });
1195
1301
  } else {
1196
1302
  newValue = payload;
1197
1303
  }
1304
+ //console.time('insertShadowArrayElement');
1305
+ // Pass itemId to insertShadowArrayElement
1306
+ const actualItemId = insertShadowArrayElement(
1307
+ stateKey,
1308
+ path,
1309
+ newValue,
1310
+ index,
1311
+ itemId
1312
+ );
1313
+ //console.timeEnd('insertShadowArrayElement');
1198
1314
 
1199
- const itemId = insertShadowArrayElement(stateKey, path, newValue, index);
1200
1315
  markAsDirty(stateKey, path, { bubble: true });
1201
1316
 
1202
1317
  const updatedMeta = getShadowMetadata(stateKey, path);
1203
1318
 
1204
- // Find the ID that comes before this insertion point
1205
1319
  let insertAfterId: string | undefined;
1206
1320
  if (updatedMeta?.arrayKeys && index !== undefined && index > 0) {
1207
1321
  insertAfterId = updatedMeta.arrayKeys[index - 1];
@@ -1211,11 +1325,12 @@ function handleInsert(
1211
1325
  type: 'insert',
1212
1326
  newValue,
1213
1327
  shadowMeta: updatedMeta,
1214
- path: path, // Just the array path now
1215
- itemId: itemId,
1328
+ path: path,
1329
+ itemId: actualItemId,
1216
1330
  insertAfterId: insertAfterId,
1217
1331
  };
1218
1332
  }
1333
+
1219
1334
  function handleCut(
1220
1335
  stateKey: string,
1221
1336
  path: string[]
@@ -1245,7 +1360,7 @@ function flushQueue() {
1245
1360
  if (result.shadowMeta?.signals?.length > 0) {
1246
1361
  signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
1247
1362
  }
1248
-
1363
+ // console.time('getComponentNotifications');
1249
1364
  const componentNotifications = getComponentNotifications(
1250
1365
  result.stateKey,
1251
1366
  result.path,
@@ -1255,63 +1370,64 @@ function flushQueue() {
1255
1370
  componentNotifications.forEach((component) => {
1256
1371
  allComponentsToNotify.add(component);
1257
1372
  });
1373
+ // console.timeEnd('getComponentNotifications');
1258
1374
  }
1259
-
1375
+ //console.time('logs');
1260
1376
  if (logsToAdd.length > 0) {
1261
1377
  addStateLog(logsToAdd);
1262
1378
  }
1263
-
1379
+ //console.timeEnd('logs');
1264
1380
  signalUpdates.forEach(({ shadowMeta, displayValue }) => {
1265
1381
  updateSignals(shadowMeta, displayValue);
1266
1382
  });
1267
1383
 
1384
+ // console.time('updateComponents');
1268
1385
  allComponentsToNotify.forEach((component) => {
1269
1386
  component.forceUpdate();
1270
1387
  });
1388
+ //console.timeEnd('updateComponents');
1271
1389
 
1272
1390
  // --- Step 3: CLEANUP ---
1273
1391
  // Clear the queue for the next batch of updates.
1274
1392
  updateBatchQueue = [];
1275
1393
  isFlushScheduled = false;
1276
1394
  }
1277
-
1278
1395
  function createEffectiveSetState<T>(
1279
1396
  thisKey: string,
1280
- syncApiRef: React.MutableRefObject<any>,
1281
1397
  sessionId: string | undefined,
1282
1398
  latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
1283
1399
  ): EffectiveSetState<T> {
1284
- // The returned function is the core setter that gets called by all state operations.
1285
- // It is now much simpler, delegating all work to the executeUpdate function.
1286
- return (newStateOrFunction, path, updateObj, validationKey?) => {
1400
+ return (newStateOrFunction, path, updateObj) => {
1287
1401
  executeUpdate(thisKey, path, newStateOrFunction, updateObj);
1288
1402
  };
1289
1403
 
1290
- // This inner function handles the logic for a single state update.
1291
1404
  function executeUpdate(
1292
1405
  stateKey: string,
1293
1406
  path: string[],
1294
1407
  payload: any,
1295
- options: UpdateOptions
1408
+ options: UpdateOptions // Now includes itemId
1296
1409
  ) {
1297
- // --- Step 1: Execute the core state change (Synchronous & Fast) ---
1298
- // This part modifies the in-memory state representation immediately.
1299
1410
  let result: any;
1300
1411
  switch (options.updateType) {
1301
1412
  case 'update':
1302
1413
  result = handleUpdate(stateKey, path, payload);
1303
-
1304
1414
  break;
1305
1415
  case 'insert':
1306
- result = handleInsert(stateKey, path, payload);
1307
-
1416
+ result = handleInsert(
1417
+ stateKey,
1418
+ path,
1419
+ payload,
1420
+ options.index,
1421
+ options.itemId
1422
+ );
1423
+ break;
1424
+ case 'insert_many':
1425
+ result = handleInsertMany(stateKey, path, payload);
1308
1426
  break;
1309
1427
  case 'cut':
1310
1428
  result = handleCut(stateKey, path);
1311
-
1312
1429
  break;
1313
1430
  }
1314
-
1315
1431
  result.stateKey = stateKey;
1316
1432
  result.path = path;
1317
1433
  updateBatchQueue.push(result);
@@ -1327,6 +1443,7 @@ function createEffectiveSetState<T>(
1327
1443
  newValue: result.newValue ?? null,
1328
1444
  itemId: result.itemId,
1329
1445
  insertAfterId: result.insertAfterId,
1446
+ metaData: options.metaData,
1330
1447
  };
1331
1448
 
1332
1449
  updateBatchQueue.push(newUpdate);
@@ -1343,10 +1460,8 @@ function createEffectiveSetState<T>(
1343
1460
  if (latestInitialOptionsRef.current?.middleware) {
1344
1461
  latestInitialOptionsRef.current.middleware({ update: newUpdate });
1345
1462
  }
1346
-
1347
- if (options.sync !== false && syncApiRef.current?.connected) {
1348
- syncApiRef.current.updateState({ operation: newUpdate });
1349
- }
1463
+ runValidation(newUpdate, options.validationTrigger || 'programmatic');
1464
+ notifyUpdate(newUpdate);
1350
1465
  }
1351
1466
  }
1352
1467
 
@@ -1354,22 +1469,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
1354
1469
  stateObject: TStateObject,
1355
1470
  {
1356
1471
  stateKey,
1357
-
1358
1472
  localStorage,
1359
1473
  formElements,
1360
1474
  reactiveDeps,
1361
1475
  reactiveType,
1362
1476
  componentId,
1363
1477
  defaultState,
1364
- syncUpdate,
1365
1478
  dependencies,
1366
1479
  serverState,
1367
- __useSync,
1368
1480
  }: {
1369
1481
  stateKey?: string;
1370
1482
  componentId?: string;
1371
1483
  defaultState?: TStateObject;
1372
- __useSync?: UseSyncType<TStateObject>;
1373
1484
  syncOptions?: SyncOptionsType<any>;
1374
1485
  } & OptionsType<TStateObject> = {}
1375
1486
  ) {
@@ -1384,16 +1495,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1384
1495
  latestInitialOptionsRef.current = (getInitialOptions(thisKey as string) ??
1385
1496
  null) as OptionsType<TStateObject> | null;
1386
1497
 
1387
- useEffect(() => {
1388
- if (syncUpdate && syncUpdate.stateKey === thisKey && syncUpdate.path?.[0]) {
1389
- const syncKey = `${syncUpdate.stateKey}:${syncUpdate.path.join('.')}`;
1390
- setSyncInfo(syncKey, {
1391
- timeStamp: syncUpdate.timeStamp!,
1392
- userId: syncUpdate.userId!,
1393
- });
1394
- }
1395
- }, [syncUpdate]);
1396
-
1397
1498
  const resolveInitialState = useCallback(
1398
1499
  (
1399
1500
  overrideOptions?: OptionsType<TStateObject>
@@ -1563,10 +1664,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1563
1664
  const options = getInitialOptions(thisKey as string);
1564
1665
 
1565
1666
  const features = {
1566
- syncEnabled: !!cogsSyncFn && !!syncOpt,
1567
- validationEnabled: !!(
1568
- options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
1569
- ),
1570
1667
  localStorageEnabled: !!options?.localStorage?.key,
1571
1668
  };
1572
1669
 
@@ -1666,10 +1763,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
1666
1763
  };
1667
1764
  }, []);
1668
1765
 
1669
- const syncApiRef = useRef<SyncApi | null>(null);
1670
1766
  const effectiveSetState = createEffectiveSetState(
1671
1767
  thisKey,
1672
- syncApiRef,
1768
+
1673
1769
  sessionId,
1674
1770
  latestInitialOptionsRef
1675
1771
  );
@@ -1689,16 +1785,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1689
1785
  return handler;
1690
1786
  }, [thisKey, sessionId]);
1691
1787
 
1692
- const cogsSyncFn = __useSync;
1693
- const syncOpt = latestInitialOptionsRef.current?.syncOptions;
1694
- console.log('syncOpt', syncOpt);
1695
- if (cogsSyncFn) {
1696
- syncApiRef.current = cogsSyncFn(
1697
- updaterFinal as any,
1698
- syncOpt ?? ({} as any)
1699
- );
1700
- }
1701
-
1702
1788
  return updaterFinal;
1703
1789
  }
1704
1790
 
@@ -1845,7 +1931,6 @@ function createProxyHandler<T>(
1845
1931
  sessionId?: string
1846
1932
  ): StateObject<T> {
1847
1933
  const proxyCache = new Map<string, any>();
1848
- let stateVersion = 0;
1849
1934
 
1850
1935
  function rebuildStateShape({
1851
1936
  path = [],
@@ -1859,6 +1944,7 @@ function createProxyHandler<T>(
1859
1944
  const derivationSignature = meta
1860
1945
  ? JSON.stringify(meta.arrayViews || meta.transforms)
1861
1946
  : '';
1947
+
1862
1948
  const cacheKey =
1863
1949
  path.join('.') + ':' + componentId + ':' + derivationSignature;
1864
1950
  if (proxyCache.has(cacheKey)) {
@@ -1871,10 +1957,17 @@ function createProxyHandler<T>(
1871
1957
 
1872
1958
  const handler = {
1873
1959
  get(target: any, prop: string) {
1960
+ if (typeof prop !== 'string') {
1961
+ // This is a Symbol. Let the default "get" behavior happen.
1962
+ // This allows internal JS operations and dev tools to work correctly
1963
+ // without interfering with your state logic.
1964
+ return Reflect.get(target, prop);
1965
+ }
1874
1966
  if (path.length === 0 && prop in rootLevelMethods) {
1875
1967
  return rootLevelMethods[prop as keyof typeof rootLevelMethods];
1876
1968
  }
1877
- if (!prop.startsWith('$')) {
1969
+
1970
+ if (typeof prop === 'string' && !prop.startsWith('$')) {
1878
1971
  const nextPath = [...path, prop];
1879
1972
  return rebuildStateShape({
1880
1973
  path: nextPath,
@@ -2800,8 +2893,6 @@ function createProxyHandler<T>(
2800
2893
 
2801
2894
  if (!Array.isArray(currentState)) return [];
2802
2895
 
2803
- stateVersion++;
2804
-
2805
2896
  return rebuildStateShape({
2806
2897
  path: [...path, '[*]', fieldName],
2807
2898
  componentId: componentId!,
@@ -2863,7 +2954,18 @@ function createProxyHandler<T>(
2863
2954
  payload: InsertParams<InferArrayElement<T>>,
2864
2955
  index?: number
2865
2956
  ) => {
2866
- effectiveSetState(payload as any, path, { updateType: 'insert' });
2957
+ effectiveSetState(payload as any, path, {
2958
+ updateType: 'insert',
2959
+ index,
2960
+ });
2961
+ };
2962
+ }
2963
+ if (prop === '$insertMany') {
2964
+ return (payload: InferArrayElement<T>[]) => {
2965
+ // Call the one true path for all state changes.
2966
+ effectiveSetState(payload as any, path, {
2967
+ updateType: 'insert_many',
2968
+ });
2867
2969
  };
2868
2970
  }
2869
2971
  if (prop === '$uniqueInsert') {
@@ -2911,6 +3013,7 @@ function createProxyHandler<T>(
2911
3013
  if (prop === '$cut') {
2912
3014
  return (index?: number, options?: { waitForSync?: boolean }) => {
2913
3015
  const shadowMeta = getShadowMetadata(stateKey, path);
3016
+ console.log('shadowMeta ->>>>>>>>>>>>>>>>', shadowMeta);
2914
3017
  if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
2915
3018
  return;
2916
3019
 
@@ -2921,8 +3024,11 @@ function createProxyHandler<T>(
2921
3024
  ? index
2922
3025
  : shadowMeta.arrayKeys.length - 1;
2923
3026
 
3027
+ console.log('indexToCut ->>>>>>>>>>>>>>>>', indexToCut);
3028
+
2924
3029
  const idToCut = shadowMeta.arrayKeys[indexToCut];
2925
3030
  if (!idToCut) return;
3031
+ console.log('idToCut ->>>>>>>>>>>>>>>>', idToCut);
2926
3032
 
2927
3033
  effectiveSetState(null, [...path, idToCut], {
2928
3034
  updateType: 'cut',
@@ -3024,11 +3130,7 @@ function createProxyHandler<T>(
3024
3130
  });
3025
3131
  }
3026
3132
 
3027
- return rebuildStateShape({
3028
- path: [...path, `not_found_${uuidv4()}`],
3029
- componentId: componentId!,
3030
- meta,
3031
- });
3133
+ return null;
3032
3134
  };
3033
3135
  }
3034
3136
  if (prop === '$cutThis') {
@@ -3058,10 +3160,59 @@ function createProxyHandler<T>(
3058
3160
  });
3059
3161
  }
3060
3162
 
3163
+ // if (prop === '$formInput') {
3164
+ // const _getFormElement = (path: string[]): HTMLElement | null => {
3165
+ // const metadata = getShadowMetadata(stateKey, path);
3166
+ // if (metadata?.formRef?.current) {
3167
+ // return metadata.formRef.current;
3168
+ // }
3169
+ // // This warning is helpful for debugging if a ref is missing.
3170
+ // console.warn(
3171
+ // `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
3172
+ // );
3173
+ // return null;
3174
+ // };
3175
+ // return {
3176
+ // setDisabled: (isDisabled: boolean) => {
3177
+ // const element = _getFormElement(path) as HTMLInputElement | null;
3178
+ // if (element) {
3179
+ // element.disabled = isDisabled;
3180
+ // }
3181
+ // },
3182
+ // focus: () => {
3183
+ // const element = _getFormElement(path);
3184
+ // element?.focus();
3185
+ // },
3186
+ // blur: () => {
3187
+ // const element = _getFormElement(path);
3188
+ // element?.blur();
3189
+ // },
3190
+ // scrollIntoView: (options?: ScrollIntoViewOptions) => {
3191
+ // const element = _getFormElement(path);
3192
+ // element?.scrollIntoView(
3193
+ // options ?? { behavior: 'smooth', block: 'center' }
3194
+ // );
3195
+ // },
3196
+ // click: () => {
3197
+ // const element = _getFormElement(path);
3198
+ // element?.click();
3199
+ // },
3200
+ // selectText: () => {
3201
+ // const element = _getFormElement(path) as
3202
+ // | HTMLInputElement
3203
+ // | HTMLTextAreaElement
3204
+ // | null;
3205
+ // if (element && typeof element.select === 'function') {
3206
+ // element.select();
3207
+ // }
3208
+ // },
3209
+ // };
3210
+ // }
3061
3211
  if (prop === '$$get') {
3062
3212
  return () =>
3063
3213
  $cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
3064
3214
  }
3215
+
3065
3216
  if (prop === '$lastSynced') {
3066
3217
  const syncKey = `${stateKey}:${path.join('.')}`;
3067
3218
  return getSyncInfo(syncKey);
@@ -3133,6 +3284,38 @@ function createProxyHandler<T>(
3133
3284
  return componentId;
3134
3285
  }
3135
3286
  if (path.length == 0) {
3287
+ if (prop === '$setOptions') {
3288
+ return (options: OptionsType<T>) => {
3289
+ setOptions({ stateKey, options, initialOptionsPart: {} });
3290
+ };
3291
+ }
3292
+
3293
+ if (prop === '$_applyUpdate') {
3294
+ return (
3295
+ value: any,
3296
+ path: string[],
3297
+ updateType: 'update' | 'insert' | 'cut' = 'update'
3298
+ ) => {
3299
+ effectiveSetState(value, path, { updateType });
3300
+ };
3301
+ }
3302
+
3303
+ if (prop === '$_getEffectiveSetState') {
3304
+ return effectiveSetState;
3305
+ }
3306
+ if (prop === '$getPluginMetaData') {
3307
+ return (pluginName: string) =>
3308
+ getPluginMetaDataMap(stateKey, path)?.get(pluginName);
3309
+ }
3310
+ if (prop === '$addPluginMetaData') {
3311
+ console.log('$addPluginMetaDat');
3312
+ return (pluginName: string, data: Record<string, any>) =>
3313
+ setPluginMetaData(stateKey, path, pluginName, data);
3314
+ }
3315
+ if (prop === '$removePluginMetaData') {
3316
+ return (pluginName: string) =>
3317
+ removePluginMetaData(stateKey, path, pluginName);
3318
+ }
3136
3319
  if (prop === '$addZodValidation') {
3137
3320
  return (
3138
3321
  zodErrors: any[],
@@ -3185,46 +3368,67 @@ function createProxyHandler<T>(
3185
3368
  }
3186
3369
 
3187
3370
  if (prop === '$applyOperation') {
3188
- return (operation: UpdateTypeDetail & { validation?: any[] }) => {
3189
- const validationErrorsFromServer = operation.validation || [];
3190
-
3191
- if (!operation || !operation.path) {
3192
- console.error(
3193
- 'Invalid operation received by $applyOperation:',
3194
- operation
3195
- );
3196
- return;
3197
- }
3198
-
3199
- const updatePath = operation.path;
3200
-
3201
- const currentMeta =
3371
+ return (
3372
+ operation: UpdateTypeDetail & {
3373
+ validation?: any[];
3374
+ version?: string;
3375
+ },
3376
+ metaData?: Record<string, any>
3377
+ ) => {
3378
+ // const validationErrorsFromServer = operation.validation || [];
3379
+
3380
+ // if (!operation || !operation.path) {
3381
+ // console.error(
3382
+ // 'Invalid operation received by $applyOperation:',
3383
+ // operation
3384
+ // );
3385
+ // return;
3386
+ // }
3387
+
3388
+ // const newErrors: ValidationError[] =
3389
+ // validationErrorsFromServer.map((err) => ({
3390
+ // source: 'sync_engine',
3391
+ // message: err.message,
3392
+ // severity: 'warning',
3393
+ // code: err.code,
3394
+ // }));
3395
+ // console.log('newErrors', newErrors);
3396
+ // getGlobalStore
3397
+ // .getState()
3398
+ // .setShadowMetadata(stateKey, operation.path, {
3399
+ // validation: {
3400
+ // status: newErrors.length > 0 ? 'INVALID' : 'VALID',
3401
+ // errors: newErrors,
3402
+ // lastValidated: Date.now(),
3403
+ // },
3404
+ // });
3405
+ console.log(
3406
+ 'getGlobalStore',
3202
3407
  getGlobalStore
3203
3408
  .getState()
3204
- .getShadowMetadata(stateKey, updatePath) || {};
3205
-
3206
- const newErrors: ValidationError[] =
3207
- validationErrorsFromServer.map((err) => ({
3208
- source: 'sync_engine',
3209
- message: err.message,
3210
- severity: 'warning',
3211
- code: err.code,
3212
- }));
3409
+ .getShadowMetadata(stateKey, operation.path)
3410
+ );
3411
+ let index: number | undefined;
3412
+ if (
3413
+ operation.insertAfterId &&
3414
+ operation.updateType === 'insert'
3415
+ ) {
3416
+ const shadowMeta = getShadowMetadata(stateKey, operation.path);
3417
+ if (shadowMeta?.arrayKeys) {
3418
+ const afterIndex = shadowMeta.arrayKeys.indexOf(
3419
+ operation.insertAfterId
3420
+ );
3421
+ if (afterIndex !== -1) {
3422
+ index = afterIndex + 1; // Insert after the found item
3423
+ }
3424
+ }
3425
+ }
3213
3426
 
3214
- console.log('updatePath', updatePath);
3215
- getGlobalStore
3216
- .getState()
3217
- .setShadowMetadata(stateKey, updatePath, {
3218
- ...currentMeta,
3219
- validation: {
3220
- status: newErrors.length > 0 ? 'INVALID' : 'VALID',
3221
- errors: newErrors,
3222
- lastValidated: Date.now(),
3223
- },
3224
- });
3225
- effectiveSetState(operation.newValue, updatePath, {
3427
+ effectiveSetState(operation.newValue, operation.path, {
3226
3428
  updateType: operation.updateType,
3227
- sync: false,
3429
+ itemId: operation.itemId,
3430
+ index, // Pass the calculated index
3431
+ metaData,
3228
3432
  });
3229
3433
  };
3230
3434
  }
@@ -3322,14 +3526,8 @@ function createProxyHandler<T>(
3322
3526
 
3323
3527
  if (prop === '$getComponents')
3324
3528
  return () => getShadowMetadata(stateKey, [])?.components;
3325
- if (prop === '$getAllFormRefs')
3326
- return () =>
3327
- formRefStore.getState().getFormRefsByStateKey(stateKey);
3328
- }
3329
- if (prop === '$getFormRef') {
3330
- return () =>
3331
- formRefStore.getState().getFormRef(stateKey + '.' + path.join('.'));
3332
3529
  }
3530
+
3333
3531
  if (prop === '$validationWrapper') {
3334
3532
  return ({
3335
3533
  children,
@@ -3349,6 +3547,7 @@ function createProxyHandler<T>(
3349
3547
  </ValidationWrapper>
3350
3548
  );
3351
3549
  }
3550
+
3352
3551
  if (prop === '$_stateKey') return stateKey;
3353
3552
  if (prop === '$_path') return path;
3354
3553
  if (prop === '$update') {
@@ -3469,9 +3668,11 @@ function createProxyHandler<T>(
3469
3668
 
3470
3669
  return revertState;
3471
3670
  },
3671
+ $initializeAndMergeShadowState: (newState: T) => {
3672
+ initializeAndMergeShadowState(stateKey, newState);
3673
+ notifyComponents(stateKey);
3674
+ },
3472
3675
  $updateInitialState: (newState: T) => {
3473
- stateVersion++;
3474
-
3475
3676
  const newUpdaterState = createProxyHandler(
3476
3677
  stateKey,
3477
3678
  effectiveSetState,