synstate 3.1.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +60 -2
  2. package/dist/core/base/create-child-observable.d.mts.map +1 -1
  3. package/dist/core/base/create-child-observable.mjs +60 -3
  4. package/dist/core/base/create-child-observable.mjs.map +1 -1
  5. package/dist/core/base/create-observable-base.d.mts +21 -4
  6. package/dist/core/base/create-observable-base.d.mts.map +1 -1
  7. package/dist/core/base/create-observable-base.mjs +69 -13
  8. package/dist/core/base/create-observable-base.mjs.map +1 -1
  9. package/dist/core/base/create-root-observable.d.mts.map +1 -1
  10. package/dist/core/base/create-root-observable.mjs +2 -1
  11. package/dist/core/base/create-root-observable.mjs.map +1 -1
  12. package/dist/core/combine/combine.d.mts.map +1 -1
  13. package/dist/core/combine/combine.mjs +0 -1
  14. package/dist/core/combine/combine.mjs.map +1 -1
  15. package/dist/core/combine/merge.d.mts.map +1 -1
  16. package/dist/core/combine/merge.mjs +0 -2
  17. package/dist/core/combine/merge.mjs.map +1 -1
  18. package/dist/core/combine/zip.d.mts.map +1 -1
  19. package/dist/core/combine/zip.mjs +0 -2
  20. package/dist/core/combine/zip.mjs.map +1 -1
  21. package/dist/core/create/counter.d.mts +10 -2
  22. package/dist/core/create/counter.d.mts.map +1 -1
  23. package/dist/core/create/counter.mjs +17 -7
  24. package/dist/core/create/counter.mjs.map +1 -1
  25. package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
  26. package/dist/core/create/from-abortable-promise.mjs +0 -2
  27. package/dist/core/create/from-abortable-promise.mjs.map +1 -1
  28. package/dist/core/create/from-promise.d.mts.map +1 -1
  29. package/dist/core/create/from-promise.mjs +0 -1
  30. package/dist/core/create/from-promise.mjs.map +1 -1
  31. package/dist/core/create/from-subscribable.d.mts.map +1 -1
  32. package/dist/core/create/from-subscribable.mjs +0 -1
  33. package/dist/core/create/from-subscribable.mjs.map +1 -1
  34. package/dist/core/create/just.d.mts.map +1 -1
  35. package/dist/core/create/just.mjs +0 -1
  36. package/dist/core/create/just.mjs.map +1 -1
  37. package/dist/core/operators/audit.d.mts.map +1 -1
  38. package/dist/core/operators/audit.mjs +0 -1
  39. package/dist/core/operators/audit.mjs.map +1 -1
  40. package/dist/core/operators/debounce.d.mts.map +1 -1
  41. package/dist/core/operators/debounce.mjs +0 -1
  42. package/dist/core/operators/debounce.mjs.map +1 -1
  43. package/dist/core/operators/filter.d.mts.map +1 -1
  44. package/dist/core/operators/filter.mjs +19 -0
  45. package/dist/core/operators/filter.mjs.map +1 -1
  46. package/dist/core/operators/map.d.mts.map +1 -1
  47. package/dist/core/operators/map.mjs +0 -1
  48. package/dist/core/operators/map.mjs.map +1 -1
  49. package/dist/core/operators/skip-if-no-change.d.mts.map +1 -1
  50. package/dist/core/operators/skip-if-no-change.mjs +0 -1
  51. package/dist/core/operators/skip-if-no-change.mjs.map +1 -1
  52. package/dist/core/operators/take-until.d.mts.map +1 -1
  53. package/dist/core/operators/take-until.mjs +0 -1
  54. package/dist/core/operators/take-until.mjs.map +1 -1
  55. package/dist/core/operators/throttle.d.mts.map +1 -1
  56. package/dist/core/operators/throttle.mjs +0 -1
  57. package/dist/core/operators/throttle.mjs.map +1 -1
  58. package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
  59. package/dist/core/operators/with-buffered-from.mjs +0 -1
  60. package/dist/core/operators/with-buffered-from.mjs.map +1 -1
  61. package/dist/core/predefined/operators/unwrap-optional.d.mts.map +1 -1
  62. package/dist/core/predefined/operators/unwrap-optional.mjs +0 -1
  63. package/dist/core/predefined/operators/unwrap-optional.mjs.map +1 -1
  64. package/dist/core/predefined/operators/unwrap-result-ok.d.mts.map +1 -1
  65. package/dist/core/predefined/operators/unwrap-result-ok.mjs +0 -1
  66. package/dist/core/predefined/operators/unwrap-result-ok.mjs.map +1 -1
  67. package/dist/core/types/observable.d.mts +2 -0
  68. package/dist/core/types/observable.d.mts.map +1 -1
  69. package/dist/core/types/observable.mjs.map +1 -1
  70. package/dist/core/utils/id-maker.d.mts.map +1 -1
  71. package/dist/core/utils/id-maker.mjs +0 -1
  72. package/dist/core/utils/id-maker.mjs.map +1 -1
  73. package/dist/core/utils/utils.d.mts.map +1 -1
  74. package/dist/core/utils/utils.mjs +0 -1
  75. package/dist/core/utils/utils.mjs.map +1 -1
  76. package/package.json +14 -14
  77. package/src/core/base/create-child-observable.mts +78 -3
  78. package/src/core/base/create-observable-base.mts +111 -31
  79. package/src/core/base/create-root-observable.mts +3 -1
  80. package/src/core/combine/combine.mts +0 -1
  81. package/src/core/combine/merge.mts +0 -2
  82. package/src/core/combine/zip.mts +0 -2
  83. package/src/core/create/counter.mts +20 -10
  84. package/src/core/create/counter.test.mts +183 -0
  85. package/src/core/create/from-abortable-promise.mts +0 -2
  86. package/src/core/create/from-promise.mts +0 -1
  87. package/src/core/create/from-subscribable.mts +0 -1
  88. package/src/core/create/just.mts +0 -1
  89. package/src/core/operators/audit.mts +0 -1
  90. package/src/core/operators/debounce.mts +0 -1
  91. package/src/core/operators/filter.mts +41 -0
  92. package/src/core/operators/map.mts +0 -1
  93. package/src/core/operators/skip-if-no-change.mts +0 -1
  94. package/src/core/operators/take-until.mts +0 -1
  95. package/src/core/operators/throttle.mts +0 -1
  96. package/src/core/operators/with-buffered-from.mts +0 -1
  97. package/src/core/predefined/operators/unwrap-optional.mts +0 -1
  98. package/src/core/predefined/operators/unwrap-result-ok.mts +0 -1
  99. package/src/core/types/observable.mts +2 -0
  100. package/src/core/utils/id-maker.mts +0 -1
  101. package/src/core/utils/utils.mts +0 -1
