@rs-x/state-manager 0.4.11 → 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 (4) hide show
  1. package/dist/index.d.ts +52 -47
  2. package/dist/index.js +323 -300
  3. package/package.json +2 -2
  4. package/readme.md +1125 -960
package/readme.md CHANGED
@@ -7,9 +7,9 @@ A context can be an object, and an index can be a property name — but it is no
7
7
 
8
8
  Examples of state indexes:
9
9
 
10
- - Object property or field → index = property or field name
11
- - Array item → index = numeric position
12
- - Map item → index = map key
10
+ - Object property or field → index = property or field name
11
+ - Array item → index = numeric position
12
+ - Map item → index = map key
13
13
 
14
14
  The State Manager does **not** automatically know how to detect changes for every state value data type, nor how to patch the corresponding state setter. Therefore, it relies on two services:
15
15
 
@@ -19,45 +19,38 @@ The State Manager does **not** automatically know how to detect changes for ever
19
19
  - A service implementing `IIndexValueAccessor`
20
20
  Responsible for retrieving the current value.
21
21
 
22
-
23
22
  The **State Manager** has the followng interface:
23
+
24
24
  ```ts
25
25
  export interface IStateManager {
26
- readonly changed: Observable<IStateChange>;
27
- readonly contextChanged: Observable<IContextChanged>;
28
- readonly startChangeCycle: Observable<void>;
29
- readonly endChangeCycle: Observable<void>;
30
-
31
- isWatched(
32
- context: unknown,
33
- index: unknown,
34
- mustProxify: MustProxify
35
- ): boolean;
36
-
37
- watchState(
38
- context: unknown,
39
- index: unknown,
40
- mustProxify?: MustProxify
41
- ): unknown;
42
-
43
- releaseState(
44
- oontext: unknown,
45
- index: unknown,
46
- mustProxify?: MustProxify
47
- ): void;
48
-
49
- getState<T>(
50
- context: unknown,
51
- index: unknown
52
- ): T;
53
-
54
- setState<T>(
55
- context: unknown,
56
- index: unknown,
57
- value: T
58
- ): void;
59
-
60
- clear(): void;
26
+ readonly changed: Observable<IStateChange>;
27
+ readonly contextChanged: Observable<IContextChanged>;
28
+ readonly startChangeCycle: Observable<void>;
29
+ readonly endChangeCycle: Observable<void>;
30
+
31
+ isWatched(
32
+ context: unknown,
33
+ index: unknown,
34
+ mustProxify: MustProxify,
35
+ ): boolean;
36
+
37
+ watchState(
38
+ context: unknown,
39
+ index: unknown,
40
+ mustProxify?: MustProxify,
41
+ ): unknown;
42
+
43
+ releaseState(
44
+ oontext: unknown,
45
+ index: unknown,
46
+ mustProxify?: MustProxify,
47
+ ): void;
48
+
49
+ getState<T>(context: unknown, index: unknown): T;
50
+
51
+ setState<T>(context: unknown, index: unknown, value: T): void;
52
+
53
+ clear(): void;
61
54
  }
62
55
  ```
63
56
 
