cogsbox-state 0.5.472 → 0.5.474

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 (46) hide show
  1. package/README.md +48 -18
  2. package/dist/CogsState.d.ts +98 -82
  3. package/dist/CogsState.d.ts.map +1 -1
  4. package/dist/CogsState.jsx +1030 -960
  5. package/dist/CogsState.jsx.map +1 -1
  6. package/dist/Components.d.ts.map +1 -1
  7. package/dist/Components.jsx +299 -219
  8. package/dist/Components.jsx.map +1 -1
  9. package/dist/PluginRunner.d.ts +10 -0
  10. package/dist/PluginRunner.d.ts.map +1 -0
  11. package/dist/PluginRunner.jsx +122 -0
  12. package/dist/PluginRunner.jsx.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +33 -26
  16. package/dist/index.js.map +1 -1
  17. package/dist/pluginStore.d.ts +81 -0
  18. package/dist/pluginStore.d.ts.map +1 -0
  19. package/dist/pluginStore.js +52 -0
  20. package/dist/pluginStore.js.map +1 -0
  21. package/dist/plugins.d.ts +1323 -0
  22. package/dist/plugins.d.ts.map +1 -0
  23. package/dist/plugins.js +76 -0
  24. package/dist/plugins.js.map +1 -0
  25. package/dist/store.d.ts +50 -15
  26. package/dist/store.d.ts.map +1 -1
  27. package/dist/store.js +509 -470
  28. package/dist/store.js.map +1 -1
  29. package/dist/utility.d.ts +1 -1
  30. package/dist/utility.d.ts.map +1 -1
  31. package/dist/utility.js +12 -12
  32. package/dist/utility.js.map +1 -1
  33. package/dist/validation.d.ts +7 -0
  34. package/dist/validation.d.ts.map +1 -0
  35. package/dist/validation.js +39 -0
  36. package/dist/validation.js.map +1 -0
  37. package/package.json +13 -3
  38. package/src/CogsState.tsx +657 -457
  39. package/src/Components.tsx +291 -194
  40. package/src/PluginRunner.tsx +203 -0
  41. package/src/index.ts +2 -0
  42. package/src/pluginStore.ts +176 -0
  43. package/src/plugins.ts +544 -0
  44. package/src/store.ts +748 -493
  45. package/src/utility.ts +31 -31
  46. 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,29 @@ 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
+ pluginStore
821
+ .getState()
822
+ .stateHandlers.set(stateKey as string, updater as any);
823
+
824
+ return () => {
825
+ pluginStore.getState().stateHandlers.delete(stateKey as string);
826
+ };
827
+ }, [stateKey, updater]);
828
+
755
829
  return updater;
756
830
  };
757
831
 
758
- function setCogsOptions<StateKey extends StateKeys>(
832
+ function setCogsOptionsByKey<StateKey extends StateKeys>(
759
833
  stateKey: StateKey,
760
834
  options: OptionsType<(typeof statePart)[StateKey]>
761
835
  ) {
@@ -768,109 +842,53 @@ export const createCogsState = <State extends Record<StateKeys, unknown>>(
768
842
  notifyComponents(stateKey as string);
769
843
  }
770
844
 
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;
845
+ function setCogsFormElements(
846
+ formElements: FormsElementsType<State, TPlugins>
847
+ ) {
848
+ // Get the current list of registered plugins from the store.
849
+ const currentPlugins = pluginStore.getState().registeredPlugins;
850
+
851
+ // Create a new array by mapping over the current plugins.
852
+ // This is crucial for immutability and ensuring Zustand detects the change.
853
+ const updatedPlugins = currentPlugins.map((plugin) => {
854
+ // Check if the formElements object has a wrapper for this specific plugin by name.
855
+ if (formElements.hasOwnProperty(plugin.name)) {
856
+ // If it does, return a *new* plugin object.
857
+ // Spread the existing plugin properties and add/overwrite the formWrapper.
858
+ return {
859
+ ...plugin,
860
+ formWrapper: formElements[plugin.name as keyof typeof formElements],
817
861
  };
818
- [key: string]: any;
819
862
  }
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);
863
+ // If there's no new wrapper for this plugin, return the original object.
864
+ return plugin;
865
+ });
859
866
 
860
- // Extract apiParams from the api.queryData._paramType
861
- if (entry?.api?.queryData?._paramType) {
862
- apiParamsMap[key] = entry.api.queryData._paramType;
863
- }
867
+ // Use the store's dedicated setter function to update the registered plugins list.
868
+ // This will trigger a state update that components listening to the store will react to.
869
+ pluginStore.getState().setRegisteredPlugins(updatedPlugins as any);
870
+
871
+ // For good measure and consistency, we should still update the formElements
872
+ // in the initial options, in case any other part of the system relies on it.
873
+ const allStateKeys = Object.keys(statePart);
874
+ allStateKeys.forEach((key) => {
875
+ const existingOptions = getInitialOptions(key) || {};
876
+ const finalOptions = {
877
+ ...existingOptions,
878
+ formElements: {
879
+ ...(existingOptions.formElements || {}),
880
+ ...formElements,
881
+ },
882
+ };
883
+ setInitialStateOptions(key, finalOptions);
884
+ });
864
885
  }
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
- }
886
+ return {
887
+ useCogsState,
888
+ setCogsOptionsByKey,
889
+ setCogsFormElements,
890
+ };
891
+ };
874
892
 
