@rs-x/core 0.4.10 → 0.4.12

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 +183 -95
  2. package/dist/index.js +1649 -1315
  3. package/package.json +1 -1
  4. package/readme.md +472 -494
package/readme.md CHANGED
@@ -2,25 +2,26 @@
2
2
 
3
3
  Provides shared core functionality for the RS-X project:
4
4
 
5
- * [Dependency Injection](#dependency-injection)
6
- * [Deep Clone](#deep-clone)
7
- * [Deep Equality](#deep-equality)
8
- * [Guid Factory](#guid-factory)
9
- * [Index Value Accessor](#index-value-accessor)
10
- * [Singleton factory](#singleton-factory)
11
- * [Error Log](#error-log)
12
- * [WaitForEvent](#waitforevent)
5
+ - [Dependency Injection](#dependency-injection)
6
+ - [Deep Clone](#deep-clone)
7
+ - [Deep Equality](#deep-equality)
8
+ - [Guid Factory](#guid-factory)
9
+ - [Index Value Accessor](#index-value-accessor)
10
+ - [Singleton factory](#singleton-factory)
11
+ - [Error Log](#error-log)
12
+ - [WaitForEvent](#waitforevent)
13
13
 
14
14
  ## Dependency Injection
15
+
15
16
  Implemented with [Inversify](https://github.com/inversify/InversifyJS).
16
17
 
17
18
  The following aliases were added to make them consistent with the code style used throughout the project:
18
19
 
19
- * `inject` renamed to `Inject`
20
- * `multiInject` renamed to `MultiInject`
21
- * `injectable` renamed to `Injectable`
22
- * `unmanaged` renamed to `Unmanaged`
23
- * `preDestroy` renamed to `PreDestroy`
20
+ - `inject` renamed to `Inject`
21
+ - `multiInject` renamed to `MultiInject`
22
+ - `injectable` renamed to `Injectable`
23
+ - `unmanaged` renamed to `Unmanaged`
24
+ - `preDestroy` renamed to `PreDestroy`
24
25
 
25
26
  In addition, the following extensions were added to Inversify:
26
27
 
@@ -93,11 +94,9 @@ Overrides an existing multi-inject list, removing any previous bindings for the
93
94
  - Use this function when you want to completely replace the multi-inject service list.
94
95
  - Ensures that `container.getAll(multiInjectToken)` returns only the new services without duplicates.
95
96
 
96
-
97
-
98
97
  ## Deep Clone
99
98
 
100
- - Uses [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) by default
99
+ - Uses [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) by default
101
100
  - Falls back to [Lodash `cloneDeepWith`](https://lodash.com/docs/4.17.15#cloneDeepWith) for unsupported types
102
101
 
103
102
  ### Get an instance of the Deep clone service
@@ -107,10 +106,7 @@ You must load the core module into the injection container if you want
107
106
  to use it.
108
107
 
109
108
  ```ts
110
- import {
111
- InjectionContainer,
112
- RsXCoreModule
113
- } from '@rs-x/core';
109
+ import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
114
110
 
115
111
  InjectionContainer.load(RsXCoreModule);
116
112
  ```
@@ -119,67 +115,65 @@ There are two ways to get an instance:
119
115
 
120
116
  1. Using the injection container
121
117
 
122
- ```ts
123
- import {
124
- IDeepClone,
125
- InjectionContainer,
126
- RsXCoreInjectionTokens
127
- } from '@rs-x/core';
128
-
129
- const deepClone: IDeepClone = InjectionContainer.get(
130
- RsXCoreInjectionTokens.IDeepClone
131
- );
132
- ```
118
+ ```ts
119
+ import {
120
+ IDeepClone,
121
+ InjectionContainer,
122
+ RsXCoreInjectionTokens,
123
+ } from '@rs-x/core';
124
+
125
+ const deepClone: IDeepClone = InjectionContainer.get(
126
+ RsXCoreInjectionTokens.IDeepClone,
127
+ );
128
+ ```
133
129
 
134
130
  2. Using the `@Inject` decorator
135
131
 
136
- ```ts
137
- import {
138
- IDeepClone,
139
- Inject,
140
- RsXCoreInjectionTokens
141
- } from '@rs-x/core';
142
-
143
- export class MyClass {
132
+ ```ts
133
+ import { IDeepClone, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
144
134
 
145
- constructor(
146
- @Inject(RsXCoreInjectionTokens.IDeepClone)
147
- private readonly _deepClone: IDeepClone
148
- ) {}
149
- }
150
- ```
135
+ export class MyClass {
136
+ constructor(
137
+ @Inject(RsXCoreInjectionTokens.IDeepClone)
138
+ private readonly _deepClone: IDeepClone,
139
+ ) {}
140
+ }
141
+ ```
151
142
 
152
143
  The following example shows how to use deep clone service:
153
144
 
154
145
  ```ts
155
146
  import {
156
- IDeepClone,
157
- InjectionContainer,
158
- printValue,
159
- RsXCoreInjectionTokens,
160
- RsXCoreModule
147
+ IDeepClone,
148
+ InjectionContainer,
149
+ printValue,
150
+ RsXCoreInjectionTokens,
151
+ RsXCoreModule,
161
152
  } from '@rs-x/core';
162
153
 
163
154
  // Load the core module into the injection container
164
155
  InjectionContainer.load(RsXCoreModule);
165
- const deepClone: IDeepClone = InjectionContainer.get(RsXCoreInjectionTokens.IDeepClone);
156
+ const deepClone: IDeepClone = InjectionContainer.get(
157
+ RsXCoreInjectionTokens.IDeepClone,
158
+ );
166
159
 
167
160
  export const run = (() => {
168
- const object = {
169
- a: 10,
170
- nested: {
171
- b: 20
172
- }
173
- };
174
- const clone = deepClone.clone(object);
175
-
176
- console.log(`Clone is a copy of the cloned object: ${object !== clone}`)
177
- console.log('Cloned object');
178
- printValue(clone);
161
+ const object = {
162
+ a: 10,
163
+ nested: {
164
+ b: 20,
165
+ },
166
+ };
167
+ const clone = deepClone.clone(object);
168
+
169
+ console.log(`Clone is a copy of the cloned object: ${object !== clone}`);
170
+ console.log('Cloned object');
171
+ printValue(clone);
179
172
  })();
180
173
  ```
181
174
 
182
175
  **Output:**
176
+
183
177
  ```console
184
178
  Running demo: demo/src/rs-x-core/deep-clone.ts
185
179
  Clone is a copy of the cloned object: true
@@ -203,10 +197,7 @@ You must load the core module into the injection container if you want
203
197
  to use it.
204
198
 
205
199
  ```ts
206
- import {
207
- InjectionContainer,
208
- RsXCoreModule
209
- } from '@rs-x/core';
200
+ import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
210
201
 
211
202
  InjectionContainer.load(RsXCoreModule);
212
203
  ```
@@ -215,75 +206,77 @@ There are two ways to get an instance:
215
206
 
216
207
  1. Using the injection container
217
208
 
218
- ```ts
219
- import {
220
- IEqualityService,
221
- InjectionContainer,
222
- RsXCoreInjectionTokens
223
- } from '@rs-x/core';
224
-
225
- const equalityService: IEqualityService = InjectionContainer.get(
226
- RsXCoreInjectionTokens.IEqualityService
227
- );
228
- ```
209
+ ```ts
210
+ import {
211
+ IEqualityService,
212
+ InjectionContainer,
213
+ RsXCoreInjectionTokens,
214
+ } from '@rs-x/core';
229
215
 
230
- 2. Using the `@Inject` decorator
216
+ const equalityService: IEqualityService = InjectionContainer.get(
217
+ RsXCoreInjectionTokens.IEqualityService,
218
+ );
219
+ ```
231
220
 
232
- ```ts
233
- import {
234
- IEqualityService,
235
- Inject,
236
- RsXCoreInjectionTokens
237
- } from '@rs-x/core';
238
-
239
- export class MyClass {
221
+ 2. Using the `@Inject` decorator
240
222
 
241
- constructor(
242
- @Inject(RsXCoreInjectionTokens.IEqualityService)
243
- private readonly _equalityService: IEqualityService
244
- ) {}
245
- }
246
- ```
223
+ ```ts
224
+ import {
225
+ IEqualityService,
226
+ Inject,
227
+ RsXCoreInjectionTokens,
228
+ } from '@rs-x/core';
229
+
230
+ export class MyClass {
231
+ constructor(
232
+ @Inject(RsXCoreInjectionTokens.IEqualityService)
233
+ private readonly _equalityService: IEqualityService,
234
+ ) {}
235
+ }
236
+ ```
247
237
 
248
238
  The following example shows how to use equality service
249
239
 
250
240
  ```ts
251
241
  import {
252
- IEqualityService,
253
- InjectionContainer,
254
- printValue,
255
- RsXCoreInjectionTokens,
256
- RsXCoreModule
242
+ IEqualityService,
243
+ InjectionContainer,
244
+ printValue,
245
+ RsXCoreInjectionTokens,
246
+ RsXCoreModule,
257
247
  } from '@rs-x/core';
258
248
 
259
249
  // Load the core module into the injection container
260
250
  InjectionContainer.load(RsXCoreModule);
261
- const equalityService: IEqualityService = InjectionContainer.get(RsXCoreInjectionTokens.IEqualityService);
251
+ const equalityService: IEqualityService = InjectionContainer.get(
252
+ RsXCoreInjectionTokens.IEqualityService,
253
+ );
262
254
 
263
255
  export const run = (() => {
264
- const object1 = {
265
- a: 10,
266
- nested: {
267
- b: 20
268
- }
269
- };
270
- const object2 = {
271
- a: 10,
272
- nested: {
273
- b: 20
274
- }
275
- };
256
+ const object1 = {
257
+ a: 10,
258
+ nested: {
259
+ b: 20,
260
+ },
261
+ };
262
+ const object2 = {
263
+ a: 10,
264
+ nested: {
265
+ b: 20,
266
+ },
267
+ };
276
268
 
277
- printValue(object1);
278
- console.log('is equal to')
279
- printValue(object2);
269
+ printValue(object1);
270
+ console.log('is equal to');
271
+ printValue(object2);
280
272
 
281
- const result = equalityService.isEqual(object1, object2);
282
- console.log(`Result: ${result}`)
273
+ const result = equalityService.isEqual(object1, object2);
274
+ console.log(`Result: ${result}`);
283
275
  })();
284
276
  ```
285
277
 
286
278
  **Output:**
279
+
287
280
  ```console
288
281
  Running demo: demo/src/rs-x-core/equality-service.ts
289
282
  {
@@ -313,10 +306,7 @@ You must load the core module into the injection container if you want
313
306
  to use it.
314
307
 
315
308
  ```ts
316
- import {
317
- InjectionContainer,
318
- RsXCoreModule
319
- } from '@rs-x/core';
309
+ import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
320
310
 
321
311
  InjectionContainer.load(RsXCoreModule);
322
312
  ```
@@ -325,57 +315,55 @@ There are two ways to get an instance:
325
315
 
326
316
  1. Using the injection container
327
317
 
328
- ```ts
329
- import {
330
- IGuidFactory,
331
- InjectionContainer,
332
- RsXCoreInjectionTokens
333
- } from '@rs-x/core';
334
-
335
- const guidFactory: IGuidFactory = InjectionContainer.get(
336
- RsXCoreInjectionTokens.IGuidFactory
337
- );
338
- ```
318
+ ```ts
319
+ import {
320
+ IGuidFactory,
321
+ InjectionContainer,
322
+ RsXCoreInjectionTokens,
323
+ } from '@rs-x/core';
324
+
325
+ const guidFactory: IGuidFactory = InjectionContainer.get(
326
+ RsXCoreInjectionTokens.IGuidFactory,
327
+ );
328
+ ```
339
329
 
340
330
  2. Using the `@Inject` decorator
341
331
 
342
- ```ts
343
- import {
344
- IGuidFactory,
345
- Inject,
346
- RsXCoreInjectionTokens
347
- } from '@rs-x/core';
348
-
349
- export class MyClass {
332
+ ```ts
333
+ import { IGuidFactory, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
350
334
 
351
- constructor(
352
- @Inject(RsXCoreInjectionTokens.IGuidFactory)
353
- private readonly _guidFactory: IGuidFactory
354
- ) {}
355
- }
356
- ```
335
+ export class MyClass {
336
+ constructor(
337
+ @Inject(RsXCoreInjectionTokens.IGuidFactory)
338
+ private readonly _guidFactory: IGuidFactory,
339
+ ) {}
340
+ }
341
+ ```
357
342
 
358
343
  The following example shows how to use the guid factory
359
344
 
360
345
  ```ts
361
346
  import {
362
- IGuidFactory,
363
- InjectionContainer,
364
- RsXCoreInjectionTokens,
365
- RsXCoreModule
347
+ IGuidFactory,
348
+ InjectionContainer,
349
+ RsXCoreInjectionTokens,
350
+ RsXCoreModule,
366
351
  } from '@rs-x/core';
367
352
 
368
353
  // Load the core module into the injection container
369
354
  InjectionContainer.load(RsXCoreModule);
370
- const guidFactory: IGuidFactory = InjectionContainer.get(RsXCoreInjectionTokens.IGuidFactory);
355
+ const guidFactory: IGuidFactory = InjectionContainer.get(
356
+ RsXCoreInjectionTokens.IGuidFactory,
357
+ );
371
358
 
372
359
  export const run = (() => {
373
- const guid = guidFactory.create();
374
- console.log(`Created guid: ${guid}`)
360
+ const guid = guidFactory.create();
361
+ console.log(`Created guid: ${guid}`);
375
362
  })();
376
363
  ```
377
364
 
378
365
  **Output:**
366
+
379
367
  ```console
380
368
  Running demo: demo/src/rs-x-core/guid-factory.ts
381
369
  Created guid 1f64aabb-a57e-42e7-9edf-71c24773c150
@@ -430,6 +418,7 @@ Normalizes access to object properties, array indices, map keys, and similar ind
430
418
  ### Members
431
419
 
432
420
  ### **priority**
421
+
433
422
  **Type:** `number`
434
423
  Defines the priority of the index value accessor. Higher numbers indicate higher priority when selecting which accessor to use.
435
424
 
@@ -531,14 +520,14 @@ Returns `true` if this index value accessor supports the given `(context, index)
531
520
  The default `IIndexValueAccessor` implementation internally uses the following list of `IIndexValueAccessor` implementations.
532
521
  The accessors are evaluated in order of **priority**, with higher-priority accessors being checked first:
533
522
 
534
- * **`PropertyValueAccessor`** – accesses properties or fields on an object. Priority = 7
535
- * **`MethodAccessor`** – accesses methods on an object. Priority = 6
536
- * **`ArrayIndexAccessor`** – accesses array items. Priority = 5
537
- * **`MapKeyccessor`** – accesses map items. Priority = 4
538
- * **`SetKeyAccessor`** – accesses `Set` items. Priority = 3
539
- * **`ObservableAccessor`** – accesses the latest value emitted by an `Observable`. Priority = 2
540
- * **`PromiseAccessor`** – accesses the resolved value of a `Promise`. Priority = 1
541
- * **`DatePropertyAccessor`** – accesses date-related properties. Priority = 0
523
+ - **`PropertyValueAccessor`** – accesses properties or fields on an object. Priority = 7
524
+ - **`MethodAccessor`** – accesses methods on an object. Priority = 6
525
+ - **`ArrayIndexAccessor`** – accesses array items. Priority = 5
526
+ - **`MapKeyccessor`** – accesses map items. Priority = 4
527
+ - **`SetKeyAccessor`** – accesses `Set` items. Priority = 3
528
+ - **`ObservableAccessor`** – accesses the latest value emitted by an `Observable`. Priority = 2
529
+ - **`PromiseAccessor`** – accesses the resolved value of a `Promise`. Priority = 1
530
+ - **`DatePropertyAccessor`** – accesses date-related properties. Priority = 0
542
531
 
543
532
  The default accessor attempts to find the appropriate index value accessor for a given `(context, index)` pair and delegates the operation to it.
544
533
 
@@ -546,15 +535,12 @@ If no suitable index value accessor can be found, an `UnsupportedException` is t
546
535
 
547
536
  ### Get an instance of the Index Value Accessor Service
548
537
 
549
- The index value accessor service is registered as a **singleton service**.
538
+ The index value accessor service is registered as a **singleton service**.
550
539
  You must load the core module into the injection container if you want
551
540
  to use it.
552
541
 
553
542
  ```ts
554
- import {
555
- InjectionContainer,
556
- RsXCoreModule
557
- } from '@rs-x/core';
543
+ import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
558
544
 
559
545
  InjectionContainer.load(RsXCoreModule);
560
546
  ```
@@ -563,35 +549,34 @@ There are two ways to get an instance:
563
549
 
564
550
  1. Using the injection container
565
551
 
566
- ```ts
567
- import {
568
- IIndexValueAccessor,
569
- InjectionContainer,
570
- RsXCoreInjectionTokens
571
- } from '@rs-x/core';
572
-
573
- const indexValueAccessor: IIndexValueAccessor = InjectionContainer.get(
574
- RsXCoreInjectionTokens.IIndexValueAccessor
575
- );
576
- ```
552
+ ```ts
553
+ import {
554
+ IIndexValueAccessor,
555
+ InjectionContainer,
556
+ RsXCoreInjectionTokens,
557
+ } from '@rs-x/core';
577
558
 
578
- 2. Using the `@Inject` decorator
559
+ const indexValueAccessor: IIndexValueAccessor = InjectionContainer.get(
560
+ RsXCoreInjectionTokens.IIndexValueAccessor,
561
+ );
562
+ ```
579
563
 
580
- ```ts
581
- import {
582
- IIndexValueAccessor,
583
- Inject,
584
- RsXCoreInjectionTokens
585
- } from '@rs-x/core';
586
-
587
- export class MyClass {
564
+ 2. Using the `@Inject` decorator
588
565
 
589
- constructor(
590
- @Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
591
- private readonly _indexValueAccessor: IIndexValueAccessor
592
- ) {}
593
- }
594
- ```
566
+ ```ts
567
+ import {
568
+ IIndexValueAccessor,
569
+ Inject,
570
+ RsXCoreInjectionTokens,
571
+ } from '@rs-x/core';
572
+
573
+ export class MyClass {
574
+ constructor(
575
+ @Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
576
+ private readonly _indexValueAccessor: IIndexValueAccessor,
577
+ ) {}
578
+ }
579
+ ```
595
580
 
596
581
  ### Customize the supported index value accessor list
597
582
 
@@ -657,257 +642,261 @@ For example, suppose we have a service that patches a property on an object so i
657
642
 
658
643
  ```ts
659
644
  import {
660
- IDisposable,
661
- IDisposableOwner,
662
- InvalidOperationException,
663
- IPropertyChange,
664
- IPropertyDescriptor,
665
- PropertyDescriptorType,
666
- SingletonFactory,
667
- Type,
668
- UnsupportedException
645
+ IDisposable,
646
+ IDisposableOwner,
647
+ InvalidOperationException,
648
+ IPropertyChange,
649
+ IPropertyDescriptor,
650
+ PropertyDescriptorType,
651
+ SingletonFactory,
652
+ Type,
653
+ UnsupportedException,
669
654
  } from '@rs-x/core';
670
655
  import { Observable, Subject } from 'rxjs';
671
656
 
672
657
  interface IObserver extends IDisposable {
673
- changed: Observable<IPropertyChange>;
658
+ changed: Observable<IPropertyChange>;
674
659
  }
675
660
 
676
661
  class PropertObserver implements IObserver {
677
- private _isDisposed = false;
678
- private _value: unknown;
679
- private _propertyDescriptorWithTarget: IPropertyDescriptor;
680
- private readonly _changed = new Subject<IPropertyChange>();
681
-
682
- constructor(
683
- private readonly _owner: IDisposableOwner,
684
- private readonly _target: object,
685
- private readonly _propertyName: string,
686
- ) {
687
- this.patch();
688
- }
689
-
690
- public get changed(): Observable<IPropertyChange> {
691
- return this._changed;
662
+ private _isDisposed = false;
663
+ private _value: unknown;
664
+ private _propertyDescriptorWithTarget: IPropertyDescriptor;
665
+ private readonly _changed = new Subject<IPropertyChange>();
666
+
667
+ constructor(
668
+ private readonly _owner: IDisposableOwner,
669
+ private readonly _target: object,
670
+ private readonly _propertyName: string,
671
+ ) {
672
+ this.patch();
673
+ }
674
+
675
+ public get changed(): Observable<IPropertyChange> {
676
+ return this._changed;
677
+ }
678
+
679
+ public dispose(): void {
680
+ if (this._isDisposed) {
681
+ return;
692
682
  }
693
683
 
694
- public dispose(): void {
695
- if (this._isDisposed) {
696
- return;
697
- }
698
-
699
- if (!this._owner?.canDispose || this._owner.canDispose()) {
700
- const propertyName = this._propertyName as string;
701
- const value = this._target[propertyName];
702
- //to prevent errors if is was non configurable
703
- delete this._target[propertyName];
704
-
705
- if (
706
- this._propertyDescriptorWithTarget.type !==
707
- PropertyDescriptorType.Function
708
- ) {
709
- this._target[propertyName] = value
710
- }
684
+ if (!this._owner?.canDispose || this._owner.canDispose()) {
685
+ const propertyName = this._propertyName as string;
686
+ const value = this._target[propertyName];
687
+ //to prevent errors if is was non configurable
688
+ delete this._target[propertyName];
711
689
 
712
- this._propertyDescriptorWithTarget = undefined;
713
- }
690
+ if (
691
+ this._propertyDescriptorWithTarget.type !==
692
+ PropertyDescriptorType.Function
693
+ ) {
694
+ this._target[propertyName] = value;
695
+ }
714
696
 
715
- this._owner?.release?.();
697
+ this._propertyDescriptorWithTarget = undefined;
716
698
  }
717
699
 
718
- private patch(): void {
719
- const descriptorWithTarget = Type.getPropertyDescriptor(
720
- this._target,
721
- this._propertyName
722
- );
723
- const descriptor = descriptorWithTarget.descriptor;
724
- let newDescriptor: PropertyDescriptor;
725
-
726
- if (descriptorWithTarget.type === PropertyDescriptorType.Function) {
727
- throw new UnsupportedException('Methods are not supported')
728
- } else if (!descriptor.get && !descriptor.set) {
729
- newDescriptor =
730
- this.createFieldPropertyDescriptor(descriptorWithTarget);
731
- } else if (descriptor.set) {
732
- newDescriptor =
733
- this.createWritablePropertyDescriptor(descriptorWithTarget);
734
- } else {
735
- throw new InvalidOperationException(
736
- `Property '${this._propertyName}' can not be watched because it is readonly`
737
- );
738
- }
700
+ this._owner?.release?.();
701
+ }
739
702
 
740
- Object.defineProperty(this._target, this._propertyName, newDescriptor);
741
-
742
- this._propertyDescriptorWithTarget = descriptorWithTarget;
703
+ private patch(): void {
704
+ const descriptorWithTarget = Type.getPropertyDescriptor(
705
+ this._target,
706
+ this._propertyName,
707
+ );
708
+ const descriptor = descriptorWithTarget.descriptor;
709
+ let newDescriptor: PropertyDescriptor;
710
+
711
+ if (descriptorWithTarget.type === PropertyDescriptorType.Function) {
712
+ throw new UnsupportedException('Methods are not supported');
713
+ } else if (!descriptor.get && !descriptor.set) {
714
+ newDescriptor = this.createFieldPropertyDescriptor(descriptorWithTarget);
715
+ } else if (descriptor.set) {
716
+ newDescriptor =
717
+ this.createWritablePropertyDescriptor(descriptorWithTarget);
718
+ } else {
719
+ throw new InvalidOperationException(
720
+ `Property '${this._propertyName}' can not be watched because it is readonly`,
721
+ );
743
722
  }
744
723
 
745
- private emitChange(change: Partial<IPropertyChange>, id: unknown) {
746
- this._value = change.newValue;
724
+ Object.defineProperty(this._target, this._propertyName, newDescriptor);
747
725
 
748
- this._changed.next({
749
- arguments: [],
750
- ...change,
751
- chain: [{ object: this._target, id: this._propertyName }],
752
- target: this._target,
753
- id,
754
- });
755
- }
726
+ this._propertyDescriptorWithTarget = descriptorWithTarget;
727
+ }
756
728
 
757
- private createFieldPropertyDescriptor(
758
- descriptorWithTarget: IPropertyDescriptor
759
- ): PropertyDescriptor {
760
- const newDescriptor = { ...descriptorWithTarget.descriptor };
729
+ private emitChange(change: Partial<IPropertyChange>, id: unknown) {
730
+ this._value = change.newValue;
761
731
 
762
- newDescriptor.get = () => this._value;
763
- delete newDescriptor.writable;
764
- delete newDescriptor.value;
732
+ this._changed.next({
733
+ arguments: [],
734
+ ...change,
735
+ chain: [{ object: this._target, id: this._propertyName }],
736
+ target: this._target,
737
+ id,
738
+ });
739
+ }
765
740
 
766
- newDescriptor.set = (value) => {
767
- if (value !== this._value) {
768
- this.emitChange({ newValue: value, }, this._propertyName);
769
- }
770
- };
741
+ private createFieldPropertyDescriptor(
742
+ descriptorWithTarget: IPropertyDescriptor,
743
+ ): PropertyDescriptor {
744
+ const newDescriptor = { ...descriptorWithTarget.descriptor };
771
745
 
772
- this._value = this._target[this._propertyName];
746
+ newDescriptor.get = () => this._value;
747
+ delete newDescriptor.writable;
748
+ delete newDescriptor.value;
773
749
 
774
- return newDescriptor;
775
- }
750
+ newDescriptor.set = (value) => {
751
+ if (value !== this._value) {
752
+ this.emitChange({ newValue: value }, this._propertyName);
753
+ }
754
+ };
776
755
 
777
- private createWritablePropertyDescriptor(
778
- descriptorWithTarget: IPropertyDescriptor
779
- ): PropertyDescriptor {
780
- const newDescriptor = { ...descriptorWithTarget.descriptor };
781
- const oldSetter = descriptorWithTarget.descriptor.set;
782
- newDescriptor.set = (value) => {
783
- const oldValue = this._target[this._propertyName];
784
- if (value !== oldValue) {
785
- oldSetter.call(this._target, value);
786
- this.emitChange({ newValue: value }, this._propertyName);
787
- }
788
- };
756
+ this._value = this._target[this._propertyName];
757
+
758
+ return newDescriptor;
759
+ }
760
+
761
+ private createWritablePropertyDescriptor(
762
+ descriptorWithTarget: IPropertyDescriptor,
763
+ ): PropertyDescriptor {
764
+ const newDescriptor = { ...descriptorWithTarget.descriptor };
765
+ const oldSetter = descriptorWithTarget.descriptor.set;
766
+ newDescriptor.set = (value) => {
767
+ const oldValue = this._target[this._propertyName];
768
+ if (value !== oldValue) {
769
+ oldSetter.call(this._target, value);
770
+ this.emitChange({ newValue: value }, this._propertyName);
771
+ }
772
+ };
789
773
 
790
- this._value = this._target[this._propertyName];
774
+ this._value = this._target[this._propertyName];
791
775
 
792
- return newDescriptor;
793
- }
776
+ return newDescriptor;
777
+ }
794
778
  }
795
779
 
796
- class PropertyObserverManager
797
- extends SingletonFactory<
798
- string,
799
- string,
800
- IObserver,
801
- string
802
- > {
803
- constructor(
804
- private readonly _object: object,
805
- private readonly releaseObject: () => void
806
- ) {
807
- super();
808
- }
809
-
810
- public override getId(propertyName: string): string {
811
- return propertyName;
812
- }
813
-
814
- protected override createId(propertyName: string): string {
815
- return propertyName;
816
- }
780
+ class PropertyObserverManager extends SingletonFactory<
781
+ string,
782
+ string,
783
+ IObserver,
784
+ string
785
+ > {
786
+ constructor(
787
+ private readonly _object: object,
788
+ private readonly releaseObject: () => void,
789
+ ) {
790
+ super();
791
+ }
792
+
793
+ public override getId(propertyName: string): string {
794
+ return propertyName;
795
+ }
796
+
797
+ protected override createId(propertyName: string): string {
798
+ return propertyName;
799
+ }
800
+
801
+ protected override createInstance(
802
+ propertyName: string,
803
+ id: string,
804
+ ): IObserver {
805
+ return new PropertObserver(
806
+ {
807
+ canDispose: () => this.getReferenceCount(id) === 1,
808
+ release: () => this.release(id),
809
+ },
810
+ this._object,
811
+ propertyName,
812
+ );
813
+ }
817
814
 
818
- protected override createInstance(
819
- propertyName: string,
820
- id: string
821
- ): IObserver {
822
- return new PropertObserver(
823
- {
824
- canDispose: () => this.getReferenceCount(id) === 1,
825
- release: () => this.release(id),
826
- },
827
- this._object,
828
- propertyName
829
- );
830
- }
815
+ protected override onReleased(): void {
816
+ this.releaseObject();
817
+ }
831
818
 
832
- protected override onReleased(): void {
833
- this.releaseObject();
834
- }
835
-
836
- protected override releaseInstance(observer: IObserver): void {
837
- observer.dispose();
838
- }
819
+ protected override releaseInstance(observer: IObserver): void {
820
+ observer.dispose();
821
+ }
839
822
  }
840
823
 
841
- class ObjectPropertyObserverManager
842
- extends SingletonFactory<object, object, PropertyObserverManager> {
843
- constructor() { super(); }
844
-
845
- public override getId(context: object): object {
846
- return context;
847
- }
848
-
849
- protected override createId(context: object): object {
850
- return context;
851
- }
852
-
853
- protected override createInstance(
854
- context: object
855
- ): PropertyObserverManager {
856
- return new PropertyObserverManager(
857
- context,
858
- () => this.release(context)
859
- );
860
- }
861
- protected override releaseInstance(propertyObserverManager: PropertyObserverManager): void {
862
- propertyObserverManager.dispose();
863
- }
824
+ class ObjectPropertyObserverManager extends SingletonFactory<
825
+ object,
826
+ object,
827
+ PropertyObserverManager
828
+ > {
829
+ constructor() {
830
+ super();
831
+ }
832
+
833
+ public override getId(context: object): object {
834
+ return context;
835
+ }
836
+
837
+ protected override createId(context: object): object {
838
+ return context;
839
+ }
840
+
841
+ protected override createInstance(context: object): PropertyObserverManager {
842
+ return new PropertyObserverManager(context, () => this.release(context));
843
+ }
844
+ protected override releaseInstance(
845
+ propertyObserverManager: PropertyObserverManager,
846
+ ): void {
847
+ propertyObserverManager.dispose();
848
+ }
864
849
  }
865
850
 
866
851
  class PropertyObserverFactory {
867
- private readonly _objectPropertyObserverManager = new ObjectPropertyObserverManager();
868
-
869
- public create(context: object, propertyName: string): IObserver {
870
- return this._objectPropertyObserverManager
871
- .create(context).instance
872
- .create(propertyName).instance;
873
- }
852
+ private readonly _objectPropertyObserverManager =
853
+ new ObjectPropertyObserverManager();
854
+
855
+ public create(context: object, propertyName: string): IObserver {
856
+ return this._objectPropertyObserverManager
857
+ .create(context)
858
+ .instance.create(propertyName).instance;
859
+ }
874
860
  }
875
861
 
876
862
  export const run = (() => {
877
- const context = {
878
- a: 10
879
- };
880
- const propertyObserverFactory = new PropertyObserverFactory();
881
-
882
- const aObserver1 = propertyObserverFactory.create(context, 'a');
883
- const aObserver2 = propertyObserverFactory.create(context, 'a');
884
-
885
- const changeSubsription1 = aObserver1.changed.subscribe((change) => {
886
- console.log('Observer 1:')
887
- console.log(change.newValue)
888
- });
889
- const changeSubsription2 = aObserver1.changed.subscribe((change) => {
890
- console.log('Observer 2:')
891
- console.log(change.newValue)
892
- });
893
-
894
- console.log('You can observe the same property multiple times but only one observer will be create:');
895
- console.log(aObserver1 === aObserver2);
896
-
897
- console.log('Changing value to 20:')
898
-
899
- context.a = 20;
900
-
901
- // Dispose of the observers
902
- aObserver1.dispose();
903
- aObserver2.dispose();
904
- // Unsubsribe to the changed event
905
- changeSubsription1.unsubscribe();
906
- changeSubsription2.unsubscribe();
863
+ const context = {
864
+ a: 10,
865
+ };
866
+ const propertyObserverFactory = new PropertyObserverFactory();
867
+
868
+ const aObserver1 = propertyObserverFactory.create(context, 'a');
869
+ const aObserver2 = propertyObserverFactory.create(context, 'a');
870
+
871
+ const changeSubsription1 = aObserver1.changed.subscribe((change) => {
872
+ console.log('Observer 1:');
873
+ console.log(change.newValue);
874
+ });
875
+ const changeSubsription2 = aObserver1.changed.subscribe((change) => {
876
+ console.log('Observer 2:');
877
+ console.log(change.newValue);
878
+ });
879
+
880
+ console.log(
881
+ 'You can observe the same property multiple times but only one observer will be create:',
882
+ );
883
+ console.log(aObserver1 === aObserver2);
884
+
885
+ console.log('Changing value to 20:');
886
+
887
+ context.a = 20;
888
+
889
+ // Dispose of the observers
890
+ aObserver1.dispose();
891
+ aObserver2.dispose();
892
+ // Unsubsribe to the changed event
893
+ changeSubsription1.unsubscribe();
894
+ changeSubsription2.unsubscribe();
907
895
  })();
908
896
  ```
909
897
 
910
898
  **Output:**
899
+
911
900
  ```console
912
901
  Running demo: demo/src/rs-x-core/implementation-of-singleton-factory.ts
913
902
  You can observe the same property multiple times but only one observer will be create:
@@ -921,61 +910,60 @@ Observer 2:
921
910
 
922
911
  In this example, we have derived two classes from `SingletonFactory`:
923
912
 
924
- * **`PropertyObserverManager`** – ensures that only one `PropertyObserver` is created per property.
925
- * **`ObjectPropertyObserverManager`** – ensures that only one `PropertyObserverManager` is created per object.
913
+ - **`PropertyObserverManager`** – ensures that only one `PropertyObserver` is created per property.
914
+ - **`ObjectPropertyObserverManager`** – ensures that only one `PropertyObserverManager` is created per object.
926
915
 
927
- It is good practice **not to expose classes derived from `SingletonFactory`** directly, but to use them internally to keep the interface simple.
916
+ It is good practice **not to expose classes derived from `SingletonFactory`** directly, but to use them internally to keep the interface simple.
928
917
 
929
- For example, we have created a class **`PropertyObserverFactory`** that internally uses `ObjectPropertyObserverManager`.
918
+ For example, we have created a class **`PropertyObserverFactory`** that internally uses `ObjectPropertyObserverManager`.
930
919
 
931
920
  The `PropertyObserver` class implements a `dispose` method, which ensures that it is released when there are no references left.
932
921
 
933
-
934
922
  ## Error Log
935
923
 
936
924
  Basic logging using `console.error`
937
925
 
938
926
  ### interface IErrorLog
939
927
 
940
-
941
928
  ```ts
942
929
  export interface IErrorLog {
943
- readonly error: Observable<IError>;
944
- add(error: IError): void;
945
- clear(): void;
930
+ readonly error: Observable<IError>;
931
+ add(error: IError): void;
932
+ clear(): void;
946
933
  }
947
934
  ```
948
935
 
949
936
  ### Members
950
937
 
951
938
  ### **error**
939
+
952
940
  **Type:** `Observable<IError>`
953
941
  event emitted when error is added
954
942
 
955
943
  ---
956
944
 
957
945
  #### **add(error)**
946
+
958
947
  log a new error and emit error event.
959
948
 
960
949
  | Parameter | Type | Description |
961
950
  | --------- | -------- | ----------- |
962
951
  | **error** | `IError` | error. |
963
952
 
964
-
965
- **Returns:** `void`
953
+ **Returns:** `void`
966
954
 
967
955
  ---
968
956
 
969
957
  #### **clear()**
958
+
970
959
  removes all logged errors
971
960
 
972
- **Returns:** `void`
961
+ **Returns:** `void`
973
962
 
974
963
  ---
975
964
 
976
965
  The default implementation uses `console.error` to log an error.
977
966
 
978
-
979
967
  ### Get an instance of the Error Log
980
968
 
981
969
  The error log is registered as a **singleton service**.
@@ -983,10 +971,7 @@ You must load the core module into the injection container if you want
983
971
  to use it.
984
972
 
985
973
  ```ts
986
- import {
987
- InjectionContainer,
988
- RsXCoreModule
989
- } from '@rs-x/core';
974
+ import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
990
975
 
991
976
  InjectionContainer.load(RsXCoreModule);
992
977
  ```
@@ -995,75 +980,73 @@ There are two ways to get an instance:
995
980
 
996
981
  1. Using the injection container
997
982
 
998
- ```ts
999
- import {
1000
- IErrorLog,
1001
- InjectionContainer,
1002
- RsXCoreInjectionTokens
1003
- } from '@rs-x/core';
1004
-
1005
- const errorLog: IErrorLog = InjectionContainer.get(
1006
- RsXCoreInjectionTokens.IErrorLog
1007
- );
1008
- ```
983
+ ```ts
984
+ import {
985
+ IErrorLog,
986
+ InjectionContainer,
987
+ RsXCoreInjectionTokens,
988
+ } from '@rs-x/core';
989
+
990
+ const errorLog: IErrorLog = InjectionContainer.get(
991
+ RsXCoreInjectionTokens.IErrorLog,
992
+ );
993
+ ```
1009
994
 
1010
995
  2. Using the `@Inject` decorator
1011
996
 
1012
- ```ts
1013
- import {
1014
- IErrorLog,
1015
- Inject,
1016
- RsXCoreInjectionTokens
1017
- } from '@rs-x/core';
1018
-
1019
- export class MyClass {
997
+ ```ts
998
+ import { IErrorLog, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
1020
999
 
1021
- constructor(
1022
- @Inject(RsXCoreInjectionTokens.IErrorLog)
1023
- private readonly _errorLog: IErrorLog
1024
- ) {}
1025
- }
1026
- ```
1000
+ export class MyClass {
1001
+ constructor(
1002
+ @Inject(RsXCoreInjectionTokens.IErrorLog)
1003
+ private readonly _errorLog: IErrorLog,
1004
+ ) {}
1005
+ }
1006
+ ```
1027
1007
 
1028
1008
  The following example shows how to use the error log
1029
1009
 
1030
1010
  ```ts
1031
1011
  import {
1032
- IErrorLog,
1033
- InjectionContainer,
1034
- printValue,
1035
- RsXCoreInjectionTokens,
1036
- RsXCoreModule
1012
+ IErrorLog,
1013
+ InjectionContainer,
1014
+ printValue,
1015
+ RsXCoreInjectionTokens,
1016
+ RsXCoreModule,
1037
1017
  } from '@rs-x/core';
1038
1018
 
1039
1019
  // Load the core module into the injection container
1040
1020
  InjectionContainer.load(RsXCoreModule);
1041
- const errorLog: IErrorLog = InjectionContainer.get(RsXCoreInjectionTokens.IErrorLog);
1021
+ const errorLog: IErrorLog = InjectionContainer.get(
1022
+ RsXCoreInjectionTokens.IErrorLog,
1023
+ );
1042
1024
 
1043
1025
  export const run = (() => {
1044
- const context = {
1045
- name: 'My error context'
1046
- };
1047
- const changeSubscription = errorLog.error.subscribe(e => {
1048
- console.log('Emmitted error');
1049
- printValue(e);
1026
+ const context = {
1027
+ name: 'My error context',
1028
+ };
1029
+ const changeSubscription = errorLog.error.subscribe((e) => {
1030
+ console.log('Emmitted error');
1031
+ printValue(e);
1032
+ });
1033
+
1034
+ try {
1035
+ throw new Error('Oops an error');
1036
+ } catch (e) {
1037
+ errorLog.add({
1038
+ exception: e,
1039
+ message: 'Oops',
1040
+ context,
1050
1041
  });
1051
-
1052
- try {
1053
- throw new Error('Oops an error');
1054
- } catch (e) {
1055
- errorLog.add({
1056
- exception: e,
1057
- message: 'Oops',
1058
- context,
1059
- });
1060
- } finally {
1061
- changeSubscription.unsubscribe();
1062
- }
1042
+ } finally {
1043
+ changeSubscription.unsubscribe();
1044
+ }
1063
1045
  })();
1064
1046
  ```
1065
1047
 
1066
1048
  **Output:**
1049
+
1067
1050
  ```console
1068
1051
  Running demo: demo/src/rs-x-core/error-log.ts
1069
1052
  Emmitted error
@@ -1090,7 +1073,7 @@ It is particularly useful in **tests**, **async workflows**, and **event-driven
1090
1073
  - Waits for one or multiple observable emissions
1091
1074
  - Supports synchronous, `Promise`, or `Observable` triggers
1092
1075
  - Optional timeout handling
1093
- - Ability to ignore the initial observable value. For example when the event is implemented with `BehaviorSubject` or `ReplaySubject`
1076
+ - Ability to ignore the initial observable value. For example when the event is implemented with `BehaviorSubject` or `ReplaySubject`
1094
1077
 
1095
1078
  ---
1096
1079
 
@@ -1110,7 +1093,6 @@ constructor(
1110
1093
  | eventName | E | Name of the observable property to wait on |
1111
1094
  | options | WaitOptions<T, E, R> | Optional configuration |
1112
1095
 
1113
-
1114
1096
  #### `WaitOptions<T, E, R>`
1115
1097
 
1116
1098
  Configuration options for waiting.
@@ -1121,9 +1103,8 @@ Configuration options for waiting.
1121
1103
  | timeout | `number` | 100 | Timeout in milliseconds |
1122
1104
  | ignoreInitialValue | `boolean` | false | Ignore the first emitted value |
1123
1105
 
1124
- ---
1106
+ ---
1125
1107
 
1126
-
1127
1108
  ### Methods
1128
1109
 
1129
1110
  #### **wait(trigger)**
@@ -1138,42 +1119,39 @@ Waits for the observable to emit the specified number of events after running th
1138
1119
 
1139
1120
  ---
1140
1121
 
1141
-
1142
1122
  ### Example
1143
1123
 
1144
1124
  ```ts
1145
- import {
1146
- printValue,
1147
- WaitForEvent,
1148
-
1149
- } from '@rs-x/core';
1125
+ import { printValue, WaitForEvent } from '@rs-x/core';
1150
1126
  import { Observable, Subject } from 'rxjs';
1151
1127
 
1152
1128
  export const run = (async () => {
1153
- class MyEventContext {
1154
- private readonly _message = new Subject<string>();
1155
-
1129
+ class MyEventContext {
1130
+ private readonly _message = new Subject<string>();
1156
1131
 
1157
- public get message(): Observable<string> {
1158
- return this._message;
1159
- }
1160
-
1161
- public emitMessage(message: string): void {
1162
- this._message.next(message);
1163
- }
1132
+ public get message(): Observable<string> {
1133
+ return this._message;
1164
1134
  }
1165
1135
 
1166
- const eventContext = new MyEventContext();
1167
- const result = await new WaitForEvent(eventContext, 'message', { count: 2 }).wait(() => {
1168
- eventContext.emitMessage('Hello');
1169
- eventContext.emitMessage('hi');
1170
- });
1171
- console.log('Emitted events:');
1172
- printValue(result);
1136
+ public emitMessage(message: string): void {
1137
+ this._message.next(message);
1138
+ }
1139
+ }
1140
+
1141
+ const eventContext = new MyEventContext();
1142
+ const result = await new WaitForEvent(eventContext, 'message', {
1143
+ count: 2,
1144
+ }).wait(() => {
1145
+ eventContext.emitMessage('Hello');
1146
+ eventContext.emitMessage('hi');
1147
+ });
1148
+ console.log('Emitted events:');
1149
+ printValue(result);
1173
1150
  })();
1174
1151
  ```
1175
1152
 
1176
1153
  **Output:**
1154
+
1177
1155
  ```console
1178
1156
  Running demo: demo/src/rs-x-core/wait-for-event.ts
1179
1157
  Emitted events:
@@ -1181,4 +1159,4 @@ Emitted events:
1181
1159
  Hello,
1182
1160
  hi
1183
1161
  ]
1184
- ```
1162
+ ```