@@ -66,12 +59,14 @@ export interface IStateManager {
66
59
  ## Members
67
60
 
68
61
  ### **changed**
62
+
69
63
  **Type:** `Observable<IStateChange>`
70
64
  Emits whenever a state item value changes.
71
65
 
72
66
  ---
73
67
 
74
68
  ### **contextChanged**
69
+
75
70
  **Type:** `Observable<IContextChanged>`
76
71
  Emits whenever an entire context is replaced.
77
72
  This happens, for example, when a nested object is replaced.
@@ -79,38 +74,42 @@ This happens, for example, when a nested object is replaced.
79
74
  ---
80
75
 
81
76
  ### **startChangeCycle**
77
+
82
78
  **Type:** `Observable<void>`
83
79
  Emits at the start of processing a state item change.
84
80
 
85
81
  ---
86
82
 
87
83
  ### **endChangeCycle**
84
+
88
85
  **Type:** `Observable<void>`
89
86
  Emits at the end of processing a state item change.
90
87
 
91
88
  ---
92
89
 
93
90
  ### **isWatched(context, index, mustProxify)**
91
+
94
92
  Returns whether the state item identified by the `(context, index, mustProxify)` triplet is currently being watched.
95
93
 
96
- | Parameter | Type | Description |
97
- | --------------- | -------------------------- | --------------------------------------------------------------- |
98
- | **context** | `unknown` | The context to which the state index belongs. |
99
- | **index** | `unknown` | The index identifying the state on the given context. |
100
- | **mustProxify** | `MustProxify` *(optional)* | Predicate determining whether a nested state value must be proxified. It should be the same predicate that was passed to `watchState`. |
94
+ | Parameter | Type | Description |
95
+ | --------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | **context** | `unknown` | The context to which the state index belongs. |
97
+ | **index** | `unknown` | The index identifying the state on the given context. |
98
+ | **mustProxify** | `MustProxify` _(optional)_ | Predicate determining whether a nested state value must be proxified. It should be the same predicate that was passed to `watchState`. |
101
99
 
102
100
  **Returns:** `boolean`
103
101
 
104
102
  ---
105
103
 
106
104
  ### **watchState(context, index, mustProxify?)**
105
+
107
106
  Watches a state item identified by the `(context, index, mustProxify)` triplet so that its value is proxified and tracked.
108
107
 
109
- | Parameter | Type | Description |
110
- | --------------- | -------------------------- | --------------------------------------------------------------- |
111
- | **context** | `unknown` | The state context. |
112
- | **index** | `unknown` | The index identifying the state on the given context. |
113
- | **mustProxify** | `MustProxify` *(optional)* | Predicate determining whether a nested state value must be proxified.|
108
+ | Parameter | Type | Description |
109
+ | --------------- | -------------------------- | --------------------------------------------------------------------- |
110
+ | **context** | `unknown` | The state context. |
111
+ | **index** | `unknown` | The index identifying the state on the given context. |
112
+ | **mustProxify** | `MustProxify` _(optional)_ | Predicate determining whether a nested state value must be proxified. |
114
113
 
115
114
  **Returns:**
116
115
  `unknown` — the state item value if it was already being watched; otherwise `undefined`.
@@ -118,46 +117,50 @@ Watches a state item identified by the `(context, index, mustProxify)` triplet s
118
117
  ---
119
118
 
120
119
  ### **releaseState(context, index, mustProxify?)**
120
+
121
121
  Releases the state item identified by the `(context, index, mustProxify)` triplet.
122
122
  Each call to `watchState` should have a corresponding `releaseState` call to ensure the state item is released when it is no longer needed.
123
123
 
124
- | Parameter | Type | Description |
125
- | --------------- | -------------------------- | --------------------------------------------------------------- |
126
- | **context** | `unknown` | The state context. |
127
- | **index** | `unknown` | The index identifying the state on the given context. |
128
- | **mustProxify** | `MustProxify` *(optional)* | Predicate determining whether a nested state value must be proxified. It should be the same predicate that was passed to `watchState`. . |
124
+ | Parameter | Type | Description |
125
+ | --------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | **context** | `unknown` | The state context. |
127
+ | **index** | `unknown` | The index identifying the state on the given context. |
128
+ | **mustProxify** | `MustProxify` _(optional)_ | Predicate determining whether a nested state value must be proxified. It should be the same predicate that was passed to `watchState`. . |
129
129
 
130
130
  **Returns:** `void`
131
131
 
132
132
  ---
133
133
 
134
134
  ### **getState(context, index)**
135
+
135
136
  Retrieves the current state item value identified by the `(context, index)` pair.
136
137
 
137
- | Parameter | Type | Description |
138
- | ----------- | --------- | --------------------------------------------------------------- |
139
- | **context** | `unknown` | The state context. |
140
- | **index** | `unknown` | The index identifying the state on the given context. |
138
+ | Parameter | Type | Description |
139
+ | ----------- | --------- | ----------------------------------------------------- |
140
+ | **context** | `unknown` | The state context. |
141
+ | **index** | `unknown` | The index identifying the state on the given context. |
141
142
 
142
143
  **Returns:** `unknown`
143
144
 
144
145
  ---
145
146
 
146
147
  ### **setState(context, index, value)**
148
+
147
149
  Sets the value of the state item identified by the `(context, index)` pair.
148
150
 
149
151
  Unlike `watchState`, `setState` does **not** track changes. It does not patch setters or proxify values.
150
152
  A change event is emitted on the first `setState` call and again whenever the value changes in subsequent calls.
151
153
 
152
- | Parameter | Type | Description |
153
- | ----------- | --------- | --------------------------------------------------------------- |
154
- | **context** | `unknown` | The state context. |
155
- | **index** | `unknown` | The index identifying the state on the given context. |
156
- | **value** | `unknown` | The state value. |
154
+ | Parameter | Type | Description |
155
+ | ----------- | --------- | ----------------------------------------------------- |
156
+ | **context** | `unknown` | The state context. |
157
+ | **index** | `unknown` | The index identifying the state on the given context. |
158
+ | **value** | `unknown` | The state value. |
157
159
 
158
160
  ---
159
161
 
160
162
  ### **clear()**
163
+
161
164
  Releases all registered state items.
162
165
 
163
166
  **Returns:** `void`
@@ -183,10 +186,13 @@ There are two ways to get an instance:
183
186
 
184
187
  ```ts
185
188
  import { InjectionContainer } from '@rs-x/core';
186
- import { IIStateManager, RsXStateManagerInjectionTokens } from '@rs-x/state-manager';
189
+ import {
190
+ IIStateManager,
191
+ RsXStateManagerInjectionTokens,
192
+ } from '@rs-x/state-manager';
187
193
 
188
194
  const stateManager: IIStateManager = InjectionContainer.get(
189
- RsXStateManagerInjectionTokens.IStateManager
195
+ RsXStateManagerInjectionTokens.IStateManager,
190
196
  );
191
197
  ```
192
198
 
@@ -194,14 +200,16 @@ const stateManager: IIStateManager = InjectionContainer.get(
194
200
 
195
201
  ```ts
196
202
  import { Inject } from '@rs-x/core';
197
- import { IIStateManager, RsXStateManagerInjectionTokens } from '@rs-x/state-manager';
203
+ import {
204
+ IIStateManager,
205
+ RsXStateManagerInjectionTokens,
206
+ } from '@rs-x/state-manager';
198
207
 
199
208
  export class MyClass {
200
-
201
- constructor(
202
- @Inject(RsXStateManagerInjectionTokens.IStateManager)
203
- private readonly _stateManager: IIStateManager
204
- ) {}
209
+ constructor(
210
+ @Inject(RsXStateManagerInjectionTokens.IStateManager)
211
+ private readonly _stateManager: IIStateManager,
212
+ ) {}
205
213
  }
206
214
  ```
207
215
 
@@ -211,60 +219,61 @@ export class MyClass {
211
219
 
212
220
  There are two variants:
213
221
 
214
- ### Non-recursive
222
+ ### Non-recursive
223
+
215
224
  Monitors only assignment of a **new value** to the index.
216
225
 
217
226
  ```ts
218
227
  import { InjectionContainer, printValue } from '@rs-x/core';
219
228
  import {
220
- IStateChange,
221
- IStateManager,
222
- RsXStateManagerInjectionTokens,
223
- RsXStateManagerModule
229
+ type IStateChange,
230
+ type IStateManager,
231
+ RsXStateManagerInjectionTokens,
232
+ RsXStateManagerModule,
224
233
  } from '@rs-x/state-manager';
225
234
 
226
235
  // Load the state manager module into the injection container
227
236
  InjectionContainer.load(RsXStateManagerModule);
228
237
 
229
238
  export const run = (() => {
230
- const stateManager: IStateManager = InjectionContainer.get(
231
- RsXStateManagerInjectionTokens.IStateManager
232
- );
233
-
234
- const stateContext = {
235
- x: { y: 10 }
239
+ const stateManager: IStateManager = InjectionContainer.get(
240
+ RsXStateManagerInjectionTokens.IStateManager,
241
+ );
242
+
243
+ const model = {
244
+ x: { y: 10 },
245
+ };
246
+
247
+ // This will emit a change event with the initial (current) value.
248
+ console.log('Initial value:');
249
+ const changedSubsription = stateManager.changed.subscribe(
250
+ (change: IStateChange) => {
251
+ printValue(change.newValue);
252
+ },
253
+ );
254
+
255
+ try {
256
+ // This will emit the new value { y: 10 }
257
+ stateManager.watchState(model, 'x');
258
+
259
+ console.log('Changed value:');
260
+ // This will emit the new value { y: 10 }
261
+ model.x = {
262
+ y: 20,
236
263
  };
237
264
 
238
- // This will emit a change event with the initial (current) value.
239
- console.log('Initial value:');
240
- const changedSubsription = stateManager.changed.subscribe((change: IStateChange) => {
241
- printValue(change.newValue);
242
- });
243
-
244
- try {
245
- // This will emit the new value { y: 10 }
246
- stateManager.watchState(stateContext, 'x');
247
-
248
- console.log('Changed value:');
249
- // This will emit the new value { y: 10 }
250
- stateContext.x = {
251
- y: 20
252
- };
253
-
254
- console.log(`Latest value:`);
255
- printValue(stateManager.getState(stateContext, 'x'));
256
-
257
- // This will emit no change because the state is not recursive.
258
- console.log('\nstateContext.x.y = 30 will not emit any change:\n---\n');
259
- stateContext.x.y = 30;
260
-
261
- } finally {
262
- changedSubsription.unsubscribe();
263
- // Always release the state when it is no longer needed.
264
- stateManager.releaseState(stateContext, 'x');
265
- }
265
+ console.log(`Latest value:`);
266
+ printValue(stateManager.getState(model, 'x'));
267
+
268
+ // This will emit no change because the state is not recursive.
269
+ console.log('\nmodel.x.y = 30 will not emit any change:\n---\n');
270
+ model.x.y = 30;
271
+ } finally {
272
+ changedSubsription.unsubscribe();
273
+ // Always release the state when it is no longer needed.
274
+ stateManager.releaseState(model, 'x');
275
+ }
266
276
  })();
267
-
268
277
  ```
269
278
 
270
279
  **Output:**
@@ -290,61 +299,64 @@ stateContext.x.y = 30 will not emit any change:
290
299
 
291
300
  ---
292
301
 
293
- ### Recursive
294
- Monitors assignments **and** changes *inside* the value.
302
+ ### Recursive
303
+
304
+ Monitors assignments **and** changes _inside_ the value.
295
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.
296
306
 
297
307
  ```ts
298
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
308
+ import { InjectionContainer, printValue } from '@rs-x/core';
299
309
  import {
300
- IStateChange,
301
- IStateManager,
302
- RsXStateManagerInjectionTokens,
303
- RsXStateManagerModule
310
+ type IStateChange,
311
+ type IStateManager,
312
+ RsXStateManagerInjectionTokens,
313
+ RsXStateManagerModule,
314
+ watchIndexRecursiveRule,
304
315
  } from '@rs-x/state-manager';
305
316
 
306
317
  // Load the state manager module into the injection container
307
318
  InjectionContainer.load(RsXStateManagerModule);
308
319
 
309
320
  export const run = (() => {
310
- const stateManager: IStateManager = InjectionContainer.get(
311
- RsXStateManagerInjectionTokens.IStateManager
312
- );
313
- const stateContext = {
314
- x: { y: 10 }
321
+ const stateManager: IStateManager = InjectionContainer.get(
322
+ RsXStateManagerInjectionTokens.IStateManager,
323
+ );
324
+ const model = {
325
+ x: { y: 10 },
326
+ };
327
+ const changedSubscription = stateManager.changed.subscribe(
328
+ (change: IStateChange) => {
329
+ printValue(change.newValue);
330
+ },
331
+ );
332
+
333
+ try {
334
+ // We register recursive state by passing
335
+ // a predicate as the third argument.
336
+ // In this case, we want to watch the entire value,
337
+ // so we pass a predicate that always returns true.
338
+ // This will emit an initial value { y: 10 }
339
+ console.log('Initial value:');
340
+ stateManager.watchState(model, 'x', watchIndexRecursiveRule);
341
+
342
+ console.log('Changed value:');
343
+ // This will emit the new value { y: 10 }
344
+ model.x = {
345
+ y: 20,
315
346
  };
316
- const changedSubscription = stateManager.changed.subscribe((change: IStateChange) => {
317
- printValue(change.newValue)
318
- });
319
347
 
320
- try {
321
- // We register recursive state by passing
322
- // a predicate as the third argument.
323
- // In this case, we want to watch the entire value,
324
- // so we pass a predicate that always returns true.
325
- // This will emit an initial value { y: 10 }
326
- console.log('Initial value:');
327
- stateManager.watchState(stateContext, 'x', truePredicate);
328
-
329
- console.log('Changed value:');
330
- // This will emit the new value { y: 10 }
331
- stateContext.x = {
332
- y: 20
333
- };
334
-
335
- console.log('Changed (recursive) value:');
336
- // This will emit the new value { y: 30 } because x
337
- // is registered as a recursive state.
338
- stateContext.x.y = 30;
339
-
340
- console.log(`Latest value:`);
341
- printValue(stateManager.getState(stateContext, 'x'));
342
-
343
- } finally {
344
- changedSubscription.unsubscribe();
345
- // Always release the state when it is no longer needed.
346
- stateManager.releaseState(stateContext, 'x', truePredicate);
347
- }
348
+ console.log('Changed (recursive) value:');
349
+ // This will emit the new value { y: 30 } because x
350
+ // is registered as a recursive state.
351
+ model.x.y = 30;
352
+
353
+ console.log(`Latest value:`);
354
+ printValue(stateManager.getState(model, 'x'));
355
+ } finally {
356
+ changedSubscription.unsubscribe();
357
+ // Always release the state when it is no longer needed.
358
+ stateManager.releaseState(model, 'x', watchIndexRecursiveRule);
359
+ }
348
360
  })();
349
361
  ```
350
362
 
@@ -370,82 +382,87 @@ Besides that you can register a watched stated (calling `watchedState`) you can
370
382
  ```ts
371
383
  import { InjectionContainer, printValue } from '@rs-x/core';
372
384
  import {
373
- IStateChange,
374
- IStateManager,
375
- RsXStateManagerInjectionTokens,
376
- RsXStateManagerModule
385
+ type IStateChange,
386
+ type IStateManager,
387
+ RsXStateManagerInjectionTokens,
388
+ RsXStateManagerModule,
377
389
  } from '@rs-x/state-manager';
378
390
 
379
391
  // Load the state manager module into the injection container
380
392
  InjectionContainer.load(RsXStateManagerModule);
381
393
 
382
394
  export const run = (() => {
383
- const stateManager: IStateManager = InjectionContainer.get(
384
- RsXStateManagerInjectionTokens.IStateManager
385
- );
395
+ const stateManager: IStateManager = InjectionContainer.get(
396
+ RsXStateManagerInjectionTokens.IStateManager,
397
+ );
386
398
 
387
- class StateContext {
388
- private readonly _aPlusBId = 'a+b';
389
- private _a = 10;
390
- private _b = 20;
399
+ class MyModel {
400
+ private readonly _aPlusBId = 'a+b';
401
+ private _a = 10;
402
+ private _b = 20;
391
403
 
392
- constructor() {
393
- this.setAPlusB();
394
- }
395
-
396
- public dispose(): void {
397
- return stateManager.releaseState(this, this._aPlusBId);
398
- }
404
+ constructor() {
405
+ this.setAPlusB();
406
+ }
399
407
 
400
- public get aPlusB(): number {
401
- return stateManager.getState(this, this._aPlusBId);
402
- }
408
+ public dispose(): void {
409
+ return stateManager.releaseState(this, this._aPlusBId);
410
+ }
403
411
 
404
- public get a(): number {
405
- return this._a;
406
- }
412
+ public get aPlusB(): number {
413
+ return stateManager.getState(this, this._aPlusBId);
414
+ }
407
415
 
408
- public set a(value: number) {
409
- this._a = value;
410
- this.setAPlusB();
411
- }
416
+ public get a(): number {
417
+ return this._a;
418
+ }
412
419
 
413
- public get b(): number {
414
- return this._b;
415
- }
420
+ public set a(value: number) {
421
+ this._a = value;
422
+ this.setAPlusB();
423
+ }
416
424
 
417
- public set b(value: number) {
418
- this._b = value;
419
- this.setAPlusB();
420
- }
425
+ public get b(): number {
426
+ return this._b;
427
+ }
421
428
 
422
- private setAPlusB(): void {
423
- stateManager.setState(this, this._aPlusBId, this._a + this._b)
424
- }
429
+ public set b(value: number) {
430
+ this._b = value;
431
+ this.setAPlusB();
425
432
  }
426
433
 
427
- const stateContext = new StateContext();
428
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
429
- printValue(change.newValue);
430
- });
434
+ private setAPlusB(): void {
435
+ stateManager.setState(this, this._aPlusBId, this._a + this._b);
436
+ }
437
+ }
431
438
 
432
- try {
433
- console.log(`Initial value for readonly property 'aPlusB':`);
434
- console.log(stateContext.aPlusB);
439
+ const model = new MyModel();
440
+ const changeSubscription = stateManager.changed.subscribe(
441
+ (change: IStateChange) => {
442
+ printValue(change.newValue);
443
+ },
444
+ );
435
445
 
436
- console.log(`set 'stateContext.a' to '100' will emit a change event for readonly property 'aPlusB'`);
437
- console.log(`Changed value for readonly property 'aPlusB':`);
438
- stateContext.a = 100;
446
+ try {
447
+ console.log(`Initial value for readonly property 'aPlusB':`);
448
+ console.log(model.aPlusB);
439
449
 
440
- console.log(`set 'stateContext.b' to '200' will emit a change event for readonly property 'aPlusB'`);
441
- console.log(`Changed value for readonly property 'aPlusB':`);
442
- stateContext.b = 200;
450
+ console.log(
451
+ `set 'model.a' to '100' will emit a change event for readonly property 'aPlusB'`,
452
+ );
453
+ console.log(`Changed value for readonly property 'aPlusB':`);
454
+ model.a = 100;
443
455
 
444
- } finally {
445
- changeSubscription.unsubscribe();
446
- // Always release the state when it is no longer needed.
447
- stateContext.dispose();
448
- }
456
+ console.log(
457
+ `set 'model.b' to '200' will emit a change event for readonly property 'aPlusB'`,
458
+ );
459
+ console.log(`Changed value for readonly property 'aPlusB':`);
460
+ model.b = 200;
461
+ } finally {
462
+ changeSubscription.unsubscribe();
463
+ // Always release the state when it is no longer needed.
464
+ model.dispose();
465
+ }
449
466
  })();
450
467
  ```
451
468
 
@@ -476,52 +493,57 @@ When done, release the state:
476
493
  ```ts
477
494
  import { InjectionContainer, printValue } from '@rs-x/core';
478
495
  import {
479
- IStateChange,
480
- IStateManager,
481
- RsXStateManagerInjectionTokens,
482
- RsXStateManagerModule
496
+ type IStateChange,
497
+ type IStateManager,
498
+ RsXStateManagerInjectionTokens,
499
+ RsXStateManagerModule,
483
500
  } from '@rs-x/state-manager';
484
501
 
485
502
  // Load the state manager module into the injection container
486
503
  InjectionContainer.load(RsXStateManagerModule);
487
504
 
488
505
  export const run = (() => {
489
- const stateManager: IStateManager = InjectionContainer.get(
490
- RsXStateManagerInjectionTokens.IStateManager
491
- );
492
- const stateContext = {
493
- x: { y: 10 }
494
- };
495
- const changedSubscription = stateManager.changed.subscribe((change: IStateChange) => {
496
- printValue(change.newValue);
497
- });
498
-
499
- try {
500
- // Register is idempotent: you can register the same state multiple times.
501
- // For every register call, make sure you call unregister when you're done.
502
- console.log('Initial value:');
503
- stateManager.watchState(stateContext, 'x');
504
- stateManager.watchState(stateContext, 'x');
505
-
506
- console.log('Changed value:');
507
- stateContext.x = { y: 20 };
506
+ const stateManager: IStateManager = InjectionContainer.get(
507
+ RsXStateManagerInjectionTokens.IStateManager,
508
+ );
509
+ const model = {
510
+ x: { y: 10 },
511
+ };
512
+ const changedSubscription = stateManager.changed.subscribe(
513
+ (change: IStateChange) => {
514
+ printValue(change.newValue);
515
+ },
516
+ );
517
+
518
+ try {
519
+ // Register is idempotent: you can register the same state multiple times.
520
+ // For every register call, make sure you call unregister when you're done.
521
+ console.log('Initial value:');
522
+ stateManager.watchState(model, 'x');
523
+ stateManager.watchState(model, 'x');
508
524
 
509
- stateManager.releaseState(stateContext, 'x');
525
+ console.log('Changed value:');
526
+ model.x = { y: 20 };
510
527
 
511
- console.log('Changed event is still emitted after unregister because one observer remains.');
512
- console.log('Changed value:');
513
- stateContext.x = { y: 30 };
528
+ stateManager.releaseState(model, 'x');
514
529
 
515
- stateManager.releaseState(stateContext, 'x');
530
+ console.log(
531
+ 'Changed event is still emitted after unregister because one observer remains.',
532
+ );
533
+ console.log('Changed value:');
534
+ model.x = { y: 30 };
516
535
 
517
- console.log('Changed event is no longer emitted after the last observer unregisters.');
518
- console.log('Changed value:');
519
- console.log('---');
520
- stateContext.x = { y: 30 };
536
+ stateManager.releaseState(model, 'x');
521
537
 
522
- } finally {
523
- changedSubscription.unsubscribe();
524
- }
538
+ console.log(
539
+ 'Changed event is no longer emitted after the last observer unregisters.',
540
+ );
541
+ console.log('Changed value:');
542
+ console.log('---');
543
+ model.x = { y: 30 };
544
+ } finally {
545
+ changedSubscription.unsubscribe();
546
+ }
525
547
  })();
526
548
  ```
527
549
 
@@ -560,7 +582,7 @@ You can override this factory list by providing your own custom provider service
560
582
  | Observable | Not indexable | Subscribe | [example](#observable) |
561
583
  | Custom type | user defined | user defined | [example](#customtype) |
562
584
 
563
- State item is identified by a **context**, **index** and **mustProxify** predicate for a recursive state item
585
+ State item is identified by a **context**, **index** and **mustProxify** predicate for a recursive state item
564
586
  The manager checks each observer factory to determine support based on the **context** and **index**.
565
587
 
566
588
  Behavior:
@@ -571,86 +593,92 @@ Behavior:
571
593
  The following example illustrates the different state types:
572
594
 
573
595
  ### Object property/field
596
+
574
597
  **Example**
598
+
575
599
  ```ts
576
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
600
+ import { InjectionContainer, printValue, Type } from '@rs-x/core';
577
601
  import {
578
- IStateChange,
579
- IStateManager,
580
- RsXStateManagerInjectionTokens,
581
- RsXStateManagerModule
602
+ type IStateChange,
603
+ type IStateManager,
604
+ RsXStateManagerInjectionTokens,
605
+ RsXStateManagerModule,
606
+ watchIndexRecursiveRule,
582
607
  } from '@rs-x/state-manager';
583
608
 
584
609
  // Load the state manager module into the injection container
585
610
  InjectionContainer.load(RsXStateManagerModule);
586
611
 
587
612
  interface INestStateConext {
588
- a: number;
589
- nested?: INestStateConext;
613
+ a: number;
614
+ nested?: INestStateConext;
590
615
  }
591
616
 
592
- class StateContext {
593
- private _b: INestStateConext = {
594
- a: 10,
617
+ class MyModel {
618
+ private _b: INestStateConext = {
619
+ a: 10,
620
+ nested: {
621
+ a: 20,
622
+ nested: {
623
+ a: 30,
595
624
  nested: {
596
- a: 20,
597
- nested: {
598
- a: 30,
599
- nested: {
600
- a: 40
601
- }
602
- }
603
- }
604
- };
605
-
606
- public get b(): INestStateConext {
607
- return this._b;
608
- }
609
-
610
- public set b(value: INestStateConext) {
611
- this._b = value;
612
- }
625
+ a: 40,
626
+ },
627
+ },
628
+ },
629
+ };
630
+
631
+ public get b(): INestStateConext {
632
+ return this._b;
633
+ }
634
+
635
+ public set b(value: INestStateConext) {
636
+ this._b = value;
637
+ }
613
638
  }
614
639
 
615
640
  export const run = (() => {
616
- const stateManager: IStateManager = InjectionContainer.get(
617
- RsXStateManagerInjectionTokens.IStateManager
618
- );
641
+ const stateManager: IStateManager = InjectionContainer.get(
642
+ RsXStateManagerInjectionTokens.IStateManager,
643
+ );
619
644
 
620
- const stateContext = new StateContext();
645
+ const model = new MyModel();
621
646
 
622
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
623
- printValue(change.newValue);
624
- });
647
+ const changeSubscription = stateManager.changed.subscribe(
648
+ (change: IStateChange) => {
649
+ printValue(change.newValue);
650
+ },
651
+ );
625
652
 
626
- try {
627
- // Observe property `b` recursively.
628
- // Otherwise, only assigning a new value to stateContext.b would emit a change event.
629
- // This will emit a change event with the initial (current) value.
630
- console.log('Initial value:');
631
- stateManager.watchState(stateContext, 'b', truePredicate);
653
+ try {
654
+ // Observe property `b` recursively.
655
+ // Otherwise, only assigning a new value to model.b would emit a change event.
656
+ // This will emit a change event with the initial (current) value.
657
+ console.log('Initial value:');
658
+ stateManager.watchState(model, 'b', watchIndexRecursiveRule);
632
659
 
633
- console.log('\nReplacing stateContext.b.nested.nested will emit a change event');
634
- console.log('Changed value:');
660
+ console.log('\nReplacing model.b.nested.nested will emit a change event');
661
+ console.log('Changed value:');
635
662
 
636
- stateContext.b.nested.nested = {
637
- a: -30,
638
- nested: {
639
- a: -40
640
- }
641
- };
642
-
643
- console.log(`Latest value:`);
644
- printValue(stateManager.getState(stateContext, 'b'));
663
+ (Type.toObject(model.b.nested) ?? {}).nested = {
664
+ a: -30,
665
+ nested: {
666
+ a: -40,
667
+ },
668
+ };
645
669
 
646
- } finally {
647
- changeSubscription.unsubscribe();
648
- // Always release the state when it is no longer needed.
649
- stateManager.releaseState(stateContext, 'b', truePredicate);
650
- }
670
+ console.log(`Latest value:`);
671
+ printValue(stateManager.getState(model, 'b'));
672
+ } finally {
673
+ changeSubscription.unsubscribe();
674
+ // Always release the state when it is no longer needed.
675
+ stateManager.releaseState(model, 'b', watchIndexRecursiveRule);
676
+ }
651
677
  })();
652
678
  ```
679
+
653
680
  **Output:**
681
+
654
682
  ```console
655
683
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-property.ts
656
684
  Initial value:
@@ -697,86 +725,98 @@ Latest value:
697
725
  ```
698
726
 
699
727
  ### Date
728
+
700
729
  **Example**
730
+
701
731
  ```ts
702
- import { InjectionContainer, truePredicate, utCDate } from '@rs-x/core';
732
+ import { InjectionContainer, utCDate } from '@rs-x/core';
703
733
  import {
704
- IProxyRegistry,
705
- IStateChange,
706
- IStateManager,
707
- RsXStateManagerInjectionTokens,
708
- RsXStateManagerModule
734
+ type IProxyRegistry,
735
+ type IStateChange,
736
+ type IStateManager,
737
+ RsXStateManagerInjectionTokens,
738
+ RsXStateManagerModule,
739
+ watchIndexRecursiveRule,
709
740
  } from '@rs-x/state-manager';
710
741
 
711
742
  // Load the state manager module into the injection container
712
743
  InjectionContainer.load(RsXStateManagerModule);
713
744
 
714
745
  function watchDate(stateManager: IStateManager) {
715
- console.log('\n******************************************');
716
- console.log('* Watching date');
717
- console.log('******************************************\n');
746
+ console.log('\n******************************************');
747
+ console.log('* Watching date');
748
+ console.log('******************************************\n');
749
+
750
+ const model = {
751
+ date: utCDate(2021, 2, 5),
752
+ };
753
+ const changeSubscription = stateManager.changed.subscribe(
754
+ (change: IStateChange) => {
755
+ console.log(
756
+ `${change.index}: ${(change.newValue as Date).toUTCString()}`,
757
+ );
758
+ },
759
+ );
760
+ try {
761
+ console.log('Initial value:');
762
+ stateManager.watchState(model, 'date', watchIndexRecursiveRule);
718
763
 
719
- const stateContext = {
720
- date: utCDate(2021, 2, 5)
721
- };
722
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
723
- console.log(`${change.key}: ${(change.newValue as Date).toUTCString()}`);
724
- });
725
- try {
726
- console.log('Initial value:');
727
- stateManager.watchState(stateContext, 'date', truePredicate);
728
-
729
- console.log('Changed value:');
730
- stateContext.date.setFullYear(2023);
731
-
732
- console.log('Set value:');
733
- stateContext.date = new Date(2024, 5, 6);
734
-
735
- console.log('Latest value:');
736
- console.log(stateManager.getState<Date>(stateContext, 'date').toUTCString());
737
- } finally {
738
- changeSubscription.unsubscribe();
739
- // Always release the state when it is no longer needed.
740
- stateManager.releaseState(stateContext, 'date', truePredicate);
741
- }
764
+ console.log('Changed value:');
765
+ model.date.setFullYear(2023);
766
+
767
+ console.log('Set value:');
768
+ model.date = new Date(2024, 5, 6);
769
+
770
+ console.log('Latest value:');
771
+ console.log(stateManager.getState<Date>(model, 'date').toUTCString());
772
+ } finally {
773
+ changeSubscription.unsubscribe();
774
+ // Always release the state when it is no longer needed.
775
+ stateManager.releaseState(model, 'date', watchIndexRecursiveRule);
776
+ }
742
777
  }
743
778
 
744
779
  function watchDateProperty(stateManager: IStateManager) {
745
- console.log('\n******************************************');
746
- console.log('* Watching year');
747
- console.log('******************************************\n');
748
- const date = utCDate(2021, 2, 5);
749
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
750
- console.log(change.newValue);
751
- });
752
- try {
753
- // This will emit a change event with the initial (current) value.
754
- console.log('Initial value:');
755
- stateManager.watchState(date, 'year');
756
-
757
- const proxyRegister: IProxyRegistry = InjectionContainer.get(RsXStateManagerInjectionTokens.IProxyRegistry);
758
- const dateProxy = proxyRegister.getProxy<Date>(date);
759
- console.log('Changed value:');
760
- dateProxy.setFullYear(2023);
761
-
762
- console.log('Latest value:');
763
- console.log(stateManager.getState(date, 'year'));
764
-
765
- } finally {
766
- changeSubscription.unsubscribe();
767
- stateManager.releaseState(date, 'year');
768
- }
780
+ console.log('\n******************************************');
781
+ console.log('* Watching year');
782
+ console.log('******************************************\n');
783
+ const date = utCDate(2021, 2, 5);
784
+ const changeSubscription = stateManager.changed.subscribe(
785
+ (change: IStateChange) => {
786
+ console.log(change.newValue);
787
+ },
788
+ );
789
+ try {
790
+ // This will emit a change event with the initial (current) value.
791
+ console.log('Initial value:');
792
+ stateManager.watchState(date, 'year');
793
+
794
+ const proxyRegister: IProxyRegistry = InjectionContainer.get(
795
+ RsXStateManagerInjectionTokens.IProxyRegistry,
796
+ );
797
+ const dateProxy = proxyRegister.getProxy<Date>(date);
798
+ console.log('Changed value:');
799
+ dateProxy.setFullYear(2023);
800
+
801
+ console.log('Latest value:');
802
+ console.log(stateManager.getState(date, 'year'));
803
+ } finally {
804
+ changeSubscription.unsubscribe();
805
+ stateManager.releaseState(date, 'year');
806
+ }
769
807
  }
770
808
 
771
809
  export const run = (() => {
772
- const stateManager: IStateManager = InjectionContainer.get(
773
- RsXStateManagerInjectionTokens.IStateManager
774
- );
775
- watchDate(stateManager);
776
- watchDateProperty(stateManager);
810
+ const stateManager: IStateManager = InjectionContainer.get(
811
+ RsXStateManagerInjectionTokens.IStateManager,
812
+ );
813
+ watchDate(stateManager);
814
+ watchDateProperty(stateManager);
777
815
  })();
778
816
  ```
817
+
779
818
  **Output:**
819
+
780
820
  ```console
781
821
  Running demo: demo/src/rs-x-state-manager/register-date.ts
782
822
 
@@ -806,54 +846,60 @@ Latest value:
806
846
  ```
807
847
 
808
848
  ### Array
849
+
809
850
  **Example**
851
+
810
852
  ```ts
811
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
853
+ import { InjectionContainer, printValue } from '@rs-x/core';
812
854
  import {
813
- IStateChange,
814
- IStateManager,
815
- RsXStateManagerInjectionTokens,
816
- RsXStateManagerModule
855
+ type IStateChange,
856
+ type IStateManager,
857
+ RsXStateManagerInjectionTokens,
858
+ RsXStateManagerModule,
859
+ watchIndexRecursiveRule,
817
860
  } from '@rs-x/state-manager';
818
861
 
819
862
  // Load the state manager module into the injection container
820
863
  InjectionContainer.load(RsXStateManagerModule);
821
864
 
822
865
  export const run = (() => {
823
- const stateManager: IStateManager = InjectionContainer.get(
824
- RsXStateManagerInjectionTokens.IStateManager
825
- );
826
-
827
- const stateContext = {
828
- array: [
829
- [1, 2],
830
- [3, 4]
831
- ]
832
- };
833
-
834
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
835
- printValue(change.newValue);
836
- });
837
-
838
- try {
839
- // This will emit a change event with the initial (current) value.
840
- console.log('Initial value:');
841
- stateManager.watchState(stateContext, 'array', truePredicate);
842
-
843
- console.log('Changed value:');
844
- stateContext.array[1].push(5);
866
+ const stateManager: IStateManager = InjectionContainer.get(
867
+ RsXStateManagerInjectionTokens.IStateManager,
868
+ );
869
+
870
+ const model = {
871
+ array: [
872
+ [1, 2],
873
+ [3, 4],
874
+ ],
875
+ };
845
876
 
846
- console.log('Latest value:');
847
- printValue(stateManager.getState(stateContext, 'array'));
877
+ const changeSubscription = stateManager.changed.subscribe(
878
+ (change: IStateChange) => {
879
+ printValue(change.newValue);
880
+ },
881
+ );
848
882
 
849
- } finally {
850
- changeSubscription.unsubscribe();
851
- // Always release the state when it is no longer needed.
852
- stateManager.releaseState(stateContext, 'array', truePredicate);
853
- }
883
+ try {
884
+ // This will emit a change event with the initial (current) value.
885
+ console.log('Initial value:');
886
+ stateManager.watchState(model, 'array', watchIndexRecursiveRule);
887
+
888
+ console.log('Changed value:');
889
+ model.array[1].push(5);
890
+
891
+ console.log('Latest value:');
892
+ printValue(stateManager.getState(model, 'array'));
893
+ } finally {
894
+ changeSubscription.unsubscribe();
895
+ // Always release the state when it is no longer needed.
896
+ stateManager.releaseState(model, 'array', watchIndexRecursiveRule);
897
+ }
854
898
  })();
855
899
  ```
900
+
856
901
  **Output:**
902
+
857
903
  ```console
858
904
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-array.ts
859
905
  Initial value:
@@ -894,54 +940,60 @@ Latest value:
894
940
  ```
895
941
 
896
942
  ### Map
943
+
897
944
  **Example**
945
+
898
946
  ```ts
899
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
947
+ import { InjectionContainer, printValue } from '@rs-x/core';
900
948
  import {
901
- IStateChange,
902
- IStateManager,
903
- RsXStateManagerInjectionTokens,
904
- RsXStateManagerModule
949
+ type IStateChange,
950
+ type IStateManager,
951
+ RsXStateManagerInjectionTokens,
952
+ RsXStateManagerModule,
953
+ watchIndexRecursiveRule,
905
954
  } from '@rs-x/state-manager';
906
955
 
907
956
  // Load the state manager module into the injection container
908
957
  InjectionContainer.load(RsXStateManagerModule);
909
958
 
910
959
  const stateManager: IStateManager = InjectionContainer.get(
911
- RsXStateManagerInjectionTokens.IStateManager
960
+ RsXStateManagerInjectionTokens.IStateManager,
912
961
  );
913
962
 
914
963
  export const run = (() => {
915
- const stateContext = {
916
- map: new Map([
917
- ['a', [1, 2]],
918
- ['b', [3, 4]]
919
- ])
920
- };
921
-
922
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
923
- printValue(change.newValue);
924
- });
925
-
926
- try {
927
- // This will emit a change event with the initial (current) value.
928
- console.log('Initial value:');
929
- stateManager.watchState(stateContext, 'map', truePredicate);
930
-
931
- console.log('Changed value:');
932
- stateContext.map.get('b').push(5);
933
-
934
- console.log('Latest value:');
935
- printValue(stateManager.getState(stateContext, 'map'))
936
-
937
- } finally {
938
- changeSubscription.unsubscribe();
939
- // Always release the state when it is no longer needed.
940
- stateManager.releaseState(stateContext, 'array', truePredicate);
941
- }
964
+ const model = {
965
+ map: new Map([
966
+ ['a', [1, 2]],
967
+ ['b', [3, 4]],
968
+ ]),
969
+ };
970
+
971
+ const changeSubscription = stateManager.changed.subscribe(
972
+ (change: IStateChange) => {
973
+ printValue(change.newValue);
974
+ },
975
+ );
976
+
977
+ try {
978
+ // This will emit a change event with the initial (current) value.
979
+ console.log('Initial value:');
980
+ stateManager.watchState(model, 'map', watchIndexRecursiveRule);
981
+
982
+ console.log('Changed value:');
983
+ model.map.get('b')?.push(5);
984
+
985
+ console.log('Latest value:');
986
+ printValue(stateManager.getState(model, 'map'));
987
+ } finally {
988
+ changeSubscription.unsubscribe();
989
+ // Always release the state when it is no longer needed.
990
+ stateManager.releaseState(model, 'array', watchIndexRecursiveRule);
991
+ }
942
992
  })();
943
993
  ```
994
+
944
995
  **Output:**
996
+
945
997
  ```console
946
998
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-map.ts
947
999
  Initial value:
@@ -1000,53 +1052,61 @@ Latest value:
1000
1052
  ```
1001
1053
 
1002
1054
  ### Set
1055
+
1003
1056
  **Example**
1057
+
1004
1058
  ```ts
1005
- import { InjectionContainer, printValue, truePredicate } from '@rs-x/core';
1059
+ import { InjectionContainer, printValue } from '@rs-x/core';
1006
1060
  import {
1007
- IProxyRegistry,
1008
- IStateChange,
1009
- IStateManager,
1010
- RsXStateManagerInjectionTokens,
1011
- RsXStateManagerModule
1061
+ type IProxyRegistry,
1062
+ type IStateChange,
1063
+ type IStateManager,
1064
+ RsXStateManagerInjectionTokens,
1065
+ RsXStateManagerModule,
1066
+ watchIndexRecursiveRule,
1012
1067
  } from '@rs-x/state-manager';
1013
1068
 
1014
1069
  // Load the state manager module into the injection container
1015
1070
  InjectionContainer.load(RsXStateManagerModule);
1016
1071
 
1017
1072
  export const run = (() => {
1018
- const stateManager: IStateManager = InjectionContainer.get(
1019
- RsXStateManagerInjectionTokens.IStateManager
1020
- );
1021
- const item1 = [1, 2];
1022
- const item2 = [3, 4];
1023
- const stateContext = {
1024
- set: new Set([item1, item2])
1025
- };
1026
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
1027
- printValue(change.newValue);
1028
- });
1029
-
1030
- try {
1031
- // This will emit a change event with the initial (current) value.
1032
- console.log('Initial value:');
1033
- stateManager.watchState(stateContext, 'set', truePredicate);
1034
-
1035
- console.log('Changed value:');
1036
- const proxyRegister: IProxyRegistry = InjectionContainer.get(RsXStateManagerInjectionTokens.IProxyRegistry);
1037
- proxyRegister.getProxy<number[]>(item2).push(5);
1038
-
1039
- console.log('Latest value:');
1040
- printValue(stateManager.getState(stateContext, 'set'));
1073
+ const stateManager: IStateManager = InjectionContainer.get(
1074
+ RsXStateManagerInjectionTokens.IStateManager,
1075
+ );
1076
+ const item1 = [1, 2];
1077
+ const item2 = [3, 4];
1078
+ const model = {
1079
+ set: new Set([item1, item2]),
1080
+ };
1081
+ const changeSubscription = stateManager.changed.subscribe(
1082
+ (change: IStateChange) => {
1083
+ printValue(change.newValue);
1084
+ },
1085
+ );
1086
+
1087
+ try {
1088
+ // This will emit a change event with the initial (current) value.
1089
+ console.log('Initial value:');
1090
+ stateManager.watchState(model, 'set', watchIndexRecursiveRule);
1041
1091
 
1042
- } finally {
1043
- changeSubscription.unsubscribe();
1044
- // Always release the state when it is no longer needed.
1045
- stateManager.releaseState(stateContext, 'set', truePredicate);
1046
- }
1092
+ console.log('Changed value:');
1093
+ const proxyRegister: IProxyRegistry = InjectionContainer.get(
1094
+ RsXStateManagerInjectionTokens.IProxyRegistry,
1095
+ );
1096
+ proxyRegister.getProxy<number[]>(item2).push(5);
1097
+
1098
+ console.log('Latest value:');
1099
+ printValue(stateManager.getState(model, 'set'));
1100
+ } finally {
1101
+ changeSubscription.unsubscribe();
1102
+ // Always release the state when it is no longer needed.
1103
+ stateManager.releaseState(model, 'set', watchIndexRecursiveRule);
1104
+ }
1047
1105
  })();
1048
1106
  ```
1107
+
1049
1108
  **Output:**
1109
+
1050
1110
  ```console
1051
1111
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-set.ts
1052
1112
  Initial value:
@@ -1087,54 +1147,64 @@ Latest value:
1087
1147
  ```
1088
1148
 
1089
1149
  ### Promise
1150
+
1090
1151
  **Example**
1152
+
1091
1153
  ```ts
1092
1154
  import { InjectionContainer, WaitForEvent } from '@rs-x/core';
1093
1155
  import {
1094
- IStateChange,
1095
- IStateManager,
1096
- RsXStateManagerInjectionTokens,
1097
- RsXStateManagerModule
1156
+ type IStateChange,
1157
+ type IStateManager,
1158
+ RsXStateManagerInjectionTokens,
1159
+ RsXStateManagerModule,
1098
1160
  } from '@rs-x/state-manager';
1099
1161
 
1100
1162
  // Load the state manager module into the injection container
1101
1163
  InjectionContainer.load(RsXStateManagerModule);
1102
1164
 
1103
1165
  export const run = (async () => {
1104
- const stateManager: IStateManager = InjectionContainer.get(
1105
- RsXStateManagerInjectionTokens.IStateManager
1106
- );
1107
-
1108
- const stateContext = {
1109
- promise: Promise.resolve(10)
1110
- };
1111
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
1112
- console.log(change.newValue);
1166
+ const stateManager: IStateManager = InjectionContainer.get(
1167
+ RsXStateManagerInjectionTokens.IStateManager,
1168
+ );
1169
+
1170
+ const model = {
1171
+ promise: Promise.resolve(10),
1172
+ };
1173
+ const changeSubscription = stateManager.changed.subscribe(
1174
+ (change: IStateChange) => {
1175
+ console.log(change.newValue);
1176
+ },
1177
+ );
1178
+
1179
+ try {
1180
+ await new WaitForEvent(stateManager, 'changed').wait(() => {
1181
+ // This will emit a change event with the initial (current) value.
1182
+ console.log('Initial value:');
1183
+ stateManager.watchState(model, 'promise');
1113
1184
  });
1114
1185
 
1115
- try {
1116
- await new WaitForEvent(stateManager, 'changed').wait(() => {
1117
- // This will emit a change event with the initial (current) value.
1118
- console.log('Initial value:');
1119
- stateManager.watchState(stateContext, 'promise');
1120
- });
1186
+ await new WaitForEvent(stateManager, 'changed').wait(() => {
1187
+ console.log('Changed value:');
1188
+ let resolveHandler!: (value: number) => void;
1121
1189
 
1122
- await new WaitForEvent(stateManager, 'changed').wait(() => {
1123
- console.log('Changed value:');
1124
- let resolveHandler: (value: number) => void;
1125
- stateContext.promise = new Promise((resolve) => { resolveHandler = resolve; });
1126
- resolveHandler(30);
1127
- });
1190
+ model.promise = new Promise<number>((resolve) => {
1191
+ resolveHandler = resolve;
1192
+ });
1128
1193
 
1129
- console.log(`Latest value: ${stateManager.getState(stateContext, 'promise')}`);
1130
- } finally {
1131
- changeSubscription.unsubscribe();
1132
- // Always release the state when it is no longer needed.
1133
- stateManager.releaseState(stateContext, 'promise');
1134
- }
1194
+ resolveHandler(30);
1195
+ });
1196
+
1197
+ console.log(`Latest value: ${stateManager.getState(model, 'promise')}`);
1198
+ } finally {
1199
+ changeSubscription.unsubscribe();
1200
+ // Always release the state when it is no longer needed.
1201
+ stateManager.releaseState(model, 'promise');
1202
+ }
1135
1203
  })();
1136
1204
  ```
1205
+
1137
1206
  **Output:**
1207
+
1138
1208
  ```console
1139
1209
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-promise.ts
1140
1210
  Initial value:
@@ -1145,59 +1215,65 @@ Latest value: 30
1145
1215
  ```
1146
1216
 
1147
1217
  ### Observable
1218
+
1148
1219
  **Example**
1220
+
1149
1221
  ```ts
1222
+ import { of, Subject } from 'rxjs';
1223
+
1150
1224
  import { InjectionContainer, WaitForEvent } from '@rs-x/core';
1151
1225
  import {
1152
- IStateChange,
1153
- IStateManager,
1154
- RsXStateManagerInjectionTokens,
1155
- RsXStateManagerModule
1226
+ type IStateChange,
1227
+ type IStateManager,
1228
+ RsXStateManagerInjectionTokens,
1229
+ RsXStateManagerModule,
1156
1230
  } from '@rs-x/state-manager';
1157
- import { of, Subject } from 'rxjs';
1158
1231
 
1159
1232
  // Load the state manager module into the injection container
1160
1233
  InjectionContainer.load(RsXStateManagerModule);
1161
1234
 
1162
1235
  export const run = (async () => {
1163
- const stateManager: IStateManager = InjectionContainer.get(
1164
- RsXStateManagerInjectionTokens.IStateManager
1165
- );
1166
-
1167
- const stateContext = {
1168
- observable: of(10)
1169
- };
1170
-
1171
- const changeSubscription = stateManager.changed.subscribe((change: IStateChange) => {
1172
- console.log(change.newValue);
1236
+ const stateManager: IStateManager = InjectionContainer.get(
1237
+ RsXStateManagerInjectionTokens.IStateManager,
1238
+ );
1239
+
1240
+ const model = {
1241
+ observable: of(10),
1242
+ };
1243
+
1244
+ const changeSubscription = stateManager.changed.subscribe(
1245
+ (change: IStateChange) => {
1246
+ console.log(change.newValue);
1247
+ },
1248
+ );
1249
+
1250
+ try {
1251
+ // We need to wait here until the event is emitted,
1252
+ // otherwise the demo will exit before the change event occurs.
1253
+ await new WaitForEvent(stateManager, 'changed').wait(() => {
1254
+ // This will emit a change event with the initial (current) value.
1255
+ console.log('Initial value:');
1256
+ stateManager.watchState(model, 'observable');
1173
1257
  });
1174
1258
 
1175
- try {
1176
- // We need to wait here until the event is emitted,
1177
- // otherwise the demo will exit before the change event occurs.
1178
- await new WaitForEvent(stateManager, 'changed').wait(() => {
1179
- // This will emit a change event with the initial (current) value.
1180
- console.log('Initial value:');
1181
- stateManager.watchState(stateContext, 'observable');
1182
- });
1183
-
1184
- await new WaitForEvent(stateManager, 'changed').wait(() => {
1185
- console.log('Changed value:');
1186
- const subject = new Subject<number>();
1187
- stateContext.observable = subject
1188
- subject.next(30);
1189
- });
1190
-
1191
- console.log(`Latest value: ${stateManager.getState(stateContext, 'observable')}`);
1259
+ await new WaitForEvent(stateManager, 'changed').wait(() => {
1260
+ console.log('Changed value:');
1261
+ const subject = new Subject<number>();
1262
+ model.observable = subject;
1263
+ subject.next(30);
1264
+ });
1192
1265
 
1193
- } finally {
1194
- changeSubscription.unsubscribe();
1195
- // Always release the state when it is no longer needed.
1196
- stateManager.releaseState(stateContext, 'observable');
1197
- }
1266
+ console.log(`Latest value: ${stateManager.getState(model, 'observable')}`);
1267
+ } finally {
1268
+ changeSubscription.unsubscribe();
1269
+ // Always release the state when it is no longer needed.
1270
+ stateManager.releaseState(model, 'observable');
1271
+ }
1198
1272
  })();
1199
1273
  ```
1274
+
1200
1275
  **Output:**
1276
+
1201
1277
  ```console
1202
1278
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/register-observable.ts
1203
1279
  Initial value:
@@ -1208,511 +1284,601 @@ Latest value: 30
1208
1284
  ```
1209
1285
 
1210
1286
  ### Custom type
1211
- 1. Create an accessor to retrieve index values on your type.
1212
- 2. Create a factory to create an observer for your data type.
1287
+
1288
+ 1. Create an accessor to retrieve index values on your type.
1289
+ 2. Create a factory to create an observer for your data type.
1213
1290
  3. Create a factory to create an observer for an index on your data instance.
1214
1291
 
1215
1292
  The following example demonstrates adding support for a custom `TextDocument` class:
1216
1293
 
1217
1294
  **Example**
1218
- ```ts
1295
+
1296
+ ````ts
1297
+ import { ReplaySubject, Subscription } from 'rxjs';
1298
+
1219
1299
  import {
1220
- ContainerModule,
1221
- defaultIndexValueAccessorList,
1222
- IDisposableOwner,
1223
- IErrorLog,
1224
- IGuidFactory,
1225
- IIndexValueAccessor,
1226
- Inject,
1227
- Injectable,
1228
- InjectionContainer,
1229
- IPropertyChange,
1230
- overrideMultiInjectServices,
1231
- RsXCoreInjectionTokens,
1232
- SingletonFactory,
1233
- 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,
1234
1315
  } from '@rs-x/core';
1235
1316
  import {
1236
- AbstractObserver,
1237
- defaultObjectObserverProxyPairFactoryList,
1238
- defaultPropertyObserverProxyPairFactoryList,
1239
- IIndexObserverInfo,
1240
- IndexObserverProxyPairFactory,
1241
- IObjectObserverProxyPairFactory,
1242
- IObjectObserverProxyPairManager,
1243
- IObserverProxyPair,
1244
- IPropertyInfo,
1245
- IProxyRegistry,
1246
- IProxyTarget,
1247
- IStateChange,
1248
- IStateManager,
1249
- RsXStateManagerInjectionTokens,
1250
- 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,
1251
1333
  } from '@rs-x/state-manager';
1252
- import { ReplaySubject, Subscription } from 'rxjs';
1253
1334
 
1254
1335
  const MyInjectTokens = {
1255
- TextDocumentObserverManager: Symbol('TextDocumentObserverManager'),
1256
- TextDocumenIndexObserverManager: Symbol('TextDocumenIndexObserverManager'),
1257
- TextDocumentIndexAccessor: Symbol('TextDocumentIndexAccessor'),
1258
- TextDocumentObserverProxyPairFactory: Symbol('TextDocumentObserverProxyPairFactory'),
1259
- TextDocumentInxdexObserverProxyPairFactory: Symbol('TextDocumentInxdexObserverProxyPairFactory')
1260
-
1336
+ TextDocumentObserverManager: Symbol('TextDocumentObserverManager'),
1337
+ TextDocumenIndexObserverManager: Symbol('TextDocumenIndexObserverManager'),
1338
+ TextDocumentIndexAccessor: Symbol('TextDocumentIndexAccessor'),
1339
+ TextDocumentObserverProxyPairFactory: Symbol(
1340
+ 'TextDocumentObserverProxyPairFactory',
1341
+ ),
1342
+ TextDocumentInxdexObserverProxyPairFactory: Symbol(
1343
+ 'TextDocumentInxdexObserverProxyPairFactory',
1344
+ ),
1261
1345
  };
1262
1346
 
1263
- class IndexForTextDocumentxObserverManager
1264
- extends SingletonFactory<
1265
- number,
1266
- IIndexObserverInfo<ITextDocumentIndex>,
1267
- TextDocumentIndexObserver> {
1268
- constructor(
1269
- private readonly _textDocument: TextDocument,
1270
- private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1271
- private readonly releaseOwner: () => void
1272
- ) {
1273
- super();
1274
- }
1275
-
1276
- public override getId(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>): number {
1277
- return this.createId(indexObserverInfo);
1278
- }
1279
-
1280
- protected override createInstance(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>, id: number): TextDocumentIndexObserver {
1281
- const textDocumentObserver = this._textDocumentObserverManager.create(this._textDocument).instance;
1282
- return new TextDocumentIndexObserver(
1283
- {
1284
- canDispose: () => this.getReferenceCount(id) === 1,
1285
- release: () => {
1286
- textDocumentObserver.dispose();
1287
- this.release(id);
1288
- },
1289
- },
1290
- textDocumentObserver, indexObserverInfo.index
1291
- );
1292
- }
1293
-
1294
- protected override createId(indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>): number {
1295
- // Using Cantor pairing to create a unique id from page and line index
1296
- const { pageIndex, lineIndex } = indexObserverInfo.index;
1297
- return ((pageIndex + lineIndex) * (pageIndex + lineIndex + 1)) / 2 + lineIndex;
1298
- }
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
+ }
1359
+
1360
+ public override getId(
1361
+ indexObserverInfo: IIndexObserverInfo<ITextDocumentIndex>,
1362
+ ): number {
1363
+ return this.createId(indexObserverInfo);
1364
+ }
1365
+
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
+ }
1385
+
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
+ }
1299
1395
 
1300
- protected override onReleased(): void {
1301
- if (this.isEmpty) {
1302
- this.releaseOwner();
1303
- }
1396
+ protected override onReleased(): void {
1397
+ if (this.isEmpty) {
1398
+ this.releaseOwner();
1304
1399
  }
1400
+ }
1305
1401
  }
1306
1402
 
1307
1403
  // We want to ensure that for the same TextDocument we always have the same observer
1308
1404
  @Injectable()
1309
- class TextDocumentObserverManager extends SingletonFactory<TextDocument, TextDocument, TextDocumentObserver> {
1310
- constructor(
1311
- @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1312
- private readonly _proxyRegister: IProxyRegistry) {
1313
- super();
1314
- }
1315
-
1316
- public override getId(textDocument: TextDocument): TextDocument {
1317
- return textDocument;
1318
- }
1319
-
1320
- protected override createInstance(textDocument: TextDocument, id: TextDocument): TextDocumentObserver {
1321
- return new TextDocumentObserver(
1322
- textDocument,
1323
- this._proxyRegister,
1324
- {
1325
- canDispose: () => this.getReferenceCount(id) === 1,
1326
- release: () => this.release(id)
1327
- }
1328
- );
1329
- }
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
+ }
1416
+
1417
+ public override getId(textDocument: TextDocument): TextDocument {
1418
+ return textDocument;
1419
+ }
1420
+
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
+ }
1330
1430
 
1331
- protected override createId(textDocument: TextDocument): TextDocument {
1332
- return textDocument;
1333
- }
1431
+ protected override createId(textDocument: TextDocument): TextDocument {
1432
+ return textDocument;
1433
+ }
1334
1434
  }
1335
1435
 
1336
1436
  // We want to ensure we create only one index-manager per TextDocument
1337
1437
  @Injectable()
1338
- export class TextDocumenIndexObserverManager
1339
- extends SingletonFactory<
1340
- TextDocument,
1341
- TextDocument,
1342
- IndexForTextDocumentxObserverManager
1343
- > {
1344
- constructor(
1345
- @Inject(MyInjectTokens.TextDocumentObserverManager)
1346
- private readonly _textDocumentObserverManager: TextDocumentObserverManager,
1347
- ) {
1348
- super();
1349
- }
1350
-
1351
- public override getId(textDocument: TextDocument): TextDocument {
1352
- return textDocument;
1353
- }
1354
-
1355
- protected override createId(textDocument: TextDocument): TextDocument {
1356
- return textDocument;
1357
- }
1358
-
1359
- protected override createInstance(
1360
- textDocument: TextDocument
1361
- ): IndexForTextDocumentxObserverManager {
1362
-
1363
- return new IndexForTextDocumentxObserverManager(textDocument, this._textDocumentObserverManager, () => this.release(textDocument));
1364
- }
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
+ }
1449
+
1450
+ public override getId(textDocument: TextDocument): TextDocument {
1451
+ return textDocument;
1452
+ }
1453
+
1454
+ protected override createId(textDocument: TextDocument): TextDocument {
1455
+ return textDocument;
1456
+ }
1457
+
1458
+ protected override createInstance(
1459
+ textDocument: TextDocument,
1460
+ ): IndexForTextDocumentxObserverManager {
1461
+ return new IndexForTextDocumentxObserverManager(
1462
+ textDocument,
1463
+ this._textDocumentObserverManager,
1464
+ () => this.release(textDocument),
1465
+ );
1466
+ }
1365
1467
 
1366
- protected override releaseInstance(
1367
- indexForTextDocumentxObserverManager: IndexForTextDocumentxObserverManager
1368
- ): void {
1369
- indexForTextDocumentxObserverManager.dispose();
1370
- }
1468
+ protected override releaseInstance(
1469
+ indexForTextDocumentxObserverManager: IndexForTextDocumentxObserverManager,
1470
+ ): void {
1471
+ indexForTextDocumentxObserverManager.dispose();
1472
+ }
1371
1473
  }
1372
1474
 
1373
-
1374
-
1375
-
1376
1475
  @Injectable()
1377
- export class TextDocumentIndexAccessor implements IIndexValueAccessor<TextDocument, ITextDocumentIndex> {
1378
- public readonly priority: 200;
1379
-
1380
- public hasValue(context: TextDocument, index: ITextDocumentIndex): boolean {
1381
- return context.getLine(index) !== undefined;
1382
- }
1383
-
1384
- // We don’t have any properties that can be iterated through.
1385
- public getIndexes(_context: TextDocument, _index?: ITextDocumentIndex): IterableIterator<ITextDocumentIndex> {
1386
- return [].values();
1387
- }
1388
-
1389
- // Indicate whether the value is async. For example when the value is a Promise
1390
- public isAsync(_context: TextDocument, _index: ITextDocumentIndex): boolean {
1391
- return false;
1392
- }
1393
-
1394
- // Here it is the same as getValue.
1395
- // For example, for a Promise accessor getValue returns the promise
1396
- // and getResolvedValue returns the resolved promise value
1397
- public getResolvedValue(context: TextDocument, index: ITextDocumentIndex): string {
1398
- return this.getValue(context, index);
1399
- }
1400
-
1401
- public getValue(context: TextDocument, index: ITextDocumentIndex): string {
1402
- return context.getLine(index);
1403
- }
1404
-
1405
- public setValue(context: TextDocument, index: ITextDocumentIndex, value: string): void {
1406
- context.setLine(index, value);
1407
- }
1408
-
1409
- public applies(context: unknown, _index: ITextDocumentIndex): boolean {
1410
- return context instanceof TextDocument;
1411
- }
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
+ }
1488
+
1489
+ // We don’t have any properties that can be iterated through.
1490
+ public getIndexes(): IterableIterator<ITextDocumentIndex> {
1491
+ return [].values();
1492
+ }
1493
+
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
+ }
1503
+
1504
+ public getValue(
1505
+ textDocument: TextDocument,
1506
+ index: ITextDocumentIndex,
1507
+ ): string | undefined {
1508
+ return textDocument.getLine(index);
1509
+ }
1510
+
1511
+ public setValue(
1512
+ textDocument: TextDocument,
1513
+ index: ITextDocumentIndex,
1514
+ value: string,
1515
+ ): void {
1516
+ textDocument.setLine(index, value);
1517
+ }
1518
+
1519
+ public applies(textDocument: unknown, _index: ITextDocumentIndex): boolean {
1520
+ return textDocument instanceof TextDocument;
1521
+ }
1412
1522
  }
1413
1523
 
1414
1524
  @Injectable()
1415
- export class TextDocumentInxdexObserverProxyPairFactory extends IndexObserverProxyPairFactory<TextDocument, unknown> {
1416
- constructor(
1417
- @Inject(RsXStateManagerInjectionTokens.IObjectObserverProxyPairManager)
1418
- objectObserverManager: IObjectObserverProxyPairManager,
1419
- @Inject( MyInjectTokens.TextDocumenIndexObserverManager)
1420
- textDocumenIndexObserverManager: TextDocumenIndexObserverManager,
1421
- @Inject(RsXCoreInjectionTokens.IErrorLog)
1422
- errorLog: IErrorLog,
1423
- @Inject(RsXCoreInjectionTokens.IGuidFactory)
1424
- guidFactory: IGuidFactory,
1425
- @Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
1426
- indexValueAccessor: IIndexValueAccessor,
1427
- @Inject(RsXStateManagerInjectionTokens.IProxyRegistry)
1428
- proxyRegister: IProxyRegistry
1429
- ) {
1430
- super(
1431
- objectObserverManager,
1432
- textDocumenIndexObserverManager,
1433
- errorLog,
1434
- guidFactory,
1435
- indexValueAccessor,
1436
- proxyRegister
1437
- );
1438
- }
1439
-
1440
- public applies(object: unknown, propertyInfo: IPropertyInfo): boolean {
1441
- const documentKey = propertyInfo.key as ITextDocumentIndex;
1442
- return object instanceof TextDocument && documentKey?.lineIndex >= 0 && documentKey?.pageIndex >= 0;
1443
- }
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
+ }
1555
+
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
+ }
1444
1564
  }
1445
1565
 
1446
1566
  @Injectable()
1447
1567
  export class TextDocumentObserverProxyPairFactory implements IObjectObserverProxyPairFactory {
1448
- public readonly priority = 100;
1449
-
1450
- constructor(
1451
- @Inject( MyInjectTokens.TextDocumentObserverManager)
1452
- private readonly _textDocumentObserverManager: TextDocumentObserverManager) { }
1453
-
1454
- public create(
1455
- _: IDisposableOwner,
1456
- proxyTarget: IProxyTarget<TextDocument>): IObserverProxyPair<TextDocument> {
1457
-
1458
- const observer = this._textDocumentObserverManager.create(proxyTarget.target).instance;
1459
- return {
1460
- observer,
1461
- proxy: observer.target as TextDocument,
1462
- proxyTarget: proxyTarget.target,
1463
- };
1464
- }
1568
+ public readonly priority = 100;
1569
+
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
+ }
1465
1588
 
1466
- public applies(object: unknown): boolean {
1467
- return object instanceof TextDocument;
1468
- }
1589
+ public applies(object: unknown): boolean {
1590
+ return object instanceof TextDocument;
1591
+ }
1469
1592
  }
1470
1593
 
1471
1594
  interface ITextDocumentIndex {
1472
- pageIndex: number;
1473
- lineIndex: number;
1595
+ pageIndex: number;
1596
+ lineIndex: number;
1474
1597
  }
1475
1598
 
1476
1599
  class TextDocument {
1477
- private readonly _pages = new Map<number, Map<number, string>>();
1478
- constructor(
1479
- pages?: string[][],
1480
- ) {
1481
-
1482
- pages?.forEach((page, pageIndex) => {
1483
- const pageText = new Map<number, string>();
1484
-
1485
- this._pages.set(pageIndex, pageText);
1486
- page.forEach((lineText, lineIndex) => {
1487
- pageText.set(lineIndex, lineText);
1488
- });
1489
- });
1490
- }
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
+ }
1491
1611
 
1492
- public toString(): string {
1493
- const pages: string[] = [];
1612
+ public toString(): string {
1613
+ const pages: string[] = [];
1494
1614
 
1495
- // Sort pages by pageIndex
1496
- const sortedPageIndexes = Array.from(this._pages.keys()).sort((a, b) => a - b);
1615
+ // Sort pages by pageIndex
1616
+ const sortedPageIndexes = Array.from(this._pages.keys()).sort(
1617
+ (a, b) => a - b,
1618
+ );
1497
1619
 
1498
- for (const pageIndex of sortedPageIndexes) {
1499
- const page = this._pages.get(pageIndex);
1500
- if (!page) {
1501
- continue;
1502
- }
1620
+ for (const pageIndex of sortedPageIndexes) {
1621
+ const page = this._pages.get(pageIndex);
1622
+ if (!page) {
1623
+ continue;
1624
+ }
1503
1625
 
1504
- // Sort lines by lineIndex
1505
- const sortedLineIndexes = Array.from(page.keys()).sort((a, b) => a - b);
1626
+ // Sort lines by lineIndex
1627
+ const sortedLineIndexes = Array.from(page.keys()).sort((a, b) => a - b);
1506
1628
 
1507
- const lines = sortedLineIndexes.map(lineIndex => ` ${lineIndex}: ${page.get(lineIndex)}`);
1508
- pages.push(`Page ${pageIndex}:\n${lines.join('\n')}`);
1509
- }
1510
-
1511
- 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')}`);
1512
1633
  }
1513
1634
 
1514
- public setLine(index: ITextDocumentIndex, text: string): void {
1515
- const { pageIndex, lineIndex } = index;
1516
- let page = this._pages.get(pageIndex);
1517
- if (!page) {
1518
- page = new Map();
1519
- this._pages.set(pageIndex, page);
1520
- }
1635
+ return pages.join('\n\n');
1636
+ }
1521
1637
 
1522
- 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);
1523
1644
  }
1524
1645
 
1525
- public getLine(index: ITextDocumentIndex): string {
1526
- const { pageIndex, lineIndex } = index;
1527
- return this._pages.get(pageIndex)?.get(lineIndex);
1528
- }
1529
- }
1646
+ page.set(lineIndex, text);
1647
+ }
1530
1648
 