875
893
  const saveToLocalStorage = <T,>(
876
894
  state: T,
@@ -1042,35 +1060,14 @@ let isFlushScheduled = false;
1042
1060
  function scheduleFlush() {
1043
1061
  if (!isFlushScheduled) {
1044
1062
  isFlushScheduled = true;
1045
- queueMicrotask(flushQueue);
1063
+ console.log('Scheduling flush');
1064
+ queueMicrotask(() => {
1065
+ console.log('Actually flushing');
1066
+ flushQueue();
1067
+ });
1046
1068
  }
1047
1069
  }
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
1070
 
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
1071
  // 2. Update signals
1075
1072
  function updateSignals(shadowMeta: any, displayValue: any) {
1076
1073
  if (!shadowMeta?.signals?.length) return;
@@ -1114,72 +1111,179 @@ function getComponentNotifications(
1114
1111
 
1115
1112
  const componentsToNotify = new Set<any>();
1116
1113
 
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);
1114
+ // --- PASS 1: Notify specific subscribers based on update type ---
1115
+
1116
+ if (result.type === 'update') {
1117
+ // --- Bubble-up Notification ---
1118
+ // An update to `user.address.street` notifies listeners of `street`, `address`, and `user`.
1119
+ let currentPath = [...path];
1120
+ while (true) {
1121
+ const pathMeta = getShadowMetadata(stateKey, currentPath);
1122
+
1123
+ if (pathMeta?.pathComponents) {
1124
+ pathMeta.pathComponents.forEach((componentId: string) => {
1125
+ const component = rootMeta.components?.get(componentId);
1126
+ // NEW: Add component to the set instead of calling forceUpdate()
1127
+ if (component) {
1128
+ const reactiveTypes = Array.isArray(component.reactiveType)
1129
+ ? component.reactiveType
1130
+ : [component.reactiveType || 'component'];
1131
+ if (!reactiveTypes.includes('none')) {
1132
+ componentsToNotify.add(component);
1133
+ }
1138
1134
  }
1135
+ });
1136
+ }
1137
+
1138
+ if (currentPath.length === 0) break;
1139
+ currentPath.pop(); // Go up one level
1140
+ }
1141
+
1142
+ // --- Deep Object Change Notification ---
1143
+ // If the new value is an object, notify components subscribed to sub-paths that changed.
1144
+ if (
1145
+ result.newValue &&
1146
+ typeof result.newValue === 'object' &&
1147
+ !isArray(result.newValue)
1148
+ ) {
1149
+ const changedSubPaths = getDifferences(result.newValue, result.oldValue);
1150
+
1151
+ changedSubPaths.forEach((subPathString: string) => {
1152
+ const subPath = subPathString.split('.');
1153
+ const fullSubPath = [...path, ...subPath];
1154
+ const subPathMeta = getShadowMetadata(stateKey, fullSubPath);
1155
+
1156
+ if (subPathMeta?.pathComponents) {
1157
+ subPathMeta.pathComponents.forEach((componentId: string) => {
1158
+ const component = rootMeta.components?.get(componentId);
1159
+ // NEW: Add component to the set
1160
+ if (component) {
1161
+ const reactiveTypes = Array.isArray(component.reactiveType)
1162
+ ? component.reactiveType
1163
+ : [component.reactiveType || 'component'];
1164
+ if (!reactiveTypes.includes('none')) {
1165
+ componentsToNotify.add(component);
1166
+ }
1167
+ }
1168
+ });
1139
1169
  }
1140
1170
  });
1141
1171
  }
1172
+ } else if (
1173
+ result.type === 'insert' ||
1174
+ result.type === 'cut' ||
1175
+ result.type === 'insert_many'
1176
+ ) {
1177
+ // This path is the array itself (e.g., ['pets'])
1178
+ const parentArrayPath = result.type === 'insert' ? path : path.slice(0, -1);
1179
+
1180
+ // --- FIX: ADD BUBBLE-UP LOGIC HERE ---
1181
+ // Start from the array's path and go up to the root
1182
+ let currentPath = [...parentArrayPath];
1183
+ while (true) {
1184
+ const pathMeta = getShadowMetadata(stateKey, currentPath);
1185
+
1186
+ if (pathMeta?.pathComponents) {
1187
+ pathMeta.pathComponents.forEach((componentId: string) => {
1188
+ const component = rootMeta.components?.get(componentId);
1189
+ if (component) {
1190
+ componentsToNotify.add(component);
1191
+ }
1192
+ });
1193
+ }
1142
1194
 
1143
- if (currentPath.length === 0) break;
1144
- currentPath.pop(); // Go up one level
1195
+ if (currentPath.length === 0) break;
1196
+ currentPath.pop(); // Go up one level
1197
+ }
1198
+ // --- END FIX ---
1145
1199
  }
1146
1200
 
1147
1201
  // --- PASS 2: Handle 'all' and 'deps' reactivity types ---
1148
1202
  // 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
- });
1203
+ // rootMeta.components.forEach((component, componentId) => {
1204
+ // // If we've already added this component, skip it.
1205
+ // if (componentsToNotify.has(component)) {
1206
+ // return;
1207
+ // }
1208
+
1209
+ // const reactiveTypes = Array.isArray(component.reactiveType)
1210
+ // ? component.reactiveType
1211
+ // : [component.reactiveType || 'component'];
1212
+
1213
+ // if (reactiveTypes.includes('all')) {
1214
+ // componentsToNotify.add(component);
1215
+ // } else if (reactiveTypes.includes('deps') && component.depsFunction) {
1216
+ // const currentState = getShadowValue(stateKey, []);
1217
+ // const newDeps = component.depsFunction(currentState);
1218
+
1219
+ // if (
1220
+ // newDeps === true ||
1221
+ // (Array.isArray(newDeps) && !isDeepEqual(component.prevDeps, newDeps))
1222
+ // ) {
1223
+ // component.prevDeps = newDeps as any; // Update the dependencies for the next check
1224
+ // componentsToNotify.add(component);
1225
+ // }
1226
+ // }
1227
+ // });
1174
1228
 
