flinker 2.0.2 → 2.0.4
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 +206 -3
- package/dist/esm/RX.js +0 -3
- package/dist/esm/RXPublisher.js +0 -18
- package/dist/esm/RXSubscriber.js +1 -1
- package/dist/types/RXPublisher.d.ts +3 -3
- package/dist/types/index.d.ts +1 -1
- package/package.json +3 -11
package/README.md
CHANGED
|
@@ -1,7 +1,210 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
## Intro
|
|
2
|
+
__Flinker__ is a functional reactive programming library. __Flinker__ is not limited to user interfaces. It also can be adapted to React.js (e.g. [flinker-react](https://github.com/Dittner/Flinker-React)) or your own frontend framework (e.g. [flinker-dom](https://github.com/Dittner/FlinkerDom)).
|
|
3
|
+
|
|
4
|
+
## RXPublisher, RXPipeline, RXOperator and RXSubscriber
|
|
5
|
+
Four key concepts: publisher, pipeline, operator and subscriber. A publisher provides data when available and upon request. To subscribe to changes, the publisher provides the `pipe()` method, which creates a pipeline.
|
|
6
|
+
|
|
7
|
+
RXPipeline is a queue of operators (RXOperator) to control the flow of data. Operators are a convenient name for a number of pre-built functions such as: map, flatMap, filter, debounce, etc.
|
|
8
|
+
|
|
9
|
+
Subscribers are responsible for accepting the data (and possible errors) provided by a publisher. Subscribers are joined to the end of a pipeline.
|
|
10
|
+
|
|
11
|
+
A publisher is generating and sending data, operators are reacting to that data and potentially changing it, and subscribers accepting result. RXPublisher cannot be used directly, its subclasses must be used instead:
|
|
12
|
+
|
|
13
|
+
+ RXObservableValue
|
|
14
|
+
+ RXObservableEntity
|
|
15
|
+
+ RXSubject
|
|
16
|
+
+ RXEmitter
|
|
17
|
+
+ RXOperation
|
|
18
|
+
+ RXBuffer
|
|
19
|
+
+ RXCombine
|
|
20
|
+
+ RXJustComplete
|
|
21
|
+
+ RXDelayedComplete
|
|
22
|
+
+ RXJustError
|
|
23
|
+
+ RXDelayedError
|
|
24
|
+
+ RXFrom
|
|
25
|
+
+ RXQueue
|
|
26
|
+
|
|
27
|
+
## RXObservableValue
|
|
28
|
+
It does not dispatch errors and completion, has a default value.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
let buffer = ''
|
|
32
|
+
const rx = new RXObservableValue(1) // extends RXPublisher
|
|
33
|
+
rx.pipe() // creates RXPipeline
|
|
34
|
+
.onReceive(v => buffer += v) // creates RXSubscriber
|
|
35
|
+
.subscribe() // returns unsubscribe function: () => void
|
|
36
|
+
|
|
37
|
+
expect(buffer).toBe('1') //true
|
|
38
|
+
|
|
39
|
+
rx.value = 2
|
|
40
|
+
rx.value = 3
|
|
41
|
+
|
|
42
|
+
expect(buffer).toBe('123') //true
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## RXOperation
|
|
46
|
+
It dispatches value or error and immediately after that completion.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const op = new RXOperation<number, void>()
|
|
50
|
+
let buffer1 = ''
|
|
51
|
+
let buffer2 = ''
|
|
52
|
+
|
|
53
|
+
op.pipe()
|
|
54
|
+
.onReceive(v => (buffer1 += v + ''))
|
|
55
|
+
.onError(() => (buffer1 += 'e'))
|
|
56
|
+
.onComplete(() => (buffer1 += 'c'))
|
|
57
|
+
.subscribe()
|
|
58
|
+
|
|
59
|
+
op.fail()
|
|
60
|
+
op.fail() // no effect
|
|
61
|
+
op.success(10) // no effect
|
|
62
|
+
op.success(20) // no effect
|
|
63
|
+
|
|
64
|
+
op.pipe()
|
|
65
|
+
.onReceive(value => (buffer2 += value + ''))
|
|
66
|
+
.onError(() => (buffer2 += 'e'))
|
|
67
|
+
.onComplete(() => (buffer2 += 'c'))
|
|
68
|
+
.subscribe()
|
|
69
|
+
|
|
70
|
+
expect(buffer1).toBe('ec') // true
|
|
71
|
+
expect(buffer2).toBe('ec') // true
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## RXSubject
|
|
75
|
+
It has a default value. If you do not want to have a default value use RXEmitter instead.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const rx = new RXSubject(0)
|
|
79
|
+
let buffer = -1
|
|
80
|
+
|
|
81
|
+
rx.pipe()
|
|
82
|
+
.onReceive(v => buffer = v)
|
|
83
|
+
.subscribe()
|
|
84
|
+
|
|
85
|
+
expect(buffer).toBe(0) // true
|
|
86
|
+
|
|
87
|
+
rx.send(1)
|
|
88
|
+
expect(buffer).toBe(1) // true
|
|
89
|
+
|
|
90
|
+
rx.sendComplete()
|
|
91
|
+
expect(buffer).toBe(1) // true
|
|
92
|
+
|
|
93
|
+
rx.send(2) // no effect after complete
|
|
94
|
+
expect(buffer).toBe(1) // true
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Map Operator
|
|
98
|
+
```ts
|
|
99
|
+
const rx = new RXEmitter<string, never>()
|
|
100
|
+
let buffer = ''
|
|
101
|
+
|
|
102
|
+
rx.pipe()
|
|
103
|
+
.map(v => v.toUpperCase())
|
|
104
|
+
.map(v => v ? v : '-')
|
|
105
|
+
.onReceive(v => buffer += v)
|
|
106
|
+
.subscribe()
|
|
107
|
+
|
|
108
|
+
expect(buffer).toBe('') // true
|
|
109
|
+
|
|
110
|
+
rx.send('a')
|
|
111
|
+
rx.send('b')
|
|
112
|
+
rx.send('')
|
|
113
|
+
rx.send('c')
|
|
114
|
+
expect(buffer).toBe('AB-C') // true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Filter Operator
|
|
118
|
+
```ts
|
|
119
|
+
const rx = new RXSubject(0)
|
|
120
|
+
let buffer = ''
|
|
121
|
+
|
|
122
|
+
rx.pipe()
|
|
123
|
+
.removeDuplicates()
|
|
124
|
+
.filter(v => v % 2 === 0)
|
|
125
|
+
.map(v => v + '')
|
|
126
|
+
.filter(v => v.length === 1)
|
|
127
|
+
.onReceive(v => buffer += v)
|
|
128
|
+
.subscribe()
|
|
129
|
+
|
|
130
|
+
rx.send(0)
|
|
131
|
+
rx.send(0)
|
|
132
|
+
rx.send(1)
|
|
133
|
+
rx.send(1)
|
|
134
|
+
rx.send(2)
|
|
135
|
+
rx.send(3)
|
|
136
|
+
rx.send(4)
|
|
137
|
+
rx.send(5)
|
|
138
|
+
rx.send(6)
|
|
139
|
+
rx.send(7)
|
|
140
|
+
rx.send(8)
|
|
141
|
+
rx.send(9)
|
|
142
|
+
rx.send(10)
|
|
143
|
+
rx.send(11)
|
|
144
|
+
rx.send(12)
|
|
145
|
+
|
|
146
|
+
expect(buffer).toBe('02468') // true
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## SkipNullable Operator
|
|
150
|
+
```ts
|
|
151
|
+
const rx = new RXSubject<number | undefined | null, never>(0)
|
|
152
|
+
let buffer = ''
|
|
153
|
+
|
|
154
|
+
rx.pipe()
|
|
155
|
+
.removeDuplicates()
|
|
156
|
+
.skipNullable()
|
|
157
|
+
.map(v => v + '')
|
|
158
|
+
.onReceive(v => buffer += v)
|
|
159
|
+
.subscribe()
|
|
160
|
+
|
|
161
|
+
rx.send(0)
|
|
162
|
+
rx.send(1)
|
|
163
|
+
rx.send(undefined)
|
|
164
|
+
rx.send(2)
|
|
165
|
+
rx.send(2)
|
|
166
|
+
rx.send(3)
|
|
167
|
+
rx.send(null)
|
|
168
|
+
rx.send(4)
|
|
169
|
+
rx.send(5)
|
|
170
|
+
|
|
171
|
+
expect(buffer).toBe('012345') // true
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## FlatMap Operator
|
|
175
|
+
Transforms all elements from an upstream publisher into a new publisher.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
class Task {
|
|
179
|
+
readonly $isDone = new RXObservableValue(false)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const task = new Task()
|
|
183
|
+
const $selectedTask = new RXObservableValue<Task | undefined>(undefined)
|
|
184
|
+
let buffer = ''
|
|
185
|
+
|
|
186
|
+
$selectedTask.pipe()
|
|
187
|
+
.skipNullable()
|
|
188
|
+
.flatMap(t => t.$isDone)
|
|
189
|
+
.onReceive(isDone => buffer = isDone + '')
|
|
190
|
+
.subscribe()
|
|
191
|
+
|
|
192
|
+
expect(buffer).toBe('') // no submission: selectedTask is undefined
|
|
193
|
+
|
|
194
|
+
$selectedTask.value = task
|
|
195
|
+
expect(buffer).toBe('false') // buffer is updated with default value false
|
|
196
|
+
|
|
197
|
+
task.$isDone.value = true
|
|
198
|
+
expect(buffer).toBe('true') // buffer is updated with value true
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## More examples
|
|
202
|
+
You can find more examples in the test-folder: [https://github.com/Dittner/Flinker/tree/main/test/rx](https://github.com/Dittner/Flinker/tree/main/test/rx)
|
|
203
|
+
|
|
204
|
+
## Install
|
|
205
|
+
```code
|
|
3
206
|
npm i flinker
|
|
4
207
|
```
|
|
5
208
|
|
|
6
|
-
|
|
209
|
+
## License
|
|
7
210
|
MIT
|
package/dist/esm/RX.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { RXCombine, RXDelayedComplete, RXDelayedError, RXFrom, RXJustComplete, RXJustError, RXQueue, RXWaitUntilComplete } from './RXPublisher';
|
|
2
2
|
export class RX {
|
|
3
|
-
//--------------------------------------
|
|
4
|
-
// STATIC METHODS
|
|
5
|
-
//--------------------------------------
|
|
6
3
|
static combine(...list) {
|
|
7
4
|
return new RXCombine(list).asObservable;
|
|
8
5
|
}
|
package/dist/esm/RXPublisher.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
import { RXPipeline } from './RXPipeline';
|
|
2
|
-
//WeakRef is not supported in RN
|
|
3
|
-
// export class RXPipelineGarbage {
|
|
4
|
-
// static self = new RXPipelineGarbage()
|
|
5
|
-
// private readonly list = Array<WeakRef<RXAnyPipeline>>()
|
|
6
|
-
// private total = 0
|
|
7
|
-
//
|
|
8
|
-
// register(p: RXAnyPipeline) {
|
|
9
|
-
// this.list.push(new WeakRef(p))
|
|
10
|
-
// this.total++
|
|
11
|
-
// this.log()
|
|
12
|
-
// }
|
|
13
|
-
//
|
|
14
|
-
// log() {
|
|
15
|
-
// const disposedCount = this.list.reduce((res, ref) => ref.deref() ? res + 1 : res, 0)
|
|
16
|
-
// Logger.i('GC: disposed pipelines:', disposedCount + '/' + this.total)
|
|
17
|
-
// }
|
|
18
|
-
// }
|
|
19
2
|
const generateSUID = (() => {
|
|
20
3
|
let value = 0;
|
|
21
4
|
return () => {
|
|
@@ -40,7 +23,6 @@ export class RXPublisher {
|
|
|
40
23
|
pipe() {
|
|
41
24
|
const pipe = new RXPipeline(this);
|
|
42
25
|
this.pipelines.push(pipe);
|
|
43
|
-
//RXPipelineGarbage.self.register(pipe)
|
|
44
26
|
return pipe.asOperator;
|
|
45
27
|
}
|
|
46
28
|
didSubscribe(p) {
|
package/dist/esm/RXSubscriber.js
CHANGED
|
@@ -24,7 +24,7 @@ export class RXSubscriber {
|
|
|
24
24
|
}
|
|
25
25
|
onReceive(f) {
|
|
26
26
|
if (this.isComplete)
|
|
27
|
-
throw new Error('RXSubscriber is complete: It can update onReceiveCallback');
|
|
27
|
+
throw new Error('RXSubscriber is complete: It can not update onReceiveCallback');
|
|
28
28
|
else if (this.isSubscribed)
|
|
29
29
|
throw new Error('RXSubscriber can not update onReceiveCallback: subscribe() is already evoked');
|
|
30
30
|
this.onReceiveCallback = f;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { type RXAnyOperatorProtocol, type RXOperatorProtocol } from './RXOperator';
|
|
2
2
|
import { type RXAnyPipeline } from './RXPipeline';
|
|
3
3
|
import { type RXObject, type RXObjectType } from './RX';
|
|
4
|
+
export type SessionUID = number;
|
|
4
5
|
export type AnyRXObservable = RXObservable<any, any>;
|
|
5
6
|
export interface RXObservable<V, E> extends RXObject {
|
|
6
|
-
suid:
|
|
7
|
+
suid: SessionUID;
|
|
7
8
|
isComplete: boolean;
|
|
8
9
|
pipe(): RXOperatorProtocol<V, E>;
|
|
9
10
|
}
|
|
10
|
-
export type RXPublisherUID = number;
|
|
11
11
|
export type RXAnyPublisher = RXPublisher<any, any>;
|
|
12
12
|
export declare class RXPublisher<V, E> implements RXObservable<V, E> {
|
|
13
|
-
readonly suid:
|
|
13
|
+
readonly suid: number;
|
|
14
14
|
readonly type: RXObjectType;
|
|
15
15
|
protected readonly pipelines: RXAnyPipeline[];
|
|
16
16
|
constructor();
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { RXAnySender, RXSender, RX, } from "./RX";
|
|
2
|
-
export { AnyRXObservable, RXObservable,
|
|
2
|
+
export { AnyRXObservable, RXObservable, SessionUID, RXPublisher, RXJustComplete, RXJustError, RXDelayedComplete, RXDelayedError, RXEmitter, RXSubject, RXBuffer, RXOperation, RXCombine, RXFrom, RXWaitUntilComplete, RXObservableEntity, RXObservableValue, RXAnyQueueOperator, RXQueueOperator, RXQueue, } from "./RXPublisher";
|
|
3
3
|
export { RXOperatorProtocol, RXAnyOperator, RXOperator, RXMap, RXFlatMap, RXForEach, RXSequent, RXParallel, RXFilter, RXSpread, RXSkipFirst, RXSkipNullable, RXRemoveDuplicates, RXDebounce, RXReplaceError, } from "./RXOperator";
|
|
4
4
|
export { RXAnyPipeline, RXPipeline } from "./RXPipeline";
|
|
5
5
|
export { ErrorMethod, CompleteMethod, SubscribeMethod, RXAnySubscriber, RXSubscriber } from "./RXSubscriber";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flinker",
|
|
3
3
|
"description": "RX.ts lib for building frontend apps",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.4",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Dittner/Flinker.git"
|
|
@@ -16,9 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"homepage": "https://github.com/Dittner/Flinker",
|
|
18
18
|
"private": false,
|
|
19
|
-
"main": "./dist/cjs/index.js",
|
|
20
19
|
"module": "./dist/esm/index.js",
|
|
21
|
-
"types": "./dist/index.d.ts",
|
|
20
|
+
"types": "./dist/types/index.d.ts",
|
|
22
21
|
"files": [
|
|
23
22
|
"dist/",
|
|
24
23
|
"index.ts",
|
|
@@ -33,10 +32,6 @@
|
|
|
33
32
|
"build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs"
|
|
34
33
|
},
|
|
35
34
|
"eslintConfig": {
|
|
36
|
-
"extends": [
|
|
37
|
-
"react-app",
|
|
38
|
-
"react-app/jest"
|
|
39
|
-
]
|
|
40
35
|
},
|
|
41
36
|
"browserslist": {
|
|
42
37
|
"production": [
|
|
@@ -52,7 +47,6 @@
|
|
|
52
47
|
},
|
|
53
48
|
"devDependencies": {
|
|
54
49
|
"@testing-library/jest-dom": "^6.6.3",
|
|
55
|
-
"@testing-library/react": "^16.2.0",
|
|
56
50
|
"@testing-library/user-event": "^14.6.1",
|
|
57
51
|
"@types/jest": "^29.5.14",
|
|
58
52
|
"@types/node": "^22.13.13",
|
|
@@ -60,9 +54,7 @@
|
|
|
60
54
|
"jest": "^29.7.0",
|
|
61
55
|
"jest-environment-jsdom": "^29.7.0",
|
|
62
56
|
"ts-jest": "^29.3.0",
|
|
63
|
-
"typescript": "^5.8.2"
|
|
64
|
-
"@types/react": "^19.0.12",
|
|
65
|
-
"@types/react-dom": "^19.0.4"
|
|
57
|
+
"typescript": "^5.8.2"
|
|
66
58
|
},
|
|
67
59
|
"directories": {
|
|
68
60
|
"test": "test"
|