1531
- class TextDocumentIndexObserver extends AbstractObserver<TextDocument, string, ITextDocumentIndex> {
1532
- private readonly _changeSubscription: Subscription;
1649
+ public getLine(index: ITextDocumentIndex): string | undefined {
1650
+ const { pageIndex, lineIndex } = index;
1651
+ return this._pages.get(pageIndex)?.get(lineIndex);
1652
+ }
1653
+ }
1533
1654
 
1534
- constructor(
1535
- owner: IDisposableOwner,
1536
- private readonly _observer: TextDocumentObserver,
1537
- index: ITextDocumentIndex,
1655
+ class TextDocumentIndexObserver extends AbstractObserver<
1656
+ TextDocument,
1657
+ string,
1658
+ ITextDocumentIndex
1659
+ > {
1660
+ private readonly _changeSubscription: Subscription;
1661
+
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
+ }
1676
+
1677
+ protected override disposeInternal(): void {
1678
+ this._changeSubscription.unsubscribe();
1679
+ this._observer.dispose();
1680
+ }
1681
+
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
1538
1687
  ) {
1539
- super(owner, _observer.target, _observer.target.getLine(index), new ReplaySubject(), index);
1540
- this._changeSubscription = _observer.changed.subscribe(this.onChange);
1541
- }
1542
-
1543
- protected override disposeInternal(): void {
1544
- this._changeSubscription.unsubscribe();
1545
- this._observer.dispose();
1546
- }
1547
-
1548
- private readonly onChange = (change: IPropertyChange) => {
1549
- const changeIndex = change.id as ITextDocumentIndex;
1550
- if (changeIndex.lineIndex === this.id.lineIndex && changeIndex.pageIndex === this.id.pageIndex) {
1551
- this.emitChange(change);
1552
- }
1688
+ this.emitChange(change);
1553
1689
  }