@@ -1,3 +1,4 @@
1
+ import { Arr } from 'ts-data-forge';
1
2
  import { counter } from './counter.mjs';
2
3
 
3
4
  describe(counter, () => {
@@ -8,4 +9,186 @@ describe(counter, () => {
8
9
 
9
10
  assert.isTrue(counter$.isCompleted);
10
11
  });
12
+
13
+ test('emits 0 right after start(), then 1, 2, … every interval', () => {
14
+ vi.useFakeTimers();
15
+
16
+ const start = Date.now();
17
+
18
+ const counter$ = counter(100);
19
+
20
+ const mut_history: number[] = [];
21
+
22
+ counter$.subscribe((n) => {
23
+ mut_history.push(n);
24
+ });
25
+
26
+ vi.advanceTimersToNextTimer();
27
+
28
+ assert.deepStrictEqual(mut_history, [0]);
29
+
30
+ assert.strictEqual(vi.getMockedSystemTime()?.getTime(), start);
31
+
32
+ vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
33
+
34
+ assert.deepStrictEqual(mut_history, [0, 1, 2]);
35
+
36
+ counter$.complete();
37
+
38
+ vi.useRealTimers();
39
+ });
40
+
41
+ test('emitInitial: false skips the 0 and starts with 1 after one interval', () => {
42
+ vi.useFakeTimers();
43
+
44
+ const start = Date.now();
45
+
46
+ const counter$ = counter(100, { emitInitial: false });
47
+
48
+ const mut_history: number[] = [];
49
+
50
+ counter$.subscribe((n) => {
51
+ mut_history.push(n);
52
+ });
53
+
54
+ vi.advanceTimersToNextTimer();
55
+
56
+ assert.deepStrictEqual(mut_history, [1]);
57
+
58
+ assert.strictEqual(vi.getMockedSystemTime()?.getTime(), start + 100);
59
+
60
+ vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
61
+
62
+ assert.deepStrictEqual(mut_history, [1, 2, 3]);
63
+
64
+ counter$.complete();
65
+
66
+ vi.useRealTimers();
67
+ });
68
+
69
+ test('emitInitial: false still clears the interval on complete', () => {
70
+ vi.useFakeTimers();
71
+
72
+ const counter$ = counter(100, { emitInitial: false });
73
+
74
+ const mut_history: number[] = [];
75
+
76
+ counter$.subscribe((n) => {
77
+ mut_history.push(n);
78
+ });
79
+
80
+ vi.advanceTimersToNextTimer();
81
+
82
+ counter$.complete();
83
+
84
+ assert.strictEqual(vi.getTimerCount(), 0);
85
+
86
+ assert.deepStrictEqual(mut_history, [1]);
87
+
88
+ vi.useRealTimers();
89
+ });
90
+
91
+ test.each([
92
+ { emitInitial: true, expected: [0, 1, 2], firstAt: 0 },
93
+ { emitInitial: false, expected: [1, 2, 3], firstAt: 100 },
94
+ ] as const)(
95
+ 'startManually: false, emitInitial: $emitInitial starts at creation',
96
+ ({ emitInitial, expected, firstAt }) => {
97
+ vi.useFakeTimers();
98
+
99
+ const start = Date.now();
100
+
101
+ const counter$ = counter(100, { startManually: false, emitInitial });
102
+
103
+ const mut_history: number[] = [];
104
+
105
+ counter$.subscribe((n) => {
106
+ mut_history.push(n);
107
+ });
108
+
109
+ vi.advanceTimersToNextTimer();
110
+
111
+ assert.deepStrictEqual<readonly number[]>(
112
+ mut_history,
113
+ Arr.take(expected, 1),
114
+ );
115
+
116
+ assert.strictEqual(vi.getMockedSystemTime()?.getTime(), start + firstAt);
117
+
118
+ vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
119
+
120
+ assert.deepStrictEqual<readonly number[]>(mut_history, expected);
121
+
122
+ counter$.complete();
123
+
124
+ vi.useRealTimers();
125
+ },
126
+ );
127
+
128
+ test.each([
129
+ { emitInitial: true, expected: [0, 1, 2], firstAt: 0 },
130
+ { emitInitial: false, expected: [1, 2, 3], firstAt: 100 },
131
+ ] as const)(
132
+ 'startManually: true, emitInitial: $emitInitial waits for start()',
133
+ ({ emitInitial, expected, firstAt }) => {
134
+ vi.useFakeTimers();
135
+
136
+ const counter$ = counter(100, { startManually: true, emitInitial });
137
+
138
+ const mut_history: number[] = [];
139
+
140
+ counter$.subscribe((n) => {
141
+ mut_history.push(n);
142
+ });
143
+
144
+ assert.strictEqual(vi.getTimerCount(), 0);
145
+
146
+ vi.setSystemTime(Date.now() + 1000);
147
+
148
+ const start = Date.now();
149
+
150
+ counter$.start();
151
+
152
+ vi.advanceTimersToNextTimer();
153
+
154
+ assert.deepStrictEqual<readonly number[]>(
155
+ mut_history,
156
+ Arr.take(expected, 1),
157
+ );
158
+
159
+ assert.strictEqual(vi.getMockedSystemTime()?.getTime(), start + firstAt);
160
+
161
+ vi.advanceTimersToNextTimer().advanceTimersToNextTimer();
162
+
163
+ assert.deepStrictEqual<readonly number[]>(mut_history, expected);
164
+
165
+ counter$.complete();
166
+
167
+ assert.strictEqual(vi.getTimerCount(), 0);
168
+
169
+ vi.useRealTimers();
170
+ },
171
+ );
172
+
173
+ test('completing before the first emission cancels the pending 0', () => {
174
+ vi.useFakeTimers();
175
+
176
+ const counter$ = counter(100);
177
+
178
+ const mut_history: number[] = [];
179
+
180
+ counter$.subscribe((n) => {
181
+ mut_history.push(n);
182
+ });
183
+
184
+ counter$.complete();
185
+
186
+ assert.strictEqual(vi.getTimerCount(), 0);
187
+
188
+ vi.runAllTimers();
189
+
190
+ assert.deepStrictEqual(mut_history, []);
191
+
192
+ vi.useRealTimers();
193
+ });
11
194
  });
