@rs-x/state-manager 0.4.12 → 0.4.13

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 (2) hide show
  1. package/package.json +2 -2
  2. package/readme.md +594 -505
package/readme.md CHANGED
@@ -226,8 +226,8 @@ Monitors only assignment of a **new value** to the index.
226
226
  ```ts
227
227
  import { InjectionContainer, printValue } from '@rs-x/core';
228
228
  import {
229
- IStateChange,
230
- IStateManager,
229
+ type IStateChange,
230
+ type IStateManager,
231
231
  RsXStateManagerInjectionTokens,
232
232
  RsXStateManagerModule,
233
233
  } from '@rs-x/state-manager';
@@ -240,7 +240,7 @@ export const run = (() => {
240
240
  RsXStateManagerInjectionTokens.IStateManager,
241
241
  );
242
242
 
243
- const stateContext = {
243
+ const model = {
244
244
  x: { y: 10 },
245
245
  };
246
246
 
@@ -254,24 +254,24 @@ export const run = (() => {
254
254
 
255
255
  try {
256
256
  // This will emit the new value { y: 10 }
257
- stateManager.watchState(stateContext, 'x');
257
+ stateManager.watchState(model, 'x');
258
258
 
259
259
  console.log('Changed value:');
260
260
  // This will emit the new value { y: 10 }
261
- stateContext.x = {
261
+ model.x = {
262
262
  y: 20,
263
263
  };
264
264
 
265
265
  console.log(`Latest value:`);
266
- printValue(stateManager.getState(stateContext, 'x'));
266
+ printValue(stateManager.getState(model, 'x'));
267
267
 
268
268
  // This will emit no change because the state is not recursive.
269
- console.log('\nstateContext.x.y = 30 will not emit any change:\n---\n');
270
- stateContext.x.y = 30;
269
+ console.log('\nmodel.x.y = 30 will not emit any change:\n---\n');
270
+ model.x.y = 30;
271
271
  } finally {
272
272
  changedSubsription.unsubscribe();
273
273
  // Always release the state when it is no longer needed.
274
- stateManager.releaseState(stateContext, 'x');
274
+ stateManager.releaseState(model, 'x');
275
275
  }
276
276
  })();
277
277
  ```
@@ -305,12 +305,13 @@ Monitors assignments **and** changes _inside_ the value.
305
305
  Example: if the value is an object, internal object changes are also observed. You can make a state item recursive by passing in a **mustProxify** predicate to a **watchState** call. The mustProxify will be called for every nested index. If you return true it will watch the index otherwise not.
306
306
 
307
307
  ```ts
308
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
308
+ import { InjectionContainer, printValue } from '@rs-x/core';
309
309
  import {
310
- IStateChange,
311
- IStateManager,
310
+ type IStateChange,
311
+ type IStateManager,
312
312
  RsXStateManagerInjectionTokens,
313
313
  RsXStateManagerModule,
314
+ watchIndexRecursiveRule,
314
315
  } from '@rs-x/state-manager';
315
316
 
316
317
  // Load the state manager module into the injection container
@@ -320,7 +321,7 @@ export const run = (() => {
320
321
  const stateManager: IStateManager = InjectionContainer.get(
321
322
  RsXStateManagerInjectionTokens.IStateManager,
322
323
  );
323
- const stateContext = {
324
+ const model = {
324
325
  x: { y: 10 },
325
326
  };
326
327
  const changedSubscription = stateManager.changed.subscribe(
@@ -336,25 +337,25 @@ export const run = (() => {
336
337
  // so we pass a predicate that always returns true.
337
338
  // This will emit an initial value { y: 10 }
338
339
  console.log('Initial value:');
339
- stateManager.watchState(stateContext, 'x', truePredicate);
340
+ stateManager.watchState(model, 'x', watchIndexRecursiveRule);
340
341
 
341
342
  console.log('Changed value:');
342
343
  // This will emit the new value { y: 10 }
343
- stateContext.x = {
344
+ model.x = {
344
345
  y: 20,
345
346
  };
346
347
 
347
348
  console.log('Changed (recursive) value:');
348
349
  // This will emit the new value { y: 30 } because x
349
350
  // is registered as a recursive state.
350
- stateContext.x.y = 30;
351
+ model.x.y = 30;
351
352
 
352
353
  console.log(`Latest value:`);
353
- printValue(stateManager.getState(stateContext, 'x'));
354
+ printValue(stateManager.getState(model, 'x'));
354
355
  } finally {
355
356
  changedSubscription.unsubscribe();
356
357
  // Always release the state when it is no longer needed.
357
- stateManager.releaseState(stateContext, 'x', truePredicate);
358
+ stateManager.releaseState(model, 'x', watchIndexRecursiveRule);
358
359
  }
359
360
  })();
360
361
  ```
@@ -381,8 +382,8 @@ Besides that you can register a watched stated (calling `watchedState`) you can
381
382
  ```ts
382
383
  import { InjectionContainer, printValue } from '@rs-x/core';
383
384
  import {
384
- IStateChange,
385
- IStateManager,
385
+ type IStateChange,
386
+ type IStateManager,
386
387
  RsXStateManagerInjectionTokens,
387
388
  RsXStateManagerModule,
388
389
  } from '@rs-x/state-manager';
@@ -395,7 +396,7 @@ export const run = (() => {
395
396
  RsXStateManagerInjectionTokens.IStateManager,
396
397
  );
397
398
 
398
- class StateContext {
399
+ class MyModel {
399
400
  private readonly _aPlusBId = 'a+b';
400
401
  private _a = 10;
401
402
  private _b = 20;
@@ -435,7 +436,7 @@ export const run = (() => {
435
436
  }
436
437
  }
437
438
 
438
- const stateContext = new StateContext();
439
+ const model = new MyModel();
439
440
  const changeSubscription = stateManager.changed.subscribe(
440
441
  (change: IStateChange) => {
441
442
  printValue(change.newValue);
@@ -444,23 +445,23 @@ export const run = (() => {
444
445
 
445
446
  try {
446
447
  console.log(`Initial value for readonly property 'aPlusB':`);
447
- console.log(stateContext.aPlusB);
448
+ console.log(model.aPlusB);
448
449
 
449
450
  console.log(
450
- `set 'stateContext.a' to '100' will emit a change event for readonly property 'aPlusB'`,
451
+ `set 'model.a' to '100' will emit a change event for readonly property 'aPlusB'`,
451
452
  );
452
453
  console.log(`Changed value for readonly property 'aPlusB':`);
453
- stateContext.a = 100;
454
+ model.a = 100;
454
455
 
455
456
  console.log(
456
- `set 'stateContext.b' to '200' will emit a change event for readonly property 'aPlusB'`,
457
+ `set 'model.b' to '200' will emit a change event for readonly property 'aPlusB'`,
457
458
  );
458
459
  console.log(`Changed value for readonly property 'aPlusB':`);
459
- stateContext.b = 200;
460
+ model.b = 200;
460
461
  } finally {
461
462
  changeSubscription.unsubscribe();
462
463
  // Always release the state when it is no longer needed.
463
- stateContext.dispose();
464
+ model.dispose();
464
465
  }
465
466
  })();
466
467
  ```
@@ -492,8 +493,8 @@ When done, release the state:
492
493
  ```ts
493
494
  import { InjectionContainer, printValue } from '@rs-x/core';
494
495
  import {
495
- IStateChange,
496
- IStateManager,
496
+ type IStateChange,
497
+ type IStateManager,
497
498
  RsXStateManagerInjectionTokens,
498
499
  RsXStateManagerModule,
499
500
  } from '@rs-x/state-manager';
@@ -505,7 +506,7 @@ export const run = (() => {
505
506
  const stateManager: IStateManager = InjectionContainer.get(
506
507
  RsXStateManagerInjectionTokens.IStateManager,
507
508
  );
508
- const stateContext = {
509
+ const model = {
509
510
  x: { y: 10 },
510
511
  };
511
512
  const changedSubscription = stateManager.changed.subscribe(
@@ -518,28 +519,28 @@ export const run = (() => {
518
519
  // Register is idempotent: you can register the same state multiple times.
519
520
  // For every register call, make sure you call unregister when you're done.
520
521
  console.log('Initial value:');
521
- stateManager.watchState(stateContext, 'x');
522
- stateManager.watchState(stateContext, 'x');
522
+ stateManager.watchState(model, 'x');
523
+ stateManager.watchState(model, 'x');
523
524
 
524
525
  console.log('Changed value:');
525
- stateContext.x = { y: 20 };
526
+ model.x = { y: 20 };
526
527
 
527
- stateManager.releaseState(stateContext, 'x');
528
+ stateManager.releaseState(model, 'x');
528
529
 
529
530
  console.log(
530
531
  'Changed event is still emitted after unregister because one observer remains.',
531
532
  );
532
533
  console.log('Changed value:');
533
- stateContext.x = { y: 30 };
534
+ model.x = { y: 30 };
534
535
 
535
- stateManager.releaseState(stateContext, 'x');
536
+ stateManager.releaseState(model, 'x');
536
537
 
537
538
  console.log(
538
539
  'Changed event is no longer emitted after the last observer unregisters.',
539
540
  );
540
541
  console.log('Changed value:');
541
542
  console.log('---');
542
- stateContext.x = { y: 30 };
543
+ model.x = { y: 30 };
543
544
  } finally {
544
545
  changedSubscription.unsubscribe();
545
546
  }
@@ -596,12 +597,13 @@ The following example illustrates the different state types:
596
597
  **Example**
597
598
 
598
599
  ```ts
