synstate 5.0.1 → 6.0.0
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/README.md +2 -2
- package/dist/core/operators/keep-equal-parts.d.mts +1 -3
- package/dist/core/operators/keep-equal-parts.d.mts.map +1 -1
- package/dist/core/operators/keep-equal-parts.mjs +0 -2
- package/dist/core/operators/keep-equal-parts.mjs.map +1 -1
- package/dist/core/operators/map.d.mts +30 -18
- package/dist/core/operators/map.d.mts.map +1 -1
- package/dist/core/operators/map.mjs +24 -10
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts +4 -2
- package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
- package/dist/core/predefined/operators/attach-index.mjs +36 -2
- package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
- package/dist/core/predefined/operators/map-to.d.mts +6 -2
- package/dist/core/predefined/operators/map-to.d.mts.map +1 -1
- package/dist/core/predefined/operators/map-to.mjs +5 -1
- package/dist/core/predefined/operators/map-to.mjs.map +1 -1
- package/dist/utils/create-reducer.d.mts +1 -1
- package/dist/utils/create-state.d.mts +1 -1
- package/package.json +3 -3
- package/src/core/operators/keep-equal-parts.mts +1 -3
- package/src/core/operators/map-cut-off.test.mts +215 -0
- package/src/core/operators/map.mts +48 -31
- package/src/core/predefined/operators/attach-index.mts +62 -5
- package/src/core/predefined/operators/map-to.mts +7 -3
- package/src/utils/create-reducer.mts +1 -1
- package/src/utils/create-state.mts +1 -1
package/README.md
CHANGED
|
@@ -336,7 +336,7 @@ assert.deepStrictEqual(countHistory, [0, 0, 2]);
|
|
|
336
336
|
|
|
337
337
|
### Keeping the Parts an Update Did Not Change (`keepEqualParts`)
|
|
338
338
|
|
|
339
|
-
A state written whole from a source that answers with a new object every time, such as a server polled for a report, can take `keepEqualParts: true`. The next state is compared with the current one before it is written: every part deeply equal to the same part of the current state is replaced by that part, so whatever reads an unchanged part keeps the object it has and `
|
|
339
|
+
A state written whole from a source that answers with a new object every time, such as a server polled for a report, can take `keepEqualParts: true`. The next state is compared with the current one before it is written: every part deeply equal to the same part of the current state is replaced by that part, so whatever reads an unchanged part keeps the object it has and `map`, comparing with `Object.is`, stops there, and an answer that changes nothing is not passed on at all. Array items are matched by index; `keepEqualParts: { key }` matches them by key. The comparison is `replaceEqualDeep` of ts-data-forge, which serves any other write too.
|
|
340
340
|
|
|
341
341
|
```tsx
|
|
342
342
|
type PullReport = DeepReadonly<{
|
|
@@ -349,7 +349,7 @@ const [report, setReport] = createState<PullReport>(
|
|
|
349
349
|
{ keepEqualParts: true },
|
|
350
350
|
);
|
|
351
351
|
|
|
352
|
-
const summary = report.pipe(map((r) => r.summary))
|
|
352
|
+
const summary = report.pipe(map((r) => r.summary));
|
|
353
353
|
|
|
354
354
|
const summaryHistory: unknown[] = [];
|
|
355
355
|
|
|
@@ -41,9 +41,7 @@ import type { KeepInitialValueOperator } from '../types/index.mjs';
|
|
|
41
41
|
*
|
|
42
42
|
* const board$ = message$.pipe(keepEqualParts());
|
|
43
43
|
*
|
|
44
|
-
* const firstCard$ = board
|
|
45
|
-
* .pipe(map((board) => board.cards[0]))
|
|
46
|
-
* .pipe(skipIfNoChange());
|
|
44
|
+
* const firstCard$ = board$.pipe(map((board) => board.cards[0]));
|
|
47
45
|
*
|
|
48
46
|
* const boardHistory: Board[] = [];
|
|
49
47
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keep-equal-parts.d.mts","sourceRoot":"","sources":["../../../src/core/operators/keep-equal-parts.mts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAEV,wBAAwB,EAGzB,MAAM,oBAAoB,CAAC;AAE5B
|
|
1
|
+
{"version":3,"file":"keep-equal-parts.d.mts","sourceRoot":"","sources":["../../../src/core/operators/keep-equal-parts.mts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAEV,wBAAwB,EAGzB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,YACpB,uBAAuB,KAChC,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAMQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keep-equal-parts.mjs","sourceRoot":"","sources":["../../../src/core/operators/keep-equal-parts.mts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,yCAAyC;AACzC,2CAA2C;AAC3C,gDAAgD;AAChD,0DAA0D;AAC1D,kEAAkE;AAClE,OAAO,EACL,QAAQ,EACR,gBAAgB,GAEjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAQ9D
|
|
1
|
+
{"version":3,"file":"keep-equal-parts.mjs","sourceRoot":"","sources":["../../../src/core/operators/keep-equal-parts.mts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,yCAAyC;AACzC,2CAA2C;AAC3C,gDAAgD;AAChD,0DAA0D;AAC1D,kEAAkE;AAClE,OAAO,EACL,QAAQ,EACR,gBAAgB,GAEjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,OAAiC,EACD,EAAE;AAClC,4DAA4D;AAC5D,CAAC,CAAC,gBAAgB,EAAE,EAAE,CACpB,IAAI,6BAA6B,CAC/B,gBAAgB,EAChB,OAAO,CACR,CAAmC,CAAC;AAEzC,MAAM,6BACJ,SAAQ,wBAAyC;IAGxC,QAAQ,CAAsC;IACvD,kBAAkB,CAAc;IAEhC,YACE,gBAA+B,EAC/B,OAA4C;QAE5C,KAAK,CAAC;YACJ,OAAO,EAAE,CAAC,gBAAgB,CAAC;YAC3B,YAAY,EAAE,gBAAgB,CAAC,WAAW,EAAE;SAC7C,CAAC,CAAC;QAEH,IAAI,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,WAAW,EAAE,CAAC;QAEzD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAExB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEQ,SAAS,CAAC,WAAwB;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE5B,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAE7B,IAAI,GAAG,CAAC,WAAW,KAAK,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,cAAc;QACxB,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC;QAErC,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;YAE7B,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YAEpC,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEnE,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO;QACT,CAAC;QAED,sDAAsD;QACtD,IAAI,CAAC,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC;YACjD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -1,42 +1,54 @@
|
|
|
1
|
-
import { SafeUint } from 'ts-data-forge';
|
|
2
1
|
import type { KeepInitialValueOperator } from '../types/index.mjs';
|
|
3
2
|
/**
|
|
4
|
-
* Transforms each value emitted by the source
|
|
3
|
+
* Transforms each value emitted by the source with a mapping function, and
|
|
4
|
+
* emits the result unless it equals the one already held.
|
|
5
|
+
*
|
|
6
|
+
* The comparison is `Object.is` unless `equals` says otherwise, so a result
|
|
7
|
+
* that did not change wakes nothing below. To pass on every result — an event
|
|
8
|
+
* turned into a value, say — give `equals: () => false`, or use `mapTo`. The
|
|
9
|
+
* mapping function receives the value alone; `withIndex` attaches an index.
|
|
5
10
|
*
|
|
6
11
|
* @template A - The type of values from the source
|
|
7
12
|
* @template B - The type of mapped values
|
|
8
|
-
* @param mapFn - A function that maps each value
|
|
9
|
-
* @
|
|
13
|
+
* @param mapFn - A function that maps each value
|
|
14
|
+
* @param options - `equals` compares a result with the one held
|
|
15
|
+
* @returns An operator that maps values
|
|
10
16
|
*
|
|
11
17
|
* @example
|
|
12
18
|
* ```ts
|
|
13
19
|
* // Timeline:
|
|
14
20
|
* //
|
|
15
|
-
* // num$
|
|
16
|
-
* //
|
|
21
|
+
* // num$ 1 3 4 6 7
|
|
22
|
+
* // parity$ 1 0 1
|
|
17
23
|
* //
|
|
18
24
|
* // Explanation:
|
|
19
|
-
* // -
|
|
20
|
-
* // -
|
|
25
|
+
* // - map transforms each value
|
|
26
|
+
* // - A result equal to the one held is not emitted
|
|
21
27
|
*
|
|
22
|
-
* const num$ = source<
|
|
28
|
+
* const num$ = source<number>();
|
|
23
29
|
*
|
|
24
|
-
* const
|
|
30
|
+
* const parity$ = num$.pipe(map((x) => x % 2));
|
|
25
31
|
*
|
|
26
|
-
* const valueHistory:
|
|
32
|
+
* const valueHistory: number[] = [];
|
|
27
33
|
*
|
|
28
|
-
*
|
|
29
|
-
* valueHistory.push(
|
|
34
|
+
* parity$.subscribe((p) => {
|
|
35
|
+
* valueHistory.push(p);
|
|
30
36
|
* });
|
|
31
37
|
*
|
|
32
|
-
* num$.next(
|
|
38
|
+
* num$.next(1); // 1
|
|
39
|
+
*
|
|
40
|
+
* num$.next(3); // still 1: not emitted
|
|
41
|
+
*
|
|
42
|
+
* num$.next(4); // 0
|
|
33
43
|
*
|
|
34
|
-
* num$.next(
|
|
44
|
+
* num$.next(6); // still 0: not emitted
|
|
35
45
|
*
|
|
36
|
-
* num$.next(
|
|
46
|
+
* num$.next(7); // 1
|
|
37
47
|
*
|
|
38
|
-
* assert.deepStrictEqual(valueHistory, [
|
|
48
|
+
* assert.deepStrictEqual(valueHistory, [1, 0, 1]);
|
|
39
49
|
* ```
|
|
40
50
|
*/
|
|
41
|
-
export declare const map: <A, B>(mapFn: (x: A
|
|
51
|
+
export declare const map: <A, B>(mapFn: (x: A) => B, options?: Readonly<{
|
|
52
|
+
equals?: (x: B, y: B) => boolean;
|
|
53
|
+
}>) => KeepInitialValueOperator<A, B>;
|
|
42
54
|
//# sourceMappingURL=map.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map.d.mts","sourceRoot":"","sources":["../../../src/core/operators/map.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"map.d.mts","sourceRoot":"","sources":["../../../src/core/operators/map.mts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,wBAAwB,EAIzB,MAAM,oBAAoB,CAAC;AAG5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,CAAC,SACf,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YACR,QAAQ,CAAC;IAAE,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAAE,CAAC,KACvD,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAOQ,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
import { Optional
|
|
8
|
+
import { Optional } from 'ts-data-forge';
|
|
9
9
|
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
10
10
|
|
|
11
11
|
|
|
@@ -47,20 +47,31 @@ import { SyncChildObservableClass } from '../class/index.mjs';
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
export const map = (mapFn) =>
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export const map = (mapFn, options) =>
|
|
62
|
+
|
|
63
|
+
((parentObservable) => new MapObservableClass(parentObservable, mapFn, options?.equals ?? Object.is));
|
|
53
64
|
|
|
54
65
|
class MapObservableClass extends SyncChildObservableClass {
|
|
55
66
|
#mapFn;
|
|
56
|
-
#
|
|
57
|
-
constructor(parentObservable, mapFn) {
|
|
67
|
+
#equals;
|
|
68
|
+
constructor(parentObservable, mapFn, equals) {
|
|
58
69
|
super({
|
|
59
70
|
parents: [parentObservable],
|
|
60
|
-
initialValue: Optional.map(parentObservable.getSnapshot(),
|
|
71
|
+
initialValue: Optional.map(parentObservable.getSnapshot(), mapFn),
|
|
61
72
|
});
|
|
62
|
-
this.#mut_index = -1;
|
|
63
73
|
this.#mapFn = mapFn;
|
|
74
|
+
this.#equals = equals;
|
|
64
75
|
this.attach();
|
|
65
76
|
}
|
|
66
77
|
tryUpdate(updateToken) {
|
|
@@ -69,9 +80,12 @@ class MapObservableClass extends SyncChildObservableClass {
|
|
|
69
80
|
if (par.updateToken !== updateToken || Optional.isNone(sn)) {
|
|
70
81
|
return;
|
|
71
82
|
}
|
|
72
|
-
this.#
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
const next = this.#mapFn(sn.value);
|
|
84
|
+
const held = this.getSnapshot();
|
|
85
|
+
if (Optional.isSome(held) && this.#equals(held.value, next)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.setNext(next, updateToken);
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map.mjs","sourceRoot":"","sources":["../../../src/core/operators/map.mts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,yCAAyC;AACzC,2CAA2C;AAC3C,gDAAgD;AAChD,0DAA0D;AAC1D,kEAAkE;AAClE,OAAO,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"map.mjs","sourceRoot":"","sources":["../../../src/core/operators/map.mts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SafeUint } from 'ts-data-forge';
|
|
2
2
|
import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
3
3
|
/**
|
|
4
4
|
* Attaches a sequential index to each emitted value, producing `[index, value]` tuples.
|
|
5
|
-
* Index starts at 0 and increments with each emission
|
|
5
|
+
* Index starts at 0 and increments with each emission; a value the source
|
|
6
|
+
* already holds when this is applied gets -1. Use it where `map` used to
|
|
7
|
+
* receive an index.
|
|
6
8
|
*
|
|
7
9
|
* @template A - The type of values from the source
|
|
8
10
|
* @returns An operator that emits `[index, value]` tuples
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attach-index.d.mts","sourceRoot":"","sources":["../../../../src/core/predefined/operators/attach-index.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attach-index.d.mts","sourceRoot":"","sources":["../../../../src/core/predefined/operators/attach-index.mts"],"names":[],"mappings":"AAOA,OAAO,EAAY,QAAQ,EAAc,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EACV,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,OAAM,wBAAwB,CACvD,CAAC,EACD,SAAS,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAO1B,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,WAAW,kBAAY,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { map } from '../../operators/index.mjs';
|
|
2
1
|
|
|
3
2
|
|
|
4
3
|
|
|
@@ -6,6 +5,8 @@ import { map } from '../../operators/index.mjs';
|
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
|
|
8
|
+
import { Optional, SafeUint, asSafeUint } from 'ts-data-forge';
|
|
9
|
+
import { SyncChildObservableClass } from '../../class/index.mjs';
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
|
|
@@ -48,10 +49,43 @@ import { map } from '../../operators/index.mjs';
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export const withIndex = () =>
|
|
62
|
+
|
|
63
|
+
((parentObservable) => new WithIndexObservableClass(parentObservable));
|
|
52
64
|
|
|
53
65
|
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|
export const attachIndex = withIndex;
|
|
69
|
+
|
|
70
|
+
class WithIndexObservableClass extends SyncChildObservableClass {
|
|
71
|
+
#mut_index;
|
|
72
|
+
constructor(parentObservable) {
|
|
73
|
+
super({
|
|
74
|
+
parents: [parentObservable],
|
|
75
|
+
initialValue: Optional.map(parentObservable.getSnapshot(), (a) => [-1, a]),
|
|
76
|
+
});
|
|
77
|
+
this.#mut_index = -1;
|
|
78
|
+
this.attach();
|
|
79
|
+
}
|
|
80
|
+
tryUpdate(updateToken) {
|
|
81
|
+
const par = this.parents[0];
|
|
82
|
+
const sn = par.getSnapshot();
|
|
83
|
+
if (par.updateToken !== updateToken || Optional.isNone(sn)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.#mut_index =
|
|
87
|
+
this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
|
|
88
|
+
this.setNext([this.#mut_index, sn.value], updateToken);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
//# sourceMappingURL=attach-index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attach-index.mjs","sourceRoot":"","sources":["../../../../src/core/predefined/operators/attach-index.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attach-index.mjs","sourceRoot":"","sources":["../../../../src/core/predefined/operators/attach-index.mts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,yCAAyC;AACzC,2CAA2C;AAC3C,gDAAgD;AAChD,0DAA0D;AAC1D,kEAAkE;AAClE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAOjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAGvB,EAAE;AACF,4DAA4D;AAC5D,CAAC,CAAC,gBAAgB,EAAE,EAAE,CACpB,IAAI,wBAAwB,CAAC,gBAAgB,CAAC,CAG/C,CAAC;AAEJ;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC,oBAAoB;AAEpB,MAAM,wBAA4B,SAAQ,wBAGzC;IACC,UAAU,CAAgB;IAE1B,YAAY,gBAA+B;QACzC,KAAK,CAAC;YACJ,OAAO,EAAE,CAAC,gBAAgB,CAAC;YAC3B,YAAY,EAAE,QAAQ,CAAC,GAAG,CACxB,gBAAgB,CAAC,WAAW,EAAE,EAC9B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAU,CACxB;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;QAErB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAEQ,SAAS,CAAC,WAAwB;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAE5B,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAE7B,IAAI,GAAG,CAAC,WAAW,KAAK,WAAW,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3D,OAAO,CAAC,cAAc;QACxB,CAAC;QAED,IAAI,CAAC,UAAU;YACb,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAE5E,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC;IACzD,CAAC;CACF"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* Maps all emitted values to a constant value, ignoring the source values.
|
|
4
|
-
*
|
|
4
|
+
* Emits on every source value although the value never changes — where
|
|
5
|
+
* `map(() => value)` would stop every result after the first as equal — so
|
|
6
|
+
* it is the one for turning events into a value. The value's type is
|
|
7
|
+
* inferred as written, as with `as const`: `mapTo('reset')` gives
|
|
8
|
+
* `'reset'`, not `string`.
|
|
5
9
|
*
|
|
6
10
|
* @template A - The type of values from the source
|
|
7
11
|
* @template B - The type of the constant value
|
|
@@ -39,5 +43,5 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
|
39
43
|
* assert.deepStrictEqual(valueHistory, [1, 1, 1]);
|
|
40
44
|
* ```
|
|
41
45
|
*/
|
|
42
|
-
export declare const mapTo: <A, B>(value: B) => KeepInitialValueOperator<A, B>;
|
|
46
|
+
export declare const mapTo: <A, const B>(value: B) => KeepInitialValueOperator<A, B>;
|
|
43
47
|
//# sourceMappingURL=map-to.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-to.d.mts","sourceRoot":"","sources":["../../../../src/core/predefined/operators/map-to.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEtE
|
|
1
|
+
{"version":3,"file":"map-to.d.mts","sourceRoot":"","sources":["../../../../src/core/predefined/operators/map-to.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,KAAG,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAC/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-to.mjs","sourceRoot":"","sources":["../../../../src/core/predefined/operators/map-to.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAGhD
|
|
1
|
+
{"version":3,"file":"map-to.mjs","sourceRoot":"","sources":["../../../../src/core/predefined/operators/map-to.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAGhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAa,KAAQ,EAAkC,EAAE,CAC5E,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC"}
|
|
@@ -110,7 +110,7 @@ export type StateOptions<S> = Readonly<{
|
|
|
110
110
|
* written, and every part of it deeply equal to the same part of the
|
|
111
111
|
* current state is replaced by that part (`replaceEqualDeep` of
|
|
112
112
|
* ts-data-forge): a reader of an unchanged part keeps the object it has, so
|
|
113
|
-
* `
|
|
113
|
+
* `map`, comparing with `Object.is`, stops there. When the whole next state is
|
|
114
114
|
* deeply equal, the current state is kept and nothing is passed on, so
|
|
115
115
|
* `equals` can stay `Object.is`.
|
|
116
116
|
*
|
|
@@ -80,7 +80,7 @@ import { type StateOptions } from './create-reducer.mjs';
|
|
|
80
80
|
* { keepEqualParts: true },
|
|
81
81
|
* );
|
|
82
82
|
*
|
|
83
|
-
* const summary = report.pipe(map((r) => r.summary))
|
|
83
|
+
* const summary = report.pipe(map((r) => r.summary));
|
|
84
84
|
*
|
|
85
85
|
* const summaryHistory: unknown[] = [];
|
|
86
86
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synstate",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Type-safe State Management Library for TypeScript/JavaScript",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"prettier-plugin-packagejson": "3.0.2",
|
|
52
52
|
"react": "19.3.1",
|
|
53
53
|
"rxjs": "7.8.2",
|
|
54
|
-
"synstate-react-hooks": "
|
|
55
|
-
"synstate-react-hooks-compat": "
|
|
54
|
+
"synstate-react-hooks": "6.0.0",
|
|
55
|
+
"synstate-react-hooks-compat": "6.0.0",
|
|
56
56
|
"ts-repo-utils": "10.7.1",
|
|
57
57
|
"valtio": "2.3.2",
|
|
58
58
|
"zustand": "5.0.15"
|
|
@@ -59,9 +59,7 @@ import type {
|
|
|
59
59
|
*
|
|
60
60
|
* const board$ = message$.pipe(keepEqualParts());
|
|
61
61
|
*
|
|
62
|
-
* const firstCard$ = board
|
|
63
|
-
* .pipe(map((board) => board.cards[0]))
|
|
64
|
-
* .pipe(skipIfNoChange());
|
|
62
|
+
* const firstCard$ = board$.pipe(map((board) => board.cards[0]));
|
|
65
63
|
*
|
|
66
64
|
* const boardHistory: Board[] = [];
|
|
67
65
|
*
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { expectType } from 'ts-data-forge';
|
|
2
|
+
import { combine } from '../combine/index.mjs';
|
|
3
|
+
import { source } from '../create/index.mjs';
|
|
4
|
+
import { mapTo, withIndex } from '../predefined/index.mjs';
|
|
5
|
+
import type { Observable } from '../types/index.mjs';
|
|
6
|
+
import { map } from './map.mjs';
|
|
7
|
+
|
|
8
|
+
describe('map stops a result equal to the one it holds', () => {
|
|
9
|
+
test('a result equal by Object.is is not emitted', () => {
|
|
10
|
+
const a$ = source<number>();
|
|
11
|
+
|
|
12
|
+
const parity$ = a$.pipe(map((x) => x % 2));
|
|
13
|
+
|
|
14
|
+
const mut_history: number[] = [];
|
|
15
|
+
|
|
16
|
+
parity$.subscribe((p) => {
|
|
17
|
+
mut_history.push(p);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
a$.next(1);
|
|
21
|
+
|
|
22
|
+
a$.next(3);
|
|
23
|
+
|
|
24
|
+
a$.next(4);
|
|
25
|
+
|
|
26
|
+
a$.next(6);
|
|
27
|
+
|
|
28
|
+
a$.next(7);
|
|
29
|
+
|
|
30
|
+
assert.deepStrictEqual(mut_history, [1, 0, 1]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('the value computed from an initial value counts as the one held', () => {
|
|
34
|
+
const a$ = source<number>(1);
|
|
35
|
+
|
|
36
|
+
const parity$ = a$.pipe(map((x) => x % 2));
|
|
37
|
+
|
|
38
|
+
const mut_history: number[] = [];
|
|
39
|
+
|
|
40
|
+
parity$.subscribe((p) => {
|
|
41
|
+
mut_history.push(p);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
a$.next(3);
|
|
45
|
+
|
|
46
|
+
a$.next(4);
|
|
47
|
+
|
|
48
|
+
assert.deepStrictEqual(mut_history, [1, 0]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('a node below is not woken when the result is equal', () => {
|
|
52
|
+
const a$ = source<number>(1);
|
|
53
|
+
|
|
54
|
+
const parity$ = a$.pipe(map((x) => x % 2));
|
|
55
|
+
|
|
56
|
+
let mut_calls = 0;
|
|
57
|
+
|
|
58
|
+
const label$ = parity$.pipe(
|
|
59
|
+
map((p) => {
|
|
60
|
+
mut_calls += 1;
|
|
61
|
+
|
|
62
|
+
return p === 0 ? 'even' : 'odd';
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
label$.subscribe(() => {});
|
|
67
|
+
|
|
68
|
+
const callsAfterSetup = mut_calls;
|
|
69
|
+
|
|
70
|
+
a$.next(3);
|
|
71
|
+
|
|
72
|
+
a$.next(5);
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(mut_calls, callsAfterSetup);
|
|
75
|
+
|
|
76
|
+
a$.next(6);
|
|
77
|
+
|
|
78
|
+
assert.strictEqual(mut_calls, callsAfterSetup + 1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('a node reading a stopped map and another parent still updates once', () => {
|
|
82
|
+
const a$ = source<number>(1);
|
|
83
|
+
|
|
84
|
+
const parity$ = a$.pipe(map((x) => x % 2));
|
|
85
|
+
|
|
86
|
+
const mut_history: string[] = [];
|
|
87
|
+
|
|
88
|
+
combine([a$, parity$]).subscribe(([a, p]) => {
|
|
89
|
+
mut_history.push(`${a.toString()},${p.toString()}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
a$.next(3);
|
|
93
|
+
|
|
94
|
+
a$.next(4);
|
|
95
|
+
|
|
96
|
+
assert.deepStrictEqual(mut_history, ['1,1', '3,1', '4,0']);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('NaN equals NaN, and 0 differs from -0, as Object.is says', () => {
|
|
100
|
+
const a$ = source<number>();
|
|
101
|
+
|
|
102
|
+
const r$ = a$.pipe(map((x) => x));
|
|
103
|
+
|
|
104
|
+
const mut_history: number[] = [];
|
|
105
|
+
|
|
106
|
+
r$.subscribe((v) => {
|
|
107
|
+
mut_history.push(v);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
a$.next(Number.NaN);
|
|
111
|
+
|
|
112
|
+
a$.next(Number.NaN);
|
|
113
|
+
|
|
114
|
+
a$.next(0);
|
|
115
|
+
|
|
116
|
+
a$.next(-0);
|
|
117
|
+
|
|
118
|
+
assert.deepStrictEqual(mut_history, [Number.NaN, 0, -0]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('`equals` replaces Object.is', () => {
|
|
122
|
+
const a$ = source<Readonly<{ id: number; name: string }>>();
|
|
123
|
+
|
|
124
|
+
const id$ = a$.pipe(
|
|
125
|
+
map((x) => ({ id: x.id }), { equals: (p, q) => p.id === q.id }),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const mut_history: number[] = [];
|
|
129
|
+
|
|
130
|
+
id$.subscribe(({ id }) => {
|
|
131
|
+
mut_history.push(id);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
a$.next({ id: 1, name: 'a' });
|
|
135
|
+
|
|
136
|
+
a$.next({ id: 1, name: 'b' });
|
|
137
|
+
|
|
138
|
+
a$.next({ id: 2, name: 'b' });
|
|
139
|
+
|
|
140
|
+
assert.deepStrictEqual(mut_history, [1, 2]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('`equals: () => false` emits every result', () => {
|
|
144
|
+
const click$ = source<undefined>();
|
|
145
|
+
|
|
146
|
+
const one$ = click$.pipe(map(() => 1, { equals: () => false }));
|
|
147
|
+
|
|
148
|
+
const mut_history: number[] = [];
|
|
149
|
+
|
|
150
|
+
one$.subscribe((v) => {
|
|
151
|
+
mut_history.push(v);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
click$.next(undefined);
|
|
155
|
+
|
|
156
|
+
click$.next(undefined);
|
|
157
|
+
|
|
158
|
+
assert.deepStrictEqual(mut_history, [1, 1]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('the mapping function no longer receives an index', () => {
|
|
162
|
+
const a$ = source<number>();
|
|
163
|
+
|
|
164
|
+
// @ts-expect-error `map` passes the value alone; use `withIndex`.
|
|
165
|
+
const r$ = a$.pipe(map((x: number, i: number) => x + i));
|
|
166
|
+
|
|
167
|
+
expect(r$).toBeDefined();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('operators that turn events into values emit every event', () => {
|
|
172
|
+
test('mapTo emits its value on every event', () => {
|
|
173
|
+
const reset$ = source<undefined>();
|
|
174
|
+
|
|
175
|
+
const action$ = reset$.pipe(mapTo('reset'));
|
|
176
|
+
|
|
177
|
+
expectType<typeof action$, Observable<'reset'>>('=');
|
|
178
|
+
|
|
179
|
+
const mut_history: string[] = [];
|
|
180
|
+
|
|
181
|
+
action$.subscribe((v) => {
|
|
182
|
+
mut_history.push(v);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
reset$.next(undefined);
|
|
186
|
+
|
|
187
|
+
reset$.next(undefined);
|
|
188
|
+
|
|
189
|
+
assert.deepStrictEqual(mut_history, ['reset', 'reset']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('withIndex counts from 0, and gives -1 to an initial value', () => {
|
|
193
|
+
const a$ = source<string>('init');
|
|
194
|
+
|
|
195
|
+
const indexed$ = a$.pipe(withIndex());
|
|
196
|
+
|
|
197
|
+
assert.deepStrictEqual(indexed$.getSnapshot().value, [-1, 'init']);
|
|
198
|
+
|
|
199
|
+
const mut_history: (readonly [number, string])[] = [];
|
|
200
|
+
|
|
201
|
+
indexed$.subscribe((v) => {
|
|
202
|
+
mut_history.push(v);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
a$.next('x');
|
|
206
|
+
|
|
207
|
+
a$.next('x');
|
|
208
|
+
|
|
209
|
+
assert.deepStrictEqual(mut_history, [
|
|
210
|
+
[-1, 'init'],
|
|
211
|
+
[0, 'x'],
|
|
212
|
+
[1, 'x'],
|
|
213
|
+
]);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
7
|
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
8
|
-
import { Optional,
|
|
8
|
+
import { Optional, expectType } from 'ts-data-forge';
|
|
9
9
|
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
10
10
|
import { source } from '../create/index.mjs';
|
|
11
11
|
import type {
|
|
@@ -18,51 +18,64 @@ import type {
|
|
|
18
18
|
import { withInitialValue } from './with-initial-value.mjs';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Transforms each value emitted by the source
|
|
21
|
+
* Transforms each value emitted by the source with a mapping function, and
|
|
22
|
+
* emits the result unless it equals the one already held.
|
|
23
|
+
*
|
|
24
|
+
* The comparison is `Object.is` unless `equals` says otherwise, so a result
|
|
25
|
+
* that did not change wakes nothing below. To pass on every result — an event
|
|
26
|
+
* turned into a value, say — give `equals: () => false`, or use `mapTo`. The
|
|
27
|
+
* mapping function receives the value alone; `withIndex` attaches an index.
|
|
22
28
|
*
|
|
23
29
|
* @template A - The type of values from the source
|
|
24
30
|
* @template B - The type of mapped values
|
|
25
|
-
* @param mapFn - A function that maps each value
|
|
26
|
-
* @
|
|
31
|
+
* @param mapFn - A function that maps each value
|
|
32
|
+
* @param options - `equals` compares a result with the one held
|
|
33
|
+
* @returns An operator that maps values
|
|
27
34
|
*
|
|
28
35
|
* @example
|
|
29
36
|
* ```ts
|
|
30
37
|
* // Timeline:
|
|
31
38
|
* //
|
|
32
|
-
* // num$
|
|
33
|
-
* //
|
|
39
|
+
* // num$ 1 3 4 6 7
|
|
40
|
+
* // parity$ 1 0 1
|
|
34
41
|
* //
|
|
35
42
|
* // Explanation:
|
|
36
|
-
* // -
|
|
37
|
-
* // -
|
|
43
|
+
* // - map transforms each value
|
|
44
|
+
* // - A result equal to the one held is not emitted
|
|
38
45
|
*
|
|
39
|
-
* const num$ = source<
|
|
46
|
+
* const num$ = source<number>();
|
|
40
47
|
*
|
|
41
|
-
* const
|
|
48
|
+
* const parity$ = num$.pipe(map((x) => x % 2));
|
|
42
49
|
*
|
|
43
|
-
* const valueHistory:
|
|
50
|
+
* const valueHistory: number[] = [];
|
|
44
51
|
*
|
|
45
|
-
*
|
|
46
|
-
* valueHistory.push(
|
|
52
|
+
* parity$.subscribe((p) => {
|
|
53
|
+
* valueHistory.push(p);
|
|
47
54
|
* });
|
|
48
55
|
*
|
|
49
|
-
* num$.next(
|
|
56
|
+
* num$.next(1); // 1
|
|
57
|
+
*
|
|
58
|
+
* num$.next(3); // still 1: not emitted
|
|
50
59
|
*
|
|
51
|
-
* num$.next(
|
|
60
|
+
* num$.next(4); // 0
|
|
52
61
|
*
|
|
53
|
-
* num$.next(
|
|
62
|
+
* num$.next(6); // still 0: not emitted
|
|
54
63
|
*
|
|
55
|
-
*
|
|
64
|
+
* num$.next(7); // 1
|
|
65
|
+
*
|
|
66
|
+
* assert.deepStrictEqual(valueHistory, [1, 0, 1]);
|
|
56
67
|
* ```
|
|
57
68
|
*/
|
|
58
69
|
export const map = <A, B>(
|
|
59
|
-
mapFn: (x: A
|
|
70
|
+
mapFn: (x: A) => B,
|
|
71
|
+
options?: Readonly<{ equals?: (x: B, y: B) => boolean }>,
|
|
60
72
|
): KeepInitialValueOperator<A, B> =>
|
|
61
73
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
62
74
|
((parentObservable) =>
|
|
63
75
|
new MapObservableClass(
|
|
64
76
|
parentObservable,
|
|
65
77
|
mapFn,
|
|
78
|
+
options?.equals ?? Object.is,
|
|
66
79
|
)) as KeepInitialValueOperator<A, B>;
|
|
67
80
|
|
|
68
81
|
/* implementation */
|
|
@@ -71,24 +84,23 @@ class MapObservableClass<A, B>
|
|
|
71
84
|
extends SyncChildObservableClass<B, readonly [A]>
|
|
72
85
|
implements MapOperatorObservable<A, B>
|
|
73
86
|
{
|
|
74
|
-
readonly #mapFn: (x: A
|
|
75
|
-
#
|
|
87
|
+
readonly #mapFn: (x: A) => B;
|
|
88
|
+
readonly #equals: (x: B, y: B) => boolean;
|
|
76
89
|
|
|
77
90
|
constructor(
|
|
78
91
|
parentObservable: Observable<A>,
|
|
79
|
-
mapFn: (x: A
|
|
92
|
+
mapFn: (x: A) => B,
|
|
93
|
+
equals: (x: B, y: B) => boolean,
|
|
80
94
|
) {
|
|
81
95
|
super({
|
|
82
96
|
parents: [parentObservable],
|
|
83
|
-
initialValue: Optional.map(parentObservable.getSnapshot(),
|
|
84
|
-
mapFn(x, -1),
|
|
85
|
-
),
|
|
97
|
+
initialValue: Optional.map(parentObservable.getSnapshot(), mapFn),
|
|
86
98
|
});
|
|
87
99
|
|
|
88
|
-
this.#mut_index = -1;
|
|
89
|
-
|
|
90
100
|
this.#mapFn = mapFn;
|
|
91
101
|
|
|
102
|
+
this.#equals = equals;
|
|
103
|
+
|
|
92
104
|
this.attach();
|
|
93
105
|
}
|
|
94
106
|
|
|
@@ -101,10 +113,15 @@ class MapObservableClass<A, B>
|
|
|
101
113
|
return; // skip update
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
this.#
|
|
105
|
-
|
|
116
|
+
const next = this.#mapFn(sn.value);
|
|
117
|
+
|
|
118
|
+
const held = this.getSnapshot();
|
|
119
|
+
|
|
120
|
+
if (Optional.isSome(held) && this.#equals(held.value, next)) {
|
|
121
|
+
return; // the result did not change
|
|
122
|
+
}
|
|
106
123
|
|
|
107
|
-
this.setNext(
|
|
124
|
+
this.setNext(next, updateToken);
|
|
108
125
|
}
|
|
109
126
|
}
|
|
110
127
|
|
|
@@ -116,7 +133,7 @@ if (import.meta.vitest !== undefined) {
|
|
|
116
133
|
{
|
|
117
134
|
const s: Observable<number> = source<number>();
|
|
118
135
|
|
|
119
|
-
const _d1 = s.pipe(map((x
|
|
136
|
+
const _d1 = s.pipe(map((x) => x + 1));
|
|
120
137
|
|
|
121
138
|
expectType<typeof _d1, Observable<number>>('=');
|
|
122
139
|
}
|
|
@@ -126,7 +143,7 @@ if (import.meta.vitest !== undefined) {
|
|
|
126
143
|
|
|
127
144
|
const m: InitializedObservable<number> = s.pipe(withInitialValue(0));
|
|
128
145
|
|
|
129
|
-
const _d = m.pipe(map((x
|
|
146
|
+
const _d = m.pipe(map((x) => x + 1));
|
|
130
147
|
|
|
131
148
|
expectType<typeof _d, InitializedObservable<number>>('=');
|
|
132
149
|
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Sumi bans classes, `this`, method syntax and writes to `this` outside a
|
|
2
|
+
// `mut_` binding; an observable is a class (`ObservableBaseClass` says why),
|
|
3
|
+
// so those rules are waived here (D-63).
|
|
4
|
+
// @sumi-expect-error-file classes/no-class
|
|
5
|
+
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
|
+
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
|
+
// @sumi-expect-error-file mutation/no-mutation-without-mut-prefix
|
|
8
|
+
import { Optional, SafeUint, asSafeUint } from 'ts-data-forge';
|
|
9
|
+
import { SyncChildObservableClass } from '../../class/index.mjs';
|
|
10
|
+
import type {
|
|
11
|
+
KeepInitialValueOperator,
|
|
12
|
+
Observable,
|
|
13
|
+
UpdateToken,
|
|
14
|
+
} from '../../types/index.mjs';
|
|
4
15
|
|
|
5
16
|
/**
|
|
6
17
|
* Attaches a sequential index to each emitted value, producing `[index, value]` tuples.
|
|
7
|
-
* Index starts at 0 and increments with each emission
|
|
18
|
+
* Index starts at 0 and increments with each emission; a value the source
|
|
19
|
+
* already holds when this is applied gets -1. Use it where `map` used to
|
|
20
|
+
* receive an index.
|
|
8
21
|
*
|
|
9
22
|
* @template A - The type of values from the source
|
|
10
23
|
* @returns An operator that emits `[index, value]` tuples
|
|
@@ -54,10 +67,54 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
|
54
67
|
export const withIndex = <A,>(): KeepInitialValueOperator<
|
|
55
68
|
A,
|
|
56
69
|
readonly [SafeUint | -1, A]
|
|
57
|
-
> =>
|
|
70
|
+
> =>
|
|
71
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
72
|
+
((parentObservable) =>
|
|
73
|
+
new WithIndexObservableClass(parentObservable)) as KeepInitialValueOperator<
|
|
74
|
+
A,
|
|
75
|
+
readonly [SafeUint | -1, A]
|
|
76
|
+
>;
|
|
58
77
|
|
|
59
78
|
/**
|
|
60
79
|
* Alias for `withIndex`.
|
|
61
80
|
* @see withIndex
|
|
62
81
|
*/
|
|
63
82
|
export const attachIndex = withIndex;
|
|
83
|
+
|
|
84
|
+
/* implementation */
|
|
85
|
+
|
|
86
|
+
class WithIndexObservableClass<A> extends SyncChildObservableClass<
|
|
87
|
+
readonly [SafeUint | -1, A],
|
|
88
|
+
readonly [A]
|
|
89
|
+
> {
|
|
90
|
+
#mut_index: SafeUint | -1;
|
|
91
|
+
|
|
92
|
+
constructor(parentObservable: Observable<A>) {
|
|
93
|
+
super({
|
|
94
|
+
parents: [parentObservable],
|
|
95
|
+
initialValue: Optional.map(
|
|
96
|
+
parentObservable.getSnapshot(),
|
|
97
|
+
(a) => [-1, a] as const,
|
|
98
|
+
),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.#mut_index = -1;
|
|
102
|
+
|
|
103
|
+
this.attach();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override tryUpdate(updateToken: UpdateToken): void {
|
|
107
|
+
const par = this.parents[0];
|
|
108
|
+
|
|
109
|
+
const sn = par.getSnapshot();
|
|
110
|
+
|
|
111
|
+
if (par.updateToken !== updateToken || Optional.isNone(sn)) {
|
|
112
|
+
return; // skip update
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.#mut_index =
|
|
116
|
+
this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
|
|
117
|
+
|
|
118
|
+
this.setNext([this.#mut_index, sn.value], updateToken);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -3,7 +3,11 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Maps all emitted values to a constant value, ignoring the source values.
|
|
6
|
-
*
|
|
6
|
+
* Emits on every source value although the value never changes — where
|
|
7
|
+
* `map(() => value)` would stop every result after the first as equal — so
|
|
8
|
+
* it is the one for turning events into a value. The value's type is
|
|
9
|
+
* inferred as written, as with `as const`: `mapTo('reset')` gives
|
|
10
|
+
* `'reset'`, not `string`.
|
|
7
11
|
*
|
|
8
12
|
* @template A - The type of values from the source
|
|
9
13
|
* @template B - The type of the constant value
|
|
@@ -41,5 +45,5 @@ import type { KeepInitialValueOperator } from '../../types/index.mjs';
|
|
|
41
45
|
* assert.deepStrictEqual(valueHistory, [1, 1, 1]);
|
|
42
46
|
* ```
|
|
43
47
|
*/
|
|
44
|
-
export const mapTo = <A, B>(value: B): KeepInitialValueOperator<A, B> =>
|
|
45
|
-
map(() => value);
|
|
48
|
+
export const mapTo = <A, const B>(value: B): KeepInitialValueOperator<A, B> =>
|
|
49
|
+
map(() => value, { equals: () => false });
|
|
@@ -150,7 +150,7 @@ export type StateOptions<S> = Readonly<{
|
|
|
150
150
|
* written, and every part of it deeply equal to the same part of the
|
|
151
151
|
* current state is replaced by that part (`replaceEqualDeep` of
|
|
152
152
|
* ts-data-forge): a reader of an unchanged part keeps the object it has, so
|
|
153
|
-
* `
|
|
153
|
+
* `map`, comparing with `Object.is`, stops there. When the whole next state is
|
|
154
154
|
* deeply equal, the current state is kept and nothing is passed on, so
|
|
155
155
|
* `equals` can stay `Object.is`.
|
|
156
156
|
*
|
|
@@ -102,7 +102,7 @@ const reducer = <S,>(state: S, action: Action<S>): S => {
|
|
|
102
102
|
* { keepEqualParts: true },
|
|
103
103
|
* );
|
|
104
104
|
*
|
|
105
|
-
* const summary = report.pipe(map((r) => r.summary))
|
|
105
|
+
* const summary = report.pipe(map((r) => r.summary));
|
|
106
106
|
*
|
|
107
107
|
* const summaryHistory: unknown[] = [];
|
|
108
108
|
*
|