@@ -71,7 +71,6 @@ import type { FromPromiseObservable } from '../types/index.mjs';
71
71
  */
72
72
  export const fromAbortablePromise = <A, E = unknown>(
73
73
  // @sumi-expect-error readonly/require-readonly-parameter
74
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
75
74
  factory: (signal: AbortSignal) => Promise<A>,
76
75
  ): FromPromiseObservable<A, E> => {
77
76
  const abortController = new AbortController();
@@ -108,7 +107,6 @@ export const fromAbortablePromise = <A, E = unknown>(
108
107
  startUpdate(
109
108
  Result.err(
110
109
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
111
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
112
110
  error as E,
113
111
  ),
114
112
  );
@@ -71,7 +71,6 @@ export const fromPromise = <A, E = unknown>(
71
71
  startUpdate(
72
72
  Result.err(
73
73
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
74
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
75
74
  error as E,
76
75
  ),
77
76
  );
@@ -77,7 +77,6 @@ export const fromSubscribable = <A, E = unknown>(
77
77
  startUpdate(
78
78
  Result.err(
79
79
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
80
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
81
80
  error as E,
82
81
  ),
83
82
  );
@@ -40,6 +40,5 @@ export const just = <const A,>(value: A): InitializedRootObservable<A> => {
40
40
  obs.complete();
41
41
 
42
42
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
43
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
44
43
  return obs as InitializedRootObservable<A>;
45
44
  };
@@ -105,7 +105,6 @@ export const audit = <A,>(
105
105
  milliSeconds: number,
106
106
  ): KeepInitialValueOperator<A, A> =>
107
107
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
108
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
109
108
  ((parentObservable) =>
110
109
  createAuditObservable(
111
110
  parentObservable,
@@ -96,7 +96,6 @@ export const debounce = <A,>(
96
96
  milliSeconds: number,
97
97
  ): KeepInitialValueOperator<A, A> =>
98
98
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
99
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
100
99
  ((parentObservable) =>
101
100
  createDebounceObservable(
102
101
  parentObservable,
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  asSafeUint,
3
3
  expectType,
4
+ isNotUndefined,
5
+ isString,
4
6
  Optional,
5
7
  pipe,
6
8
  SafeUint,
@@ -68,6 +70,11 @@ export function filter<A, B extends A>(
68
70
  predicate: (value: A, index: SafeUint | -1) => value is B,
69
71
  ): DropInitialValueOperator<A, B>;
70
72
 
73
+ // A type-guard predicate and a boolean one are both functions at run time, so
74
+ // this set only refines the return type (#1952). It stays overloaded because
75
+ // one signature with a union parameter infers `unknown` for a generic guard
76
+ // such as `isNotUndefined`; see the type test below.
77
+ // @sumi-expect-error functions/no-refinement-overload
71
78
  export function filter<A>(
72
79
  predicate: (value: A, index: SafeUint | -1) => boolean,
73
80
  ): DropInitialValueOperator<A, A>;
@@ -139,4 +146,38 @@ if (import.meta.vitest !== undefined) {
139
146
 
140
147
  expectType<typeof _d, Observable<number>>('=');
141
148
  }
149
+
150
+ {
151
+ const s = source<number | undefined>();
152
+
153
+ const _guarded = s.pipe(filter((x): x is number => x !== undefined));
154
+
155
+ expectType<typeof _guarded, Observable<number>>('=');
156
+
157
+ const _inferred = s.pipe(filter((x) => x !== undefined));
158
+
159
+ expectType<typeof _inferred, Observable<number>>('=');
160
+
161
+ // A generic guard is what a single signature with a union parameter
162
+ // loses (it infers `unknown`), and why this set stays overloaded.
163
+ const _generic = s.pipe(filter(isNotUndefined));
164
+
165
+ expectType<typeof _generic, Observable<number>>('=');
166
+
167
+ const _withIndex = s.pipe(filter((x, i) => i === -1 || x === 0));
168
+
169
+ expectType<typeof _withIndex, Observable<number | undefined>>('=');
170
+ }
171
+
172
+ {
173
+ const _d = source<unknown>().pipe(filter(isString));
174
+
175
+ expectType<typeof _d, Observable<string>>('=');
176
+
177
+ const _standalone = filter((x: number) => x > 0);
178
+
179
+ expectType<typeof _standalone, DropInitialValueOperator<number, number>>(
180
+ '=',
181
+ );
182
+ }
142
183
  }
@@ -51,7 +51,6 @@ export const map = <A, B>(
51
51
  mapFn: (x: A, index: SafeUint | -1) => B,
52
52
  ): KeepInitialValueOperator<A, B> =>
53
53
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
54
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
55
54
  ((parentObservable) =>
56
55
  createMapObservable(parentObservable, mapFn)) as KeepInitialValueOperator<
57
56
  A,
@@ -71,7 +71,6 @@ export const skipIfNoChange = <A,>(
71
71
  eq: (x: A, y: A) => boolean = (x, y) => Object.is(x, y),
72
72
  ): KeepInitialValueOperator<A, A> =>
73
73
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
74
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
75
74
  ((parentObservable) =>
76
75
  createSkipIfNoChangeObservable(
77
76
  parentObservable,
@@ -58,7 +58,6 @@ export const takeUntil = <A,>(
58
58
  notifier: Observable<unknown>,
59
59
  ): KeepInitialValueOperator<A, A> =>
60
60
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
61
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
62
61
  ((parentObservable) =>
63
62
  createTakeUntilObservable(
64
63
  parentObservable,
@@ -89,7 +89,6 @@ export const throttle = <A,>(
89
89
  milliSeconds: number,
90
90
  ): KeepInitialValueOperator<A, A> =>
91
91
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
92
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
93
92
  ((parentObservable) =>
94
93
  createThrottleObservable(
95
94
  parentObservable,
@@ -64,7 +64,6 @@ export const withBufferedFrom = <A, B>(
64
64
  observable: Observable<B>,
65
65
  ): KeepInitialValueOperator<A, readonly [A, readonly B[]]> =>
66
66
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
67
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
68
67
  ((parentObservable) =>
69
68
  createWithBufferedFromObservable(
70
69
  parentObservable,
@@ -47,5 +47,4 @@ export const unwrapOptional = <
47
47
  O extends UnknownOptional,
48
48
  >(): KeepInitialValueOperator<O, Optional.Unwrap<O> | undefined> =>
49
49
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
50
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
51
50
  map(Optional.unwrap as Fn<O, Optional.Unwrap<O> | undefined>);
@@ -47,5 +47,4 @@ export const unwrapResultOk = <
47
47
  R extends UnknownResult,
48
48
  >(): KeepInitialValueOperator<R, Result.UnwrapOk<R> | undefined> =>
49
49
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
50
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
51
50
  map(Result.unwrapOk as Fn<R, Result.UnwrapOk<R> | undefined>);
@@ -25,6 +25,7 @@ type CreateObservableType<A, Kind extends ObservableKind> = Readonly<{
25
25
  // reactive dependency tree structure
26
26
  depth: Kind extends 'root' ? 0 : number;
27
27
  addChild: <B>(child: ChildObservable<B>) => void;
28
+ deleteChild: <B>(child: ChildObservable<B>) => void;
28
29
 
29
30
  // state
30
31
  getSnapshot: () => Optional<A>;
@@ -84,6 +85,7 @@ namespace ObservableTypeConverter {
84
85
  > = O &
85
86
  Readonly<{
86
87
  addDescendant: <B>(child: ChildObservable<B>) => void;
88
+ deleteDescendant: <B>(child: ChildObservable<B>) => void;
87
89
  }>;
88
90
  }
89
91
 
@@ -9,7 +9,6 @@ function* idMaker<T extends symbol>(): Generator<T, T, T> {
9
9
 
10
10
  while (true) {
11
11
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
12
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
13
12
  yield Symbol(mut_i.toString()) as T;
14
13
 
15
14
  mut_i += 1;
@@ -52,6 +52,5 @@ export const binarySearch = <N extends number>(
52
52
  }
53
53
 
54
54
  // @sumi-expect-error banned-syntax/no-unsafe-type-assertion
55
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
56
55
  return mut_mid as NegativeInt32 | Uint32;
57
56
  };