synstate 3.3.0 → 3.4.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/base/create-child-observable.d.mts +9 -2
- package/dist/core/base/create-child-observable.d.mts.map +1 -1
- package/dist/core/base/create-child-observable.mjs +26 -8
- package/dist/core/base/create-child-observable.mjs.map +1 -1
- package/dist/core/base/create-observable-base.d.mts +2 -0
- package/dist/core/base/create-observable-base.d.mts.map +1 -1
- package/dist/core/base/create-observable-base.mjs +21 -3
- package/dist/core/base/create-observable-base.mjs.map +1 -1
- package/dist/core/base/create-root-observable.d.mts +9 -0
- package/dist/core/base/create-root-observable.d.mts.map +1 -1
- package/dist/core/base/create-root-observable.mjs +25 -2
- package/dist/core/base/create-root-observable.mjs.map +1 -1
- package/dist/core/combine/merge.d.mts +0 -3
- package/dist/core/combine/merge.d.mts.map +1 -1
- package/dist/core/combine/merge.mjs +0 -3
- package/dist/core/combine/merge.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts +4 -2
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +21 -3
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.d.mts +9 -3
- package/dist/core/create/counter.d.mts.map +1 -1
- package/dist/core/create/counter.mjs +16 -3
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts.map +1 -1
- package/dist/core/create/from-subscribable.mjs +34 -11
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/source.d.mts +7 -0
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +6 -2
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +0 -3
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +0 -3
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts.map +1 -1
- package/dist/core/operators/skip-until.mjs +15 -6
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +0 -3
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +0 -3
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-until.d.mts.map +1 -1
- package/dist/core/operators/take-until.mjs +13 -1
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +12 -3
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/types/observable-family.d.mts +5 -1
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-family.mjs.map +1 -1
- package/dist/utils/create-event-emitter.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +10 -1
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +13 -5
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +7 -0
- package/dist/utils/create-state.d.mts.map +1 -1
- package/dist/utils/create-state.mjs +7 -0
- package/dist/utils/create-state.mjs.map +1 -1
- package/package.json +4 -4
- package/src/core/base/create-child-observable.mts +34 -6
- package/src/core/base/create-observable-base.mts +23 -3
- package/src/core/base/create-root-observable.mts +35 -1
- package/src/core/base/write-during-propagation.test.mts +234 -0
- package/src/core/combine/merge.mts +0 -3
- package/src/core/combine/zip.mts +36 -5
- package/src/core/create/counter.mts +27 -6
- package/src/core/create/counter.test.mts +82 -0
- package/src/core/create/from-subscribable.mts +33 -5
- package/src/core/create/from-subscribable.test.mts +129 -0
- package/src/core/create/source.mts +18 -3
- package/src/core/create/source.test.mts +41 -0
- package/src/core/operators/merge-map.mts +0 -3
- package/src/core/operators/skip-until.mts +21 -9
- package/src/core/operators/switch-map.mts +0 -3
- package/src/core/operators/take-until-skip-until-teardown.test.mts +127 -0
- package/src/core/operators/take-until.mts +15 -1
- package/src/core/operators/with-buffered-from.mts +15 -3
- package/src/core/operators/with-buffered-from.test.mts +56 -0
- package/src/core/types/observable-family.mts +5 -1
- package/src/utils/create-event-emitter.mts +2 -2
- package/src/utils/create-reducer.mts +14 -8
- package/src/utils/create-reducer.test.mts +12 -0
- package/src/utils/create-state.mts +7 -0
- package/src/utils/create-state.test.mts +20 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Arr } from 'ts-data-forge';
|
|
2
|
+
import { map } from '../operators/index.mjs';
|
|
2
3
|
import { counter } from './counter.mjs';
|
|
3
4
|
|
|
4
5
|
describe(counter, () => {
|
|
@@ -191,4 +192,85 @@ describe(counter, () => {
|
|
|
191
192
|
|
|
192
193
|
vi.useRealTimers();
|
|
193
194
|
});
|
|
195
|
+
|
|
196
|
+
test.each([
|
|
197
|
+
{ emitInitial: true, expected: [0, 1, 2] },
|
|
198
|
+
{ emitInitial: false, expected: [1, 2, 3] },
|
|
199
|
+
] as const)(
|
|
200
|
+
'times: 3, emitInitial: $emitInitial completes right after the third emission',
|
|
201
|
+
({ emitInitial, expected }) => {
|
|
202
|
+
vi.useFakeTimers();
|
|
203
|
+
|
|
204
|
+
const counter$ = counter(100, { times: 3, emitInitial });
|
|
205
|
+
|
|
206
|
+
const mut_history: number[] = [];
|
|
207
|
+
|
|
208
|
+
counter$.subscribe((n) => {
|
|
209
|
+
mut_history.push(n);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
|
|
213
|
+
|
|
214
|
+
assert.deepStrictEqual<readonly number[]>(
|
|
215
|
+
mut_history,
|
|
216
|
+
Arr.take(expected, 2),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
assert.isFalse(counter$.isCompleted);
|
|
220
|
+
|
|
221
|
+
vi.advanceTimersToNextTimer();
|
|
222
|
+
|
|
223
|
+
assert.deepStrictEqual<readonly number[]>(mut_history, expected);
|
|
224
|
+
|
|
225
|
+
assert.isTrue(counter$.isCompleted);
|
|
226
|
+
|
|
227
|
+
assert.strictEqual(vi.getTimerCount(), 0);
|
|
228
|
+
|
|
229
|
+
vi.useRealTimers();
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
test('times: 1 emits only the 0 and clears the interval', () => {
|
|
234
|
+
vi.useFakeTimers();
|
|
235
|
+
|
|
236
|
+
const counter$ = counter(100, { times: 1 });
|
|
237
|
+
|
|
238
|
+
const mut_history: number[] = [];
|
|
239
|
+
|
|
240
|
+
counter$.subscribe((n) => {
|
|
241
|
+
mut_history.push(n);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
vi.advanceTimersToNextTimer();
|
|
245
|
+
|
|
246
|
+
assert.deepStrictEqual(mut_history, [0]);
|
|
247
|
+
|
|
248
|
+
assert.isTrue(counter$.isCompleted);
|
|
249
|
+
|
|
250
|
+
assert.strictEqual(vi.getTimerCount(), 0);
|
|
251
|
+
|
|
252
|
+
vi.useRealTimers();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('times completes the downstream after the last value reaches it', () => {
|
|
256
|
+
vi.useFakeTimers();
|
|
257
|
+
|
|
258
|
+
const counter$ = counter(100, { times: 2 });
|
|
259
|
+
|
|
260
|
+
const doubled$ = counter$.pipe(map((n) => n * 2));
|
|
261
|
+
|
|
262
|
+
const mut_history: number[] = [];
|
|
263
|
+
|
|
264
|
+
doubled$.subscribe((n) => {
|
|
265
|
+
mut_history.push(n);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
|
|
269
|
+
|
|
270
|
+
assert.deepStrictEqual(mut_history, [0, 2]);
|
|
271
|
+
|
|
272
|
+
assert.isTrue(doubled$.isCompleted);
|
|
273
|
+
|
|
274
|
+
vi.useRealTimers();
|
|
275
|
+
});
|
|
194
276
|
});
|
|
@@ -3,6 +3,7 @@ import { createRootObservable } from '../base/index.mjs';
|
|
|
3
3
|
import type {
|
|
4
4
|
FromSubscribableObservable,
|
|
5
5
|
Subscribable,
|
|
6
|
+
Subscription,
|
|
6
7
|
} from '../types/index.mjs';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -65,11 +66,25 @@ import type {
|
|
|
65
66
|
*/
|
|
66
67
|
export const fromSubscribable = <A, E = unknown>(
|
|
67
68
|
subscribable: Subscribable<A>,
|
|
68
|
-
): FromSubscribableObservable<A, E> =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
): FromSubscribableObservable<A, E> => {
|
|
70
|
+
let mut_subscription: Subscription | undefined = undefined;
|
|
71
|
+
|
|
72
|
+
// Set once the subscribable has completed: its subscription is over, so
|
|
73
|
+
// completing this observable must not unsubscribe from it again.
|
|
74
|
+
let mut_isSourceCompleted = false;
|
|
75
|
+
|
|
76
|
+
const unsubscribe = (): void => {
|
|
77
|
+
if (!mut_isSourceCompleted) {
|
|
78
|
+
mut_subscription?.unsubscribe();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mut_subscription = undefined;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return createRootObservable<Result<A, E>>(
|
|
85
|
+
{ initialValue: Optional.none, onComplete: unsubscribe },
|
|
86
|
+
({ startUpdate, isCompleted, complete }) => {
|
|
87
|
+
const subscription = subscribable.subscribe(
|
|
73
88
|
(nextValue) => {
|
|
74
89
|
startUpdate(Result.ok(nextValue));
|
|
75
90
|
},
|
|
@@ -82,10 +97,23 @@ export const fromSubscribable = <A, E = unknown>(
|
|
|
82
97
|
);
|
|
83
98
|
},
|
|
84
99
|
() => {
|
|
100
|
+
mut_isSourceCompleted = true;
|
|
101
|
+
|
|
85
102
|
complete();
|
|
86
103
|
},
|
|
87
104
|
);
|
|
88
105
|
|
|
106
|
+
// `subscribe` may have completed this observable before returning
|
|
107
|
+
// the subscription, so `unsubscribe` above had nothing to release.
|
|
108
|
+
if (isCompleted()) {
|
|
109
|
+
if (!mut_isSourceCompleted) {
|
|
110
|
+
subscription.unsubscribe();
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
mut_subscription = subscription;
|
|
114
|
+
}
|
|
115
|
+
|
|
89
116
|
return {};
|
|
90
117
|
},
|
|
91
118
|
);
|
|
119
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Result } from 'ts-data-forge';
|
|
2
|
+
import { take } from '../predefined/index.mjs';
|
|
3
|
+
import type { Subscribable } from '../types/index.mjs';
|
|
4
|
+
import { fromSubscribable } from './from-subscribable.mjs';
|
|
5
|
+
|
|
6
|
+
describe(fromSubscribable, () => {
|
|
7
|
+
test('complete() unsubscribes from the subscribable once', () => {
|
|
8
|
+
const { subscribable, emit, unsubscribe } = createManualSubscribable();
|
|
9
|
+
|
|
10
|
+
const observable$ = fromSubscribable(subscribable);
|
|
11
|
+
|
|
12
|
+
observable$.complete();
|
|
13
|
+
|
|
14
|
+
observable$.complete();
|
|
15
|
+
|
|
16
|
+
expect(unsubscribe).toHaveBeenCalledExactlyOnceWith();
|
|
17
|
+
|
|
18
|
+
emit(1);
|
|
19
|
+
|
|
20
|
+
assert.isTrue(observable$.isCompleted);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('dispose() unsubscribes from the subscribable', () => {
|
|
24
|
+
const { subscribable, unsubscribe } = createManualSubscribable();
|
|
25
|
+
|
|
26
|
+
fromSubscribable(subscribable).dispose();
|
|
27
|
+
|
|
28
|
+
expect(unsubscribe).toHaveBeenCalledExactlyOnceWith();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('does not unsubscribe when the subscribable completes first', () => {
|
|
32
|
+
const { subscribable, emit, completeSource, unsubscribe } =
|
|
33
|
+
createManualSubscribable();
|
|
34
|
+
|
|
35
|
+
const observable$ = fromSubscribable(subscribable);
|
|
36
|
+
|
|
37
|
+
const mut_history: number[] = [];
|
|
38
|
+
|
|
39
|
+
observable$.subscribe((result) => {
|
|
40
|
+
if (Result.isOk(result)) {
|
|
41
|
+
mut_history.push(result.value);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
emit(1);
|
|
46
|
+
|
|
47
|
+
completeSource();
|
|
48
|
+
|
|
49
|
+
assert.isTrue(observable$.isCompleted);
|
|
50
|
+
|
|
51
|
+
observable$.complete();
|
|
52
|
+
|
|
53
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
54
|
+
|
|
55
|
+
assert.deepStrictEqual(mut_history, [1]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('does not unsubscribe when the subscribable completes synchronously during subscribe', () => {
|
|
59
|
+
const unsubscribe = vi.fn<() => void>();
|
|
60
|
+
|
|
61
|
+
const observable$ = fromSubscribable<number>({
|
|
62
|
+
subscribe: (_onNext, _onError, onComplete) => {
|
|
63
|
+
onComplete?.();
|
|
64
|
+
|
|
65
|
+
return { unsubscribe };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.isTrue(observable$.isCompleted);
|
|
70
|
+
|
|
71
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('a child that completes with nothing else using it unsubscribes from the subscribable', () => {
|
|
75
|
+
const { subscribable, emit, unsubscribe } = createManualSubscribable();
|
|
76
|
+
|
|
77
|
+
const observable$ = fromSubscribable(subscribable);
|
|
78
|
+
|
|
79
|
+
const taken$ = observable$.pipe(take(1));
|
|
80
|
+
|
|
81
|
+
taken$.subscribe(() => {});
|
|
82
|
+
|
|
83
|
+
emit(1);
|
|
84
|
+
|
|
85
|
+
emit(2);
|
|
86
|
+
|
|
87
|
+
assert.isTrue(taken$.isCompleted);
|
|
88
|
+
|
|
89
|
+
assert.isTrue(observable$.isCompleted);
|
|
90
|
+
|
|
91
|
+
expect(unsubscribe).toHaveBeenCalledExactlyOnceWith();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const createManualSubscribable = (): Readonly<{
|
|
96
|
+
subscribable: Subscribable<number>;
|
|
97
|
+
emit: (value: number) => void;
|
|
98
|
+
completeSource: () => void;
|
|
99
|
+
unsubscribe: () => void;
|
|
100
|
+
}> => {
|
|
101
|
+
let mut_onNext: ((value: number) => void) | undefined = undefined;
|
|
102
|
+
|
|
103
|
+
let mut_onComplete: (() => void) | undefined = undefined;
|
|
104
|
+
|
|
105
|
+
const unsubscribe = vi.fn<() => void>(() => {
|
|
106
|
+
mut_onNext = undefined;
|
|
107
|
+
|
|
108
|
+
mut_onComplete = undefined;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
subscribable: {
|
|
113
|
+
subscribe: (onNext, _onError, onComplete) => {
|
|
114
|
+
mut_onNext = onNext;
|
|
115
|
+
|
|
116
|
+
mut_onComplete = onComplete;
|
|
117
|
+
|
|
118
|
+
return { unsubscribe };
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
emit: (value) => {
|
|
122
|
+
mut_onNext?.(value);
|
|
123
|
+
},
|
|
124
|
+
completeSource: () => {
|
|
125
|
+
mut_onComplete?.();
|
|
126
|
+
},
|
|
127
|
+
unsubscribe,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
@@ -43,6 +43,13 @@ import type {
|
|
|
43
43
|
*
|
|
44
44
|
* assert.deepStrictEqual(valueHistory, [1, 2, 3]);
|
|
45
45
|
* ```
|
|
46
|
+
*
|
|
47
|
+
* @note Do not call `next` from a subscriber or a projection while another
|
|
48
|
+
* update is propagating: the write propagates immediately, so subscribers can
|
|
49
|
+
* see an intermediate state, and it logs a `console.warn` (once per
|
|
50
|
+
* observable). Derive the value from its source instead —
|
|
51
|
+
* `const b$ = a$.pipe(map((a) => a * 2))` rather than
|
|
52
|
+
* `a$.subscribe((a) => { setB(a * 2); })`.
|
|
46
53
|
*/
|
|
47
54
|
export function source<const A>(
|
|
48
55
|
initialValue: A,
|
|
@@ -51,19 +58,27 @@ export function source<const A>(
|
|
|
51
58
|
export function source<const A>(): SourceObservable<A>;
|
|
52
59
|
|
|
53
60
|
export function source<const A>(...args: readonly A[]): SourceObservable<A> {
|
|
54
|
-
return createRootObservable<
|
|
61
|
+
return createRootObservable<
|
|
62
|
+
A,
|
|
63
|
+
Readonly<{ next: (nextValue: A) => A | undefined }>
|
|
64
|
+
>(
|
|
55
65
|
{
|
|
56
66
|
initialValue: Arr.isNonEmpty(args)
|
|
57
67
|
? Optional.some(args[0])
|
|
58
68
|
: Optional.none,
|
|
59
69
|
},
|
|
60
|
-
({ startUpdate, isCompleted }) => ({
|
|
70
|
+
({ startUpdate, getSnapshot, isCompleted }) => ({
|
|
71
|
+
// Returns the value held after the call: `nextValue`, or, once
|
|
72
|
+
// completed, the value already there — `undefined` only on a source
|
|
73
|
+
// that never had one, whose `next` is typed `void`.
|
|
61
74
|
next: (nextValue: A) => {
|
|
62
75
|
if (isCompleted()) {
|
|
63
|
-
return;
|
|
76
|
+
return Optional.unwrap(getSnapshot());
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
startUpdate(nextValue);
|
|
80
|
+
|
|
81
|
+
return nextValue;
|
|
67
82
|
},
|
|
68
83
|
}),
|
|
69
84
|
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expectType } from 'ts-data-forge';
|
|
2
|
+
import type { SourceObservable } from '../types/index.mjs';
|
|
3
|
+
import { source } from './source.mjs';
|
|
4
|
+
|
|
5
|
+
describe(source, () => {
|
|
6
|
+
test('`next` returns the value it passed on', () => {
|
|
7
|
+
const count$ = source<number>(0);
|
|
8
|
+
|
|
9
|
+
assert.strictEqual(count$.next(1), 1);
|
|
10
|
+
|
|
11
|
+
assert.strictEqual(count$.getSnapshot().value, 1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('once completed, `next` passes nothing on and returns the value held', () => {
|
|
15
|
+
const count$ = source<number>(0);
|
|
16
|
+
|
|
17
|
+
const mut_seen: number[] = [];
|
|
18
|
+
|
|
19
|
+
count$.subscribe((value) => {
|
|
20
|
+
mut_seen.push(value);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
count$.next(1);
|
|
24
|
+
|
|
25
|
+
count$.complete();
|
|
26
|
+
|
|
27
|
+
assert.strictEqual(count$.next(2), 1);
|
|
28
|
+
|
|
29
|
+
assert.deepStrictEqual(mut_seen, [0, 1]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('`next` returns the value only on a source with an initial value', () => {
|
|
33
|
+
const passed = source<number>(0).next(1);
|
|
34
|
+
|
|
35
|
+
expectType<typeof passed, number>('=');
|
|
36
|
+
|
|
37
|
+
assert.strictEqual(passed, 1);
|
|
38
|
+
|
|
39
|
+
expectType<SourceObservable<number>['next'], (value: number) => void>('=');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -98,9 +98,6 @@ import type {
|
|
|
98
98
|
* 'C3',
|
|
99
99
|
* ]);
|
|
100
100
|
* ```
|
|
101
|
-
*
|
|
102
|
-
* @note To improve code readability, consider using `createState` instead of `mergeMap`,
|
|
103
|
-
* subscribing to `parentObservable` and calling `setState` within it.
|
|
104
101
|
*/
|
|
105
102
|
export const mergeMap =
|
|
106
103
|
<A, B>(
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
DropInitialValueOperator,
|
|
5
5
|
Observable,
|
|
6
6
|
SkipUntilOperatorObservable,
|
|
7
|
+
Subscription,
|
|
7
8
|
} from '../types/index.mjs';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -65,12 +66,28 @@ const createSkipUntilObservable = <A,>(
|
|
|
65
66
|
parentObservable: Observable<A>,
|
|
66
67
|
notifier: Observable<unknown>,
|
|
67
68
|
): SkipUntilOperatorObservable<A> => {
|
|
68
|
-
|
|
69
|
+
// A notifier that already holds a value, or has completed, would call
|
|
70
|
+
// `stopSkipping` inside `subscribe`, before the subscription is stored.
|
|
71
|
+
let mut_isSkipping =
|
|
72
|
+
Optional.isNone(notifier.getSnapshot()) && !notifier.isCompleted;
|
|
73
|
+
|
|
74
|
+
let mut_subscription: Subscription | undefined = undefined;
|
|
75
|
+
|
|
76
|
+
const stopSkipping = (): void => {
|
|
77
|
+
mut_isSkipping = false;
|
|
78
|
+
|
|
79
|
+
mut_subscription?.unsubscribe();
|
|
80
|
+
|
|
81
|
+
mut_subscription = undefined;
|
|
82
|
+
};
|
|
69
83
|
|
|
70
84
|
const observable = createSyncChildObservable<A, readonly [A]>(
|
|
71
85
|
{
|
|
72
86
|
parents: [parentObservable],
|
|
73
87
|
initialValue: Optional.none,
|
|
88
|
+
onComplete: () => {
|
|
89
|
+
mut_subscription?.unsubscribe();
|
|
90
|
+
},
|
|
74
91
|
},
|
|
75
92
|
({ setNext }) =>
|
|
76
93
|
(updateToken) => {
|
|
@@ -88,14 +105,9 @@ const createSkipUntilObservable = <A,>(
|
|
|
88
105
|
},
|
|
89
106
|
);
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
()
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
() => {
|
|
96
|
-
mut_isSkipping = false;
|
|
97
|
-
},
|
|
98
|
-
);
|
|
108
|
+
if (mut_isSkipping) {
|
|
109
|
+
mut_subscription = notifier.subscribe(stopSkipping, stopSkipping);
|
|
110
|
+
}
|
|
99
111
|
|
|
100
112
|
return observable;
|
|
101
113
|
};
|
|
@@ -96,9 +96,6 @@ import type {
|
|
|
96
96
|
* 'C3',
|
|
97
97
|
* ]);
|
|
98
98
|
* ```
|
|
99
|
-
*
|
|
100
|
-
* @note To improve code readability, consider using `createState` instead of `switchMap`,
|
|
101
|
-
* subscribe to `parentObservable` and call `setState` within it.
|
|
102
99
|
*/
|
|
103
100
|
export const switchMap =
|
|
104
101
|
<A, B>(
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { source } from '../create/index.mjs';
|
|
2
|
+
import { take } from '../predefined/index.mjs';
|
|
3
|
+
import { skipUntil } from './skip-until.mjs';
|
|
4
|
+
import { takeUntil } from './take-until.mjs';
|
|
5
|
+
|
|
6
|
+
describe('takeUntil releases the notifier', () => {
|
|
7
|
+
test('when the result is completed before the notifier emits', () => {
|
|
8
|
+
const notifier$ = source<undefined>();
|
|
9
|
+
|
|
10
|
+
const result$ = source<number>().pipe(takeUntil(notifier$));
|
|
11
|
+
|
|
12
|
+
assert.isTrue(notifier$.hasSubscriber);
|
|
13
|
+
|
|
14
|
+
result$.complete();
|
|
15
|
+
|
|
16
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('when the parent completes', () => {
|
|
20
|
+
const notifier$ = source<undefined>();
|
|
21
|
+
|
|
22
|
+
const num$ = source<number>();
|
|
23
|
+
|
|
24
|
+
num$.pipe(takeUntil(notifier$));
|
|
25
|
+
|
|
26
|
+
num$.complete();
|
|
27
|
+
|
|
28
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('when the downstream completes', () => {
|
|
32
|
+
const notifier$ = source<undefined>();
|
|
33
|
+
|
|
34
|
+
const num$ = source<number>();
|
|
35
|
+
|
|
36
|
+
const taken$ = num$.pipe(takeUntil(notifier$)).pipe(take(1));
|
|
37
|
+
|
|
38
|
+
taken$.subscribe(() => {});
|
|
39
|
+
|
|
40
|
+
// `take(n)` completes on the emission after its `n`th.
|
|
41
|
+
num$.next(1);
|
|
42
|
+
|
|
43
|
+
num$.next(2);
|
|
44
|
+
|
|
45
|
+
assert.isTrue(taken$.isCompleted);
|
|
46
|
+
|
|
47
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('when the notifier emits', () => {
|
|
51
|
+
const notifier$ = source<undefined>();
|
|
52
|
+
|
|
53
|
+
const result$ = source<number>().pipe(takeUntil(notifier$));
|
|
54
|
+
|
|
55
|
+
notifier$.next(undefined);
|
|
56
|
+
|
|
57
|
+
assert.isTrue(result$.isCompleted);
|
|
58
|
+
|
|
59
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('when the notifier has already emitted', () => {
|
|
63
|
+
const notifier$ = source<number>(0);
|
|
64
|
+
|
|
65
|
+
const result$ = source<number>().pipe(takeUntil(notifier$));
|
|
66
|
+
|
|
67
|
+
assert.isTrue(result$.isCompleted);
|
|
68
|
+
|
|
69
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('skipUntil releases the notifier', () => {
|
|
74
|
+
test('when the notifier emits', () => {
|
|
75
|
+
const notifier$ = source<undefined>();
|
|
76
|
+
|
|
77
|
+
const num$ = source<number>();
|
|
78
|
+
|
|
79
|
+
const result$ = num$.pipe(skipUntil(notifier$));
|
|
80
|
+
|
|
81
|
+
const mut_history: number[] = [];
|
|
82
|
+
|
|
83
|
+
result$.subscribe((x) => {
|
|
84
|
+
mut_history.push(x);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
num$.next(1);
|
|
88
|
+
|
|
89
|
+
notifier$.next(undefined);
|
|
90
|
+
|
|
91
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
92
|
+
|
|
93
|
+
num$.next(2);
|
|
94
|
+
|
|
95
|
+
assert.deepStrictEqual(mut_history, [2]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('when the notifier has already emitted', () => {
|
|
99
|
+
const notifier$ = source<number>(0);
|
|
100
|
+
|
|
101
|
+
const num$ = source<number>();
|
|
102
|
+
|
|
103
|
+
const result$ = num$.pipe(skipUntil(notifier$));
|
|
104
|
+
|
|
105
|
+
const mut_history: number[] = [];
|
|
106
|
+
|
|
107
|
+
result$.subscribe((x) => {
|
|
108
|
+
mut_history.push(x);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
112
|
+
|
|
113
|
+
num$.next(1);
|
|
114
|
+
|
|
115
|
+
assert.deepStrictEqual(mut_history, [1]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('when the result is completed before the notifier emits', () => {
|
|
119
|
+
const notifier$ = source<undefined>();
|
|
120
|
+
|
|
121
|
+
const result$ = source<number>().pipe(skipUntil(notifier$));
|
|
122
|
+
|
|
123
|
+
result$.complete();
|
|
124
|
+
|
|
125
|
+
assert.isFalse(notifier$.hasSubscriber);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -3,6 +3,7 @@ import { createSyncChildObservable } from '../base/index.mjs';
|
|
|
3
3
|
import type {
|
|
4
4
|
KeepInitialValueOperator,
|
|
5
5
|
Observable,
|
|
6
|
+
Subscription,
|
|
6
7
|
TakeUntilOperatorObservable,
|
|
7
8
|
} from '../types/index.mjs';
|
|
8
9
|
|
|
@@ -68,10 +69,15 @@ const createTakeUntilObservable = <A,>(
|
|
|
68
69
|
parentObservable: Observable<A>,
|
|
69
70
|
notifier: Observable<unknown>,
|
|
70
71
|
): TakeUntilOperatorObservable<A> => {
|
|
72
|
+
let mut_subscription: Subscription | undefined = undefined;
|
|
73
|
+
|
|
71
74
|
const observable = createSyncChildObservable<A, readonly [A]>(
|
|
72
75
|
{
|
|
73
76
|
parents: [parentObservable],
|
|
74
77
|
initialValue: parentObservable.getSnapshot(),
|
|
78
|
+
onComplete: () => {
|
|
79
|
+
mut_subscription?.unsubscribe();
|
|
80
|
+
},
|
|
75
81
|
},
|
|
76
82
|
({ setNext }) =>
|
|
77
83
|
(updateToken) => {
|
|
@@ -88,7 +94,7 @@ const createTakeUntilObservable = <A,>(
|
|
|
88
94
|
},
|
|
89
95
|
);
|
|
90
96
|
|
|
91
|
-
notifier.subscribe(
|
|
97
|
+
const subscription = notifier.subscribe(
|
|
92
98
|
() => {
|
|
93
99
|
observable.complete();
|
|
94
100
|
},
|
|
@@ -97,5 +103,13 @@ const createTakeUntilObservable = <A,>(
|
|
|
97
103
|
},
|
|
98
104
|
);
|
|
99
105
|
|
|
106
|
+
// A notifier that already holds a value completes `observable` inside
|
|
107
|
+
// `subscribe`, before `onComplete` can see the subscription.
|
|
108
|
+
if (observable.isCompleted) {
|
|
109
|
+
subscription.unsubscribe();
|
|
110
|
+
} else {
|
|
111
|
+
mut_subscription = subscription;
|
|
112
|
+
}
|
|
113
|
+
|
|
100
114
|
return observable;
|
|
101
115
|
};
|
|
@@ -3,6 +3,7 @@ import { createSyncChildObservable } from '../base/index.mjs';
|
|
|
3
3
|
import type {
|
|
4
4
|
KeepInitialValueOperator,
|
|
5
5
|
Observable,
|
|
6
|
+
Subscription,
|
|
6
7
|
WithBufferedFromOperatorObservable,
|
|
7
8
|
} from '../types/index.mjs';
|
|
8
9
|
import { maxDepth } from '../utils/index.mjs';
|
|
@@ -86,6 +87,8 @@ const createWithBufferedFromObservable = <A, B>(
|
|
|
86
87
|
mut_bufferedValues = [];
|
|
87
88
|
};
|
|
88
89
|
|
|
90
|
+
let mut_subscription: Subscription | undefined = undefined;
|
|
91
|
+
|
|
89
92
|
const result = createSyncChildObservable<
|
|
90
93
|
readonly [A, readonly B[]],
|
|
91
94
|
readonly [A]
|
|
@@ -104,6 +107,11 @@ const createWithBufferedFromObservable = <A, B>(
|
|
|
104
107
|
Optional.isNone(me) ? [] : [me.value],
|
|
105
108
|
] as const),
|
|
106
109
|
).value,
|
|
110
|
+
onComplete: () => {
|
|
111
|
+
mut_subscription?.unsubscribe();
|
|
112
|
+
|
|
113
|
+
clearBuffer();
|
|
114
|
+
},
|
|
107
115
|
},
|
|
108
116
|
({ setNext }) =>
|
|
109
117
|
(updateToken) => {
|
|
@@ -122,9 +130,13 @@ const createWithBufferedFromObservable = <A, B>(
|
|
|
122
130
|
},
|
|
123
131
|
);
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
// A child completed at creation has already run `onComplete`; subscribing
|
|
134
|
+
// after it would leave a subscription nothing releases.
|
|
135
|
+
if (!result.isCompleted) {
|
|
136
|
+
mut_subscription = observable.subscribe((value) => {
|
|
137
|
+
mut_bufferedValues = Arr.toPushed(mut_bufferedValues, value);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
128
140
|
|
|
129
141
|
return result;
|
|
130
142
|
};
|