@guanghechen/observable 6.1.8 → 7.0.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.
- package/LICENSE +21 -0
- package/README.md +98 -65
- package/lib/cjs/index.cjs +8 -2
- package/lib/esm/index.mjs +8 -2
- package/lib/types/index.d.ts +8 -2
- package/package.json +20 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present guanghechen (https://github.com/guanghechen)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<header>
|
|
2
2
|
<h1 align="center">
|
|
3
|
-
<a href="https://github.com/guanghechen/sora/tree/@guanghechen/observable@
|
|
3
|
+
<a href="https://github.com/guanghechen/sora/tree/@guanghechen/observable@7.0.0/packages/observable#readme">@guanghechen/observable</a>
|
|
4
4
|
</h1>
|
|
5
5
|
<div align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/@guanghechen/observable">
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
</header>
|
|
50
50
|
<br/>
|
|
51
51
|
|
|
52
|
-
Observable pattern implementation with ticker functionality for reactive programming.
|
|
52
|
+
Observable pattern implementation with ticker functionality for reactive programming. Provides value
|
|
53
|
+
change notification with optional delay debouncing and custom equality comparison.
|
|
53
54
|
|
|
54
55
|
## Install
|
|
55
56
|
|
|
@@ -67,88 +68,120 @@ Observable pattern implementation with ticker functionality for reactive program
|
|
|
67
68
|
|
|
68
69
|
## Usage
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
| :--------------: | :-------------------------------------------------------: |
|
|
72
|
-
| `Observable` | Reactive observable value with change notification |
|
|
73
|
-
| `Ticker` | Timer-based ticker for scheduled notifications |
|
|
71
|
+
### Basic Observable
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
import { Observable } from '@guanghechen/observable'
|
|
75
|
+
import { Subscriber } from '@guanghechen/subscriber'
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
const count = new Observable<number>(0)
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const subscriber = new Subscriber({
|
|
80
|
+
onNext: (newValue, oldValue) => {
|
|
81
|
+
console.log(`Value changed from ${oldValue} to ${newValue}`)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
const unsubscribable = count.subscribe(subscriber)
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.log(`Value changed from ${oldValue} to ${newValue}`)
|
|
89
|
-
},
|
|
90
|
-
onDispose: () => {
|
|
91
|
-
console.log('Subscriber disposed')
|
|
92
|
-
}
|
|
93
|
-
})
|
|
87
|
+
count.next(1) // "Value changed from 0 to 1"
|
|
88
|
+
count.next(2) // "Value changed from 1 to 2"
|
|
89
|
+
count.next(2) // No notification (same value)
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
const unsubscribe = observable.subscribe(subscriber)
|
|
91
|
+
console.log(count.getSnapshot()) // 2
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
unsubscribable.unsubscribe()
|
|
94
|
+
count.dispose()
|
|
95
|
+
```
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
```
|
|
97
|
+
### Observable with Custom Equality
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
```typescript
|
|
100
|
+
import { Observable } from '@guanghechen/observable'
|
|
101
|
+
import { Subscriber } from '@guanghechen/subscriber'
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
interface IUser {
|
|
104
|
+
id: number
|
|
105
|
+
name: string
|
|
106
|
+
}
|
|
109
107
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
const user = new Observable<IUser>(
|
|
109
|
+
{ id: 1, name: 'John' },
|
|
110
|
+
{ equals: (a, b) => a.id === b.id }
|
|
111
|
+
)
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
})
|
|
113
|
+
const subscriber = new Subscriber<IUser>({
|
|
114
|
+
onNext: (value) => console.log('User changed:', value)
|
|
115
|
+
})
|
|
120
116
|
|
|
121
|
-
|
|
117
|
+
user.subscribe(subscriber)
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
// This won't trigger notification (same id)
|
|
120
|
+
user.next({ id: 1, name: 'Jane' })
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
```
|
|
122
|
+
// This will trigger notification (different id)
|
|
123
|
+
user.next({ id: 2, name: 'Bob' })
|
|
129
124
|
|
|
130
|
-
|
|
125
|
+
// Force notification even if equal
|
|
126
|
+
user.next({ id: 2, name: 'Bob Updated' }, { force: true })
|
|
127
|
+
```
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
import { Ticker } from '@guanghechen/observable'
|
|
134
|
-
import { Subscriber } from '@guanghechen/subscriber'
|
|
129
|
+
### Observable with Delay (Debouncing)
|
|
135
130
|
|
|
136
|
-
|
|
131
|
+
```typescript
|
|
132
|
+
import { Observable } from '@guanghechen/observable'
|
|
133
|
+
import { Subscriber } from '@guanghechen/subscriber'
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log('Ticker value:', value)
|
|
141
|
-
}
|
|
142
|
-
})
|
|
135
|
+
// Debounce notifications by 100ms
|
|
136
|
+
const search = new Observable<string>('', { delay: 100 })
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
138
|
+
const subscriber = new Subscriber<string>({
|
|
139
|
+
onNext: (value) => console.log('Search:', value)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
search.subscribe(subscriber)
|
|
143
|
+
|
|
144
|
+
// Rapid updates - only the last value will be notified after 100ms
|
|
145
|
+
search.next('h')
|
|
146
|
+
search.next('he')
|
|
147
|
+
search.next('hel')
|
|
148
|
+
search.next('hell')
|
|
149
|
+
search.next('hello')
|
|
150
|
+
// Output after 100ms: "Search: hello"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Ticker
|
|
154
|
+
|
|
155
|
+
Ticker is a specialized observable that increments a counter value:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { Observable, Ticker } from '@guanghechen/observable'
|
|
159
|
+
import { Subscriber } from '@guanghechen/subscriber'
|
|
160
|
+
|
|
161
|
+
const ticker = new Ticker({ start: 0, delay: 100 })
|
|
162
|
+
|
|
163
|
+
const subscriber = new Subscriber<number>({
|
|
164
|
+
onNext: (tick) => console.log('Tick:', tick)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
ticker.subscribe(subscriber)
|
|
168
|
+
|
|
169
|
+
ticker.tick() // Tick: 1
|
|
170
|
+
ticker.tick() // Tick: 2
|
|
171
|
+
|
|
172
|
+
// Observe other observables - ticker increments when they change
|
|
173
|
+
const name = new Observable<string>('John')
|
|
174
|
+
const unobservable = ticker.observe(name)
|
|
175
|
+
|
|
176
|
+
name.next('Jane') // Also triggers: Tick: 3
|
|
177
|
+
|
|
178
|
+
unobservable.unobserve()
|
|
179
|
+
ticker.dispose()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Reference
|
|
183
|
+
|
|
184
|
+
- [homepage][homepage]
|
|
152
185
|
|
|
153
186
|
[homepage]:
|
|
154
|
-
https://github.com/guanghechen/sora/tree/@guanghechen/observable@
|
|
187
|
+
https://github.com/guanghechen/sora/tree/@guanghechen/observable@7.0.0/packages/observable#readme
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ class Observable extends disposable.BatchDisposable {
|
|
|
22
22
|
equals;
|
|
23
23
|
_delay;
|
|
24
24
|
_subscribers;
|
|
25
|
+
_onError;
|
|
25
26
|
_value;
|
|
26
27
|
_updateTick;
|
|
27
28
|
_notifyTick;
|
|
@@ -29,7 +30,7 @@ class Observable extends disposable.BatchDisposable {
|
|
|
29
30
|
_timer;
|
|
30
31
|
constructor(defaultValue, options = {}) {
|
|
31
32
|
super();
|
|
32
|
-
const { equals = defaultEquals } = options;
|
|
33
|
+
const { equals = defaultEquals, onError } = options;
|
|
33
34
|
this._delay = Math.max(0, Number(options.delay) || 0);
|
|
34
35
|
this._subscribers = new subscriber.Subscribers();
|
|
35
36
|
this._value = defaultValue;
|
|
@@ -38,6 +39,11 @@ class Observable extends disposable.BatchDisposable {
|
|
|
38
39
|
this._lastNotifiedValue = undefined;
|
|
39
40
|
this._timer = undefined;
|
|
40
41
|
this.equals = equals;
|
|
42
|
+
this._onError =
|
|
43
|
+
onError ??
|
|
44
|
+
(error => {
|
|
45
|
+
console.error('Error in observable notification:', error);
|
|
46
|
+
});
|
|
41
47
|
}
|
|
42
48
|
dispose() {
|
|
43
49
|
if (this.disposed)
|
|
@@ -105,7 +111,7 @@ class Observable extends disposable.BatchDisposable {
|
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
catch (error) {
|
|
108
|
-
|
|
114
|
+
this._onError(error);
|
|
109
115
|
}
|
|
110
116
|
finally {
|
|
111
117
|
this._timer = undefined;
|
package/lib/esm/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ class Observable extends BatchDisposable {
|
|
|
20
20
|
equals;
|
|
21
21
|
_delay;
|
|
22
22
|
_subscribers;
|
|
23
|
+
_onError;
|
|
23
24
|
_value;
|
|
24
25
|
_updateTick;
|
|
25
26
|
_notifyTick;
|
|
@@ -27,7 +28,7 @@ class Observable extends BatchDisposable {
|
|
|
27
28
|
_timer;
|
|
28
29
|
constructor(defaultValue, options = {}) {
|
|
29
30
|
super();
|
|
30
|
-
const { equals = defaultEquals } = options;
|
|
31
|
+
const { equals = defaultEquals, onError } = options;
|
|
31
32
|
this._delay = Math.max(0, Number(options.delay) || 0);
|
|
32
33
|
this._subscribers = new Subscribers();
|
|
33
34
|
this._value = defaultValue;
|
|
@@ -36,6 +37,11 @@ class Observable extends BatchDisposable {
|
|
|
36
37
|
this._lastNotifiedValue = undefined;
|
|
37
38
|
this._timer = undefined;
|
|
38
39
|
this.equals = equals;
|
|
40
|
+
this._onError =
|
|
41
|
+
onError ??
|
|
42
|
+
(error => {
|
|
43
|
+
console.error('Error in observable notification:', error);
|
|
44
|
+
});
|
|
39
45
|
}
|
|
40
46
|
dispose() {
|
|
41
47
|
if (this.disposed)
|
|
@@ -103,7 +109,7 @@ class Observable extends BatchDisposable {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
catch (error) {
|
|
106
|
-
|
|
112
|
+
this._onError(error);
|
|
107
113
|
}
|
|
108
114
|
finally {
|
|
109
115
|
this._timer = undefined;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BatchDisposable } from '@guanghechen/disposable';
|
|
2
2
|
import { ISubscribable, ISubscribers, ISubscriber, IUnsubscribable } from '@guanghechen/subscriber';
|
|
3
|
+
import { IBatchDisposable } from '@guanghechen/types';
|
|
3
4
|
|
|
4
5
|
type IEquals<T> = (x: T, y: T) => boolean;
|
|
5
6
|
interface IObservableOptions<T> {
|
|
@@ -11,6 +12,10 @@ interface IObservableOptions<T> {
|
|
|
11
12
|
* Determine whether the two values are equal.
|
|
12
13
|
*/
|
|
13
14
|
readonly equals?: IEquals<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Error handler for async notifications.
|
|
17
|
+
*/
|
|
18
|
+
readonly onError?: (error: unknown) => void;
|
|
14
19
|
}
|
|
15
20
|
interface IObservableNextOptions {
|
|
16
21
|
/**
|
|
@@ -48,6 +53,7 @@ declare class Observable<T> extends BatchDisposable implements IObservable<T> {
|
|
|
48
53
|
readonly equals: IEquals<T>;
|
|
49
54
|
protected readonly _delay: number;
|
|
50
55
|
protected readonly _subscribers: ISubscribers<T>;
|
|
56
|
+
protected readonly _onError: (error: unknown) => void;
|
|
51
57
|
protected _value: T;
|
|
52
58
|
protected _updateTick: number;
|
|
53
59
|
protected _notifyTick: number;
|
|
@@ -108,7 +114,7 @@ declare class Ticker extends Observable<number> implements ITicker {
|
|
|
108
114
|
observe<T>(observable: IBaseObservable<T>, options?: ITickerObserveOptions): IUnobservable;
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
declare const noop: (..._args:
|
|
117
|
+
declare const noop: (..._args: unknown[]) => void;
|
|
112
118
|
declare const noopUnsubscribable: IUnsubscribable;
|
|
113
119
|
declare const noopUnobservable: IUnobservable;
|
|
114
120
|
declare const isObservable: (obj: unknown) => obj is IObservable<unknown>;
|
package/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanghechen/observable",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "guanghechen",
|
|
6
6
|
"url": "https://github.com/guanghechen/"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/guanghechen/sora/tree/@guanghechen/observable@
|
|
10
|
+
"url": "https://github.com/guanghechen/sora/tree/@guanghechen/observable@7.0.0",
|
|
11
11
|
"directory": "packages/observable"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.com/guanghechen/sora/tree/@guanghechen/observable@
|
|
13
|
+
"homepage": "https://github.com/guanghechen/sora/tree/@guanghechen/observable@7.0.0/packages/observable#readme",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"observable"
|
|
16
16
|
],
|
|
17
17
|
"type": "module",
|
|
18
18
|
"exports": {
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./lib/types/index.d.ts",
|
|
21
|
+
"source": "./src/index.ts",
|
|
22
|
+
"import": "./lib/esm/index.mjs",
|
|
23
|
+
"require": "./lib/cjs/index.cjs"
|
|
24
|
+
}
|
|
22
25
|
},
|
|
23
26
|
"types": "./lib/types/index.d.ts",
|
|
24
27
|
"main": "./lib/cjs/index.cjs",
|
|
@@ -34,8 +37,15 @@
|
|
|
34
37
|
"README.md"
|
|
35
38
|
],
|
|
36
39
|
"dependencies": {
|
|
37
|
-
"@guanghechen/disposable": "^
|
|
38
|
-
"@guanghechen/subscriber": "^
|
|
40
|
+
"@guanghechen/disposable": "^2.0.1",
|
|
41
|
+
"@guanghechen/subscriber": "^2.0.1",
|
|
42
|
+
"@guanghechen/types": "^2.0.1"
|
|
39
43
|
},
|
|
40
|
-
"
|
|
41
|
-
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "rollup -c ../../rollup.config.mjs",
|
|
46
|
+
"clean": "rimraf lib",
|
|
47
|
+
"test": "vitest run --config ../../vitest.config.ts",
|
|
48
|
+
"test:coverage": "vitest run --config ../../vitest.config.ts --coverage",
|
|
49
|
+
"test:update": "vitest run --config ../../vitest.config.ts -u"
|
|
50
|
+
}
|
|
51
|
+
}
|