1690
+ };
1554
1691
  }
1555
1692
 
1556
1693
  class TextDocumentObserver extends AbstractObserver<TextDocument> {
1557
- constructor(
1558
- textDocument: TextDocument,
1559
- private readonly _proxyRegister: IProxyRegistry,
1560
- owner?: IDisposableOwner,) {
1561
- super(owner, null, textDocument);
1562
-
1563
- this.target = new Proxy(textDocument, this);
1564
-
1565
- // Always register a proxy at the proxy registry
1566
- // so we can determine if an instance is a proxy or not.
1567
- this._proxyRegister.register(textDocument, this.target);
1568
- }
1569
-
1570
- protected override disposeInternal(): void {
1571
- this._proxyRegister.unregister(this.value);
1572
- }
1573
-
1574
- public get(
1575
- textDocument: TextDocument,
1576
- property: PropertyKey,
1577
- receiver: unknown
1578
- ): unknown {
1579
- if (property == 'setLine') {
1580
- return (index: ITextDocumentIndex, text: string) => {
1581
- textDocument.setLine(index, text);
1582
- this.emitChange({
1583
- arguments: [],
1584
- id: index,
1585
- target: textDocument,
1586
- newValue: text,
1587
- });
1588
- };
1589
-
1590
- } else {
1591
- return Reflect.get(textDocument, property, receiver);
1592
- }
1694
+ constructor(
1695
+ textDocument: TextDocument,
1696
+ private readonly _proxyRegister: IProxyRegistry,
1697
+ owner?: IDisposableOwner,
1698
+ ) {
1699
+ super(owner, Type.cast(undefined), textDocument);
1700
+
1701
+ this.target = new Proxy(textDocument, this);
1702
+
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
+ }
1707
+
1708
+ protected override disposeInternal(): void {
1709
+ this._proxyRegister.unregister(this.value);
1710
+ }
1711
+
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);
1593
1729
  }