1175
1229
  return componentsToNotify;
1176
1230
  }
1177
1231
 
1232
+ function handleUpdate(
1233
+ stateKey: string,
1234
+ path: string[],
1235
+ payload: any
1236
+ ): { type: 'update'; oldValue: any; newValue: any; shadowMeta: any } {
1237
+ // ✅ FIX: Get the old value before the update.
1238
+ const oldValue = getGlobalStore.getState().getShadowValue(stateKey, path);
1239
+
1240
+ const newValue = isFunction(payload) ? payload(oldValue) : payload;
1241
+
1242
+ // ✅ FIX: The new `updateShadowAtPath` handles metadata preservation automatically.
1243
+ // The manual loop has been removed.
1244
+ updateShadowAtPath(stateKey, path, newValue);
1245
+
1246
+ markAsDirty(stateKey, path, { bubble: true });
1247
+
1248
+ // Return the metadata of the node *after* the update.
1249
+ const newShadowMeta = getShadowMetadata(stateKey, path);
1250
+
1251
+ return {
1252
+ type: 'update',
1253
+ oldValue: oldValue,
1254
+ newValue,
1255
+ shadowMeta: newShadowMeta,
1256
+ };
1257
+ }
1258
+ function handleInsertMany(
1259
+ stateKey: string,
1260
+ path: string[],
1261
+ payload: any[]
1262
+ ): {
1263
+ type: 'insert_many';
1264
+ count: number;
1265
+ shadowMeta: any;
1266
+ path: string[];
1267
+ } {
1268
+ // Use the existing, optimized global store function to perform the state update
1269
+ insertManyShadowArrayElements(stateKey, path, payload);
1270
+
1271
+ markAsDirty(stateKey, path, { bubble: true });
1272
+ const updatedMeta = getShadowMetadata(stateKey, path);
1273
+
1274
+ return {
1275
+ type: 'insert_many',
1276
+ count: payload.length,
1277
+ shadowMeta: updatedMeta,
1278
+ path: path,
1279
+ };
1280
+ }
1178
1281
  function handleInsert(
1179
1282
  stateKey: string,
1180
1283
  path: string[],
1181
1284
  payload: any,
1182
- index?: number
1285
+ index?: number,
1286
+ itemId?: string
1183
1287
  ): {
1184
1288
  type: 'insert';
1185
1289
  newValue: any;
@@ -1189,19 +1293,28 @@ function handleInsert(
1189
1293
  insertAfterId?: string;
1190
1294
  } {
1191
1295
  let newValue;
1296
+
1192
1297
  if (isFunction(payload)) {
1193
1298
  const { value: currentValue } = getScopedData(stateKey, path);
1194
- newValue = payload({ state: currentValue, uuid: uuidv4() });
1299
+ newValue = payload({ state: currentValue });
1195
1300
  } else {
1196
1301
  newValue = payload;
1197
1302
  }
1303
+ //console.time('insertShadowArrayElement');
1304
+ // Pass itemId to insertShadowArrayElement
1305
+ const actualItemId = insertShadowArrayElement(
1306
+ stateKey,
1307
+ path,
1308
+ newValue,
1309
+ index,
1310
+ itemId
1311
+ );
1312
+ //console.timeEnd('insertShadowArrayElement');
1198
1313
 
1199
- const itemId = insertShadowArrayElement(stateKey, path, newValue, index);
1200
1314
  markAsDirty(stateKey, path, { bubble: true });
1201
1315
 
1202
1316
  const updatedMeta = getShadowMetadata(stateKey, path);
1203
1317
 
1204
- // Find the ID that comes before this insertion point
1205
1318
  let insertAfterId: string | undefined;
1206
1319
  if (updatedMeta?.arrayKeys && index !== undefined && index > 0) {
1207
1320
  insertAfterId = updatedMeta.arrayKeys[index - 1];
@@ -1211,11 +1324,12 @@ function handleInsert(
1211
1324
  type: 'insert',
1212
1325
  newValue,
1213
1326
  shadowMeta: updatedMeta,
1214
- path: path, // Just the array path now
1215
- itemId: itemId,
1327
+ path: path,
1328
+ itemId: actualItemId,
1216
1329
  insertAfterId: insertAfterId,
1217
1330
  };
1218
1331
  }
1332
+
1219
1333
  function handleCut(
1220
1334
  stateKey: string,
1221
1335
  path: string[]
@@ -1245,7 +1359,7 @@ function flushQueue() {
1245
1359
  if (result.shadowMeta?.signals?.length > 0) {
1246
1360
  signalUpdates.push({ shadowMeta: result.shadowMeta, displayValue });
1247
1361
  }
1248
-
1362
+ // console.time('getComponentNotifications');
1249
1363
  const componentNotifications = getComponentNotifications(
1250
1364
  result.stateKey,
1251
1365
  result.path,
@@ -1255,63 +1369,64 @@ function flushQueue() {
1255
1369
  componentNotifications.forEach((component) => {
1256
1370
  allComponentsToNotify.add(component);
1257
1371
  });
1372
+ // console.timeEnd('getComponentNotifications');
1258
1373
  }
1259
-
1374
+ //console.time('logs');
1260
1375
  if (logsToAdd.length > 0) {
1261
1376
  addStateLog(logsToAdd);
1262
1377
  }
1263
-
1378
+ //console.timeEnd('logs');
1264
1379
  signalUpdates.forEach(({ shadowMeta, displayValue }) => {
1265
1380
  updateSignals(shadowMeta, displayValue);
1266
1381
  });
1267
1382
 
1383
+ // console.time('updateComponents');
1268
1384
  allComponentsToNotify.forEach((component) => {
1269
1385
  component.forceUpdate();
1270
1386
  });
1387
+ //console.timeEnd('updateComponents');
1271
1388
 
1272
1389
  // --- Step 3: CLEANUP ---
1273
1390
  // Clear the queue for the next batch of updates.
1274
1391
  updateBatchQueue = [];
1275
1392
  isFlushScheduled = false;
1276
1393
  }
1277
-
1278
1394
  function createEffectiveSetState<T>(
1279
1395
  thisKey: string,
1280
- syncApiRef: React.MutableRefObject<any>,
1281
1396
  sessionId: string | undefined,
1282
1397
  latestInitialOptionsRef: React.MutableRefObject<OptionsType<T> | null>
1283
1398
  ): 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?) => {
1399
+ return (newStateOrFunction, path, updateObj) => {
1287
1400
  executeUpdate(thisKey, path, newStateOrFunction, updateObj);
1288
1401
  };
1289
1402
 
1290
- // This inner function handles the logic for a single state update.
1291
1403
  function executeUpdate(
1292
1404
  stateKey: string,
1293
1405
  path: string[],
1294
1406
  payload: any,
1295
- options: UpdateOptions
1407
+ options: UpdateOptions // Now includes itemId
1296
1408
  ) {
1297
- // --- Step 1: Execute the core state change (Synchronous & Fast) ---
1298
- // This part modifies the in-memory state representation immediately.
1299
1409
  let result: any;
1300
1410
  switch (options.updateType) {
1301
1411
  case 'update':
1302
1412
  result = handleUpdate(stateKey, path, payload);
1303
-
1304
1413
  break;
1305
1414
  case 'insert':
1306
- result = handleInsert(stateKey, path, payload);
1307
-
1415
+ result = handleInsert(
1416
+ stateKey,
1417
+ path,
1418
+ payload,
1419
+ options.index,
1420
+ options.itemId
1421
+ );
1422
+ break;
1423
+ case 'insert_many':
1424
+ result = handleInsertMany(stateKey, path, payload);
1308
1425
  break;
1309
1426
  case 'cut':
1310
1427
  result = handleCut(stateKey, path);
1311
-
1312
1428
  break;
1313
1429
  }
1314
-
1315
1430
  result.stateKey = stateKey;
1316
1431
  result.path = path;
1317
1432
  updateBatchQueue.push(result);
@@ -1327,6 +1442,7 @@ function createEffectiveSetState<T>(
1327
1442
  newValue: result.newValue ?? null,
1328
1443
  itemId: result.itemId,
1329
1444
  insertAfterId: result.insertAfterId,
1445
+ metaData: options.metaData,
1330
1446
  };
1331
1447
 
1332
1448
  updateBatchQueue.push(newUpdate);
@@ -1343,10 +1459,8 @@ function createEffectiveSetState<T>(
1343
1459
  if (latestInitialOptionsRef.current?.middleware) {
1344
1460
  latestInitialOptionsRef.current.middleware({ update: newUpdate });
1345
1461
  }
1346
-
1347
- if (options.sync !== false && syncApiRef.current?.connected) {
1348
- syncApiRef.current.updateState({ operation: newUpdate });
1349
- }
1462
+ runValidation(newUpdate, options.validationTrigger || 'programmatic');
1463
+ notifyUpdate(newUpdate);
1350
1464
  }
1351
1465
  }
1352
1466
 
@@ -1354,22 +1468,18 @@ export function useCogsStateFn<TStateObject extends unknown>(
1354
1468
  stateObject: TStateObject,
1355
1469
  {
1356
1470
  stateKey,
1357
-
1358
1471
  localStorage,
1359
1472
  formElements,
1360
1473
  reactiveDeps,
1361
1474
  reactiveType,
1362
1475
  componentId,
1363
1476
  defaultState,
1364
- syncUpdate,
1365
1477
  dependencies,
1366
1478
  serverState,
1367
- __useSync,
1368
1479
  }: {
1369
1480
  stateKey?: string;
1370
1481
  componentId?: string;
1371
1482
  defaultState?: TStateObject;
1372
- __useSync?: UseSyncType<TStateObject>;
1373
1483
  syncOptions?: SyncOptionsType<any>;
1374
1484
  } & OptionsType<TStateObject> = {}
1375
1485
  ) {
@@ -1384,16 +1494,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1384
1494
  latestInitialOptionsRef.current = (getInitialOptions(thisKey as string) ??
1385
1495
  null) as OptionsType<TStateObject> | null;
1386
1496
 
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
1497
  const resolveInitialState = useCallback(
1398
1498
  (
1399
1499
  overrideOptions?: OptionsType<TStateObject>
@@ -1563,10 +1663,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1563
1663
  const options = getInitialOptions(thisKey as string);
1564
1664
 
1565
1665
  const features = {
1566
- syncEnabled: !!cogsSyncFn && !!syncOpt,
1567
- validationEnabled: !!(
1568
- options?.validation?.zodSchemaV4 || options?.validation?.zodSchemaV3
1569
- ),
1570
1666
  localStorageEnabled: !!options?.localStorage?.key,
1571
1667
  };
1572
1668
 
@@ -1666,10 +1762,9 @@ export function useCogsStateFn<TStateObject extends unknown>(
1666
1762
  };
1667
1763
  }, []);
1668
1764
 
1669
- const syncApiRef = useRef<SyncApi | null>(null);
1670
1765
  const effectiveSetState = createEffectiveSetState(
1671
1766
  thisKey,
1672
- syncApiRef,
1767
+
1673
1768
  sessionId,
1674
1769
  latestInitialOptionsRef
1675
1770
  );
@@ -1689,16 +1784,6 @@ export function useCogsStateFn<TStateObject extends unknown>(
1689
1784
  return handler;
1690
1785
  }, [thisKey, sessionId]);
1691
1786
 
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
1787
  return updaterFinal;
1703
1788
  }
1704
1789
 
@@ -1845,7 +1930,6 @@ function createProxyHandler<T>(
1845
1930
  sessionId?: string
1846
1931
  ): StateObject<T> {
1847
1932
  const proxyCache = new Map<string, any>();
1848
- let stateVersion = 0;
1849
1933
 
1850
1934
  function rebuildStateShape({
1851
1935
  path = [],
@@ -1859,6 +1943,7 @@ function createProxyHandler<T>(
1859
1943
  const derivationSignature = meta
1860
1944
  ? JSON.stringify(meta.arrayViews || meta.transforms)
1861
1945
  : '';
1946
+
1862
1947
  const cacheKey =
1863
1948
  path.join('.') + ':' + componentId + ':' + derivationSignature;
1864
1949
  if (proxyCache.has(cacheKey)) {
@@ -1871,10 +1956,17 @@ function createProxyHandler<T>(
1871
1956
 
1872
1957
  const handler = {
1873
1958
  get(target: any, prop: string) {
1959
+ if (typeof prop !== 'string') {
1960
+ // This is a Symbol. Let the default "get" behavior happen.
1961
+ // This allows internal JS operations and dev tools to work correctly
1962
+ // without interfering with your state logic.
1963
+ return Reflect.get(target, prop);
1964
+ }
1874
1965
  if (path.length === 0 && prop in rootLevelMethods) {
1875
1966
  return rootLevelMethods[prop as keyof typeof rootLevelMethods];
1876
1967
  }
1877
- if (!prop.startsWith('$')) {
1968
+
1969
+ if (typeof prop === 'string' && !prop.startsWith('$')) {
1878
1970
  const nextPath = [...path, prop];
1879
1971
  return rebuildStateShape({
1880
1972
  path: nextPath,
@@ -2800,8 +2892,6 @@ function createProxyHandler<T>(
2800
2892
 
2801
2893
  if (!Array.isArray(currentState)) return [];
2802
2894
 
2803
- stateVersion++;
2804
-
2805
2895
  return rebuildStateShape({
2806
2896
  path: [...path, '[*]', fieldName],
2807
2897
  componentId: componentId!,
@@ -2863,7 +2953,18 @@ function createProxyHandler<T>(
2863
2953
  payload: InsertParams<InferArrayElement<T>>,
2864
2954
  index?: number
2865
2955
  ) => {
2866
- effectiveSetState(payload as any, path, { updateType: 'insert' });
2956
+ effectiveSetState(payload as any, path, {
2957
+ updateType: 'insert',
2958
+ index,
2959
+ });
2960
+ };
2961
+ }
2962
+ if (prop === '$insertMany') {
2963
+ return (payload: InferArrayElement<T>[]) => {
2964
+ // Call the one true path for all state changes.
2965
+ effectiveSetState(payload as any, path, {
2966
+ updateType: 'insert_many',
2967
+ });
2867
2968
  };
2868
2969
  }
2869
2970
  if (prop === '$uniqueInsert') {
@@ -2911,6 +3012,7 @@ function createProxyHandler<T>(
2911
3012
  if (prop === '$cut') {
2912
3013
  return (index?: number, options?: { waitForSync?: boolean }) => {
2913
3014
  const shadowMeta = getShadowMetadata(stateKey, path);
3015
+ console.log('shadowMeta ->>>>>>>>>>>>>>>>', shadowMeta);
2914
3016
  if (!shadowMeta?.arrayKeys || shadowMeta.arrayKeys.length === 0)
2915
3017
  return;
2916
3018
 
@@ -2921,8 +3023,11 @@ function createProxyHandler<T>(
2921
3023
  ? index
2922
3024
  : shadowMeta.arrayKeys.length - 1;
2923
3025
 
3026
+ console.log('indexToCut ->>>>>>>>>>>>>>>>', indexToCut);
3027
+
2924
3028
  const idToCut = shadowMeta.arrayKeys[indexToCut];
2925
3029
  if (!idToCut) return;
3030
+ console.log('idToCut ->>>>>>>>>>>>>>>>', idToCut);
2926
3031
 
2927
3032
  effectiveSetState(null, [...path, idToCut], {
2928
3033
  updateType: 'cut',
@@ -3024,11 +3129,7 @@ function createProxyHandler<T>(
3024
3129
  });
3025
3130
  }
3026
3131
 
3027
- return rebuildStateShape({
3028
- path: [...path, `not_found_${uuidv4()}`],
3029
- componentId: componentId!,
3030
- meta,
3031
- });
3132
+ return null;
3032
3133
  };
3033
3134
  }
3034
3135
  if (prop === '$cutThis') {
@@ -3058,10 +3159,59 @@ function createProxyHandler<T>(
3058
3159
  });
3059
3160
  }
3060
3161
 
3162
+ // if (prop === '$formInput') {
3163
+ // const _getFormElement = (path: string[]): HTMLElement | null => {
3164
+ // const metadata = getShadowMetadata(stateKey, path);
3165
+ // if (metadata?.formRef?.current) {
3166
+ // return metadata.formRef.current;
3167
+ // }
3168
+ // // This warning is helpful for debugging if a ref is missing.
3169
+ // console.warn(
3170
+ // `Form element ref not found for stateKey "${stateKey}" at path "${path.join('.')}"`
3171
+ // );
3172
+ // return null;
3173
+ // };
3174
+ // return {
3175
+ // setDisabled: (isDisabled: boolean) => {
3176
+ // const element = _getFormElement(path) as HTMLInputElement | null;
3177
+ // if (element) {
3178
+ // element.disabled = isDisabled;
3179
+ // }
3180
+ // },
3181
+ // focus: () => {
3182
+ // const element = _getFormElement(path);
3183
+ // element?.focus();
3184
+ // },
3185
+ // blur: () => {
3186
+ // const element = _getFormElement(path);
3187
+ // element?.blur();
3188
+ // },
3189
+ // scrollIntoView: (options?: ScrollIntoViewOptions) => {
3190
+ // const element = _getFormElement(path);
3191
+ // element?.scrollIntoView(
3192
+ // options ?? { behavior: 'smooth', block: 'center' }
3193
+ // );
3194
+ // },
3195
+ // click: () => {
3196
+ // const element = _getFormElement(path);
3197
+ // element?.click();
3198
+ // },
3199
+ // selectText: () => {
3200
+ // const element = _getFormElement(path) as
3201
+ // | HTMLInputElement
3202
+ // | HTMLTextAreaElement
3203
+ // | null;
3204
+ // if (element && typeof element.select === 'function') {
3205
+ // element.select();
3206
+ // }
3207
+ // },
3208
+ // };
3209
+ // }
3061
3210
  if (prop === '$$get') {
3062
3211
  return () =>
3063
3212
  $cogsSignal({ _stateKey: stateKey, _path: path, _meta: meta });
3064
3213
  }
3214
+
3065
3215
  if (prop === '$lastSynced') {
3066
3216
  const syncKey = `${stateKey}:${path.join('.')}`;
3067
3217
  return getSyncInfo(syncKey);
@@ -3133,6 +3283,38 @@ function createProxyHandler<T>(
3133
3283
  return componentId;
3134
3284
  }
3135
3285
  if (path.length == 0) {
3286
+ if (prop === '$setOptions') {
3287
+ return (options: OptionsType<T>) => {
3288
+ setOptions({ stateKey, options, initialOptionsPart: {} });
3289
+ };
3290
+ }
3291
+
3292
+ if (prop === '$_applyUpdate') {
3293
+ return (
3294
+ value: any,
3295
+ path: string[],
3296
+ updateType: 'update' | 'insert' | 'cut' = 'update'
3297
+ ) => {
3298
+ effectiveSetState(value, path, { updateType });
3299
+ };
3300
+ }
3301
+
3302
+ if (prop === '$_getEffectiveSetState') {
3303
+ return effectiveSetState;
3304
+ }
3305
+ if (prop === '$getPluginMetaData') {
3306
+ return (pluginName: string) =>
3307
+ getPluginMetaDataMap(stateKey, path)?.get(pluginName);
3308
+ }
3309
+ if (prop === '$addPluginMetaData') {
3310
+ console.log('$addPluginMetaDat');
3311
+ return (pluginName: string, data: Record<string, any>) =>
3312
+ setPluginMetaData(stateKey, path, pluginName, data);
3313
+ }
3314
+ if (prop === '$removePluginMetaData') {
3315
+ return (pluginName: string) =>
3316
+ removePluginMetaData(stateKey, path, pluginName);
3317
+ }
3136
3318
  if (prop === '$addZodValidation') {
3137
3319
  return (
3138
3320
  zodErrors: any[],
@@ -3185,46 +3367,67 @@ function createProxyHandler<T>(
3185
3367
  }
3186
3368
 
3187
3369
  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 =
3370
+ return (
3371
+ operation: UpdateTypeDetail & {
3372
+ validation?: any[];
3373
+ version?: string;
3374
+ },
3375
+ metaData?: Record<string, any>
3376
+ ) => {
3377
+ // const validationErrorsFromServer = operation.validation || [];
3378
+
3379
+ // if (!operation || !operation.path) {
3380
+ // console.error(
3381
+ // 'Invalid operation received by $applyOperation:',
3382
+ // operation
3383
+ // );
3384
+ // return;
3385
+ // }
3386
+
3387
+ // const newErrors: ValidationError[] =
3388
+ // validationErrorsFromServer.map((err) => ({
3389
+ // source: 'sync_engine',
3390
+ // message: err.message,
3391
+ // severity: 'warning',
3392
+ // code: err.code,
3393
+ // }));
3394
+ // console.log('newErrors', newErrors);
3395
+ // getGlobalStore
3396
+ // .getState()
3397
+ // .setShadowMetadata(stateKey, operation.path, {
3398
+ // validation: {
3399
+ // status: newErrors.length > 0 ? 'INVALID' : 'VALID',
3400
+ // errors: newErrors,
3401
+ // lastValidated: Date.now(),
3402
+ // },
3403
+ // });
3404
+ console.log(
3405
+ 'getGlobalStore',
3202
3406
  getGlobalStore
3203
3407
  .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
- }));
3408
+ .getShadowMetadata(stateKey, operation.path)
3409
+ );
3410
+ let index: number | undefined;
3411
+ if (
3412
+ operation.insertAfterId &&
3413
+ operation.updateType === 'insert'
3414
+ ) {
3415
+ const shadowMeta = getShadowMetadata(stateKey, operation.path);
3416
+ if (shadowMeta?.arrayKeys) {
3417
+ const afterIndex = shadowMeta.arrayKeys.indexOf(
3418
+ operation.insertAfterId
3419
+ );
3420
+ if (afterIndex !== -1) {
3421
+ index = afterIndex + 1; // Insert after the found item
3422
+ }
3423
+ }
3424
+ }
3213
3425
 
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, {
3426
+ effectiveSetState(operation.newValue, operation.path, {
3226
3427
  updateType: operation.updateType,
3227
- sync: false,
3428
+ itemId: operation.itemId,
3429
+ index, // Pass the calculated index
3430
+ metaData,
3228
3431
  });
3229
3432
  };
3230
3433
  }
@@ -3322,14 +3525,8 @@ function createProxyHandler<T>(
3322
3525
 
3323
3526
  if (prop === '$getComponents')
3324
3527
  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
3528
  }
3529
+
3333
3530
  if (prop === '$validationWrapper') {
3334
3531
  return ({
3335
3532
  children,
@@ -3349,6 +3546,7 @@ function createProxyHandler<T>(
3349
3546
  </ValidationWrapper>
3350
3547
  );
3351
3548
  }
3549
+
3352
3550
  if (prop === '$_stateKey') return stateKey;
3353
3551
  if (prop === '$_path') return path;
3354
3552
  if (prop === '$update') {
@@ -3469,9 +3667,11 @@ function createProxyHandler<T>(
3469
3667
 
3470
3668
  return revertState;
3471
3669
  },
3670
+ $initializeAndMergeShadowState: (newState: T) => {
3671
+ initializeAndMergeShadowState(stateKey, newState);
3672
+ notifyComponents(stateKey);
3673
+ },
3472
3674
  $updateInitialState: (newState: T) => {
3473
- stateVersion++;
3474
-
3475
3675
  const newUpdaterState = createProxyHandler(
3476
3676
  stateKey,
3477
3677
  effectiveSetState,