599
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
600
+ import { InjectionContainer, printValue, Type } from '@rs-x/core';
600
601
  import {
601
- IStateChange,
602
- IStateManager,
602
+ type IStateChange,
603
+ type IStateManager,
603
604
  RsXStateManagerInjectionTokens,
604
605
  RsXStateManagerModule,
606
+ watchIndexRecursiveRule,
605
607
  } from '@rs-x/state-manager';
606
608
 
607
609
  // Load the state manager module into the injection container
@@ -612,7 +614,7 @@ interface INestStateConext {
612
614
  nested?: INestStateConext;
613
615
  }
614
616
 
615
- class StateContext {
617
+ class MyModel {
616
618
  private _b: INestStateConext = {
617
619
  a: 10,
618
620
  nested: {
@@ -640,7 +642,7 @@ export const run = (() => {
640
642
  RsXStateManagerInjectionTokens.IStateManager,
641
643
  );
642
644
 
643
- const stateContext = new StateContext();
645
+ const model = new MyModel();
644
646
 
645
647
  const changeSubscription = stateManager.changed.subscribe(
646
648
  (change: IStateChange) => {
@@ -650,17 +652,15 @@ export const run = (() => {
650
652
 
651
653
  try {
652
654
  // Observe property `b` recursively.
653
- // Otherwise, only assigning a new value to stateContext.b would emit a change event.
655
+ // Otherwise, only assigning a new value to model.b would emit a change event.
654
656
  // This will emit a change event with the initial (current) value.
655
657
  console.log('Initial value:');
656
- stateManager.watchState(stateContext, 'b', truePredicate);
658
+ stateManager.watchState(model, 'b', watchIndexRecursiveRule);
657
659
 
658
- console.log(
659
- '\nReplacing stateContext.b.nested.nested will emit a change event',
660
- );
660
+ console.log('\nReplacing model.b.nested.nested will emit a change event');
661
661
  console.log('Changed value:');
662
662
 
663
- stateContext.b.nested.nested = {
663
+ (Type.toObject(model.b.nested) ?? {}).nested = {
664
664
  a: -30,
665
665
  nested: {
666
666
  a: -40,
@@ -668,11 +668,11 @@ export const run = (() => {
668
668
  };
669
669
 
670
670
  console.log(`Latest value:`);
671
- printValue(stateManager.getState(stateContext, 'b'));
671
+ printValue(stateManager.getState(model, 'b'));
672
672
  } finally {
673
673
  changeSubscription.unsubscribe();
674
674
  // Always release the state when it is no longer needed.
675
- stateManager.releaseState(stateContext, 'b', truePredicate);
675
+ stateManager.releaseState(model, 'b', watchIndexRecursiveRule);
676
676
  }
677
677
  })();
678
678
  ```
@@ -729,13 +729,14 @@ Latest value:
729
729
  **Example**
730
730
 
731
731
  ```ts
732
- import { InjectionContainer, truePredicate, utCDate } from '@rs-x/core';
732
+ import { InjectionContainer, utCDate } from '@rs-x/core';
733
733
  import {
734
- IProxyRegistry,
735
- IStateChange,
736
- IStateManager,
734
+ type IProxyRegistry,
735
+ type IStateChange,
736
+ type IStateManager,
737
737
  RsXStateManagerInjectionTokens,
738
738
  RsXStateManagerModule,
739
+ watchIndexRecursiveRule,
739
740
  } from '@rs-x/state-manager';
740
741
 
741
742
  // Load the state manager module into the injection container
@@ -746,32 +747,32 @@ function watchDate(stateManager: IStateManager) {
746
747
  console.log('* Watching date');
747
748
  console.log('******************************************\n');
748
749
 
749
- const stateContext = {
750
+ const model = {
750
751
  date: utCDate(2021, 2, 5),
751
752
  };
752
753
  const changeSubscription = stateManager.changed.subscribe(
753
754
  (change: IStateChange) => {
754
- console.log(`${change.key}: ${(change.newValue as Date).toUTCString()}`);
755
+ console.log(
756
+ `${change.index}: ${(change.newValue as Date).toUTCString()}`,
757
+ );
755
758
  },
756
759
  );
757
760
  try {
758
761
  console.log('Initial value:');
759
- stateManager.watchState(stateContext, 'date', truePredicate);
762
+ stateManager.watchState(model, 'date', watchIndexRecursiveRule);
760
763
 
761
764
  console.log('Changed value:');
762
- stateContext.date.setFullYear(2023);
765
+ model.date.setFullYear(2023);
763
766
 
764
767
  console.log('Set value:');
765
- stateContext.date = new Date(2024, 5, 6);
768
+ model.date = new Date(2024, 5, 6);
766
769
 
767
770
  console.log('Latest value:');
768
- console.log(
769
- stateManager.getState<Date>(stateContext, 'date').toUTCString(),
770
- );
771
+ console.log(stateManager.getState<Date>(model, 'date').toUTCString());
771
772
  } finally {
772
773
  changeSubscription.unsubscribe();
773
774
  // Always release the state when it is no longer needed.
774
- stateManager.releaseState(stateContext, 'date', truePredicate);
775
+ stateManager.releaseState(model, 'date', watchIndexRecursiveRule);
775
776
  }
776
777
  }
777
778
 
@@ -849,12 +850,13 @@ Latest value:
849
850
  **Example**
850
851
 
851
852
  ```ts
852
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
853
+ import { InjectionContainer, printValue } from '@rs-x/core';
853
854
  import {
854
- IStateChange,
855
- IStateManager,
855
+ type IStateChange,
856
+ type IStateManager,
856
857
  RsXStateManagerInjectionTokens,
857
858
  RsXStateManagerModule,
859
+ watchIndexRecursiveRule,
858
860
  } from '@rs-x/state-manager';
859
861
 
860
862
  // Load the state manager module into the injection container
@@ -865,7 +867,7 @@ export const run = (() => {
865
867
  RsXStateManagerInjectionTokens.IStateManager,
866
868
  );
867
869
 
868
- const stateContext = {
870
+ const model = {
869
871
  array: [
870
872
  [1, 2],
871
873
  [3, 4],
@@ -881,17 +883,17 @@ export const run = (() => {
881
883
  try {
882
884
  // This will emit a change event with the initial (current) value.
883
885
  console.log('Initial value:');
884
- stateManager.watchState(stateContext, 'array', truePredicate);
886
+ stateManager.watchState(model, 'array', watchIndexRecursiveRule);
885
887
 
886
888
  console.log('Changed value:');
887
- stateContext.array[1].push(5);
889
+ model.array[1].push(5);
888
890
 
889
891
  console.log('Latest value:');
890
- printValue(stateManager.getState(stateContext, 'array'));
892
+ printValue(stateManager.getState(model, 'array'));
891
893
  } finally {
892
894
  changeSubscription.unsubscribe();
893
895
  // Always release the state when it is no longer needed.
894
- stateManager.releaseState(stateContext, 'array', truePredicate);
896
+ stateManager.releaseState(model, 'array', watchIndexRecursiveRule);
895
897
  }
896
898
  })();
897
899
  ```
@@ -942,12 +944,13 @@ Latest value:
942
944
  **Example**
943
945
 
944
946
  ```ts
945
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
947
+ import { InjectionContainer, printValue } from '@rs-x/core';
946
948
  import {
947
- IStateChange,
948
- IStateManager,
949
+ type IStateChange,
950
+ type IStateManager,
949
951
  RsXStateManagerInjectionTokens,
950
952
  RsXStateManagerModule,
953
+ watchIndexRecursiveRule,
951
954
  } from '@rs-x/state-manager';
952
955
 
953
956
  // Load the state manager module into the injection container
@@ -958,7 +961,7 @@ const stateManager: IStateManager = InjectionContainer.get(
958
961
  );
959
962
 
960
963
  export const run = (() => {
961
- const stateContext = {
964
+ const model = {
962
965
  map: new Map([
963
966
  ['a', [1, 2]],
964
967
  ['b', [3, 4]],
@@ -974,17 +977,17 @@ export const run = (() => {
974
977
  try {
975
978
  // This will emit a change event with the initial (current) value.
976
979
  console.log('Initial value:');
977
- stateManager.watchState(stateContext, 'map', truePredicate);
980
+ stateManager.watchState(model, 'map', watchIndexRecursiveRule);
978
981
 
979
982
  console.log('Changed value:');
980
- stateContext.map.get('b').push(5);
983
+ model.map.get('b')?.push(5);
981
984
 
982
985
  console.log('Latest value:');
983
- printValue(stateManager.getState(stateContext, 'map'));
986
+ printValue(stateManager.getState(model, 'map'));
984
987
  } finally {
985
988
  changeSubscription.unsubscribe();
986
989
  // Always release the state when it is no longer needed.
987
- stateManager.releaseState(stateContext, 'array', truePredicate);
990
+ stateManager.releaseState(model, 'array', watchIndexRecursiveRule);
988
991
  }
989
992
  })();
990
993
  ```
@@ -1053,13 +1056,14 @@ Latest value:
1053
1056
  **Example**
1054
1057
 
1055
1058
  ```ts
1056
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
1059
+ import { InjectionContainer, printValue } from '@rs-x/core';
1057
1060
  import {
1058
- IProxyRegistry,
1059
- IStateChange,
1060
- IStateManager,
1061
+ type IProxyRegistry,
1062
+ type IStateChange,
1063
+ type IStateManager,
1061
1064
  RsXStateManagerInjectionTokens,
1062
1065
  RsXStateManagerModule,
1066
+ watchIndexRecursiveRule,
1063
1067
  } from '@rs-x/state-manager';
1064
1068
 
1065
1069
  // Load the state manager module into the injection container
@@ -1071,7 +1075,7 @@ export const run = (() => {
1071
1075
  );
1072
1076
  const item1 = [1, 2];
1073
1077
  const item2 = [3, 4];
1074
- const stateContext = {
1078
+ const model = {
1075
1079
  set: new Set([item1, item2]),
1076
1080
  };
1077
1081
  const changeSubscription = stateManager.changed.subscribe(
@@ -1083,7 +1087,7 @@ export const run = (() => {
1083
1087
  try {
1084
1088
  // This will emit a change event with the initial (current) value.
1085
1089
  console.log('Initial value:');
1086
- stateManager.watchState(stateContext, 'set', truePredicate);
1090
+ stateManager.watchState(model, 'set', watchIndexRecursiveRule);
1087
1091
 
1088
1092
  console.log('Changed value:');
1089
1093
  const proxyRegister: IProxyRegistry = InjectionContainer.get(
@@ -1092,11 +1096,11 @@ export const run = (() => {
1092
1096
  proxyRegister.getProxy<number[]>(item2).push(5);
1093
1097
 
1094
1098
  console.log('Latest value:');
1095
- printValue(stateManager.getState(stateContext, 'set'));
1099
+ printValue(stateManager.getState(model, 'set'));
1096
1100
  } finally {
1097
1101
  changeSubscription.unsubscribe();
1098
1102
  // Always release the state when it is no longer needed.
1099
- stateManager.releaseState(stateContext, 'set', truePredicate);
1103
+ stateManager.releaseState(model, 'set', watchIndexRecursiveRule);
1100
1104
  }
1101
1105
  })();
1102
1106
  ```
@@ -1149,8 +1153,8 @@ Latest value:
1149
1153
  ```ts
1150
1154
  import { InjectionContainer, WaitForEvent } from '@rs-x/core';
1151
1155
  import {
1152
- IStateChange,
1153
- IStateManager,
1156
+ type IStateChange,
1157
+ type IStateManager,
1154
1158
  RsXStateManagerInjectionTokens,
1155
1159
  RsXStateManagerModule,
1156
1160
  } from '@rs-x/state-manager';
@@ -1163,7 +1167,7 @@ export const run = (async () => {
1163
1167
  RsXStateManagerInjectionTokens.IStateManager,
1164
1168
  );
1165
1169
 
1166
- const stateContext = {
1170
+ const model = {
1167
1171
  promise: Promise.resolve(10),
1168
1172
  };
1169
1173
  const changeSubscription = stateManager.changed.subscribe(
@@ -1176,25 +1180,25 @@ export const run = (async () => {
1176
1180
  await new WaitForEvent(stateManager, 'changed').wait(() => {
1177
1181
  // This will emit a change event with the initial (current) value.
1178
1182
  console.log('Initial value:');
1179
- stateManager.watchState(stateContext, 'promise');
1183
+ stateManager.watchState(model, 'promise');
1180
1184
  });
1181
1185
 
1182
1186
  await new WaitForEvent(stateManager, 'changed').wait(() => {
1183
1187
  console.log('Changed value:');
1184
- let resolveHandler: (value: number) => void;
1185
- stateContext.promise = new Promise((resolve) => {
1188
+ let resolveHandler!: (value: number) => void;
1189
+
1190
+ model.promise = new Promise<number>((resolve) => {
1186
1191
  resolveHandler = resolve;
1187
1192
  });
1193
+
1188
1194
  resolveHandler(30);
1189
1195
  });
1190
1196
 
1191
- console.log(
1192
- `Latest value: ${stateManager.getState(stateContext, 'promise')}`,
1193
- );
1197
+ console.log(`Latest value: ${stateManager.getState(model, 'promise')}`);
1194
1198
  } finally {
1195
1199
  changeSubscription.unsubscribe();
1196
1200
  // Always release the state when it is no longer needed.
1197
- stateManager.releaseState(stateContext, 'promise');
1201
+ stateManager.releaseState(model, 'promise');
1198
1202
  }
1199
1203
  })();
1200
1204
  ```
@@ -1215,14 +1219,15 @@ Latest value: 30
1215
1219
  **Example**
1216
1220
 
1217
1221
  ```ts
1222
+ import { of, Subject } from 'rxjs';
1223
+
1218
1224
  import { InjectionContainer, WaitForEvent } from '@rs-x/core';
1219
1225
  import {
1220
- IStateChange,
1221
- IStateManager,
1226
+ type IStateChange,
1227
+ type IStateManager,
1222
1228
  RsXStateManagerInjectionTokens,
1223
1229
  RsXStateManagerModule,
1224
1230
  } from '@rs-x/state-manager';
1225
- import { of, Subject } from 'rxjs';
1226
1231
 
1227
1232
  // Load the state manager module into the injection container
1228
1233
  InjectionContainer.load(RsXStateManagerModule);
@@ -1232,7 +1237,7 @@ export const run = (async () => {
1232
1237
  RsXStateManagerInjectionTokens.IStateManager,
1233
1238
  );
1234
1239
 
1235
- const stateContext = {
1240
+ const model = {
1236
1241
  observable: of(10),
1237
1242
  };
1238
1243
 
@@ -1248,23 +1253,21 @@ export const run = (async () => {
1248
1253
  await new WaitForEvent(stateManager, 'changed').wait(() => {
1249
1254
  // This will emit a change event with the initial (current) value.
1250
1255
  console.log('Initial value:');
1251
- stateManager.watchState(stateContext, 'observable');
1256
+ stateManager.watchState(model, 'observable');
1252
1257
  });
1253
1258
 
1254
1259
  await new WaitForEvent(stateManager, 'changed').wait(() => {
1255
1260
  console.log('Changed value:');
1256
1261
  const subject = new Subject<number>();
1257
- stateContext.observable = subject;
1262
+ model.observable = subject;
1258
1263
  subject.next(30);
1259
1264
  });
1260
1265
 
1261
- console.log(
1262
- `Latest value: ${stateManager.getState(stateContext, 'observable')}`,
1263
- );
1266
+ console.log(`Latest value: ${stateManager.getState(model, 'observable')}`);
1264
1267
  } finally {
1265
1268
  changeSubscription.unsubscribe();
1266
1269
  // Always release the state when it is no longer needed.
1267
- stateManager.releaseState(stateContext, 'observable');
1270
+ stateManager.releaseState(model, 'observable');
1268
1271
  }
1269
1272
  })();
1270
1273
  ```
@@ -1291,500 +1294,586 @@ The following example demonstrates adding support for a custom `TextDocument` cl
1291
1294
  **Example**
1292
1295
 
1293
1296
  ````ts
1297
+ import { ReplaySubject, Subscription } from 'rxjs';
1298
+
1294
1299
  import {
1295
- ContainerModule,
1296
- defaultIndexValueAccessorList,
1297
- IDisposableOwner,
1298
- IErrorLog,
1299
- IGuidFactory,
1300
- IIndexValueAccessor,
1301
- Inject,
1302
- Injectable,
1303
- InjectionContainer,
1304
- IPropertyChange,
1305
- overrideMultiInjectServices,
1306
- RsXCoreInjectionTokens,
1307
- SingletonFactory,
1308
- truePredicate
1300
+ ContainerModule,
1301
+ defaultIndexValueAccessorList,
1302
+ type IDisposableOwner,
1303
+ type IErrorLog,
1304
+ type IGuidFactory,
1305
+ type IIndexValueAccessor,
1306
+ Inject,
1307
+ Injectable,
1308
+ InjectionContainer,
1309
+ type IPropertyChange,
1310
+ type IValueMetadata,
1311
+ overrideMultiInjectServices,
1312
+ RsXCoreInjectionTokens,
1313
+ SingletonFactory,
1314
+ Type,
1309
1315
  } from '@rs-x/core';
1310
1316
  import {
1311
- AbstractObserver,
1312
- defaultObjectObserverProxyPairFactoryList,
1313
- defaultPropertyObserverProxyPairFactoryList,
1314
- IIndexObserverInfo,
1315
- IndexObserverProxyPairFactory,
1316
- IObjectObserverProxyPairFactory,
1317
- IObjectObserverProxyPairManager,
1318
- IObserverProxyPair,
1319
- IPropertyInfo,
1320
- IProxyRegistry,
1321
- IProxyTarget,
1322
- IStateChange,
1323
- IStateManager,
1324
- RsXStateManagerInjectionTokens,
1325
- RsXStateManagerModule
1317
+ AbstractObserver,
1318
+ defaultObjectObserverProxyPairFactoryList,
1319
+ defaultPropertyObserverProxyPairFactoryList,
1320
+ type IIndexObserverInfo,
1321
+ IndexObserverProxyPairFactory,
1322
+ type IObjectObserverProxyPairFactory,
1323
+ type IObjectObserverProxyPairManager,
1324
+ type IObserverProxyPair,
1325
+ type IPropertyInfo,
1326
+ type IProxyRegistry,
1327
+ type IProxyTarget,
1328
+ type IStateChange,
1329
+ type IStateManager,
1330
+ RsXStateManagerInjectionTokens,
1331
+ RsXStateManagerModule,
1332
+ watchIndexRecursiveRule,
1326
1333
  } from '@rs-x/state-manager';
1327
- import { ReplaySubject, Subscription } from 'rxjs';
1328
1334
 
1329
1335
  const MyInjectTokens = {
1330
- TextDocumentObserverManager: Symbol('TextDocumentObserverManager'),
1331
- TextDocumenIndexObserverManager: Symbol('TextDocumenIndexObserverManager'),
1332
- TextDocumentIndexAccessor: Symbol('TextDocumentIndexAccessor'),
1333
- TextDocumentObserverProxyPairFactory: Symbol('TextDocumentObserverProxyPairFactory'),
1334
- TextDocumentInxdexObserverProxyPairFactory: Symbol('TextDocumentInxdexObserverProxyPairFactory')
1335
-
1336
+ TextDocumentObserverManager: Symbol('TextDocumentObserverManager'),
1337
+ TextDocumenIndexObserverManager: Symbol('TextDocumenIndexObserverManager'),
1338
+ TextDocumentIndexAccessor: Symbol('TextDocumentIndexAccessor'),
1339
+ TextDocumentObserverProxyPairFactory: Symbol(
1340
+ 'TextDocumentObserverProxyPairFactory',
1341
+ ),
1342
+ TextDocumentInxdexObserverProxyPairFactory: Symbol(
1343
+ 'TextDocumentInxdexObserverProxyPairFactory',
1344
+ ),
1336
1345
  };
1337
1346
 
1338
- class IndexForTextDocumentxObserverManager
1339
- extends SingletonFactory<
1340
- number,
1341
- IIndexObserverInfo<ITextDocumentIndex>,
1342
- TextDocumentIndexObserver> {
1343
- constructor(
1344
- private readonly _textDocument: TextDocument,
1345
- private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1346
- private readonly releaseOwner: () => void
1347
- ) {
1348
- super();
1349
- }
1347
+ class IndexForTextDocumentxObserverManager extends SingletonFactory<
1348
+ number,
1349
+ IIndexObserverInfo<ITextDocumentIndex>,
1350
+ TextDocumentIndexObserver
1351
+ > {
1352
+ constructor(
1353
+ private readonly _textDocument: TextDocument,
1354
+ private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1355
+ private readonly releaseOwner: () => void,
1356
+ ) {
1357
+ super();
1358
+ }
1350
1359
 
1351
- public override getId(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>): number {
1352
- return this.createId(indexObserverInfo);
1353
- }
1360
+ public override getId(
1361
+ indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>,
1362
+ ): number {
1363
+ return this.createId(indexObserverInfo);
1364
+ }
1354
1365
 
1355
- protected override createInstance(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>, id: number): TextDocumentIndexObserver {
1356
- const textDocumentObserver = this._textDocumentObserverManager.create(this._textDocument).instance;
1357
- return new TextDocumentIndexObserver(
1358
- {
1359
- canDispose: () => this.getReferenceCount(id) === 1,
1360
- release: () => {
1361
- textDocumentObserver.dispose();
1362
- this.release(id);
1363
- },
1364
- },
1365
- textDocumentObserver, indexObserverInfo.index
1366
- );
1367
- }
1366
+ protected override createInstance(
1367
+ indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>,
1368
+ id: number,
1369
+ ): TextDocumentIndexObserver {
1370
+ const textDocumentObserver = this._textDocumentObserverManager.create(
1371
+ this._textDocument,
1372
+ ).instance;
1373
+ return new TextDocumentIndexObserver(
1374
+ {
1375
+ canDispose: () => this.getReferenceCount(id) === 1,
1376
+ release: () => {
1377
+ textDocumentObserver.dispose();
1378
+ this.release(id);
1379
+ },
1380
+ },
1381
+ textDocumentObserver,
1382
+ indexObserverInfo.index,
1383
+ );
1384
+ }
1368
1385
 
1369
- protected override createId(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>): number {
1370
- // Using Cantor pairing to create a unique id from page and line index
1371
- const { pageIndex, lineIndex } = indexObserverInfo.index;
1372
- return ((pageIndex + lineIndex) * (pageIndex + lineIndex + 1)) / 2 + lineIndex;
1373
- }
1386
+ protected override createId(
1387
+ indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>,
1388
+ ): number {
1389
+ // Using Cantor pairing to create a unique id from page and line index
1390
+ const { pageIndex, lineIndex } = indexObserverInfo.index;
1391
+ return (
1392
+ ((pageIndex + lineIndex) * (pageIndex + lineIndex + 1)) / 2 + lineIndex
1393
+ );
1394
+ }
1374
1395
 
1375
- protected override onReleased(): void {
1376
- if (this.isEmpty) {
1377
- this.releaseOwner();
1378
- }
1396
+ protected override onReleased(): void {
1397
+ if (this.isEmpty) {
1398
+ this.releaseOwner();
1379
1399
  }
1400
+ }
1380
1401
  }
1381
1402
 
1382
1403
  // We want to ensure that for the same TextDocument we always have the same observer
1383
1404
  @Injectable()
1384
- class TextDocumentObserverManager extends SingletonFactory<TextDocument, TextDocument, TextDocumentObserver> {
1385
- constructor(
1386
- @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1387
- private readonly _proxyRegister: IProxyRegistry) {
1388
- super();
1389
- }
1405
+ class TextDocumentObserverManager extends SingletonFactory<
1406
+ TextDocument,
1407
+ TextDocument,
1408
+ TextDocumentObserver
1409
+ > {
1410
+ constructor(
1411
+ @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1412
+ private readonly _proxyRegister: IProxyRegistry,
1413
+ ) {
1414
+ super();
1415
+ }
1390
1416
 
1391
- public override getId(textDocument: TextDocument): TextDocument {
1392
- return textDocument;
1393
- }
1417
+ public override getId(textDocument: TextDocument): TextDocument {
1418
+ return textDocument;
1419
+ }
1394
1420
 
1395
- protected override createInstance(textDocument: TextDocument, id: TextDocument): TextDocumentObserver {
1396
- return new TextDocumentObserver(
1397
- textDocument,
1398
- this._proxyRegister,
1399
- {
1400
- canDispose: () => this.getReferenceCount(id) === 1,
1401
- release: () => this.release(id)
1402
- }
1403
- );
1404
- }
1421
+ protected override createInstance(
1422
+ textDocument: TextDocument,
1423
+ id: TextDocument,
1424
+ ): TextDocumentObserver {
1425
+ return new TextDocumentObserver(textDocument, this._proxyRegister, {
1426
+ canDispose: () => this.getReferenceCount(id) === 1,
1427
+ release: () => this.release(id),
1428
+ });
1429
+ }
1405
1430
 
1406
- protected override createId(textDocument: TextDocument): TextDocument {
1407
- return textDocument;
1408
- }
1431
+ protected override createId(textDocument: TextDocument): TextDocument {
1432
+ return textDocument;
1433
+ }
1409
1434
  }
1410
1435
 
1411
1436
  // We want to ensure we create only one index-manager per TextDocument
1412
1437
  @Injectable()
1413
- export class TextDocumenIndexObserverManager
1414
- extends SingletonFactory<
1415
- TextDocument,
1416
- TextDocument,
1417
- IndexForTextDocumentxObserverManager
1418
- > {
1419
- constructor(
1420
- @Inject(MyInjectTokens.TextDocumentObserverManager)
1421
- private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1422
- ) {
1423
- super();
1424
- }
1425
-
1426
- public override getId(textDocument: TextDocument): TextDocument {
1427
- return textDocument;
1428
- }
1438
+ export class TextDocumenIndexObserverManager extends SingletonFactory<
1439
+ TextDocument,
1440
+ TextDocument,
1441
+ IndexForTextDocumentxObserverManager
1442
+ > {
1443
+ constructor(
1444
+ @Inject(MyInjectTokens.TextDocumentObserverManager)
1445
+ private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1446
+ ) {
1447
+ super();
1448
+ }
1429
1449
 
1430
- protected override createId(textDocument: TextDocument): TextDocument {
1431
- return textDocument;
1432
- }
1450
+ public override getId(textDocument: TextDocument): TextDocument {
1451
+ return textDocument;
1452
+ }
1433
1453
 
1434
- protected override createInstance(
1435
- textDocument: TextDocument
1436
- ): IndexForTextDocumentxObserverManager {
1454
+ protected override createId(textDocument: TextDocument): TextDocument {
1455
+ return textDocument;
1456
+ }
1437
1457
 
1438
- return new IndexForTextDocumentxObserverManager(textDocument, this._textDocumentObserverManager, () => this.release(textDocument));
1439
- }
1458
+ protected override createInstance(
1459
+ textDocument: TextDocument,
1460
+ ): IndexForTextDocumentxObserverManager {
1461
+ return new IndexForTextDocumentxObserverManager(
1462
+ textDocument,
1463
+ this._textDocumentObserverManager,
1464
+ () => this.release(textDocument),
1465
+ );
1466
+ }
1440
1467
 
1441
- protected override releaseInstance(
1442
- indexForTextDocumentxObserverManager: IndexForTextDocumentxObserverManager
1443
- ): void {
1444
- indexForTextDocumentxObserverManager.dispose();
1445
- }
1468
+ protected override releaseInstance(
1469
+ indexForTextDocumentxObserverManager: IndexForTextDocumentxObserverManager,
1470
+ ): void {
1471
+ indexForTextDocumentxObserverManager.dispose();
1472
+ }
1446
1473
  }
1447
1474
 
1448
-
1449
-
1450
-
1451
1475
  @Injectable()
1452
- export class TextDocumentIndexAccessor implements IIndexValueAccessor<TextDocument, ITextDocumentIndex> {
1453
- public readonly priority: 200;
1454
-
1455
- public hasValue(context: TextDocument, index: ITextDocumentIndex): boolean {
1456
- return context.getLine(index) !== undefined;
1457
- }
1458
-
1459
- // We don’t have any properties that can be iterated through.
1460
- public getIndexes(_context: TextDocument, _index?: ITextDocumentIndex): IterableIterator<ITextDocumentIndex> {
1461
- return [].values();
1462
- }
1476
+ export class TextDocumentIndexAccessor implements IIndexValueAccessor<
1477
+ TextDocument,
1478
+ ITextDocumentIndex
1479
+ > {
1480
+ public readonly priority!: 200;
1481
+
1482
+ public hasValue(
1483
+ textDocument: TextDocument,
1484
+ index: ITextDocumentIndex,
1485
+ ): boolean {
1486
+ return textDocument.getLine(index) !== undefined;
1487
+ }
1463
1488
 
1464
- // Indicate whether the value is async. For example when the value is a Promise
1465
- public isAsync(_context: TextDocument, _index: ITextDocumentIndex): boolean {
1466
- return false;
1467
- }
1489
+ // We don’t have any properties that can be iterated through.
1490
+ public getIndexes(): IterableIterator<ITextDocumentIndex> {
1491
+ return [].values();
1492
+ }
1468
1493
 
1469
- // Here it is the same as getValue.
1470
- // For example, for a Promise accessor getValue returns the promise
1471
- // and getResolvedValue returns the resolved promise value
1472
- public getResolvedValue(context: TextDocument, index: ITextDocumentIndex): string {
1473
- return this.getValue(context, index);
1474
- }
1494
+ // Here it is the same as getValue.
1495
+ // For example, for a Promise accessor getValue returns the promise
1496
+ // and getResolvedValue returns the resolved promise value
1497
+ public getResolvedValue(
1498
+ textDocument: TextDocument,
1499
+ index: ITextDocumentIndex,
1500
+ ): string | undefined {
1501
+ return this.getValue(textDocument, index);
1502
+ }
1475
1503
 
1476
- public getValue(context: TextDocument, index: ITextDocumentIndex): string {
1477
- return context.getLine(index);
1478
- }
1504
+ public getValue(
1505
+ textDocument: TextDocument,
1506
+ index: ITextDocumentIndex,
1507
+ ): string | undefined {
1508
+ return textDocument.getLine(index);
1509
+ }
1479
1510
 
1480
- public setValue(context: TextDocument, index: ITextDocumentIndex, value: string): void {
1481
- context.setLine(index, value);
1482
- }
1511
+ public setValue(
1512
+ textDocument: TextDocument,
1513
+ index: ITextDocumentIndex,
1514
+ value: string,
1515
+ ): void {
1516
+ textDocument.setLine(index, value);
1517
+ }
1483
1518
 
1484
- public applies(context: unknown, _index: ITextDocumentIndex): boolean {
1485
- return context instanceof TextDocument;
1486
- }
1519
+ public applies(textDocument: unknown, _index: ITextDocumentIndex): boolean {
1520
+ return textDocument instanceof TextDocument;
1521
+ }
1487
1522
  }
1488
1523
 
1489
1524
  @Injectable()
1490
- export class TextDocumentInxdexObserverProxyPairFactory extends IndexObserverProxyPairFactory<TextDocument, unknown> {
1491
- constructor(
1492
- @Inject(RsXStateManagerInjectionTokens.IObjectObserverProxyPairManager)
1493
- objectObserverManager: IObjectObserverProxyPairManager,
1494
- @Inject( MyInjectTokens.TextDocumenIndexObserverManager)
1495
- textDocumenIndexObserverManager: TextDocumenIndexObserverManager,
1496
- @Inject(RsXCoreInjectionTokens.IErrorLog)
1497
- errorLog: IErrorLog,
1498
- @Inject(RsXCoreInjectionTokens.IGuidFactory)
1499
- guidFactory: IGuidFactory,
1500
- @Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
1501
- indexValueAccessor: IIndexValueAccessor,
1502
- @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1503
- proxyRegister: IProxyRegistry
1504
- ) {
1505
- super(
1506
- objectObserverManager,
1507
- textDocumenIndexObserverManager,
1508
- errorLog,
1509
- guidFactory,
1510
- indexValueAccessor,
1511
- proxyRegister
1512
- );
1513
- }
1525
+ export class TextDocumentInxdexObserverProxyPairFactory extends IndexObserverProxyPairFactory<
1526
+ TextDocument,
1527
+ unknown
1528
+ > {
1529
+ constructor(
1530
+ @Inject(RsXStateManagerInjectionTokens.IObjectObserverProxyPairManager)
1531
+ objectObserverManager: IObjectObserverProxyPairManager,
1532
+ @Inject(MyInjectTokens.TextDocumenIndexObserverManager)
1533
+ textDocumenIndexObserverManager: TextDocumenIndexObserverManager,
1534
+ @Inject(RsXCoreInjectionTokens.IErrorLog)
1535
+ errorLog: IErrorLog,
1536
+ @Inject(RsXCoreInjectionTokens.IGuidFactory)
1537
+ guidFactory: IGuidFactory,
1538
+ @Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
1539
+ indexValueAccessor: IIndexValueAccessor,
1540
+ @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1541
+ proxyRegister: IProxyRegistry,
1542
+ @Inject(RsXCoreInjectionTokens.IValueMetadata)
1543
+ valueMetadata: IValueMetadata,
1544
+ ) {
1545
+ super(
1546
+ objectObserverManager,
1547
+ Type.cast(textDocumenIndexObserverManager),
1548
+ errorLog,
1549
+ guidFactory,
1550
+ indexValueAccessor,
1551
+ proxyRegister,
1552
+ valueMetadata,
1553
+ );
1554
+ }
1514
1555
 
1515
- public applies(object: unknown, propertyInfo: IPropertyInfo): boolean {
1516
- const documentKey = propertyInfo.key as ITextDocumentIndex;
1517
- return object instanceof TextDocument && documentKey?.lineIndex >= 0 && documentKey?.pageIndex >= 0;
1518
- }
1556
+ public applies(object: unknown, propertyInfo: IPropertyInfo): boolean {
1557
+ const documentKey = propertyInfo.index as ITextDocumentIndex;
1558
+ return (
1559
+ object instanceof TextDocument &&
1560
+ documentKey?.lineIndex >= 0 &&
1561
+ documentKey?.pageIndex >= 0
1562
+ );
1563
+ }
1519
1564
  }
1520
1565
 
1521
1566
  @Injectable()
1522
1567
  export class TextDocumentObserverProxyPairFactory implements IObjectObserverProxyPairFactory {
1523
- public readonly priority = 100;
1524
-
1525
- constructor(
1526
- @Inject( MyInjectTokens.TextDocumentObserverManager)
1527
- private readonly _textDocumentObserverManager: TextDocumentObserverManager) { }
1528
-
1529
- public create(
1530
- _: IDisposableOwner,
1531
- proxyTarget: IProxyTarget<TextDocument>): IObserverProxyPair<TextDocument> {
1532
-
1533
- const observer = this._textDocumentObserverManager.create(proxyTarget.target).instance;
1534
- return {
1535
- observer,
1536
- proxy: observer.target as TextDocument,
1537
- proxyTarget: proxyTarget.target,
1538
- };
1539
- }
1568
+ public readonly priority = 100;
1540
1569
 
1541
- public applies(object: unknown): boolean {
1542
- return object instanceof TextDocument;
1543
- }
1570
+ constructor(
1571
+ @Inject(MyInjectTokens.TextDocumentObserverManager)
1572
+ private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1573
+ ) {}
1574
+
1575
+ public create(
1576
+ _: IDisposableOwner,
1577
+ proxyTarget: IProxyTarget<TextDocument>,
1578
+ ): IObserverProxyPair<TextDocument> {
1579
+ const observer = this._textDocumentObserverManager.create(
1580
+ proxyTarget.target,
1581
+ ).instance;
1582
+ return {
1583
+ observer,
1584
+ proxy: observer.target as TextDocument,
1585
+ proxyTarget: proxyTarget.target,
1586
+ };
1587
+ }
1588
+
1589
+ public applies(object: unknown): boolean {
1590
+ return object instanceof TextDocument;
1591
+ }
1544
1592
  }
1545
1593
 
1546
1594
  interface ITextDocumentIndex {
1547
- pageIndex: number;
1548
- lineIndex: number;
1595
+ pageIndex: number;
1596
+ lineIndex: number;
1549
1597
  }
1550
1598
 
1551
1599
  class TextDocument {
1552
- private readonly _pages = new Map<number, Map<number, string>>();
1553
- constructor(
1554
- pages?: string[][],
1555
- ) {
1556
-
1557
- pages?.forEach((page, pageIndex) => {
1558
- const pageText = new Map<number, string>();
1559
-
1560
- this._pages.set(pageIndex, pageText);
1561
- page.forEach((lineText, lineIndex) => {
1562
- pageText.set(lineIndex, lineText);
1563
- });
1564
- });
1565
- }
1566
-
1567
- public toString(): string {
1568
- const pages: string[] = [];
1600
+ private readonly _pages = new Map<number, Map<number, string>>();
1601
+ constructor(pages?: string[][]) {
1602
+ pages?.forEach((page, pageIndex) => {
1603
+ const pageText = new Map<number, string>();
1604
+
1605
+ this._pages.set(pageIndex, pageText);
1606
+ page.forEach((lineText, lineIndex) => {
1607
+ pageText.set(lineIndex, lineText);
1608
+ });
1609
+ });
1610
+ }
1569
1611
 
1570
- // Sort pages by pageIndex
1571
- const sortedPageIndexes = Array.from(this._pages.keys()).sort((a, b) => a - b);
1612
+ public toString(): string {
1613
+ const pages: string[] = [];
1572
1614
 
1573
- for (const pageIndex of sortedPageIndexes) {
1574
- const page = this._pages.get(pageIndex);
1575
- if (!page) {
1576
- continue;
1577
- }
1615
+ // Sort pages by pageIndex
1616
+ const sortedPageIndexes = Array.from(this._pages.keys()).sort(
1617
+ (a, b) => a - b,
1618
+ );
1578
1619
 
1579
- // Sort lines by lineIndex
1580
- const sortedLineIndexes = Array.from(page.keys()).sort((a, b) => a - b);
1620
+ for (const pageIndex of sortedPageIndexes) {
1621
+ const page = this._pages.get(pageIndex);
1622
+ if (!page) {
1623
+ continue;
1624
+ }
1581
1625
 
1582
- const lines = sortedLineIndexes.map(lineIndex => ` ${lineIndex}: ${page.get(lineIndex)}`);
1583
- pages.push(`Page ${pageIndex}:\n${lines.join('\n')}`);
1584
- }
1626
+ // Sort lines by lineIndex
1627
+ const sortedLineIndexes = Array.from(page.keys()).sort((a, b) => a - b);
1585
1628
 
1586
- return pages.join('\n\n');
1629
+ const lines = sortedLineIndexes.map(
1630
+ (lineIndex) => ` ${lineIndex}: ${page.get(lineIndex)}`,
1631
+ );
1632
+ pages.push(`Page ${pageIndex}:\n${lines.join('\n')}`);
1587
1633
  }
1588
1634
 
1589
- public setLine(index: ITextDocumentIndex, text: string): void {
1590
- const { pageIndex, lineIndex } = index;
1591
- let page = this._pages.get(pageIndex);
1592
- if (!page) {
1593
- page = new Map();
1594
- this._pages.set(pageIndex, page);
1595
- }
1635
+ return pages.join('\n\n');
1636
+ }
1596
1637
 
1597
- page.set(lineIndex, text);
1638
+ public setLine(index: ITextDocumentIndex, text: string): void {
1639
+ const { pageIndex, lineIndex } = index;
1640
+ let page = this._pages.get(pageIndex);
1641
+ if (!page) {
1642
+ page = new Map();
1643
+ this._pages.set(pageIndex, page);
1598
1644
  }
1599
1645
 
1600
- public getLine(index: ITextDocumentIndex): string {
1601
- const { pageIndex, lineIndex } = index;
1602
- return this._pages.get(pageIndex)?.get(lineIndex);
1603
- }
1646
+ page.set(lineIndex, text);
1647
+ }
1648
+
1649
+ public getLine(index: ITextDocumentIndex): string | undefined {
1650
+ const { pageIndex, lineIndex } = index;
1651
+ return this._pages.get(pageIndex)?.get(lineIndex);
1652
+ }
1604
1653
  }
1605
1654
 
1606
- class TextDocumentIndexObserver extends AbstractObserver<TextDocument, string, ITextDocumentIndex> {
1607
- private readonly _changeSubscription: Subscription;
1655
+ class TextDocumentIndexObserver extends AbstractObserver<
1656
+ TextDocument,
1657
+ string,
1658
+ ITextDocumentIndex
1659
+ > {
1660
+ private readonly _changeSubscription: Subscription;
1608
1661
 
1609
- constructor(
1610
- owner: IDisposableOwner,
1611
- private readonly _observer: TextDocumentObserver,
1612
- index: ITextDocumentIndex,
1613
- ) {
1614
- super(owner, _observer.target, _observer.target.getLine(index), new ReplaySubject(), index);
1615
- this._changeSubscription = _observer.changed.subscribe(this.onChange);
1616
- }
1662
+ constructor(
1663
+ owner: IDisposableOwner,
1664
+ private readonly _observer: TextDocumentObserver,
1665
+ index: ITextDocumentIndex,
1666
+ ) {
1667
+ super(
1668
+ owner,
1669
+ _observer.target,
1670
+ _observer.target.getLine(index),
1671
+ new ReplaySubject(),
1672
+ index,
1673
+ );
1674
+ this._changeSubscription = _observer.changed.subscribe(this.onChange);
1675
+ }
1617
1676
 
1618
- protected override disposeInternal(): void {
1619
- this._changeSubscription.unsubscribe();
1620
- this._observer.dispose();
1621
- }
1677
+ protected override disposeInternal(): void {
1678
+ this._changeSubscription.unsubscribe();
1679
+ this._observer.dispose();
1680
+ }
1622
1681
 
1623
- private readonly onChange = (change: IPropertyChange) => {
1624
- const changeIndex = change.id as ITextDocumentIndex;
1625
- if (changeIndex.lineIndex === this.id.lineIndex && changeIndex.pageIndex === this.id.pageIndex) {
1626
- this.emitChange(change);
1627
- }
1682
+ private readonly onChange = (change: IPropertyChange) => {
1683
+ const changeIndex = change.index as ITextDocumentIndex;
1684
+ if (
1685
+ changeIndex.lineIndex === this.id?.lineIndex &&
1686
+ changeIndex.pageIndex === this.id?.pageIndex
1687
+ ) {
1688
+ this.emitChange(change);
1628
1689
  }
1690
+ };
1629
1691
  }
1630
1692
 
1631
1693
  class TextDocumentObserver extends AbstractObserver<TextDocument> {
1632
- constructor(
1633
- textDocument: TextDocument,
1634
- private readonly _proxyRegister: IProxyRegistry,
1635
- owner?: IDisposableOwner,) {
1636
- super(owner, null, textDocument);
1694
+ constructor(
1695
+ textDocument: TextDocument,
1696
+ private readonly _proxyRegister: IProxyRegistry,
1697
+ owner?: IDisposableOwner,
1698
+ ) {
1699
+ super(owner, Type.cast(undefined), textDocument);
1637
1700
 
1638
- this.target = new Proxy(textDocument, this);
1701
+ this.target = new Proxy(textDocument, this);
1639
1702
 
1640
- // Always register a proxy at the proxy registry
1641
- // so we can determine if an instance is a proxy or not.
1642
- this._proxyRegister.register(textDocument, this.target);
1643
- }
1703
+ // Always register a proxy at the proxy registry
1704
+ // so we can determine if an instance is a proxy or not.
1705
+ this._proxyRegister.register(textDocument, this.target);
1706
+ }
1644
1707
 
1645
- protected override disposeInternal(): void {
1646
- this._proxyRegister.unregister(this.value);
1647
- }
1708
+ protected override disposeInternal(): void {
1709
+ this._proxyRegister.unregister(this.value);
1710
+ }
1648
1711
 
1649
- public get(
1650
- textDocument: TextDocument,
1651
- property: PropertyKey,
1652
- receiver: unknown
1653
- ): unknown {
1654
- if (property == 'setLine') {
1655
- return (index: ITextDocumentIndex, text: string) => {
1656
- textDocument.setLine(index, text);
1657
- this.emitChange({
1658
- arguments: [],
1659
- id: index,
1660
- target: textDocument,
1661
- newValue: text,
1662
- });
1663
- };
1664
-
1665
- } else {
1666
- return Reflect.get(textDocument, property, receiver);
1667
- }
1712
+ public get(
1713
+ textDocument: TextDocument,
1714
+ property: PropertyKey,
1715
+ receiver: unknown,
1716
+ ): unknown {
1717
+ if (property == 'setLine') {
1718
+ return (index: ITextDocumentIndex, text: string) => {
1719
+ textDocument.setLine(index, text);
1720
+ this.emitChange({
1721
+ arguments: [],
1722
+ index: index,
1723
+ target: textDocument,
1724
+ newValue: text,
1725
+ });
1726
+ };
1727
+ } else {
1728
+ return Reflect.get(textDocument, property, receiver);
1668
1729
  }
1730
+ }
1669
1731
  }
1670
1732
 
1671
1733
  // Load the state manager module into the injection container
1672
1734
  InjectionContainer.load(RsXStateManagerModule);
1673
1735
 
1674
1736
  const MyModule = new ContainerModule((options) => {
1675
- options
1676
- .bind<TextDocumentObserverManager>(
1677
- MyInjectTokens.TextDocumentObserverManager
1678
- )
1679
- .to(TextDocumentObserverManager)
1680
- .inSingletonScope();
1681
-
1682
- options
1683
- .bind<TextDocumenIndexObserverManager>(
1684
- MyInjectTokens.TextDocumenIndexObserverManager
1685
- )
1686
- .to(TextDocumenIndexObserverManager)
1687
- .inSingletonScope();
1688
-
1689
-
1690
- overrideMultiInjectServices(options, RsXCoreInjectionTokens.IIndexValueAccessorList, [
1691
- { target: TextDocumentIndexAccessor, token: MyInjectTokens.TextDocumentIndexAccessor },
1692
- ...defaultIndexValueAccessorList
1693
- ]);
1694
-
1695
- overrideMultiInjectServices(options, RsXStateManagerInjectionTokens.IObjectObserverProxyPairFactoryList, [
1696
- { target: TextDocumentObserverProxyPairFactory, token: MyInjectTokens.TextDocumentObserverProxyPairFactory },
1697
- ...defaultObjectObserverProxyPairFactoryList
1698
- ]);
1699
-
1700
- overrideMultiInjectServices(options,RsXStateManagerInjectionTokens.IPropertyObserverProxyPairFactoryList, [
1701
- { target: TextDocumentInxdexObserverProxyPairFactory, token: MyInjectTokens.TextDocumentInxdexObserverProxyPairFactory },
1702
- ...defaultPropertyObserverProxyPairFactoryList
1703
- ]);
1737
+ options
1738
+ .bind<TextDocumentObserverManager>(
1739
+ MyInjectTokens.TextDocumentObserverManager,
1740
+ )
1741
+ .to(TextDocumentObserverManager)
1742
+ .inSingletonScope();
1743
+
1744
+ options
1745
+ .bind<TextDocumenIndexObserverManager>(
1746
+ MyInjectTokens.TextDocumenIndexObserverManager,
1747
+ )
1748
+ .to(TextDocumenIndexObserverManager)
1749
+ .inSingletonScope();
1750
+
1751
+ overrideMultiInjectServices(
1752
+ options,
1753
+ RsXCoreInjectionTokens.IIndexValueAccessorList,
1754
+ [
1755
+ {
1756
+ target: TextDocumentIndexAccessor,
1757
+ token: MyInjectTokens.TextDocumentIndexAccessor,
1758
+ },
1759
+ ...defaultIndexValueAccessorList,
1760
+ ],
1761
+ );
1762
+
1763
+ overrideMultiInjectServices(
1764
+ options,
1765
+ RsXStateManagerInjectionTokens.IObjectObserverProxyPairFactoryList,
1766
+ [
1767
+ {
1768
+ target: TextDocumentObserverProxyPairFactory,
1769
+ token: MyInjectTokens.TextDocumentObserverProxyPairFactory,
1770
+ },
1771
+ ...defaultObjectObserverProxyPairFactoryList,
1772
+ ],
1773
+ );
1774
+
1775
+ overrideMultiInjectServices(
1776
+ options,
1777
+ RsXStateManagerInjectionTokens.IPropertyObserverProxyPairFactoryList,
1778
+ [
1779
+ {
1780
+ target: TextDocumentInxdexObserverProxyPairFactory,
1781
+ token: MyInjectTokens.TextDocumentInxdexObserverProxyPairFactory,
1782
+ },
1783
+ ...defaultPropertyObserverProxyPairFactoryList,
1784
+ ],
1785
+ );
1704
1786
  });
1705
1787
 
1706
1788
  InjectionContainer.load(MyModule);
1707
1789
 
1708
- function testMonitorTextDocument(stateManager: IStateManager, stateContext: { myBook: TextDocument }): void {
1709
- const bookSubscription = stateManager.changed.subscribe(() => {
1710
- console.log(stateContext.myBook.toString());
1711
- });
1790
+ function testMonitorTextDocument(
1791
+ stateManager: IStateManager,
1792
+ model: { myBook: TextDocument },
1793
+ ): void {
1794
+ const bookSubscription = stateManager.changed.subscribe(() => {
1795
+ console.log(model.myBook.toString());
1796
+ });
1712
1797
 
1713
- // We observe the whole book
1714
- // This will use TextDocumentObserverProxyPairFactory
1715
- try {
1716
- console.log('\n***********************************************');
1717
- console.log("Start watching the whole book\n");
1718
- console.log('My initial book:\n');
1719
- stateManager.watchState(stateContext, 'myBook', truePredicate);
1720
-
1721
- console.log('\nUpdate second line on the first page:\n');
1722
- console.log('My book after change:\n');
1723
- stateContext.myBook.setLine({ pageIndex: 0, lineIndex: 1 }, 'In a far far away land');
1724
-
1725
- } finally {
1726
- // Stop monitoring the whole book
1727
- stateManager.releaseState(stateContext, 'myBook', truePredicate);
1728
- bookSubscription.unsubscribe();
1729
- }
1798
+ // We observe the whole book
1799
+ // This will use TextDocumentObserverProxyPairFactory
1800
+ try {
1801
+ console.log('\n***********************************************');
1802
+ console.log('Start watching the whole book\n');
1803
+ console.log('My initial book:\n');
1804
+ stateManager.watchState(model, 'myBook', watchIndexRecursiveRule);
1805
+
1806
+ console.log('\nUpdate second line on the first page:\n');
1807
+ console.log('My book after change:\n');
1808
+ model.myBook.setLine(
1809
+ { pageIndex: 0, lineIndex: 1 },
1810
+ 'In a far far away land',
1811
+ );
1812
+ } finally {
1813
+ // Stop monitoring the whole book
1814
+ stateManager.releaseState(model, 'myBook', watchIndexRecursiveRule);
1815
+ bookSubscription.unsubscribe();
1816
+ }
1730
1817
  }
1731
1818
 
1732
- function testMonitoreSpecificLineInDocument(stateManager: IStateManager, stateContext: { myBook: TextDocument }): void {
1733
- const line3OnPage1Index = { pageIndex: 0, lineIndex: 2 };
1734
- const lineSubscription = stateManager.changed.subscribe((change: IStateChange) => {
1735
- const documentIndex = change.key as ITextDocumentIndex;
1736
- console.log(`Line ${documentIndex.lineIndex + 1} on page ${documentIndex.pageIndex + 1} has changed to '${change.newValue}'`);
1737
- console.log('My book after change:\n');
1738
- console.log(stateContext.myBook.toString());
1739
- });
1740
-
1741
- try {
1742
- // Here we only watch line 3 on page 1.
1743
- // Notice that the line does not have to exist yet.
1744
- // The initial book does not have a line 3 on page 1.
1745
- //
1746
- // TextDocumentInxdexObserverProxyPairFactory is used here
1819
+ function testMonitoreSpecificLineInDocument(
1820
+ stateManager: IStateManager,
1821
+ model: { myBook: TextDocument },
1822
+ ): void {
1823
+ const line3OnPage1Index = { pageIndex: 0, lineIndex: 2 };
1824
+ const lineSubscription = stateManager.changed.subscribe(
1825
+ (change: IStateChange) => {
1826
+ const documentIndex = change.index as ITextDocumentIndex;
1827
+ console.log(
1828
+ `Line ${documentIndex.lineIndex + 1} on page ${documentIndex.pageIndex + 1} has changed to '${change.newValue}'`,
1829
+ );
1830
+ console.log('My book after change:\n');
1831
+ console.log(model.myBook.toString());
1832
+ },
1833
+ );
1747
1834
 
1748
- console.log('\n***********************************************');
1749
- console.log("Start watching line 3 on page 1\n");
1750
- stateManager.watchState(stateContext.myBook, line3OnPage1Index);
1835
+ try {
1836
+ // Here we only watch line 3 on page 1.
1837
+ // Notice that the line does not have to exist yet.
1838
+ // The initial book does not have a line 3 on page 1.
1839
+ //
1840
+ // TextDocumentInxdexObserverProxyPairFactory is used here
1751
1841
 
1752
- const proxRegistry: IProxyRegistry = InjectionContainer.get(RsXStateManagerInjectionTokens.IProxyRegistry);
1753
- const bookProxy: TextDocument = proxRegistry.getProxy(stateContext.myBook);
1842
+ console.log('\n***********************************************');
1843
+ console.log('Start watching line 3 on page 1\n');
1844
+ stateManager.watchState(model.myBook, line3OnPage1Index);
1754
1845
 
1755
- bookProxy.setLine(line3OnPage1Index, 'a prince was born');
1846
+ const proxRegistry: IProxyRegistry = InjectionContainer.get(
1847
+ RsXStateManagerInjectionTokens.IProxyRegistry,
1848
+ );
1849
+ const bookProxy: TextDocument = proxRegistry.getProxy(model.myBook);
1756
1850
 
1757
- console.log('\nChanging line 1 on page 1 does not emit change:');
1758
- console.log('---');
1759
- bookProxy.setLine({ pageIndex: 0, lineIndex: 0 }, 'a troll was born');
1851
+ bookProxy.setLine(line3OnPage1Index, 'a prince was born');
1760
1852
 
1761
- } finally {
1762
- // Stop monitoring line 3 on page 1.
1763
- stateManager.releaseState(stateContext.myBook, line3OnPage1Index);
1764
- lineSubscription.unsubscribe();
1765
- }
1853
+ console.log('\nChanging line 1 on page 1 does not emit change:');
1854
+ console.log('---');
1855
+ bookProxy.setLine({ pageIndex: 0, lineIndex: 0 }, 'a troll was born');
1856
+ } finally {
1857
+ // Stop monitoring line 3 on page 1.
1858
+ stateManager.releaseState(model.myBook, line3OnPage1Index);
1859
+ lineSubscription.unsubscribe();
1860
+ }
1766
1861
  }
1767
1862
 
1768
1863
  export const run = (() => {
1769
- const stateManager: IStateManager = InjectionContainer.get(
1770
- RsXStateManagerInjectionTokens.IStateManager
1771
- );
1772
- const stateContext = {
1773
- myBook: new TextDocument([
1774
- [
1775
- 'Once upon a time',
1776
- 'bla bla'
1777
- ],
1778
- [
1779
- 'bla bla',
1780
- 'They lived happily ever after.',
1781
- 'The end'
1782
- ]
1783
- ])
1784
- };
1785
- testMonitorTextDocument(stateManager, stateContext);
1786
- testMonitoreSpecificLineInDocument(stateManager, stateContext);
1864
+ const stateManager: IStateManager = InjectionContainer.get(
1865
+ RsXStateManagerInjectionTokens.IStateManager,
1866
+ );
1867
+ const model = {
1868
+ myBook: new TextDocument([
1869
+ ['Once upon a time', 'bla bla'],
1870
+ ['bla bla', 'They lived happily ever after.', 'The end'],
1871
+ ]),
1872
+ };
1873
+ testMonitorTextDocument(stateManager, model);
1874
+ testMonitoreSpecificLineInDocument(stateManager, model);
1787
1875
  })();
1876
+
1788
1877
  ```nclude_relative ../demo/src/rs-x-state-manager/register-set.ts %}
1789
1878
  ````
1790
1879