1730
+ }
1594
1731
  }
1595
1732
 
1596
1733
  // Load the state manager module into the injection container
1597
1734
  InjectionContainer.load(RsXStateManagerModule);
1598
1735
 
1599
1736
  const MyModule = new ContainerModule((options) => {
1600
- options
1601
- .bind<TextDocumentObserverManager>(
1602
- MyInjectTokens.TextDocumentObserverManager
1603
- )
1604
- .to(TextDocumentObserverManager)
1605
- .inSingletonScope();
1606
-
1607
- options
1608
- .bind<TextDocumenIndexObserverManager>(
1609
- MyInjectTokens.TextDocumenIndexObserverManager
1610
- )
1611
- .to(TextDocumenIndexObserverManager)
1612
- .inSingletonScope();
1613
-
1614
-
1615
- overrideMultiInjectServices(options, RsXCoreInjectionTokens.IIndexValueAccessorList, [
1616
- { target: TextDocumentIndexAccessor, token: MyInjectTokens.TextDocumentIndexAccessor },
1617
- ...defaultIndexValueAccessorList
1618
- ]);
1619
-
1620
- overrideMultiInjectServices(options, RsXStateManagerInjectionTokens.IObjectObserverProxyPairFactoryList, [
1621
- { target: TextDocumentObserverProxyPairFactory, token: MyInjectTokens.TextDocumentObserverProxyPairFactory },
1622
- ...defaultObjectObserverProxyPairFactoryList
1623
- ]);
1624
-
1625
- overrideMultiInjectServices(options,RsXStateManagerInjectionTokens.IPropertyObserverProxyPairFactoryList, [
1626
- { target: TextDocumentInxdexObserverProxyPairFactory, token: MyInjectTokens.TextDocumentInxdexObserverProxyPairFactory },
1627
- ...defaultPropertyObserverProxyPairFactoryList
1628
- ]);
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
+ );
1629
1786
  });
