@rs-x/core 0.4.4
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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +2222 -0
- package/package.json +65 -0
- package/readme.md +1184 -0
package/readme.md
ADDED
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
# Core
|
|
2
|
+
|
|
3
|
+
Provides shared core functionality for the RS-X project:
|
|
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)
|
|
13
|
+
|
|
14
|
+
## Dependency Injection
|
|
15
|
+
Implemented with [Inversify](https://github.com/inversify/InversifyJS).
|
|
16
|
+
|
|
17
|
+
The following aliases were added to make them consistent with the code style used throughout the project:
|
|
18
|
+
|
|
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`
|
|
24
|
+
|
|
25
|
+
In addition, the following extensions were added to Inversify:
|
|
26
|
+
|
|
27
|
+
### Multi-Inject Service Utilities
|
|
28
|
+
|
|
29
|
+
These functions help manage **multi-injectable services** in an Inversify `Container` or `ContainerModuleLoadOptions`. They allow registering multiple implementations for a single token and overriding existing multi-inject lists.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
#### `registerMultiInjectServices(options, multiInjectToken, services)`
|
|
34
|
+
|
|
35
|
+
Registers multiple services under a single multi-inject token.
|
|
36
|
+
|
|
37
|
+
**Parameters:**
|
|
38
|
+
|
|
39
|
+
| Parameter | Type | Description |
|
|
40
|
+
| ------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `options` | `ContainerModuleLoadOptions` | The container or module options used for binding. |
|
|
42
|
+
| `multiInjectToken` | `symbol` | The multi-inject token that groups the services. |
|
|
43
|
+
| `services` | `MultiInjectService[]` | Array of service definitions to register. Each service must define a `target` (class) and optional `token` (symbol). |
|
|
44
|
+
|
|
45
|
+
**Behavior:**
|
|
46
|
+
|
|
47
|
+
- Iterates through the list of services and registers each using `registerMultiInjectService`.
|
|
48
|
+
- Each service is bound to the container and added to the multi-inject token.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
#### `registerMultiInjectService(container, target, options)`
|
|
53
|
+
|
|
54
|
+
Registers a single service under a multi-inject token.
|
|
55
|
+
|
|
56
|
+
**Parameters:**
|
|
57
|
+
|
|
58
|
+
| Parameter | Type | Description |
|
|
59
|
+
| ----------- | ----------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
60
|
+
| `container` | `ContainerModuleLoadOptions \| Container` | The container or module to bind to. |
|
|
61
|
+
| `target` | `Newable<unknown>` | The class to bind. |
|
|
62
|
+
| `options` | `IMultiInjectTokens` | Object containing: `multiInjectToken` (symbol) and optional `serviceToken` (symbol). |
|
|
63
|
+
|
|
64
|
+
**Behavior:**
|
|
65
|
+
|
|
66
|
+
- Binds the class itself as a singleton.
|
|
67
|
+
- Optionally binds a service token to the class.
|
|
68
|
+
- Adds the class to the multi-inject token.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
#### `overrideMultiInjectServices(container, multiInjectToken, services)`
|
|
73
|
+
|
|
74
|
+
Overrides an existing multi-inject list, removing any previous bindings for the given token.
|
|
75
|
+
|
|
76
|
+
**Parameters:**
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Description |
|
|
79
|
+
| ------------------ | ----------------------------------------- | ----------------------------------------- |
|
|
80
|
+
| `container` | `Container \| ContainerModuleLoadOptions` | The container or module to bind to. |
|
|
81
|
+
| `multiInjectToken` | `symbol` | The multi-inject token to override. |
|
|
82
|
+
| `services` | `MultiInjectService[]` | Array of service definitions to register. |
|
|
83
|
+
|
|
84
|
+
**Behavior:**
|
|
85
|
+
|
|
86
|
+
- Removes all previous bindings for the given `multiInjectToken`.
|
|
87
|
+
- Binds each service in the list to the container as a singleton.
|
|
88
|
+
- Binds optional service tokens if provided.
|
|
89
|
+
- Ensures no duplicate classes are added to the multi-inject token.
|
|
90
|
+
|
|
91
|
+
**Usage Notes:**
|
|
92
|
+
|
|
93
|
+
- Use this function when you want to completely replace the multi-inject service list.
|
|
94
|
+
- Ensures that `container.getAll(multiInjectToken)` returns only the new services without duplicates.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## Deep Clone
|
|
99
|
+
|
|
100
|
+
- Uses [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) by default
|
|
101
|
+
- Falls back to [Lodash `cloneDeepWith`](https://lodash.com/docs/4.17.15#cloneDeepWith) for unsupported types
|
|
102
|
+
|
|
103
|
+
### Get an instance of the Deep clone service
|
|
104
|
+
|
|
105
|
+
The deep clone service is registered as a **singleton service**.
|
|
106
|
+
You must load the core module into the injection container if you want
|
|
107
|
+
to use it.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import {
|
|
111
|
+
InjectionContainer,
|
|
112
|
+
RsXCoreModule
|
|
113
|
+
} from '@rs-x/core';
|
|
114
|
+
|
|
115
|
+
InjectionContainer.load(RsXCoreModule);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
There are two ways to get an instance:
|
|
119
|
+
|
|
120
|
+
1. Using the injection container
|
|
121
|
+
|
|
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
|
+
```
|
|
133
|
+
|
|
134
|
+
2. Using the `@Inject` decorator
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import {
|
|
138
|
+
IDeepClone,
|
|
139
|
+
Inject,
|
|
140
|
+
RsXCoreInjectionTokens
|
|
141
|
+
} from '@rs-x/core';
|
|
142
|
+
|
|
143
|
+
export class MyClass {
|
|
144
|
+
|
|
145
|
+
constructor(
|
|
146
|
+
@Inject(RsXCoreInjectionTokens.IDeepClone)
|
|
147
|
+
private readonly _deepClone: IDeepClone
|
|
148
|
+
) {}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The following example shows how to use deep clone service:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
IDeepClone,
|
|
157
|
+
InjectionContainer,
|
|
158
|
+
printValue,
|
|
159
|
+
RsXCoreInjectionTokens,
|
|
160
|
+
RsXCoreModule
|
|
161
|
+
} from '@rs-x/core';
|
|
162
|
+
|
|
163
|
+
// Load the core module into the injection container
|
|
164
|
+
InjectionContainer.load(RsXCoreModule);
|
|
165
|
+
const deepClone: IDeepClone = InjectionContainer.get(RsXCoreInjectionTokens.IDeepClone);
|
|
166
|
+
|
|
167
|
+
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);
|
|
179
|
+
})();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Output:**
|
|
183
|
+
```console
|
|
184
|
+
Running demo: demo/src/rs-x-core/deep-clone.ts
|
|
185
|
+
Clone is a copy of the cloned object: true
|
|
186
|
+
Cloned object
|
|
187
|
+
{
|
|
188
|
+
a: 10
|
|
189
|
+
nested: {
|
|
190
|
+
b: 20
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Deep Equality
|
|
196
|
+
|
|
197
|
+
Uses [fast-equals](https://github.com/planttheidea/fast-equals) for deep equality
|
|
198
|
+
|
|
199
|
+
### Get an instance of the Equality Service
|
|
200
|
+
|
|
201
|
+
The equality service is registered as a **singleton service**.
|
|
202
|
+
You must load the core module into the injection container if you want
|
|
203
|
+
to use it.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import {
|
|
207
|
+
InjectionContainer,
|
|
208
|
+
RsXCoreModule
|
|
209
|
+
} from '@rs-x/core';
|
|
210
|
+
|
|
211
|
+
InjectionContainer.load(RsXCoreModule);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
There are two ways to get an instance:
|
|
215
|
+
|
|
216
|
+
1. Using the injection container
|
|
217
|
+
|
|
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
|
+
```
|
|
229
|
+
|
|
230
|
+
2. Using the `@Inject` decorator
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import {
|
|
234
|
+
IEqualityService,
|
|
235
|
+
Inject,
|
|
236
|
+
RsXCoreInjectionTokens
|
|
237
|
+
} from '@rs-x/core';
|
|
238
|
+
|
|
239
|
+
export class MyClass {
|
|
240
|
+
|
|
241
|
+
constructor(
|
|
242
|
+
@Inject(RsXCoreInjectionTokens.IEqualityService)
|
|
243
|
+
private readonly _equalityService: IEqualityService
|
|
244
|
+
) {}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The following example shows how to use equality service
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import {
|
|
252
|
+
IEqualityService,
|
|
253
|
+
InjectionContainer,
|
|
254
|
+
printValue,
|
|
255
|
+
RsXCoreInjectionTokens,
|
|
256
|
+
RsXCoreModule
|
|
257
|
+
} from '@rs-x/core';
|
|
258
|
+
|
|
259
|
+
// Load the core module into the injection container
|
|
260
|
+
InjectionContainer.load(RsXCoreModule);
|
|
261
|
+
const equalityService: IEqualityService = InjectionContainer.get(RsXCoreInjectionTokens.IEqualityService);
|
|
262
|
+
|
|
263
|
+
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
|
+
};
|
|
276
|
+
|
|
277
|
+
printValue(object1);
|
|
278
|
+
console.log('is equal to')
|
|
279
|
+
printValue(object2);
|
|
280
|
+
|
|
281
|
+
const result = equalityService.isEqual(object1, object2);
|
|
282
|
+
console.log(`Result: ${result}`)
|
|
283
|
+
})();
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Output:**
|
|
287
|
+
```console
|
|
288
|
+
Running demo: demo/src/rs-x-core/equality-service.ts
|
|
289
|
+
{
|
|
290
|
+
a: 10
|
|
291
|
+
nested: {
|
|
292
|
+
b: 20
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
is equal to
|
|
296
|
+
{
|
|
297
|
+
a: 10
|
|
298
|
+
nested: {
|
|
299
|
+
b: 20
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
Result: true
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Guid Factory
|
|
306
|
+
|
|
307
|
+
Uses crypto.randomUUID() to create GUIDs
|
|
308
|
+
|
|
309
|
+
### Get an instance of a Guid Factory
|
|
310
|
+
|
|
311
|
+
The guid factory is registered as a **singleton service**.
|
|
312
|
+
You must load the core module into the injection container if you want
|
|
313
|
+
to use it.
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
import {
|
|
317
|
+
InjectionContainer,
|
|
318
|
+
RsXCoreModule
|
|
319
|
+
} from '@rs-x/core';
|
|
320
|
+
|
|
321
|
+
InjectionContainer.load(RsXCoreModule);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
There are two ways to get an instance:
|
|
325
|
+
|
|
326
|
+
1. Using the injection container
|
|
327
|
+
|
|
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
|
+
```
|
|
339
|
+
|
|
340
|
+
2. Using the `@Inject` decorator
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import {
|
|
344
|
+
IGuidFactory,
|
|
345
|
+
Inject,
|
|
346
|
+
RsXCoreInjectionTokens
|
|
347
|
+
} from '@rs-x/core';
|
|
348
|
+
|
|
349
|
+
export class MyClass {
|
|
350
|
+
|
|
351
|
+
constructor(
|
|
352
|
+
@Inject(RsXCoreInjectionTokens.IGuidFactory)
|
|
353
|
+
private readonly _guidFactory: IGuidFactory
|
|
354
|
+
) {}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The following example shows how to use the guid factory
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import {
|
|
362
|
+
IGuidFactory,
|
|
363
|
+
InjectionContainer,
|
|
364
|
+
RsXCoreInjectionTokens,
|
|
365
|
+
RsXCoreModule
|
|
366
|
+
} from '@rs-x/core';
|
|
367
|
+
|
|
368
|
+
// Load the core module into the injection container
|
|
369
|
+
InjectionContainer.load(RsXCoreModule);
|
|
370
|
+
const guidFactory: IGuidFactory = InjectionContainer.get(RsXCoreInjectionTokens.IGuidFactory);
|
|
371
|
+
|
|
372
|
+
export const run = (() => {
|
|
373
|
+
const guid = guidFactory.create();
|
|
374
|
+
console.log(`Created guid: ${guid}`)
|
|
375
|
+
})();
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Output:**
|
|
379
|
+
```console
|
|
380
|
+
Running demo: demo/src/rs-x-core/guid-factory.ts
|
|
381
|
+
Created guid 1f64aabb-a57e-42e7-9edf-71c24773c150
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Index Value Accessor
|
|
385
|
+
|
|
386
|
+
Normalizes access to object properties, array indices, map keys, and similar index-based data structures.
|
|
387
|
+
|
|
388
|
+
### The `IIndexValueAccessor` interface
|
|
389
|
+
|
|
390
|
+
export interface IIndexValueAccessor<TContext = unknown, TIndex = unknown> {
|
|
391
|
+
isAsync(
|
|
392
|
+
context: TContext,
|
|
393
|
+
index: TIndex
|
|
394
|
+
): boolean;
|
|
395
|
+
|
|
396
|
+
getResolvedValue(
|
|
397
|
+
context: TContext,
|
|
398
|
+
index: TIndex
|
|
399
|
+
): unknown;
|
|
400
|
+
|
|
401
|
+
hasValue(
|
|
402
|
+
context: TContext,
|
|
403
|
+
index: TIndex
|
|
404
|
+
): boolean;
|
|
405
|
+
|
|
406
|
+
getValue(
|
|
407
|
+
context: TContext,
|
|
408
|
+
index: TIndex
|
|
409
|
+
): unknown;
|
|
410
|
+
|
|
411
|
+
setValue(
|
|
412
|
+
context: TContext,
|
|
413
|
+
index: TIndex,
|
|
414
|
+
value: unknown
|
|
415
|
+
): void;
|
|
416
|
+
|
|
417
|
+
getIndexes(
|
|
418
|
+
context: TContext,
|
|
419
|
+
index?: TIndex
|
|
420
|
+
): IterableIterator<TIndex>;
|
|
421
|
+
|
|
422
|
+
applies(
|
|
423
|
+
context: unknown,
|
|
424
|
+
index: TIndex
|
|
425
|
+
): boolean;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### Members
|
|
431
|
+
|
|
432
|
+
### **priority**
|
|
433
|
+
**Type:** `number`
|
|
434
|
+
Defines the priority of the index value accessor. Higher numbers indicate higher priority when selecting which accessor to use.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
#### **isAsync(context, index)**
|
|
439
|
+
|
|
440
|
+
Returns `true` if accessing the given index is asynchronous, for example when it yields a `Promise` or an `Observable`.
|
|
441
|
+
|
|
442
|
+
| Parameter | Type | Description |
|
|
443
|
+
| ----------- | --------- | -------------------- |
|
|
444
|
+
| **context** | `unknown` | The index context. |
|
|
445
|
+
| **index** | `unknown` | The index to access. |
|
|
446
|
+
|
|
447
|
+
**Returns:** `boolean` — `true` if the index access is asynchronous; otherwise `false`.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
#### **getResolvedValue(context, index)**
|
|
452
|
+
|
|
453
|
+
Returns the resolved value of the index.
|
|
454
|
+
|
|
455
|
+
The resolved value differs from the raw index value when the index returns a `Promise` or an `Observable`. In such cases, the raw value is the `Promise` or `Observable`, while the resolved value is the value produced by it.
|
|
456
|
+
|
|
457
|
+
| Parameter | Type | Description |
|
|
458
|
+
| ----------- | --------- | -------------------- |
|
|
459
|
+
| **context** | `unknown` | The index context. |
|
|
460
|
+
| **index** | `unknown` | The index to access. |
|
|
461
|
+
|
|
462
|
+
**Returns:** `unknown` — the resolved index value.
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
#### **hasValue(context, index)**
|
|
467
|
+
|
|
468
|
+
Returns `true` if the index has a value in the given context.
|
|
469
|
+
|
|
470
|
+
| Parameter | Type | Description |
|
|
471
|
+
| ----------- | --------- | -------------------- |
|
|
472
|
+
| **context** | `unknown` | The index context. |
|
|
473
|
+
| **index** | `unknown` | The index to access. |
|
|
474
|
+
|
|
475
|
+
**Returns:** `boolean` — `true` if the index has a value; otherwise `false`.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
#### **getValue(context, index)**
|
|
480
|
+
|
|
481
|
+
Returns the raw value of the index.
|
|
482
|
+
|
|
483
|
+
| Parameter | Type | Description |
|
|
484
|
+
| ----------- | --------- | -------------------- |
|
|
485
|
+
| **context** | `unknown` | The index context. |
|
|
486
|
+
| **index** | `unknown` | The index to access. |
|
|
487
|
+
|
|
488
|
+
**Returns:** `unknown` — the index value.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
#### **setValue(context, index, value)**
|
|
493
|
+
|
|
494
|
+
Sets the value of the index.
|
|
495
|
+
|
|
496
|
+
| Parameter | Type | Description |
|
|
497
|
+
| ----------- | --------- | -------------------- |
|
|
498
|
+
| **context** | `unknown` | The index context. |
|
|
499
|
+
| **index** | `unknown` | The index to access. |
|
|
500
|
+
| **value** | `unknown` | The new index value. |
|
|
501
|
+
|
|
502
|
+
**Returns:** `void`
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
#### **getIndexes(context)**
|
|
507
|
+
|
|
508
|
+
Returns all indexes defined for the given context.
|
|
509
|
+
|
|
510
|
+
| Parameter | Type | Description |
|
|
511
|
+
| ----------- | --------- | ------------------ |
|
|
512
|
+
| **context** | `unknown` | The index context. |
|
|
513
|
+
|
|
514
|
+
**Returns:** `IterableIterator<TIndex>` — the supported indexes.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
#### **applies(context, index)**
|
|
519
|
+
|
|
520
|
+
Returns `true` if this index value accessor supports the given `(context, index)` pair.
|
|
521
|
+
|
|
522
|
+
| Parameter | Type | Description |
|
|
523
|
+
| ----------- | --------- | -------------------- |
|
|
524
|
+
| **context** | `unknown` | The index context. |
|
|
525
|
+
| **index** | `unknown` | The index to access. |
|
|
526
|
+
|
|
527
|
+
**Returns:** `boolean` — `true` if the `(context, index)` pair is supported.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
The default `IIndexValueAccessor` implementation internally uses the following list of `IIndexValueAccessor` implementations.
|
|
532
|
+
The accessors are evaluated in order of **priority**, with higher-priority accessors being checked first:
|
|
533
|
+
|
|
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
|
|
542
|
+
|
|
543
|
+
The default accessor attempts to find the appropriate index value accessor for a given `(context, index)` pair and delegates the operation to it.
|
|
544
|
+
|
|
545
|
+
If no suitable index value accessor can be found, an `UnsupportedException` is thrown.
|
|
546
|
+
|
|
547
|
+
### Get an instance of the Index Value Accessor Service
|
|
548
|
+
|
|
549
|
+
The index value accessor service is registered as a **singleton service**.
|
|
550
|
+
You must load the core module into the injection container if you want
|
|
551
|
+
to use it.
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import {
|
|
555
|
+
InjectionContainer,
|
|
556
|
+
RsXCoreModule
|
|
557
|
+
} from '@rs-x/core';
|
|
558
|
+
|
|
559
|
+
InjectionContainer.load(RsXCoreModule);
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
There are two ways to get an instance:
|
|
563
|
+
|
|
564
|
+
1. Using the injection container
|
|
565
|
+
|
|
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
|
+
```
|
|
577
|
+
|
|
578
|
+
2. Using the `@Inject` decorator
|
|
579
|
+
|
|
580
|
+
```ts
|
|
581
|
+
import {
|
|
582
|
+
IIndexValueAccessor,
|
|
583
|
+
Inject,
|
|
584
|
+
RsXCoreInjectionTokens
|
|
585
|
+
} from '@rs-x/core';
|
|
586
|
+
|
|
587
|
+
export class MyClass {
|
|
588
|
+
|
|
589
|
+
constructor(
|
|
590
|
+
@Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
|
|
591
|
+
private readonly _indexValueAccessor: IIndexValueAccessor
|
|
592
|
+
) {}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Customize the supported index value accessor list
|
|
597
|
+
|
|
598
|
+
You can customize the index value accessor list by overriding it:
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
import {
|
|
602
|
+
ArrayIndexAccessor,
|
|
603
|
+
ContainerModule,
|
|
604
|
+
IIndexValueAccessor,
|
|
605
|
+
InjectionContainer,
|
|
606
|
+
overrideMultiInjectServices,
|
|
607
|
+
PropertyValueAccessor,
|
|
608
|
+
RsXCoreInjectionTokens,
|
|
609
|
+
RsXCoreModule
|
|
610
|
+
} from '@rs-x/core';
|
|
611
|
+
|
|
612
|
+
// Load the core module into the injection container
|
|
613
|
+
InjectionContainer.load(RsXCoreModule);
|
|
614
|
+
|
|
615
|
+
export const MyModule = new ContainerModule((options) => {
|
|
616
|
+
overrideMultiInjectServices(options, RsXCoreInjectionTokens.IIndexValueAccessorList,
|
|
617
|
+
[
|
|
618
|
+
{ target: PropertyValueAccessor, token: RsXCoreInjectionTokens.IPropertyValueAccessor },
|
|
619
|
+
{ target: ArrayIndexAccessor, token: RsXCoreInjectionTokens.IArrayIndexAccessor },
|
|
620
|
+
]
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
InjectionContainer.load(MyModule);
|
|
625
|
+
const indexValueAccessor: IIndexValueAccessor = InjectionContainer.get(RsXCoreInjectionTokens.IIndexValueAccessor);
|
|
626
|
+
|
|
627
|
+
export const run = (() => {
|
|
628
|
+
const object = {
|
|
629
|
+
a: 10,
|
|
630
|
+
array: [1, 2],
|
|
631
|
+
map: new Map([['x', 300]])
|
|
632
|
+
};
|
|
633
|
+
const aValue = indexValueAccessor.getValue(object, 'a');
|
|
634
|
+
console.log(`Value of field 'a': ${aValue} `);
|
|
635
|
+
|
|
636
|
+
const arrayValue = indexValueAccessor.getValue(object.array, 1);
|
|
637
|
+
console.log(`Value of 'array[1]': ${arrayValue} `);
|
|
638
|
+
|
|
639
|
+
let errrThrown = false;
|
|
640
|
+
try {
|
|
641
|
+
indexValueAccessor.getValue(object.map, 'x')
|
|
642
|
+
} catch {
|
|
643
|
+
errrThrown = true
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
console.log(`Value of 'map['x'] will throw error: ${errrThrown}`);
|
|
647
|
+
|
|
648
|
+
})();
|
|
649
|
+
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
## Singleton factory
|
|
653
|
+
|
|
654
|
+
Besides static singleton services registered via the dependency injection framework, we sometimes want to be able to create **dynamic singleton services**. These are services that are created based on dynamic data.
|
|
655
|
+
|
|
656
|
+
For example, suppose we have a service that patches a property on an object so it can emit an event whenever the property value changes. In this scenario, we want to ensure that the property is patched **only once**. The example below shows how we can use `SingletonFactory` to implement this:
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
import {
|
|
660
|
+
IDisposable,
|
|
661
|
+
IDisposableOwner,
|
|
662
|
+
InvalidOperationException,
|
|
663
|
+
IPropertyChange,
|
|
664
|
+
IPropertyDescriptor,
|
|
665
|
+
PropertyDescriptorType,
|
|
666
|
+
SingletonFactory,
|
|
667
|
+
Type,
|
|
668
|
+
UnsupportedException
|
|
669
|
+
} from '@rs-x/core';
|
|
670
|
+
import { Observable, Subject } from 'rxjs';
|
|
671
|
+
|
|
672
|
+
interface IObserver extends IDisposable {
|
|
673
|
+
changed: Observable<IPropertyChange>;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
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;
|
|
692
|
+
}
|
|
693
|
+
|
|
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
|
+
}
|
|
711
|
+
|
|
712
|
+
this._propertyDescriptorWithTarget = undefined;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
this._owner?.release?.();
|
|
716
|
+
}
|
|
717
|
+
|
|
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
|
+
}
|
|
739
|
+
|
|
740
|
+
Object.defineProperty(this._target, this._propertyName, newDescriptor);
|
|
741
|
+
|
|
742
|
+
this._propertyDescriptorWithTarget = descriptorWithTarget;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private emitChange(change: Partial<IPropertyChange>, id: unknown) {
|
|
746
|
+
this._value = change.newValue;
|
|
747
|
+
|
|
748
|
+
this._changed.next({
|
|
749
|
+
arguments: [],
|
|
750
|
+
...change,
|
|
751
|
+
chain: [{ object: this._target, id: this._propertyName }],
|
|
752
|
+
target: this._target,
|
|
753
|
+
id,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private createFieldPropertyDescriptor(
|
|
758
|
+
descriptorWithTarget: IPropertyDescriptor
|
|
759
|
+
): PropertyDescriptor {
|
|
760
|
+
const newDescriptor = { ...descriptorWithTarget.descriptor };
|
|
761
|
+
|
|
762
|
+
newDescriptor.get = () => this._value;
|
|
763
|
+
delete newDescriptor.writable;
|
|
764
|
+
delete newDescriptor.value;
|
|
765
|
+
|
|
766
|
+
newDescriptor.set = (value) => {
|
|
767
|
+
if (value !== this._value) {
|
|
768
|
+
this.emitChange({ newValue: value, }, this._propertyName);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
this._value = this._target[this._propertyName];
|
|
773
|
+
|
|
774
|
+
return newDescriptor;
|
|
775
|
+
}
|
|
776
|
+
|
|
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
|
+
};
|
|
789
|
+
|
|
790
|
+
this._value = this._target[this._propertyName];
|
|
791
|
+
|
|
792
|
+
return newDescriptor;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
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
|
+
}
|
|
817
|
+
|
|
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
|
+
}
|
|
831
|
+
|
|
832
|
+
protected override onReleased(): void {
|
|
833
|
+
this.releaseObject();
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
protected override releaseInstance(observer: IObserver): void {
|
|
837
|
+
observer.dispose();
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
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
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
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
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
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();
|
|
907
|
+
})();
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
**Output:**
|
|
911
|
+
```console
|
|
912
|
+
Running demo: demo/src/rs-x-core/implementation-of-singleton-factory.ts
|
|
913
|
+
You can observe the same property multiple times but only one observer will be create:
|
|
914
|
+
true
|
|
915
|
+
Changing value to 20:
|
|
916
|
+
Observer 1:
|
|
917
|
+
20
|
|
918
|
+
Observer 2:
|
|
919
|
+
20
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
In this example, we have derived two classes from `SingletonFactory`:
|
|
923
|
+
|
|
924
|
+
* **`PropertyObserverManager`** – ensures that only one `PropertyObserver` is created per property.
|
|
925
|
+
* **`ObjectPropertyObserverManager`** – ensures that only one `PropertyObserverManager` is created per object.
|
|
926
|
+
|
|
927
|
+
It is good practice **not to expose classes derived from `SingletonFactory`** directly, but to use them internally to keep the interface simple.
|
|
928
|
+
|
|
929
|
+
For example, we have created a class **`PropertyObserverFactory`** that internally uses `ObjectPropertyObserverManager`.
|
|
930
|
+
|
|
931
|
+
The `PropertyObserver` class implements a `dispose` method, which ensures that it is released when there are no references left.
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
## Error Log
|
|
935
|
+
|
|
936
|
+
Basic logging using `console.error`
|
|
937
|
+
|
|
938
|
+
### interface IErrorLog
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
```ts
|
|
942
|
+
export interface IErrorLog {
|
|
943
|
+
readonly error: Observable<IError>;
|
|
944
|
+
add(error: IError): void;
|
|
945
|
+
clear(): void;
|
|
946
|
+
}
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Members
|
|
950
|
+
|
|
951
|
+
### **error**
|
|
952
|
+
**Type:** `Observable<IError>`
|
|
953
|
+
event emitted when error is added
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
#### **add(error)**
|
|
958
|
+
log a new error and emit error event.
|
|
959
|
+
|
|
960
|
+
| Parameter | Type | Description |
|
|
961
|
+
| --------- | -------- | ----------- |
|
|
962
|
+
| **error** | `IError` | error. |
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
**Returns:** `void`
|
|
966
|
+
|
|
967
|
+
---
|
|
968
|
+
|
|
969
|
+
#### **clear()**
|
|
970
|
+
removes all logged errors
|
|
971
|
+
|
|
972
|
+
**Returns:** `void`
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
The default implementation uses `console.error` to log an error.
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
### Get an instance of the Error Log
|
|
980
|
+
|
|
981
|
+
The error log is registered as a **singleton service**.
|
|
982
|
+
You must load the core module into the injection container if you want
|
|
983
|
+
to use it.
|
|
984
|
+
|
|
985
|
+
```ts
|
|
986
|
+
import {
|
|
987
|
+
InjectionContainer,
|
|
988
|
+
RsXCoreModule
|
|
989
|
+
} from '@rs-x/core';
|
|
990
|
+
|
|
991
|
+
InjectionContainer.load(RsXCoreModule);
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
There are two ways to get an instance:
|
|
995
|
+
|
|
996
|
+
1. Using the injection container
|
|
997
|
+
|
|
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
|
+
```
|
|
1009
|
+
|
|
1010
|
+
2. Using the `@Inject` decorator
|
|
1011
|
+
|
|
1012
|
+
```ts
|
|
1013
|
+
import {
|
|
1014
|
+
IErrorLog,
|
|
1015
|
+
Inject,
|
|
1016
|
+
RsXCoreInjectionTokens
|
|
1017
|
+
} from '@rs-x/core';
|
|
1018
|
+
|
|
1019
|
+
export class MyClass {
|
|
1020
|
+
|
|
1021
|
+
constructor(
|
|
1022
|
+
@Inject(RsXCoreInjectionTokens.IErrorLog)
|
|
1023
|
+
private readonly _errorLog: IErrorLog
|
|
1024
|
+
) {}
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
The following example shows how to use the error log
|
|
1029
|
+
|
|
1030
|
+
```ts
|
|
1031
|
+
import {
|
|
1032
|
+
IErrorLog,
|
|
1033
|
+
InjectionContainer,
|
|
1034
|
+
printValue,
|
|
1035
|
+
RsXCoreInjectionTokens,
|
|
1036
|
+
RsXCoreModule
|
|
1037
|
+
} from '@rs-x/core';
|
|
1038
|
+
|
|
1039
|
+
// Load the core module into the injection container
|
|
1040
|
+
InjectionContainer.load(RsXCoreModule);
|
|
1041
|
+
const errorLog: IErrorLog = InjectionContainer.get(RsXCoreInjectionTokens.IErrorLog);
|
|
1042
|
+
|
|
1043
|
+
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);
|
|
1050
|
+
});
|
|
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
|
+
}
|
|
1063
|
+
})();
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
**Output:**
|
|
1067
|
+
```console
|
|
1068
|
+
Running demo: demo/src/rs-x-core/error-log.ts
|
|
1069
|
+
Emmitted error
|
|
1070
|
+
{
|
|
1071
|
+
exception: Error: Oops an error
|
|
1072
|
+
message: Oops
|
|
1073
|
+
context: {
|
|
1074
|
+
name: My error context
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
## WaitForEvent
|
|
1080
|
+
|
|
1081
|
+
### Overview
|
|
1082
|
+
|
|
1083
|
+
`WaitForEvent` is a utility class that allows you to **wait for one or more emissions from an RxJS `Observable`** exposed as a property on an object.
|
|
1084
|
+
It is particularly useful in **tests**, **async workflows**, and **event-driven logic**, where you need to trigger an action and then await observable events with optional constraints such as timeouts or emission counts.
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
### Key Features
|
|
1089
|
+
|
|
1090
|
+
- Waits for one or multiple observable emissions
|
|
1091
|
+
- Supports synchronous, `Promise`, or `Observable` triggers
|
|
1092
|
+
- Optional timeout handling
|
|
1093
|
+
- Ability to ignore the initial observable value. For example when the event is implemented with `BehaviorSubject` or `ReplaySubject`
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
### Constructor
|
|
1098
|
+
|
|
1099
|
+
```ts
|
|
1100
|
+
constructor(
|
|
1101
|
+
target: T,
|
|
1102
|
+
eventName: E,
|
|
1103
|
+
options?: WaitOptions<T, E, R>
|
|
1104
|
+
)
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
| Parameter | Type | Description |
|
|
1108
|
+
| --------- | -------------------- | ------------------------------------------ |
|
|
1109
|
+
| target | T | Object containing the observable event |
|
|
1110
|
+
| eventName | E | Name of the observable property to wait on |
|
|
1111
|
+
| options | WaitOptions<T, E, R> | Optional configuration |
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
#### `WaitOptions<T, E, R>`
|
|
1115
|
+
|
|
1116
|
+
Configuration options for waiting.
|
|
1117
|
+
|
|
1118
|
+
| Property / Option | Type | Default | Description |
|
|
1119
|
+
| ------------------ | --------- | ------- | ------------------------------ |
|
|
1120
|
+
| count | `number` | 1 | Number of events to wait for |
|
|
1121
|
+
| timeout | `number` | 100 | Timeout in milliseconds |
|
|
1122
|
+
| ignoreInitialValue | `boolean` | false | Ignore the first emitted value |
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
### Methods
|
|
1128
|
+
|
|
1129
|
+
#### **wait(trigger)**
|
|
1130
|
+
|
|
1131
|
+
Waits for the observable to emit the specified number of events after running the trigger.
|
|
1132
|
+
|
|
1133
|
+
| Parameter | Type | Description |
|
|
1134
|
+
| --------- | ----------------------------------------------------- | -------------------------- |
|
|
1135
|
+
| trigger | () => void \| Promise<unknown> \| Observable<unknown> | action triggering the even |
|
|
1136
|
+
|
|
1137
|
+
**Returns:** `Promise<R | null>` resolving to the emitted value(s) or `null` if timeout occurs.
|
|
1138
|
+
|
|
1139
|
+
---
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
### Example
|
|
1143
|
+
|
|
1144
|
+
```ts
|
|
1145
|
+
import {
|
|
1146
|
+
printValue,
|
|
1147
|
+
WaitForEvent,
|
|
1148
|
+
|
|
1149
|
+
} from '@rs-x/core';
|
|
1150
|
+
import { Observable, Subject } from 'rxjs';
|
|
1151
|
+
|
|
1152
|
+
export const run = (async () => {
|
|
1153
|
+
class MyEventContext {
|
|
1154
|
+
private readonly _message = new Subject<string>();
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
public get message(): Observable<string> {
|
|
1158
|
+
return this._message;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
public emitMessage(message: string): void {
|
|
1162
|
+
this._message.next(message);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
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);
|
|
1173
|
+
})();
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
**Output:**
|
|
1177
|
+
```console
|
|
1178
|
+
Running demo: demo/src/rs-x-core/wait-for-event.ts
|
|
1179
|
+
Emitted events:
|
|
1180
|
+
[
|
|
1181
|
+
Hello,
|
|
1182
|
+
hi
|
|
1183
|
+
]
|
|
1184
|
+
```
|