@jscutlery/operators 1.0.2 → 1.1.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/CHANGELOG.md +13 -0
- package/README.md +69 -11
- package/index.cjs +41 -21
- package/index.js +42 -22
- package/package.json +1 -1
- package/src/lib/suspensify.d.ts +11 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.1.1](https://github.com/jscutlery/devkit/compare/operators-1.1.0...operators-1.1.1) (2023-04-18)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# [1.1.0](https://github.com/jscutlery/devkit/compare/operators-1.0.2...operators-1.1.0) (2023-04-18)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **operators:** add suspensify's strict mode ([f9dd789](https://github.com/jscutlery/devkit/commit/f9dd789d82cdff2fffb2241e8b0aab9bd76cb363)), closes [#237](https://github.com/jscutlery/devkit/issues/237)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
5
18
|
## [1.0.2](https://github.com/jscutlery/devkit/compare/operators-1.0.1...operators-1.0.2) (2023-04-18)
|
|
6
19
|
|
|
7
20
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ This package regroups a couple of RxJS operators meant to simplify some common p
|
|
|
7
7
|
- [`suspensify`](#suspensify)
|
|
8
8
|
- [Goals](#goals)
|
|
9
9
|
- [Usage](#usage)
|
|
10
|
-
- [
|
|
10
|
+
- [Strict mode (will become default in 3.0.0)](#strict-mode-will-become-default-in-300)
|
|
11
|
+
- [Lax mode (default)](#lax-mode-default)
|
|
11
12
|
- [With Angular](#with-angular)
|
|
12
13
|
- [With `@rx-angular/state`](#with-rx-angularstate)
|
|
13
14
|
- [Alternatives](#alternatives)
|
|
@@ -32,7 +33,7 @@ When dealing with an asynchronous source of data in a web application, it is a c
|
|
|
32
33
|
- **pending**: the data is being fetched
|
|
33
34
|
- **error**: an error occurred while fetching the data
|
|
34
35
|
- **success**: some data has been fetched
|
|
35
|
-
- **finalized**: there is no more data to fetch or an error happened
|
|
36
|
+
- **finalized**: there is no more data to fetch or an error happened _(it is called `finalized` and not `completed` to avoid confusion with RxJS `complete` event which only happens if there is no error)_.
|
|
36
37
|
|
|
37
38
|
The most common ways of implementing this are error-prone as they are either based on a complex combination of native RxJS operators or side effects that break the reactivity chain.
|
|
38
39
|
|
|
@@ -47,7 +48,52 @@ The `suspensify` operator is meant to provide a simple and efficient way of deal
|
|
|
47
48
|
|
|
48
49
|
## Usage
|
|
49
50
|
|
|
50
|
-
###
|
|
51
|
+
### Strict mode (will become default in 3.0.0)
|
|
52
|
+
|
|
53
|
+
Thanks to strict mode the emitted suspense can be narrowed down.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
interval(1000)
|
|
57
|
+
.pipe(take(2), suspensify({strict: true}))
|
|
58
|
+
.subscribe((suspense) => {
|
|
59
|
+
suspense.value; // 💥
|
|
60
|
+
suspense.error; // 💥
|
|
61
|
+
if (suspense.hasValue) {
|
|
62
|
+
suspense.value; // ✅
|
|
63
|
+
suspense.error; // 💥
|
|
64
|
+
}
|
|
65
|
+
if (suspense.hasError) {
|
|
66
|
+
suspense.value; // 💥
|
|
67
|
+
suspense.error; // ✅
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
{
|
|
74
|
+
finalized: false,
|
|
75
|
+
hasError: false,
|
|
76
|
+
hasValue: false,
|
|
77
|
+
pending: true,
|
|
78
|
+
}
|
|
79
|
+
{
|
|
80
|
+
finalized: false,
|
|
81
|
+
hasError: false,
|
|
82
|
+
hasValue: false,
|
|
83
|
+
pending: false,
|
|
84
|
+
value: 0,
|
|
85
|
+
}
|
|
86
|
+
...
|
|
87
|
+
{
|
|
88
|
+
finalized: true,
|
|
89
|
+
hasError: false,
|
|
90
|
+
hasValue: true,
|
|
91
|
+
pending: false,
|
|
92
|
+
value: 1,
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Lax mode (default)
|
|
51
97
|
|
|
52
98
|
```ts
|
|
53
99
|
interval(1000)
|
|
@@ -57,23 +103,29 @@ interval(1000)
|
|
|
57
103
|
|
|
58
104
|
```ts
|
|
59
105
|
{
|
|
106
|
+
finalized: false,
|
|
107
|
+
hasError: false,
|
|
108
|
+
hasValue: false,
|
|
109
|
+
pending: true,
|
|
60
110
|
value: undefined,
|
|
61
111
|
error: undefined,
|
|
62
|
-
pending: true,
|
|
63
|
-
finalized: false,
|
|
64
112
|
}
|
|
65
113
|
{
|
|
114
|
+
finalized: false,
|
|
115
|
+
hasError: false,
|
|
116
|
+
hasValue: false,
|
|
117
|
+
pending: false,
|
|
66
118
|
value: 0,
|
|
67
119
|
error: undefined,
|
|
68
|
-
pending: false,
|
|
69
|
-
finalized: false,
|
|
70
120
|
}
|
|
71
121
|
...
|
|
72
122
|
{
|
|
123
|
+
finalized: true,
|
|
124
|
+
hasError: false,
|
|
125
|
+
hasValue: true,
|
|
126
|
+
pending: false,
|
|
73
127
|
value: 1,
|
|
74
128
|
error: undefined,
|
|
75
|
-
pending: false,
|
|
76
|
-
finalized: true,
|
|
77
129
|
}
|
|
78
130
|
```
|
|
79
131
|
|
|
@@ -86,9 +138,15 @@ interval(1000)
|
|
|
86
138
|
|
|
87
139
|
<my-spinner *ngIf="suspense.pending"></my-spinner>
|
|
88
140
|
|
|
89
|
-
<div *ngIf="suspense.
|
|
141
|
+
<div *ngIf="suspense.hasError">
|
|
142
|
+
{{ suspense.error }} // ✅
|
|
143
|
+
{{ suspense.value }} // 💥 will not compile in strict mode
|
|
144
|
+
</div>
|
|
90
145
|
|
|
91
|
-
<div *ngIf="suspense.
|
|
146
|
+
<div *ngIf="suspense.hasValue">
|
|
147
|
+
{{ suspense.error }} // 💥 will not compile in strict mode
|
|
148
|
+
{{ suspense.value }} // ✅
|
|
149
|
+
</div>
|
|
92
150
|
|
|
93
151
|
</ng-container>
|
|
94
152
|
`,
|
package/index.cjs
CHANGED
|
@@ -1076,13 +1076,20 @@ $({ target: 'Object', stat: true, arity: 2, forced: Object.assign !== assign },
|
|
|
1076
1076
|
assign: assign
|
|
1077
1077
|
});
|
|
1078
1078
|
|
|
1079
|
-
function suspensify(
|
|
1080
|
-
|
|
1081
|
-
|
|
1079
|
+
function suspensify({
|
|
1080
|
+
strict = false
|
|
1081
|
+
} = {}) {
|
|
1082
1082
|
return source$ => {
|
|
1083
|
-
|
|
1083
|
+
const strictSuspense$ = source$.pipe(_suspensify(), _coalesceFirstEmittedValue());
|
|
1084
|
+
return strict ? strictSuspense$ : strictSuspense$.pipe(operators.map(strictSuspense => Object.assign({
|
|
1085
|
+
error: undefined,
|
|
1086
|
+
value: undefined
|
|
1087
|
+
}, strictSuspense))
|
|
1088
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1089
|
+
);
|
|
1084
1090
|
};
|
|
1085
1091
|
}
|
|
1092
|
+
|
|
1086
1093
|
function _coalesceFirstEmittedValue() {
|
|
1087
1094
|
return source$ => {
|
|
1088
1095
|
return new rxjs.Observable(observer => {
|
|
@@ -1096,31 +1103,44 @@ function _coalesceFirstEmittedValue() {
|
|
|
1096
1103
|
});
|
|
1097
1104
|
};
|
|
1098
1105
|
}
|
|
1106
|
+
/* Use values as types for better type checking. */
|
|
1107
|
+
const TRUE = true;
|
|
1108
|
+
const FALSE = false;
|
|
1099
1109
|
function _suspensify() {
|
|
1100
1110
|
return source$ => {
|
|
1101
1111
|
const initialState = {
|
|
1102
|
-
value: undefined,
|
|
1103
|
-
error: undefined,
|
|
1104
1112
|
finalized: false,
|
|
1113
|
+
hasError: false,
|
|
1114
|
+
hasValue: false,
|
|
1105
1115
|
pending: true
|
|
1106
1116
|
};
|
|
1107
1117
|
return source$.pipe(operators.materialize(), operators.scan((state = initialState, notification) => {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1118
|
+
switch (notification.kind) {
|
|
1119
|
+
/* Value. */
|
|
1120
|
+
case 'N':
|
|
1121
|
+
return {
|
|
1122
|
+
finalized: FALSE,
|
|
1123
|
+
hasError: FALSE,
|
|
1124
|
+
hasValue: TRUE,
|
|
1125
|
+
value: notification.value,
|
|
1126
|
+
pending: FALSE
|
|
1127
|
+
};
|
|
1128
|
+
/* Error. */
|
|
1129
|
+
case 'E':
|
|
1130
|
+
return {
|
|
1131
|
+
finalized: TRUE,
|
|
1132
|
+
hasError: TRUE,
|
|
1133
|
+
hasValue: FALSE,
|
|
1134
|
+
pending: FALSE,
|
|
1135
|
+
error: notification.error
|
|
1136
|
+
};
|
|
1137
|
+
/* Complete. */
|
|
1138
|
+
case 'C':
|
|
1139
|
+
return Object.assign({}, state, {
|
|
1140
|
+
finalized: TRUE,
|
|
1141
|
+
pending: FALSE
|
|
1142
|
+
});
|
|
1115
1143
|
}
|
|
1116
|
-
/* Mark as finalized on error as complete is only triggered on success. */
|
|
1117
|
-
const finalized = notification.kind === 'E';
|
|
1118
|
-
return {
|
|
1119
|
-
value: notification.value,
|
|
1120
|
-
error: notification.error,
|
|
1121
|
-
finalized,
|
|
1122
|
-
pending: false
|
|
1123
|
-
};
|
|
1124
1144
|
}, initialState), operators.startWith(initialState));
|
|
1125
1145
|
};
|
|
1126
1146
|
}
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Observable, ReplaySubject } from 'rxjs';
|
|
2
|
-
import { debounce, materialize, scan, startWith } from 'rxjs/operators';
|
|
2
|
+
import { map, debounce, materialize, scan, startWith } from 'rxjs/operators';
|
|
3
3
|
|
|
4
4
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
5
5
|
|
|
@@ -1072,13 +1072,20 @@ $({ target: 'Object', stat: true, arity: 2, forced: Object.assign !== assign },
|
|
|
1072
1072
|
assign: assign
|
|
1073
1073
|
});
|
|
1074
1074
|
|
|
1075
|
-
function suspensify(
|
|
1076
|
-
|
|
1077
|
-
|
|
1075
|
+
function suspensify({
|
|
1076
|
+
strict = false
|
|
1077
|
+
} = {}) {
|
|
1078
1078
|
return source$ => {
|
|
1079
|
-
|
|
1079
|
+
const strictSuspense$ = source$.pipe(_suspensify(), _coalesceFirstEmittedValue());
|
|
1080
|
+
return strict ? strictSuspense$ : strictSuspense$.pipe(map(strictSuspense => Object.assign({
|
|
1081
|
+
error: undefined,
|
|
1082
|
+
value: undefined
|
|
1083
|
+
}, strictSuspense))
|
|
1084
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1085
|
+
);
|
|
1080
1086
|
};
|
|
1081
1087
|
}
|
|
1088
|
+
|
|
1082
1089
|
function _coalesceFirstEmittedValue() {
|
|
1083
1090
|
return source$ => {
|
|
1084
1091
|
return new Observable(observer => {
|
|
@@ -1092,31 +1099,44 @@ function _coalesceFirstEmittedValue() {
|
|
|
1092
1099
|
});
|
|
1093
1100
|
};
|
|
1094
1101
|
}
|
|
1102
|
+
/* Use values as types for better type checking. */
|
|
1103
|
+
const TRUE = true;
|
|
1104
|
+
const FALSE = false;
|
|
1095
1105
|
function _suspensify() {
|
|
1096
1106
|
return source$ => {
|
|
1097
1107
|
const initialState = {
|
|
1098
|
-
value: undefined,
|
|
1099
|
-
error: undefined,
|
|
1100
1108
|
finalized: false,
|
|
1109
|
+
hasError: false,
|
|
1110
|
+
hasValue: false,
|
|
1101
1111
|
pending: true
|
|
1102
1112
|
};
|
|
1103
1113
|
return source$.pipe(materialize(), scan((state = initialState, notification) => {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1114
|
+
switch (notification.kind) {
|
|
1115
|
+
/* Value. */
|
|
1116
|
+
case 'N':
|
|
1117
|
+
return {
|
|
1118
|
+
finalized: FALSE,
|
|
1119
|
+
hasError: FALSE,
|
|
1120
|
+
hasValue: TRUE,
|
|
1121
|
+
value: notification.value,
|
|
1122
|
+
pending: FALSE
|
|
1123
|
+
};
|
|
1124
|
+
/* Error. */
|
|
1125
|
+
case 'E':
|
|
1126
|
+
return {
|
|
1127
|
+
finalized: TRUE,
|
|
1128
|
+
hasError: TRUE,
|
|
1129
|
+
hasValue: FALSE,
|
|
1130
|
+
pending: FALSE,
|
|
1131
|
+
error: notification.error
|
|
1132
|
+
};
|
|
1133
|
+
/* Complete. */
|
|
1134
|
+
case 'C':
|
|
1135
|
+
return Object.assign({}, state, {
|
|
1136
|
+
finalized: TRUE,
|
|
1137
|
+
pending: FALSE
|
|
1138
|
+
});
|
|
1111
1139
|
}
|
|
1112
|
-
/* Mark as finalized on error as complete is only triggered on success. */
|
|
1113
|
-
const finalized = notification.kind === 'E';
|
|
1114
|
-
return {
|
|
1115
|
-
value: notification.value,
|
|
1116
|
-
error: notification.error,
|
|
1117
|
-
finalized,
|
|
1118
|
-
pending: false
|
|
1119
|
-
};
|
|
1120
1140
|
}, initialState), startWith(initialState));
|
|
1121
1141
|
};
|
|
1122
1142
|
}
|
package/package.json
CHANGED
package/src/lib/suspensify.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { OperatorFunction } from 'rxjs';
|
|
2
2
|
export interface SuspenseLax<T> {
|
|
3
|
-
value: undefined | T;
|
|
4
|
-
error: undefined | any;
|
|
5
3
|
finalized: boolean;
|
|
4
|
+
hasError: boolean;
|
|
5
|
+
hasValue: boolean;
|
|
6
6
|
pending: boolean;
|
|
7
|
+
error: undefined | any;
|
|
8
|
+
value: undefined | T;
|
|
7
9
|
}
|
|
8
10
|
export interface SuspensePending {
|
|
9
11
|
finalized: false;
|
|
@@ -25,15 +27,18 @@ export interface SuspenseWithError {
|
|
|
25
27
|
pending: false;
|
|
26
28
|
error: unknown;
|
|
27
29
|
}
|
|
28
|
-
export
|
|
30
|
+
export interface SuspenseEmpty {
|
|
31
|
+
finalized: true;
|
|
32
|
+
hasError: false;
|
|
33
|
+
hasValue: false;
|
|
34
|
+
pending: false;
|
|
35
|
+
}
|
|
36
|
+
export declare type SuspenseStrict<T> = SuspensePending | SuspenseWithValue<T> | SuspenseWithError | SuspenseEmpty;
|
|
29
37
|
/**
|
|
30
38
|
* @todo 3.0.0: Make `Suspense` an alias for `SuspenseStrict`.
|
|
31
39
|
*/
|
|
32
40
|
export declare type Suspense<T> = SuspenseLax<T>;
|
|
33
41
|
export interface SuspensifyOptions {
|
|
34
|
-
/**
|
|
35
|
-
* @deprecated 🚧 Work in progress.
|
|
36
|
-
*/
|
|
37
42
|
strict?: boolean;
|
|
38
43
|
}
|
|
39
44
|
/**
|