1630
1787
 
1631
1788
  InjectionContainer.load(MyModule);
1632
1789
 
1633
- function testMonitorTextDocument(stateManager: IStateManager, stateContext: { myBook: TextDocument }): void {
1634
- const bookSubscription = stateManager.changed.subscribe(() => {
1635
- console.log(stateContext.myBook.toString());
1636
- });
1637
-
1638
- // We observe the whole book
1639
- // This will use TextDocumentObserverProxyPairFactory
1640
- try {
1641
- console.log('\n***********************************************');
1642
- console.log("Start watching the whole book\n");
1643
- console.log('My initial book:\n');
1644
- stateManager.watchState(stateContext, 'myBook', truePredicate);
1645
-
1646
- console.log('\nUpdate second line on the first page:\n');
1647
- console.log('My book after change:\n');
1648
- stateContext.myBook.setLine({ pageIndex: 0, lineIndex: 1 }, 'In a far far away land');
1649
-
1650
- } finally {
1651
- // Stop monitoring the whole book
1652
- stateManager.releaseState(stateContext, 'myBook', truePredicate);
1653
- bookSubscription.unsubscribe();
1654
- }
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
+ });
1797
+
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
+ }
1655
1817
  }
1656
1818
 
1657
- function testMonitoreSpecificLineInDocument(stateManager: IStateManager, stateContext: { myBook: TextDocument }): void {
1658
- const line3OnPage1Index = { pageIndex: 0, lineIndex: 2 };
1659
- const lineSubscription = stateManager.changed.subscribe((change: IStateChange) => {
1660
- const documentIndex = change.key as ITextDocumentIndex;
1661
- console.log(`Line ${documentIndex.lineIndex + 1} on page ${documentIndex.pageIndex + 1} has changed to '${change.newValue}'`);
1662
- console.log('My book after change:\n');
1663
- console.log(stateContext.myBook.toString());
1664
- });
1665
-
1666
- try {
1667
- // Here we only watch line 3 on page 1.
1668
- // Notice that the line does not have to exist yet.
1669
- // The initial book does not have a line 3 on page 1.
1670
- //
1671
- // TextDocumentInxdexObserverProxyPairFactory is used here
1672
-
1673
- console.log('\n***********************************************');
1674
- console.log("Start watching line 3 on page 1\n");
1675
- stateManager.watchState(stateContext.myBook, line3OnPage1Index);
1676
-
1677
- const proxRegistry: IProxyRegistry = InjectionContainer.get(RsXStateManagerInjectionTokens.IProxyRegistry);
1678
- const bookProxy: TextDocument = proxRegistry.getProxy(stateContext.myBook);
1679
-
1680
- bookProxy.setLine(line3OnPage1Index, 'a prince was born');
1681
-
1682
- console.log('\nChanging line 1 on page 1 does not emit change:');
1683
- console.log('---');
1684
- bookProxy.setLine({ pageIndex: 0, lineIndex: 0 }, 'a troll was born');
1685
-
1686
- } finally {
1687
- // Stop monitoring line 3 on page 1.
1688
- stateManager.releaseState(stateContext.myBook, line3OnPage1Index);
1689
- lineSubscription.unsubscribe();
1690
- }
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
+ );
1834
+
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
1841
+
1842
+ console.log('\n***********************************************');
1843
+ console.log('Start watching line 3 on page 1\n');
1844
+ stateManager.watchState(model.myBook, line3OnPage1Index);
1845
+
1846
+ const proxRegistry: IProxyRegistry = InjectionContainer.get(
1847
+ RsXStateManagerInjectionTokens.IProxyRegistry,
1848
+ );
1849
+ const bookProxy: TextDocument = proxRegistry.getProxy(model.myBook);
1850
+
1851
+ bookProxy.setLine(line3OnPage1Index, 'a prince was born');
1852
+
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
+ }
1691
1861
  }
1692
1862
 
1693
1863
  export const run = (() => {
1694
- const stateManager: IStateManager = InjectionContainer.get(
1695
- RsXStateManagerInjectionTokens.IStateManager
1696
- );
1697
- const stateContext = {
1698
- myBook: new TextDocument([
1699
- [
1700
- 'Once upon a time',
1701
- 'bla bla'
1702
- ],
1703
- [
1704
- 'bla bla',
1705
- 'They lived happily ever after.',
1706
- 'The end'
1707
- ]
1708
- ])
1709
- };
1710
- testMonitorTextDocument(stateManager, stateContext);
1711
- 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);
1712
1875
  })();
1876
+
1713
1877
  ```nclude_relative ../demo/src/rs-x-state-manager/register-set.ts %}
1714
- ```
1878
+ ````
1879
+
1715
1880
  **Output:**
1881
+
1716
1882
  ```console
1717
1883
  Running demo: /Users/robertsanders/projects/rs-x/demo/src/rs-x-state-manager/state-manager-customize.ts
1718
1884
 
@@ -1762,4 +1928,3 @@ Page 1:
1762
1928
  Changing line 1 on page 1 does not emit change:
1763
1929
  ---
1764
1930
  ```
1765
-