synstate 3.3.0 → 4.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 +14 -9
- package/dist/core/authoring.d.mts +7 -0
- package/dist/core/authoring.d.mts.map +1 -0
- package/dist/core/authoring.mjs +7 -0
- package/dist/core/authoring.mjs.map +1 -0
- package/dist/core/class/child-observable-class.d.mts +93 -0
- package/dist/core/class/child-observable-class.d.mts.map +1 -0
- package/dist/core/class/child-observable-class.mjs +248 -0
- package/dist/core/class/child-observable-class.mjs.map +1 -0
- package/dist/core/class/create-child-observable.d.mts +159 -0
- package/dist/core/class/create-child-observable.d.mts.map +1 -0
- package/dist/core/class/create-child-observable.mjs +183 -0
- package/dist/core/class/create-child-observable.mjs.map +1 -0
- package/dist/core/class/create-root-observable.d.mts +37 -0
- package/dist/core/class/create-root-observable.d.mts.map +1 -0
- package/dist/core/class/create-root-observable.mjs +52 -0
- package/dist/core/class/create-root-observable.mjs.map +1 -0
- package/dist/core/class/index.d.mts +9 -0
- package/dist/core/class/index.d.mts.map +1 -0
- package/dist/core/class/index.mjs +9 -0
- package/dist/core/class/index.mjs.map +1 -0
- package/dist/core/class/manager-observable-class.d.mts +26 -0
- package/dist/core/class/manager-observable-class.d.mts.map +1 -0
- package/dist/core/class/manager-observable-class.mjs +116 -0
- package/dist/core/class/manager-observable-class.mjs.map +1 -0
- package/dist/core/class/observable-base-class.d.mts +97 -0
- package/dist/core/class/observable-base-class.d.mts.map +1 -0
- package/dist/core/class/observable-base-class.mjs +219 -0
- package/dist/core/class/observable-base-class.mjs.map +1 -0
- package/dist/core/class/observable-node.d.mts +40 -0
- package/dist/core/class/observable-node.d.mts.map +1 -0
- package/dist/core/class/observable-node.mjs +10 -0
- package/dist/core/class/observable-node.mjs.map +1 -0
- package/dist/core/class/propagation-state.d.mts +24 -0
- package/dist/core/class/propagation-state.d.mts.map +1 -0
- package/dist/core/class/propagation-state.mjs +66 -0
- package/dist/core/class/propagation-state.mjs.map +1 -0
- package/dist/core/class/root-observable-class.d.mts +23 -0
- package/dist/core/class/root-observable-class.d.mts.map +1 -0
- package/dist/core/class/root-observable-class.mjs +58 -0
- package/dist/core/class/root-observable-class.mjs.map +1 -0
- package/dist/core/combine/combine.d.mts.map +1 -1
- package/dist/core/combine/combine.mjs +25 -14
- package/dist/core/combine/combine.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 +24 -15
- 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 +54 -18
- 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 +69 -35
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
- package/dist/core/create/from-abortable-promise.mjs +26 -18
- package/dist/core/create/from-abortable-promise.mjs.map +1 -1
- package/dist/core/create/from-promise.d.mts.map +1 -1
- package/dist/core/create/from-promise.mjs +30 -21
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/from-subscribable.d.mts.map +1 -1
- package/dist/core/create/from-subscribable.mjs +48 -12
- package/dist/core/create/from-subscribable.mjs.map +1 -1
- package/dist/core/create/just.d.mts.map +1 -1
- package/dist/core/create/just.mjs +4 -2
- package/dist/core/create/just.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 +27 -13
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/create/timer.d.mts.map +1 -1
- package/dist/core/create/timer.mjs +40 -25
- package/dist/core/create/timer.mjs.map +1 -1
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.d.mts.map +1 -1
- package/dist/core/index.mjs +2 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/operators/audit.d.mts +3 -0
- package/dist/core/operators/audit.d.mts.map +1 -1
- package/dist/core/operators/audit.mjs +56 -28
- package/dist/core/operators/audit.mjs.map +1 -1
- package/dist/core/operators/debounce.d.mts +3 -0
- package/dist/core/operators/debounce.d.mts.map +1 -1
- package/dist/core/operators/debounce.mjs +57 -26
- package/dist/core/operators/debounce.mjs.map +1 -1
- package/dist/core/operators/filter.d.mts.map +1 -1
- package/dist/core/operators/filter.mjs +35 -21
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/index.d.mts +1 -0
- package/dist/core/operators/index.d.mts.map +1 -1
- package/dist/core/operators/index.mjs +1 -0
- package/dist/core/operators/index.mjs.map +1 -1
- package/dist/core/operators/map.d.mts.map +1 -1
- package/dist/core/operators/map.mjs +31 -17
- package/dist/core/operators/map.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +5 -3
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +49 -26
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/pairwise.d.mts.map +1 -1
- package/dist/core/operators/pairwise.mjs +31 -20
- package/dist/core/operators/pairwise.mjs.map +1 -1
- package/dist/core/operators/scan.d.mts.map +1 -1
- package/dist/core/operators/scan.mjs +27 -14
- package/dist/core/operators/scan.mjs.map +1 -1
- package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
- package/dist/core/operators/skip-if-no-change.mjs +33 -19
- package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
- package/dist/core/operators/skip-until.d.mts.map +1 -1
- package/dist/core/operators/skip-until.mjs +43 -20
- package/dist/core/operators/skip-until.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +35 -20
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +5 -3
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +53 -25
- 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 +38 -19
- package/dist/core/operators/take-until.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +36 -22
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/{predefined/operators → operators}/take.d.mts +7 -3
- package/dist/core/operators/take.d.mts.map +1 -0
- package/dist/core/operators/take.mjs +102 -0
- package/dist/core/operators/take.mjs.map +1 -0
- package/dist/core/operators/throttle.d.mts.map +1 -1
- package/dist/core/operators/throttle.mjs +41 -26
- package/dist/core/operators/throttle.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +44 -31
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/operators/with-current-value-from.d.mts.map +1 -1
- package/dist/core/operators/with-current-value-from.mjs +36 -21
- package/dist/core/operators/with-current-value-from.mjs.map +1 -1
- package/dist/core/operators/with-initial-value.d.mts.map +1 -1
- package/dist/core/operators/with-initial-value.mjs +24 -12
- package/dist/core/operators/with-initial-value.mjs.map +1 -1
- package/dist/core/predefined/operators/index.d.mts +0 -1
- package/dist/core/predefined/operators/index.d.mts.map +1 -1
- package/dist/core/predefined/operators/index.mjs +0 -1
- package/dist/core/predefined/operators/index.mjs.map +1 -1
- package/dist/core/scope/index.d.mts +2 -0
- package/dist/core/scope/index.d.mts.map +1 -0
- package/dist/core/scope/index.mjs +2 -0
- package/dist/core/scope/index.mjs.map +1 -0
- package/dist/core/scope/scope.d.mts +51 -0
- package/dist/core/scope/scope.d.mts.map +1 -0
- package/dist/core/scope/scope.mjs +194 -0
- package/dist/core/scope/scope.mjs.map +1 -0
- package/dist/core/types/id.d.mts +0 -2
- package/dist/core/types/id.d.mts.map +1 -1
- package/dist/core/types/index.d.mts +1 -0
- package/dist/core/types/index.d.mts.map +1 -1
- package/dist/core/types/index.mjs +1 -0
- package/dist/core/types/index.mjs.map +1 -1
- package/dist/core/types/observable-family.d.mts +6 -1
- package/dist/core/types/observable-family.d.mts.map +1 -1
- package/dist/core/types/observable-family.mjs.map +1 -1
- package/dist/core/types/observable.d.mts +40 -28
- package/dist/core/types/observable.d.mts.map +1 -1
- package/dist/core/types/observable.mjs +24 -0
- package/dist/core/types/observable.mjs.map +1 -1
- package/dist/core/types/scope.d.mts +27 -0
- package/dist/core/types/scope.d.mts.map +1 -0
- package/dist/core/types/scope.mjs +2 -0
- package/dist/core/types/scope.mjs.map +1 -0
- package/dist/core/utils/current-scope.d.mts +30 -0
- package/dist/core/utils/current-scope.d.mts.map +1 -0
- package/dist/core/utils/current-scope.mjs +26 -0
- package/dist/core/utils/current-scope.mjs.map +1 -0
- package/dist/core/utils/id-maker.d.mts +14 -3
- package/dist/core/utils/id-maker.d.mts.map +1 -1
- package/dist/core/utils/id-maker.mjs +18 -14
- package/dist/core/utils/id-maker.mjs.map +1 -1
- package/dist/core/utils/index.d.mts +2 -1
- package/dist/core/utils/index.d.mts.map +1 -1
- package/dist/core/utils/index.mjs +2 -1
- package/dist/core/utils/index.mjs.map +1 -1
- package/dist/core/utils/max-depth.d.mts +1 -0
- package/dist/core/utils/max-depth.d.mts.map +1 -1
- package/dist/core/utils/max-depth.mjs +9 -7
- package/dist/core/utils/max-depth.mjs.map +1 -1
- package/dist/core/utils/observable-brand.d.mts +13 -0
- package/dist/core/utils/observable-brand.d.mts.map +1 -0
- package/dist/core/utils/observable-brand.mjs +13 -0
- package/dist/core/utils/observable-brand.mjs.map +1 -0
- package/dist/utils/create-boolean-state.d.mts +2 -2
- package/dist/utils/create-event-emitter.d.mts +2 -2
- package/dist/utils/create-reducer.d.mts +17 -8
- package/dist/utils/create-reducer.d.mts.map +1 -1
- package/dist/utils/create-reducer.mjs +14 -6
- package/dist/utils/create-reducer.mjs.map +1 -1
- package/dist/utils/create-state.d.mts +9 -2
- 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 +16 -16
- package/src/core/authoring.mts +19 -0
- package/src/core/class/child-observable-class.mts +374 -0
- package/src/core/{base → class}/circular-dependency-comparison.test.mts +46 -38
- package/src/core/{base → class}/circular-dependency.test.mts +38 -2
- package/src/core/class/complete-propagation.test.mts +264 -0
- package/src/core/class/create-child-observable.mts +370 -0
- package/src/core/class/create-root-observable.mts +107 -0
- package/src/core/class/index.mts +8 -0
- package/src/core/class/manager-observable-class.mts +166 -0
- package/src/core/class/observable-base-class.mts +315 -0
- package/src/core/{base → class}/observable-base.test.mts +1 -1
- package/src/core/class/observable-node.mts +54 -0
- package/src/core/class/propagation-order.test.mts +92 -0
- package/src/core/class/propagation-state.mts +80 -0
- package/src/core/class/remove-completed.test.mts +369 -0
- package/src/core/class/root-observable-class.mts +85 -0
- package/src/core/class/write-during-propagation.test.mts +234 -0
- package/src/core/combine/combine.mts +67 -55
- package/src/core/combine/merge.mts +33 -26
- package/src/core/combine/zip.mts +103 -58
- package/src/core/create/counter.mts +105 -54
- package/src/core/create/counter.test.mts +82 -0
- package/src/core/create/from-abortable-promise.mts +54 -42
- package/src/core/create/from-promise.mts +38 -29
- package/src/core/create/from-subscribable.mts +68 -23
- package/src/core/create/from-subscribable.test.mts +134 -0
- package/src/core/create/just.mts +4 -5
- package/src/core/create/source.mts +37 -13
- package/src/core/create/source.test.mts +41 -0
- package/src/core/create/timer.mts +51 -32
- package/src/core/index.mts +2 -0
- package/src/core/operators/audit.mts +79 -45
- package/src/core/operators/debounce-audit-complete.test.mts +121 -0
- package/src/core/operators/debounce.mts +81 -40
- package/src/core/operators/filter.mts +48 -31
- package/src/core/operators/index.mts +1 -0
- package/src/core/operators/inner-ownership.test.mts +280 -0
- package/src/core/operators/map.mts +49 -32
- package/src/core/operators/merge-map.mts +81 -46
- package/src/core/operators/pairwise.mts +40 -30
- package/src/core/operators/scan.mts +41 -24
- package/src/core/operators/skip-if-no-change.mts +45 -31
- package/src/core/operators/skip-until.mts +59 -33
- package/src/core/operators/skip-while.mts +49 -31
- package/src/core/operators/switch-map.mts +74 -38
- package/src/core/operators/take-until-skip-until-teardown.test.mts +133 -0
- package/src/core/operators/take-until.mts +49 -31
- package/src/core/operators/take-while.mts +50 -33
- package/src/core/operators/take.mts +138 -0
- package/src/core/operators/throttle.mts +55 -38
- package/src/core/operators/with-buffered-from.mts +45 -35
- package/src/core/operators/with-buffered-from.test.mts +57 -0
- package/src/core/operators/with-current-value-from.mts +39 -25
- package/src/core/operators/with-initial-value.mts +30 -21
- package/src/core/predefined/operators/index.mts +0 -1
- package/src/core/scope/index.mts +1 -0
- package/src/core/scope/scope.mts +260 -0
- package/src/core/scope/scope.test.mts +462 -0
- package/src/core/types/id.mts +0 -4
- package/src/core/types/index.mts +1 -0
- package/src/core/types/observable-family.mts +7 -1
- package/src/core/types/observable.mts +77 -42
- package/src/core/types/scope.mts +27 -0
- package/src/core/utils/current-scope.mts +61 -0
- package/src/core/utils/id-maker.mts +20 -30
- package/src/core/utils/index.mts +2 -1
- package/src/core/utils/max-depth.mts +12 -9
- package/src/core/utils/observable-brand.mts +12 -0
- package/src/utils/create-boolean-state.mts +2 -2
- package/src/utils/create-boolean-state.test.mts +18 -4
- package/src/utils/create-event-emitter.mts +2 -2
- package/src/utils/create-reducer.mts +22 -16
- package/src/utils/create-reducer.test.mts +43 -14
- package/src/utils/create-state.mts +9 -2
- package/src/utils/create-state.test.mts +32 -14
- package/dist/core/base/create-child-observable.d.mts +0 -65
- package/dist/core/base/create-child-observable.d.mts.map +0 -1
- package/dist/core/base/create-child-observable.mjs +0 -267
- package/dist/core/base/create-child-observable.mjs.map +0 -1
- package/dist/core/base/create-observable-base.d.mts +0 -147
- package/dist/core/base/create-observable-base.d.mts.map +0 -1
- package/dist/core/base/create-observable-base.mjs +0 -255
- package/dist/core/base/create-observable-base.mjs.map +0 -1
- package/dist/core/base/create-root-observable.d.mts +0 -37
- package/dist/core/base/create-root-observable.d.mts.map +0 -1
- package/dist/core/base/create-root-observable.mjs +0 -57
- package/dist/core/base/create-root-observable.mjs.map +0 -1
- package/dist/core/base/index.d.mts +0 -3
- package/dist/core/base/index.d.mts.map +0 -1
- package/dist/core/base/index.mjs +0 -3
- package/dist/core/base/index.mjs.map +0 -1
- package/dist/core/predefined/operators/take.d.mts.map +0 -1
- package/dist/core/predefined/operators/take.mjs +0 -44
- package/dist/core/predefined/operators/take.mjs.map +0 -1
- package/dist/core/utils/utils.d.mts +0 -6
- package/dist/core/utils/utils.d.mts.map +0 -1
- package/dist/core/utils/utils.mjs +0 -37
- package/dist/core/utils/utils.mjs.map +0 -1
- package/src/core/base/create-child-observable.mts +0 -457
- package/src/core/base/create-observable-base.mts +0 -548
- package/src/core/base/create-root-observable.mts +0 -113
- package/src/core/base/index.mts +0 -2
- package/src/core/predefined/operators/take.mts +0 -48
- package/src/core/utils/utils.mts +0 -56
- package/src/core/utils/utils.test.mts +0 -88
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Result } from 'ts-data-forge';
|
|
2
|
+
import { take } from '../operators/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 leaves the subscribable subscribed until the observable is completed', () => {
|
|
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
|
+
// Completion flows only downstream.
|
|
90
|
+
assert.isFalse(observable$.isCompleted);
|
|
91
|
+
|
|
92
|
+
expect(unsubscribe).not.toHaveBeenCalled();
|
|
93
|
+
|
|
94
|
+
observable$.complete();
|
|
95
|
+
|
|
96
|
+
expect(unsubscribe).toHaveBeenCalledExactlyOnceWith();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const createManualSubscribable = (): Readonly<{
|
|
101
|
+
subscribable: Subscribable<number>;
|
|
102
|
+
emit: (value: number) => void;
|
|
103
|
+
completeSource: () => void;
|
|
104
|
+
unsubscribe: () => void;
|
|
105
|
+
}> => {
|
|
106
|
+
let mut_onNext: ((value: number) => void) | undefined = undefined;
|
|
107
|
+
|
|
108
|
+
let mut_onComplete: (() => void) | undefined = undefined;
|
|
109
|
+
|
|
110
|
+
const unsubscribe = vi.fn<() => void>(() => {
|
|
111
|
+
mut_onNext = undefined;
|
|
112
|
+
|
|
113
|
+
mut_onComplete = undefined;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
subscribable: {
|
|
118
|
+
subscribe: (onNext, _onError, onComplete) => {
|
|
119
|
+
mut_onNext = onNext;
|
|
120
|
+
|
|
121
|
+
mut_onComplete = onComplete;
|
|
122
|
+
|
|
123
|
+
return { unsubscribe };
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
emit: (value) => {
|
|
127
|
+
mut_onNext?.(value);
|
|
128
|
+
},
|
|
129
|
+
completeSource: () => {
|
|
130
|
+
mut_onComplete?.();
|
|
131
|
+
},
|
|
132
|
+
unsubscribe,
|
|
133
|
+
};
|
|
134
|
+
};
|
package/src/core/create/just.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Optional } from 'ts-data-forge';
|
|
2
|
-
import {
|
|
2
|
+
import { RootObservableClass } from '../class/index.mjs';
|
|
3
3
|
import type { InitializedRootObservable } from '../types/index.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -32,10 +32,9 @@ import type { InitializedRootObservable } from '../types/index.mjs';
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export const just = <const A,>(value: A): InitializedRootObservable<A> => {
|
|
35
|
-
const obs =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
);
|
|
35
|
+
const obs = new RootObservableClass<A>({
|
|
36
|
+
initialValue: Optional.some(value),
|
|
37
|
+
});
|
|
39
38
|
|
|
40
39
|
obs.complete();
|
|
41
40
|
|
|
@@ -1,5 +1,11 @@
|
|
|
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
|
|
1
7
|
import { Arr, Optional } from 'ts-data-forge';
|
|
2
|
-
import {
|
|
8
|
+
import { RootObservableClass } from '../class/index.mjs';
|
|
3
9
|
import type {
|
|
4
10
|
InitializedSourceObservable,
|
|
5
11
|
SourceObservable,
|
|
@@ -43,6 +49,13 @@ import type {
|
|
|
43
49
|
*
|
|
44
50
|
* assert.deepStrictEqual(valueHistory, [1, 2, 3]);
|
|
45
51
|
* ```
|
|
52
|
+
*
|
|
53
|
+
* @note Do not call `next` from a subscriber or a projection while another
|
|
54
|
+
* update is propagating: the write propagates immediately, so subscribers can
|
|
55
|
+
* see an intermediate state, and it logs a `console.warn` (once per
|
|
56
|
+
* observable). Derive the value from its source instead —
|
|
57
|
+
* `const b$ = a$.pipe(map((a) => a * 2))` rather than
|
|
58
|
+
* `a$.subscribe((a) => { setB(a * 2); })`.
|
|
46
59
|
*/
|
|
47
60
|
export function source<const A>(
|
|
48
61
|
initialValue: A,
|
|
@@ -51,20 +64,31 @@ export function source<const A>(
|
|
|
51
64
|
export function source<const A>(): SourceObservable<A>;
|
|
52
65
|
|
|
53
66
|
export function source<const A>(...args: readonly A[]): SourceObservable<A> {
|
|
54
|
-
return
|
|
55
|
-
|
|
67
|
+
return new SourceObservableClass<A>(...args);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class SourceObservableClass<A>
|
|
71
|
+
extends RootObservableClass<A>
|
|
72
|
+
implements SourceObservable<A>
|
|
73
|
+
{
|
|
74
|
+
constructor(...args: readonly A[]) {
|
|
75
|
+
super({
|
|
56
76
|
initialValue: Arr.isNonEmpty(args)
|
|
57
77
|
? Optional.some(args[0])
|
|
58
78
|
: Optional.none,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Returns the value held after the call: `nextValue`, or, once completed,
|
|
83
|
+
// the value already there — `undefined` only on a source that never had
|
|
84
|
+
// one, whose `next` is typed `void`.
|
|
85
|
+
next(nextValue: A): A | undefined {
|
|
86
|
+
if (this.isCompleted) {
|
|
87
|
+
return Optional.unwrap(this.getSnapshot());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.startUpdate(nextValue);
|
|
65
91
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}),
|
|
69
|
-
);
|
|
92
|
+
return nextValue;
|
|
93
|
+
}
|
|
70
94
|
}
|
|
@@ -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
|
+
});
|
|
@@ -1,5 +1,12 @@
|
|
|
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
|
|
1
8
|
import { Optional } from 'ts-data-forge';
|
|
2
|
-
import {
|
|
9
|
+
import { RootObservableClass } from '../class/index.mjs';
|
|
3
10
|
import type { TimerId, TimerObservable } from '../types/index.mjs';
|
|
4
11
|
|
|
5
12
|
/**
|
|
@@ -44,46 +51,58 @@ export const timer = (
|
|
|
44
51
|
startManually?: boolean;
|
|
45
52
|
}>,
|
|
46
53
|
): TimerObservable => {
|
|
47
|
-
|
|
54
|
+
const observable = new TimerObservableClass(milliSeconds);
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
if (!(options?.startManually ?? false)) {
|
|
57
|
+
observable.start();
|
|
58
|
+
}
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
clearTimeout(mut_timerId);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
60
|
+
return observable;
|
|
61
|
+
};
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
class TimerObservableClass
|
|
64
|
+
extends RootObservableClass<0>
|
|
65
|
+
implements TimerObservable
|
|
66
|
+
{
|
|
67
|
+
readonly #milliSeconds: number;
|
|
68
|
+
#mut_timerId: TimerId | undefined;
|
|
69
|
+
#mut_isStarted: boolean;
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
constructor(milliSeconds: number) {
|
|
72
|
+
super({ initialValue: Optional.none });
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
this.#milliSeconds = milliSeconds;
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
console.warn('cannot restart stopped TimerObservable');
|
|
76
|
+
this.#mut_timerId = undefined;
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
this.#mut_isStarted = false;
|
|
79
|
+
}
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
start(): void {
|
|
82
|
+
if (this.#mut_isStarted) {
|
|
83
|
+
console.warn('cannot start twice');
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
this.#mut_isStarted = true;
|
|
89
|
+
|
|
90
|
+
if (this.isCompleted) {
|
|
91
|
+
console.warn('cannot restart stopped TimerObservable');
|
|
92
|
+
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.#mut_timerId = setTimeout(() => {
|
|
97
|
+
this.startUpdate(0);
|
|
98
|
+
|
|
99
|
+
this.complete();
|
|
100
|
+
}, this.#milliSeconds);
|
|
86
101
|
}
|
|
87
102
|
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
protected override teardown(): void {
|
|
104
|
+
if (this.#mut_timerId !== undefined) {
|
|
105
|
+
clearTimeout(this.#mut_timerId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/core/index.mts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
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
|
|
1
8
|
import { Optional } from 'ts-data-forge';
|
|
2
|
-
import {
|
|
9
|
+
import { AsyncChildObservableClass } from '../class/index.mjs';
|
|
3
10
|
import type {
|
|
4
11
|
AuditOperatorObservable,
|
|
5
12
|
KeepInitialValueOperator,
|
|
6
13
|
Observable,
|
|
7
14
|
TimerId,
|
|
15
|
+
UpdateToken,
|
|
8
16
|
} from '../types/index.mjs';
|
|
9
17
|
|
|
10
18
|
/**
|
|
@@ -12,6 +20,9 @@ import type {
|
|
|
12
20
|
*
|
|
13
21
|
* Unlike `throttle` which emits the first value, `audit` emits the last value.
|
|
14
22
|
*
|
|
23
|
+
* Completing it, or its source completing, passes on the value its timer is
|
|
24
|
+
* still holding before the completion; `dispose()` drops that value.
|
|
25
|
+
*
|
|
15
26
|
* @template A - The type of values from the source
|
|
16
27
|
* @param milliSeconds - The audit time window in milliseconds
|
|
17
28
|
* @returns An operator that audits emissions from the observable
|
|
@@ -106,55 +117,78 @@ export const audit = <A,>(
|
|
|
106
117
|
): KeepInitialValueOperator<A, A> =>
|
|
107
118
|
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
108
119
|
((parentObservable) =>
|
|
109
|
-
|
|
120
|
+
new AuditObservableClass(
|
|
110
121
|
parentObservable,
|
|
111
122
|
milliSeconds,
|
|
112
123
|
)) as KeepInitialValueOperator<A, A>;
|
|
113
124
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
let mut_isSkipping = false;
|
|
125
|
+
class AuditObservableClass<A>
|
|
126
|
+
extends AsyncChildObservableClass<A, readonly [A]>
|
|
127
|
+
implements AuditOperatorObservable<A>
|
|
128
|
+
{
|
|
129
|
+
readonly #milliSeconds: number;
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
clearTimeout(mut_timerId);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
131
|
+
/** Set while a window is open; its end passes on the latest value. */
|
|
132
|
+
#mut_timerId: TimerId | undefined;
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
{
|
|
134
|
+
constructor(parentObservable: Observable<A>, milliSeconds: number) {
|
|
135
|
+
super({
|
|
130
136
|
parents: [parentObservable],
|
|
131
137
|
initialValue: parentObservable.getSnapshot(),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this.#mut_timerId = undefined;
|
|
141
|
+
|
|
142
|
+
this.#milliSeconds = milliSeconds;
|
|
143
|
+
|
|
144
|
+
this.attach();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Ends the open window, passing on the latest value of the source. */
|
|
148
|
+
#emitLatest(): void {
|
|
149
|
+
this.#mut_timerId = undefined;
|
|
150
|
+
|
|
151
|
+
const sn = this.parents[0].getSnapshot();
|
|
152
|
+
|
|
153
|
+
if (Optional.isSome(sn)) {
|
|
154
|
+
this.startUpdate(sn.value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override tryUpdate(updateToken: UpdateToken): void {
|
|
159
|
+
const par = this.parents[0];
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
this.#mut_timerId !== undefined ||
|
|
163
|
+
par.updateToken !== updateToken ||
|
|
164
|
+
Optional.isNone(par.getSnapshot())
|
|
165
|
+
) {
|
|
166
|
+
return; // skip update
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// set timer
|
|
170
|
+
this.#mut_timerId = setTimeout(() => {
|
|
171
|
+
this.#emitLatest();
|
|
172
|
+
}, this.#milliSeconds);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected override flush(): void {
|
|
176
|
+
if (this.#mut_timerId === undefined) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
clearTimeout(this.#mut_timerId);
|
|
181
|
+
|
|
182
|
+
this.#emitLatest();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected override teardown(): void {
|
|
186
|
+
if (this.#mut_timerId === undefined) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
clearTimeout(this.#mut_timerId);
|
|
191
|
+
|
|
192
|
+
this.#mut_timerId = undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { source } from '../create/index.mjs';
|
|
2
|
+
import type {
|
|
3
|
+
KeepInitialValueOperator,
|
|
4
|
+
Observable,
|
|
5
|
+
SourceObservable,
|
|
6
|
+
} from '../types/index.mjs';
|
|
7
|
+
import { audit } from './audit.mjs';
|
|
8
|
+
import { debounce } from './debounce.mjs';
|
|
9
|
+
|
|
10
|
+
const operators: readonly (readonly [
|
|
11
|
+
string,
|
|
12
|
+
(milliSeconds: number) => KeepInitialValueOperator<number, number>,
|
|
13
|
+
])[] = [
|
|
14
|
+
['debounce', debounce],
|
|
15
|
+
['audit', audit],
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
describe.each(operators)('%s', (_name, operator) => {
|
|
19
|
+
const setup = (): Readonly<{
|
|
20
|
+
source$: SourceObservable<number>;
|
|
21
|
+
operated$: Observable<number>;
|
|
22
|
+
history: readonly string[];
|
|
23
|
+
}> => {
|
|
24
|
+
const source$ = source<number>();
|
|
25
|
+
|
|
26
|
+
const operated$ = source$.pipe(operator(100));
|
|
27
|
+
|
|
28
|
+
const mut_history: string[] = [];
|
|
29
|
+
|
|
30
|
+
operated$.subscribe(
|
|
31
|
+
(v) => {
|
|
32
|
+
mut_history.push(`next ${v}`);
|
|
33
|
+
},
|
|
34
|
+
() => {
|
|
35
|
+
mut_history.push('complete');
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return { source$, operated$, history: mut_history };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
test('passes on the pending value, then completes, when the source completes within the window', () => {
|
|
43
|
+
vi.useFakeTimers();
|
|
44
|
+
|
|
45
|
+
const { source$, operated$, history } = setup();
|
|
46
|
+
|
|
47
|
+
source$.next(1);
|
|
48
|
+
|
|
49
|
+
source$.next(2);
|
|
50
|
+
|
|
51
|
+
source$.complete();
|
|
52
|
+
|
|
53
|
+
assert.deepStrictEqual(history, ['next 2', 'complete']);
|
|
54
|
+
|
|
55
|
+
assert.isTrue(operated$.isCompleted);
|
|
56
|
+
|
|
57
|
+
vi.runAllTimers();
|
|
58
|
+
|
|
59
|
+
assert.deepStrictEqual(history, ['next 2', 'complete']);
|
|
60
|
+
|
|
61
|
+
vi.useRealTimers();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('passes on the pending value, then completes, when it is completed itself', () => {
|
|
65
|
+
vi.useFakeTimers();
|
|
66
|
+
|
|
67
|
+
const { source$, operated$, history } = setup();
|
|
68
|
+
|
|
69
|
+
source$.next(1);
|
|
70
|
+
|
|
71
|
+
operated$.complete();
|
|
72
|
+
|
|
73
|
+
assert.deepStrictEqual(history, ['next 1', 'complete']);
|
|
74
|
+
|
|
75
|
+
vi.runAllTimers();
|
|
76
|
+
|
|
77
|
+
assert.deepStrictEqual(history, ['next 1', 'complete']);
|
|
78
|
+
|
|
79
|
+
vi.useRealTimers();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('completes without emitting when nothing is pending', () => {
|
|
83
|
+
vi.useFakeTimers();
|
|
84
|
+
|
|
85
|
+
const { source$, operated$, history } = setup();
|
|
86
|
+
|
|
87
|
+
source$.next(1);
|
|
88
|
+
|
|
89
|
+
vi.runAllTimers();
|
|
90
|
+
|
|
91
|
+
assert.deepStrictEqual(history, ['next 1']);
|
|
92
|
+
|
|
93
|
+
operated$.complete();
|
|
94
|
+
|
|
95
|
+
assert.deepStrictEqual(history, ['next 1', 'complete']);
|
|
96
|
+
|
|
97
|
+
vi.useRealTimers();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('drops the pending value when it is disposed', () => {
|
|
101
|
+
vi.useFakeTimers();
|
|
102
|
+
|
|
103
|
+
const { source$, operated$, history } = setup();
|
|
104
|
+
|
|
105
|
+
source$.next(1);
|
|
106
|
+
|
|
107
|
+
operated$.dispose();
|
|
108
|
+
|
|
109
|
+
assert.deepStrictEqual(history, ['complete']);
|
|
110
|
+
|
|
111
|
+
assert.isTrue(operated$.isCompleted);
|
|
112
|
+
|
|
113
|
+
vi.runAllTimers();
|
|
114
|
+
|
|
115
|
+
assert.deepStrictEqual(history, ['complete']);
|
|
116
|
+
|
|
117
|
+
assert.isFalse(source$.isCompleted);
|
|
118
|
+
|
|
119
|
+
vi.useRealTimers();
|
|
120
|
+
});
|
|
121
|
+
});
|