@juulsgaard/signal-tools 18.0.0 → 18.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/dist/data-structures/signal-queue.js +115 -0
- package/dist/data-structures/signal-stack.js +90 -0
- package/dist/index.d.ts +560 -59
- package/dist/index.js +12 -1
- package/dist/operators.js +13 -0
- package/dist/request-state/async-request-state.js +48 -0
- package/dist/request-state/constructors.js +25 -0
- package/dist/request-state/empty-request-state.js +20 -0
- package/dist/request-state/error-request-state.js +20 -0
- package/dist/request-state/loading-request-state.js +25 -0
- package/dist/request-state/request-state.js +39 -0
- package/dist/request-state/request-tracker.js +64 -0
- package/dist/request-state/static-request-state.js +32 -0
- package/dist/request-state/writable-request-state.js +75 -0
- package/package.json +9 -9
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
import { pairwiseSignal } from '../operators.js';
|
|
3
|
+
|
|
4
|
+
class SignalQueue {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._items = signal([]);
|
|
7
|
+
/** A signal emitting the items of the queue */
|
|
8
|
+
this.items = this._items.asReadonly();
|
|
9
|
+
/** A signal emitting the number of items in the queue */
|
|
10
|
+
this.length = computed(() => this.items().length);
|
|
11
|
+
/** A signal emitting true if there are no items in the queue */
|
|
12
|
+
this.empty = computed(() => this.length() <= 0);
|
|
13
|
+
this._maxSize = options?.size;
|
|
14
|
+
this.front = computed(() => this.items().at(0));
|
|
15
|
+
this.back = computed(() => this.items().at(-1));
|
|
16
|
+
const delta = pairwiseSignal(this.items, []);
|
|
17
|
+
this.frontDelta = computed(() => {
|
|
18
|
+
const [prev, next] = delta();
|
|
19
|
+
const item = next.at(0);
|
|
20
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
21
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
22
|
+
this.backDelta = computed(() => {
|
|
23
|
+
const [prev, next] = delta();
|
|
24
|
+
const item = next.at(-1);
|
|
25
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
26
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
27
|
+
}
|
|
28
|
+
/** The max size of the queue */
|
|
29
|
+
get maxSize() {
|
|
30
|
+
return this._maxSize;
|
|
31
|
+
}
|
|
32
|
+
//</editor-fold>
|
|
33
|
+
//<editor-fold desc="Actions">
|
|
34
|
+
/**
|
|
35
|
+
* Removes an item from the scheduler
|
|
36
|
+
* @param item
|
|
37
|
+
*/
|
|
38
|
+
removeItem(item) {
|
|
39
|
+
const index = untracked(this.items).indexOf(item);
|
|
40
|
+
if (index < 0) return false;
|
|
41
|
+
const arr = [...untracked(this.items)];
|
|
42
|
+
arr.splice(index, 1);
|
|
43
|
+
this._items.set(arr);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
dequeue(count) {
|
|
47
|
+
if (count !== void 0) {
|
|
48
|
+
if (count < 1) return [];
|
|
49
|
+
if (!untracked(this.length)) return [];
|
|
50
|
+
const items = untracked(this.items).slice(0, count);
|
|
51
|
+
this._items.set(untracked(this.items).slice(count));
|
|
52
|
+
return items;
|
|
53
|
+
}
|
|
54
|
+
if (!untracked(this.length)) return void 0;
|
|
55
|
+
const item = untracked(this.items).at(0);
|
|
56
|
+
this._items.set(untracked(this.items).slice(1));
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
removeFromBack(count) {
|
|
60
|
+
if (count !== void 0) {
|
|
61
|
+
if (count < 1) return [];
|
|
62
|
+
if (!untracked(this.length)) return [];
|
|
63
|
+
const start = count * -1;
|
|
64
|
+
const items = untracked(this.items).slice(start);
|
|
65
|
+
this._items.set(untracked(this.items).slice(0, start));
|
|
66
|
+
return items;
|
|
67
|
+
}
|
|
68
|
+
if (!untracked(this.length)) return void 0;
|
|
69
|
+
const item = untracked(this.items).at(-1);
|
|
70
|
+
this._items.set(untracked(this.items).slice(0, -1));
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
enqueue(...items) {
|
|
74
|
+
let list = [...untracked(this.items), ...items];
|
|
75
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
76
|
+
const delta = list.length - this.maxSize;
|
|
77
|
+
list = list.slice(delta);
|
|
78
|
+
}
|
|
79
|
+
this._items.set(list);
|
|
80
|
+
}
|
|
81
|
+
addToFront(...items) {
|
|
82
|
+
let list = [...items, ...untracked(this.items)];
|
|
83
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
84
|
+
const delta = list.length - this.maxSize;
|
|
85
|
+
list = list.slice(0, -delta);
|
|
86
|
+
}
|
|
87
|
+
this._items.set(list);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Checks if the item exists in the queue
|
|
91
|
+
* @param item
|
|
92
|
+
*/
|
|
93
|
+
contains(item) {
|
|
94
|
+
return untracked(this.items).includes(item);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Remove all elements from the set
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this._items.set([]);
|
|
101
|
+
}
|
|
102
|
+
//</editor-fold>
|
|
103
|
+
/**
|
|
104
|
+
* Change the max size of the queue
|
|
105
|
+
* @param size - The new max size
|
|
106
|
+
*/
|
|
107
|
+
setMaxSize(size) {
|
|
108
|
+
this._maxSize = size;
|
|
109
|
+
if (size === void 0 || untracked(this.length) <= size) return;
|
|
110
|
+
const delta = untracked(this.length) - size;
|
|
111
|
+
this._items.set(untracked(this.items).slice(delta));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { SignalQueue };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
import { pairwiseSignal } from '../operators.js';
|
|
3
|
+
|
|
4
|
+
class SignalStack {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._items = signal([]);
|
|
7
|
+
/** A signal containing the items of the stack from bottom to top */
|
|
8
|
+
this.items = this._items.asReadonly();
|
|
9
|
+
/** A signal emitting the number of items in the stack */
|
|
10
|
+
this.length = computed(() => this.items().length);
|
|
11
|
+
/** A signal emitting true if there are no items in the stack */
|
|
12
|
+
this.empty = computed(() => this.length() <= 0);
|
|
13
|
+
this.top = computed(() => this.items().at(-1));
|
|
14
|
+
this.bottom = computed(() => this.items().at(0));
|
|
15
|
+
const delta = pairwiseSignal(this.items, []);
|
|
16
|
+
this.topDelta = computed(() => {
|
|
17
|
+
const [prev, next] = delta();
|
|
18
|
+
const item = next.at(-1);
|
|
19
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
20
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
21
|
+
this.bottomDelta = computed(() => {
|
|
22
|
+
const [prev, next] = delta();
|
|
23
|
+
const item = next.at(0);
|
|
24
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
25
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
26
|
+
}
|
|
27
|
+
//</editor-fold>
|
|
28
|
+
//<editor-fold desc="Actions">
|
|
29
|
+
/**
|
|
30
|
+
* Removes an item from the scheduler
|
|
31
|
+
* @param item
|
|
32
|
+
*/
|
|
33
|
+
removeItem(item) {
|
|
34
|
+
const index = untracked(this.items).indexOf(item);
|
|
35
|
+
if (index < 0) return false;
|
|
36
|
+
const arr = [...untracked(this.items)];
|
|
37
|
+
arr.splice(index, 1);
|
|
38
|
+
this._items.set(arr);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
removeFromBottom(count) {
|
|
42
|
+
if (count !== void 0) {
|
|
43
|
+
if (count < 1) return [];
|
|
44
|
+
if (!untracked(this.length)) return [];
|
|
45
|
+
const items = untracked(this.items).slice(0, count);
|
|
46
|
+
this._items.set(untracked(this.items).slice(count));
|
|
47
|
+
return items;
|
|
48
|
+
}
|
|
49
|
+
if (!untracked(this.length)) return void 0;
|
|
50
|
+
const item = untracked(this.items).at(0);
|
|
51
|
+
this._items.set(untracked(this.items).slice(1));
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
pop(count) {
|
|
55
|
+
if (count !== void 0) {
|
|
56
|
+
if (count < 1) return [];
|
|
57
|
+
if (!untracked(this.length)) return [];
|
|
58
|
+
const start = count * -1;
|
|
59
|
+
const items = untracked(this.items).slice(start);
|
|
60
|
+
this._items.set(untracked(this.items).slice(0, start));
|
|
61
|
+
return items;
|
|
62
|
+
}
|
|
63
|
+
if (!untracked(this.length)) return void 0;
|
|
64
|
+
const item = untracked(this.items).at(-1);
|
|
65
|
+
this._items.set(untracked(this.items).slice(0, -1));
|
|
66
|
+
return item;
|
|
67
|
+
}
|
|
68
|
+
push(...items) {
|
|
69
|
+
this._items.set([...untracked(this.items), ...items]);
|
|
70
|
+
}
|
|
71
|
+
addToBottom(...items) {
|
|
72
|
+
this._items.set([...items, ...untracked(this.items)]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks if the item exists in the queue
|
|
76
|
+
* @param item
|
|
77
|
+
*/
|
|
78
|
+
contains(item) {
|
|
79
|
+
return untracked(this.items).includes(item);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove all elements from the set
|
|
83
|
+
*/
|
|
84
|
+
clear() {
|
|
85
|
+
this._items.set([]);
|
|
86
|
+
}
|
|
87
|
+
//</editor-fold>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { SignalStack };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
1
2
|
import { Signal, Injector } from '@angular/core';
|
|
2
|
-
import
|
|
3
|
+
import * as rxjs from 'rxjs';
|
|
4
|
+
import { BehaviorSubject, Subject, Subscribable, Observable, Observer, Unsubscribable } from 'rxjs';
|
|
3
5
|
import { IValueLoadingState } from '@juulsgaard/rxjs-tools';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -120,6 +122,86 @@ declare function toLoadingSignal<T>(observable: Subscribable<T>, options?: Loadi
|
|
|
120
122
|
*/
|
|
121
123
|
declare function toLoadingSignal<T>(promise: Promise<T>, options?: LoadingSignalOptions): LoadingSignal<T>;
|
|
122
124
|
|
|
125
|
+
/**
|
|
126
|
+
* A Map where the state and values are accessible as signals
|
|
127
|
+
* @category Signal Collections
|
|
128
|
+
*/
|
|
129
|
+
declare class SignalMap<TKey, TVal> implements ReadonlySignalMap<TKey, TVal> {
|
|
130
|
+
private readonly _map;
|
|
131
|
+
/** @inheritDoc */
|
|
132
|
+
readonly value: Signal<ReadonlyMap<TKey, TVal>>;
|
|
133
|
+
/** @inheritDoc */
|
|
134
|
+
readonly size: Signal<number>;
|
|
135
|
+
/** @inheritDoc */
|
|
136
|
+
readonly empty: Signal<boolean>;
|
|
137
|
+
constructor(values?: ReadonlyMap<TKey, TVal>);
|
|
138
|
+
[Symbol.iterator](): IterableIterator<[TKey, TVal]>;
|
|
139
|
+
private getCopy;
|
|
140
|
+
/**
|
|
141
|
+
* Remove all keys not in the whitelist
|
|
142
|
+
* @param whitelist - The keys to keep
|
|
143
|
+
*/
|
|
144
|
+
filter(whitelist: ReadonlyArray<TKey> | ReadonlySet<TKey> | undefined): boolean;
|
|
145
|
+
/** Clear the collection */
|
|
146
|
+
clear(): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Add a value to the collection if the key is not in use
|
|
149
|
+
* @param key - The key to use
|
|
150
|
+
* @param value - The value to add
|
|
151
|
+
* @return added - Returns true if the value was added
|
|
152
|
+
*/
|
|
153
|
+
add(key: TKey, value: TVal): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Add a value to the collection
|
|
156
|
+
* @param key - The key to use
|
|
157
|
+
* @param value - The value to add
|
|
158
|
+
* @return added - Returns true if the value was added or changed
|
|
159
|
+
*/
|
|
160
|
+
set(key: TKey, value: TVal): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Remove a key from the collection
|
|
163
|
+
* @param key - The key to remove
|
|
164
|
+
* @return removed - True if an item was removed
|
|
165
|
+
*/
|
|
166
|
+
delete(key: TKey): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Remove keys from the collection
|
|
169
|
+
* @param values - The keys to remove
|
|
170
|
+
* @return removed - True if items were removed
|
|
171
|
+
*/
|
|
172
|
+
deleteRange(values: TKey[]): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Toggle a value in the map
|
|
175
|
+
* @param key - The key to toggle
|
|
176
|
+
* @param value - The value to insert if applicable
|
|
177
|
+
* @param state - A forced state (`true` = always add, `false` = always delete)
|
|
178
|
+
* @returns The applied change (`true` = item added, `false` = item removed, `undefined` = nothing changed)
|
|
179
|
+
*/
|
|
180
|
+
toggle(key: TKey, value: TVal, state?: boolean): boolean | undefined;
|
|
181
|
+
/** Manually modify the inner collection */
|
|
182
|
+
modify(modify: (map: Map<TKey, TVal>) => void): void;
|
|
183
|
+
/** @inheritDoc */
|
|
184
|
+
has(key: TKey): Signal<boolean>;
|
|
185
|
+
/** @inheritDoc */
|
|
186
|
+
get(key: TKey): Signal<TVal | undefined>;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* An immutable Map where the state and values are accessible as signals
|
|
190
|
+
* @category Signal Collections
|
|
191
|
+
*/
|
|
192
|
+
interface ReadonlySignalMap<TKey, TVal> extends Iterable<[TKey, TVal]> {
|
|
193
|
+
/** A signal emitting the number of items in the collection */
|
|
194
|
+
readonly size: Signal<number>;
|
|
195
|
+
/** A signal emitting true if the collection is empty */
|
|
196
|
+
readonly empty: Signal<boolean>;
|
|
197
|
+
/** A signal emitting the inner non-signal map */
|
|
198
|
+
readonly value: Signal<ReadonlyMap<TKey, TVal>>;
|
|
199
|
+
/** Create a signal emitting true if a key exists in the collection */
|
|
200
|
+
has(key: TKey): Signal<boolean>;
|
|
201
|
+
/** Create a signal emitting the value under a given key */
|
|
202
|
+
get(key: TKey): Signal<TVal | undefined>;
|
|
203
|
+
}
|
|
204
|
+
|
|
123
205
|
/**
|
|
124
206
|
* A Set where the state and values are accessible as signals
|
|
125
207
|
* @category Signal Collections
|
|
@@ -205,83 +287,502 @@ interface ReadonlySignalSet<T> extends Iterable<T> {
|
|
|
205
287
|
}
|
|
206
288
|
|
|
207
289
|
/**
|
|
208
|
-
* A
|
|
290
|
+
* A signal FIFO queue
|
|
209
291
|
* @category Signal Collections
|
|
210
292
|
*/
|
|
211
|
-
declare class
|
|
212
|
-
private readonly
|
|
213
|
-
/**
|
|
214
|
-
readonly
|
|
215
|
-
/**
|
|
216
|
-
readonly
|
|
217
|
-
/**
|
|
293
|
+
declare class SignalQueue<T> {
|
|
294
|
+
private readonly _items;
|
|
295
|
+
/** A signal emitting the items of the queue */
|
|
296
|
+
readonly items: Signal<T[]>;
|
|
297
|
+
/** A signal emitting the number of items in the queue */
|
|
298
|
+
readonly length: Signal<number>;
|
|
299
|
+
/** A signal emitting true if there are no items in the queue */
|
|
218
300
|
readonly empty: Signal<boolean>;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
301
|
+
private _maxSize;
|
|
302
|
+
/** The max size of the queue */
|
|
303
|
+
get maxSize(): number | undefined;
|
|
304
|
+
constructor(options?: SignalQueueOptions);
|
|
222
305
|
/**
|
|
223
|
-
*
|
|
224
|
-
* @param whitelist - The keys to keep
|
|
306
|
+
* Signal returning the element at the front of the queue
|
|
225
307
|
*/
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
|
|
308
|
+
readonly front: Signal<T | undefined>;
|
|
309
|
+
/** Signal returning the current front element and whether it was just added */
|
|
310
|
+
readonly frontDelta: Signal<SignalQueueDelta<T>>;
|
|
229
311
|
/**
|
|
230
|
-
*
|
|
231
|
-
* @param key - The key to use
|
|
232
|
-
* @param value - The value to add
|
|
233
|
-
* @return added - Returns true if the value was added
|
|
312
|
+
* Signal returning the element at the back of the queue
|
|
234
313
|
*/
|
|
235
|
-
|
|
314
|
+
readonly back: Signal<T | undefined>;
|
|
315
|
+
/** Signal returning the current back element and whether it was just added */
|
|
316
|
+
readonly backDelta: Signal<SignalQueueDelta<T>>;
|
|
236
317
|
/**
|
|
237
|
-
*
|
|
238
|
-
* @param
|
|
239
|
-
* @param value - The value to add
|
|
240
|
-
* @return added - Returns true if the value was added or changed
|
|
318
|
+
* Removes an item from the scheduler
|
|
319
|
+
* @param item
|
|
241
320
|
*/
|
|
242
|
-
|
|
321
|
+
removeItem(item: T): boolean;
|
|
243
322
|
/**
|
|
244
|
-
* Remove
|
|
245
|
-
* @param key - The key to remove
|
|
246
|
-
* @return removed - True if an item was removed
|
|
323
|
+
* Remove the front element from the queue and return it
|
|
247
324
|
*/
|
|
248
|
-
|
|
325
|
+
dequeue(): T | undefined;
|
|
249
326
|
/**
|
|
250
|
-
* Remove
|
|
251
|
-
* @param
|
|
252
|
-
* @return removed - True if items were removed
|
|
327
|
+
* Remove the front x elements from the queue and return them
|
|
328
|
+
* @param count - The amount of elements to remove
|
|
253
329
|
*/
|
|
254
|
-
|
|
330
|
+
dequeue(count: number): T[];
|
|
255
331
|
/**
|
|
256
|
-
*
|
|
257
|
-
* @param key - The key to toggle
|
|
258
|
-
* @param value - The value to insert if applicable
|
|
259
|
-
* @param state - A forced state (`true` = always add, `false` = always delete)
|
|
260
|
-
* @returns The applied change (`true` = item added, `false` = item removed, `undefined` = nothing changed)
|
|
332
|
+
* Remove the back element from the queue and return it
|
|
261
333
|
*/
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
334
|
+
removeFromBack(): T | undefined;
|
|
335
|
+
/**
|
|
336
|
+
* Remove the back x elements from the queue and return it
|
|
337
|
+
* @param count - The amount of elements to remove
|
|
338
|
+
*/
|
|
339
|
+
removeFromBack(count: number): T[];
|
|
340
|
+
/**
|
|
341
|
+
* Add item to the back of the queue
|
|
342
|
+
* @param item - Item to add
|
|
343
|
+
*/
|
|
344
|
+
enqueue(item: T): void;
|
|
345
|
+
/**
|
|
346
|
+
* Add items to the back of the queue
|
|
347
|
+
* @param items - Items to add
|
|
348
|
+
*/
|
|
349
|
+
enqueue(...items: T[]): void;
|
|
350
|
+
/**
|
|
351
|
+
* Add item to the front of the queue
|
|
352
|
+
* @param item - Item to add
|
|
353
|
+
*/
|
|
354
|
+
addToFront(item: T): void;
|
|
355
|
+
/**
|
|
356
|
+
* Add items to the front of the queue
|
|
357
|
+
* @param items - Items to add
|
|
358
|
+
*/
|
|
359
|
+
addToFront(...items: T[]): void;
|
|
360
|
+
/**
|
|
361
|
+
* Checks if the item exists in the queue
|
|
362
|
+
* @param item
|
|
363
|
+
*/
|
|
364
|
+
contains(item: T): boolean;
|
|
365
|
+
/**
|
|
366
|
+
* Remove all elements from the set
|
|
367
|
+
*/
|
|
368
|
+
clear(): void;
|
|
369
|
+
/**
|
|
370
|
+
* Change the max size of the queue
|
|
371
|
+
* @param size - The new max size
|
|
372
|
+
*/
|
|
373
|
+
setMaxSize(size: number | undefined): void;
|
|
269
374
|
}
|
|
270
375
|
/**
|
|
271
|
-
*
|
|
376
|
+
* Options for creating a Queue
|
|
272
377
|
* @category Signal Collections
|
|
273
378
|
*/
|
|
274
|
-
interface
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
379
|
+
interface SignalQueueOptions {
|
|
380
|
+
size?: number | undefined;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Data indicating the change in state for a location in the Queue
|
|
384
|
+
* @category Signal Collections
|
|
385
|
+
*/
|
|
386
|
+
interface SignalQueueDelta<T> {
|
|
387
|
+
added: boolean;
|
|
388
|
+
item?: T;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* A signal based FILO stack
|
|
393
|
+
* @category Signal Collections
|
|
394
|
+
*/
|
|
395
|
+
declare class SignalStack<T> {
|
|
396
|
+
private readonly _items;
|
|
397
|
+
/** A signal containing the items of the stack from bottom to top */
|
|
398
|
+
readonly items: Signal<T[]>;
|
|
399
|
+
/** A signal emitting the number of items in the stack */
|
|
400
|
+
readonly length: Signal<number>;
|
|
401
|
+
/** A signal emitting true if there are no items in the stack */
|
|
278
402
|
readonly empty: Signal<boolean>;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
403
|
+
constructor();
|
|
404
|
+
/** Signal returning the element at the top of the stack */
|
|
405
|
+
readonly top: Signal<T | undefined>;
|
|
406
|
+
/** Signal returning the current top element and whether it was just added */
|
|
407
|
+
readonly topDelta: Signal<SignalStackDelta<T>>;
|
|
408
|
+
/** Signal returning the element at the bottom of the stack */
|
|
409
|
+
readonly bottom: Signal<T | undefined>;
|
|
410
|
+
/** Signal returning the current bottom element and whether it was just added */
|
|
411
|
+
readonly bottomDelta: Signal<SignalStackDelta<T>>;
|
|
412
|
+
/**
|
|
413
|
+
* Removes an item from the scheduler
|
|
414
|
+
* @param item
|
|
415
|
+
*/
|
|
416
|
+
removeItem(item: T): boolean;
|
|
417
|
+
/**
|
|
418
|
+
* Remove the bottom element from the stack and return it
|
|
419
|
+
*/
|
|
420
|
+
removeFromBottom(): T | undefined;
|
|
421
|
+
/**
|
|
422
|
+
* Remove the bottom x elements from the stack and return them
|
|
423
|
+
* @param count - The amount of elements to remove
|
|
424
|
+
*/
|
|
425
|
+
removeFromBottom(count: number): T[];
|
|
426
|
+
/**
|
|
427
|
+
* Remove the top element from the stack and return it
|
|
428
|
+
*/
|
|
429
|
+
pop(): T | undefined;
|
|
430
|
+
/**
|
|
431
|
+
* Remove the top x elements from the stack and return it
|
|
432
|
+
* @param count - The amount of elements to remove
|
|
433
|
+
*/
|
|
434
|
+
pop(count: number): T[];
|
|
435
|
+
/**
|
|
436
|
+
* Add item to the top of the stack
|
|
437
|
+
* @param item - Item to add
|
|
438
|
+
*/
|
|
439
|
+
push(item: T): void;
|
|
440
|
+
/**
|
|
441
|
+
* Add items to the top of the stack
|
|
442
|
+
* @param items - Items to add
|
|
443
|
+
*/
|
|
444
|
+
push(...items: T[]): void;
|
|
445
|
+
/**
|
|
446
|
+
* Add item to the bottom of the stack
|
|
447
|
+
* @param item - Item to add
|
|
448
|
+
*/
|
|
449
|
+
addToBottom(item: T): void;
|
|
450
|
+
/**
|
|
451
|
+
* Add items to the bottom of the stack
|
|
452
|
+
* @param items - Items to add
|
|
453
|
+
*/
|
|
454
|
+
addToBottom(...items: T[]): void;
|
|
455
|
+
/**
|
|
456
|
+
* Checks if the item exists in the queue
|
|
457
|
+
* @param item
|
|
458
|
+
*/
|
|
459
|
+
contains(item: T): boolean;
|
|
460
|
+
/**
|
|
461
|
+
* Remove all elements from the set
|
|
462
|
+
*/
|
|
463
|
+
clear(): void;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Data indicating the change in state for a location in the Stack
|
|
467
|
+
* @category Signal Collections
|
|
468
|
+
*/
|
|
469
|
+
interface SignalStackDelta<T> {
|
|
470
|
+
item?: T;
|
|
471
|
+
added: boolean;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* A class representing the state of a request
|
|
476
|
+
* @category RequestState
|
|
477
|
+
*/
|
|
478
|
+
declare abstract class IRequestState implements Subscribable<unknown> {
|
|
479
|
+
/** The underlying request */
|
|
480
|
+
abstract readonly request$: Observable<unknown>;
|
|
481
|
+
/** The result of the request */
|
|
482
|
+
abstract readonly result$: Observable<unknown>;
|
|
483
|
+
/** The result of the request, or undefined when unresolved */
|
|
484
|
+
abstract readonly result: Signal<unknown | undefined>;
|
|
485
|
+
/** Emits true when the request is running */
|
|
486
|
+
abstract readonly loading: Signal<boolean>;
|
|
487
|
+
/** Emits the error if one is thrown by the request. Otherwise undefined */
|
|
488
|
+
abstract readonly error: Signal<Error | undefined>;
|
|
489
|
+
/** Emits true if the request failed */
|
|
490
|
+
abstract readonly failed: Signal<boolean>;
|
|
491
|
+
/** Cancel a running request */
|
|
492
|
+
abstract cancel(): boolean;
|
|
493
|
+
/**
|
|
494
|
+
* Perform actions when the request finishes
|
|
495
|
+
* @param onValue - Action to perform on successful resolution
|
|
496
|
+
* @param onError - Action to perform on error
|
|
497
|
+
*/
|
|
498
|
+
abstract then(onValue: (value: unknown) => void, onError?: (error: Error) => void): this;
|
|
499
|
+
/**
|
|
500
|
+
* Perform an action if the request fails
|
|
501
|
+
* @param onError
|
|
502
|
+
*/
|
|
503
|
+
abstract catch(onError: (error: Error) => void): this;
|
|
504
|
+
/**
|
|
505
|
+
* Perform an action when the request finishes (Error or Success)
|
|
506
|
+
* @param onComplete
|
|
507
|
+
*/
|
|
508
|
+
abstract finally(onComplete: (error?: Error) => void): this;
|
|
509
|
+
/**
|
|
510
|
+
* Subscribe to the underlying request Observable
|
|
511
|
+
* @param observer
|
|
512
|
+
*/
|
|
513
|
+
subscribe(observer: Partial<Observer<unknown>>): Unsubscribable;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* A class representing the state of a request with a known return type
|
|
517
|
+
* @category RequestState
|
|
518
|
+
*/
|
|
519
|
+
declare abstract class IValueRequestState<T> extends IRequestState implements Subscribable<T> {
|
|
520
|
+
abstract readonly request$: Observable<T>;
|
|
521
|
+
abstract readonly result$: Observable<T>;
|
|
522
|
+
abstract readonly result: Signal<T | undefined>;
|
|
523
|
+
subscribe(observer: Partial<Observer<T>>): Unsubscribable;
|
|
524
|
+
then(onValue: (value: T) => void, onError?: (error: Error) => void): this;
|
|
525
|
+
catch(onError: (error: Error) => void): this;
|
|
526
|
+
finally(onComplete: (error?: Error) => void): this;
|
|
527
|
+
/**
|
|
528
|
+
* Cancel a running request
|
|
529
|
+
* @param error - Optionally provide a custom error to throw when cancelling the request
|
|
530
|
+
*/
|
|
531
|
+
abstract cancel(error?: string | Error | (() => Error)): boolean;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* A manually populated RequestState
|
|
536
|
+
* @category RequestState
|
|
537
|
+
*/
|
|
538
|
+
declare class WritableRequestState<T> extends IValueRequestState<T> {
|
|
539
|
+
private readonly onCancel?;
|
|
540
|
+
private _completed;
|
|
541
|
+
get completed(): boolean;
|
|
542
|
+
private readonly _request$;
|
|
543
|
+
readonly request$: rxjs.Observable<T>;
|
|
544
|
+
private readonly _result$;
|
|
545
|
+
readonly result$: rxjs.Observable<T>;
|
|
546
|
+
private readonly _result;
|
|
547
|
+
readonly result: _angular_core.Signal<T | undefined>;
|
|
548
|
+
private readonly _loading;
|
|
549
|
+
readonly loading: _angular_core.Signal<boolean>;
|
|
550
|
+
private readonly _error;
|
|
551
|
+
readonly error: _angular_core.Signal<Error | undefined>;
|
|
552
|
+
readonly failed: _angular_core.Signal<boolean>;
|
|
553
|
+
private readonly _cancelled;
|
|
554
|
+
/** Emits true when the RequestState has been cancelled by a consumer, otherwise emits false */
|
|
555
|
+
readonly cancelled: _angular_core.Signal<boolean>;
|
|
556
|
+
private readonly _cancelled$;
|
|
557
|
+
/** Emits true when the RequestState has been cancelled by a consumer */
|
|
558
|
+
readonly cancelled$: rxjs.Observable<true>;
|
|
559
|
+
constructor(onCancel?: (() => void) | undefined);
|
|
560
|
+
/**
|
|
561
|
+
* Create a Readonly version of the RequestState
|
|
562
|
+
*/
|
|
563
|
+
asReadonly(): IValueRequestState<T>;
|
|
564
|
+
/**
|
|
565
|
+
* Set the RequestState to an Error state
|
|
566
|
+
* @param error - The error for the RequestState
|
|
567
|
+
*/
|
|
568
|
+
setError(error: unknown | Error): void;
|
|
569
|
+
/**
|
|
570
|
+
* Set the RequestState to a completed state with a given value
|
|
571
|
+
* @param value - The value to emit
|
|
572
|
+
*/
|
|
573
|
+
setValue(value: T): void;
|
|
574
|
+
cancel(error?: string | Error | (() => Error)): boolean;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Create a RequestState from a request or Value
|
|
579
|
+
* @param data - The request or value
|
|
580
|
+
* @category RequestState
|
|
581
|
+
*/
|
|
582
|
+
declare function valueRequestState<T>(data: T | Promise<T> | Observable<T>): IValueRequestState<T>;
|
|
583
|
+
/** @category RequestState */
|
|
584
|
+
interface AltConstructors {
|
|
585
|
+
/**
|
|
586
|
+
* Create a writable RequestState
|
|
587
|
+
* @param onCancel - Optional cancellation handler
|
|
588
|
+
* @category RequestState
|
|
589
|
+
*/
|
|
590
|
+
writable<T>(onCancel?: () => void): WritableRequestState<T>;
|
|
591
|
+
/**
|
|
592
|
+
* Create a RequestState emitting an error
|
|
593
|
+
* @param error - The error to emit
|
|
594
|
+
* @category RequestState
|
|
595
|
+
*/
|
|
596
|
+
error(error: Error | (() => Error)): IRequestState;
|
|
597
|
+
/**
|
|
598
|
+
* Create a typed RequestState emitting an error
|
|
599
|
+
* @param error - The error to emit
|
|
600
|
+
* @category RequestState
|
|
601
|
+
*/
|
|
602
|
+
error<T>(error: Error | (() => Error)): IValueRequestState<T>;
|
|
603
|
+
/**
|
|
604
|
+
* Create a RequestState which is always loading
|
|
605
|
+
* @category RequestState
|
|
606
|
+
*/
|
|
607
|
+
loading(): IRequestState;
|
|
608
|
+
/**
|
|
609
|
+
* Create a typed RequestState which is always loading
|
|
610
|
+
* @category RequestState
|
|
611
|
+
*/
|
|
612
|
+
loading<T>(): IValueRequestState<T>;
|
|
613
|
+
/**
|
|
614
|
+
* Create a RequestState that resolves after an amount of time
|
|
615
|
+
* @param duration - The loading duration in milliseconds
|
|
616
|
+
* @category RequestState
|
|
617
|
+
*/
|
|
618
|
+
loading(duration: number): IRequestState;
|
|
619
|
+
/**
|
|
620
|
+
* Create a RequestState that resolves with a value after an amount of time
|
|
621
|
+
* @param duration - The loading duration in milliseconds
|
|
622
|
+
* @param value - The value to resolve with after the delay
|
|
623
|
+
* @category RequestState
|
|
624
|
+
*/
|
|
625
|
+
loading<T>(duration: number, value: T): IValueRequestState<T>;
|
|
626
|
+
/**
|
|
627
|
+
* Create an empty RequestState
|
|
628
|
+
* @category RequestState
|
|
629
|
+
*/
|
|
630
|
+
empty(): IRequestState;
|
|
631
|
+
/**
|
|
632
|
+
* Create a typed empty RequestState
|
|
633
|
+
* @category RequestState
|
|
634
|
+
*/
|
|
635
|
+
empty<T>(): IValueRequestState<T>;
|
|
636
|
+
}
|
|
637
|
+
declare const requestState: typeof valueRequestState & AltConstructors;
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* A RequestState representing a pending async action
|
|
641
|
+
* @category RequestState
|
|
642
|
+
*/
|
|
643
|
+
declare class AsyncRequestState<T> extends IValueRequestState<T> {
|
|
644
|
+
private readonly _request$;
|
|
645
|
+
readonly request$: Observable<T>;
|
|
646
|
+
private readonly _result$;
|
|
647
|
+
readonly result$: Observable<T>;
|
|
648
|
+
private readonly _result;
|
|
649
|
+
readonly result: _angular_core.Signal<T | undefined>;
|
|
650
|
+
private readonly _loading;
|
|
651
|
+
readonly loading: _angular_core.Signal<boolean>;
|
|
652
|
+
private readonly _error;
|
|
653
|
+
readonly error: _angular_core.Signal<Error | undefined>;
|
|
654
|
+
readonly failed: _angular_core.Signal<boolean>;
|
|
655
|
+
private readonly sub;
|
|
656
|
+
constructor(request: Promise<T> | Observable<T>);
|
|
657
|
+
cancel(error?: string | Error | (() => Error)): boolean;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* An Empty RequestState
|
|
662
|
+
* @category RequestState
|
|
663
|
+
*/
|
|
664
|
+
declare class EmptyRequestState<T> extends IValueRequestState<T> {
|
|
665
|
+
readonly request$: rxjs.Observable<never>;
|
|
666
|
+
readonly result$: rxjs.Observable<never>;
|
|
667
|
+
readonly result: Signal<undefined>;
|
|
668
|
+
readonly loading: Signal<false>;
|
|
669
|
+
readonly error: Signal<undefined>;
|
|
670
|
+
readonly failed: Signal<false>;
|
|
671
|
+
cancel(): boolean;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* A RequestState which always emits an error
|
|
676
|
+
* @category RequestState
|
|
677
|
+
*/
|
|
678
|
+
declare class ErrorRequestState<T> extends IValueRequestState<T> {
|
|
679
|
+
readonly request$: Observable<never>;
|
|
680
|
+
readonly result$: Observable<never>;
|
|
681
|
+
readonly result: Signal<undefined>;
|
|
682
|
+
readonly loading: Signal<boolean>;
|
|
683
|
+
readonly error: Signal<Error | undefined>;
|
|
684
|
+
readonly failed: Signal<boolean>;
|
|
685
|
+
constructor(getError: () => Error);
|
|
686
|
+
cancel(): boolean;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* A RequestState which is perpetually loading
|
|
691
|
+
* @category RequestState
|
|
692
|
+
*/
|
|
693
|
+
declare class LoadingRequestState<T> extends IValueRequestState<T> {
|
|
694
|
+
private readonly _request$;
|
|
695
|
+
readonly request$: rxjs.Observable<T>;
|
|
696
|
+
readonly result$: rxjs.Observable<T>;
|
|
697
|
+
readonly result: Signal<undefined>;
|
|
698
|
+
private readonly _loading;
|
|
699
|
+
readonly loading: Signal<boolean>;
|
|
700
|
+
readonly error: Signal<undefined>;
|
|
701
|
+
readonly failed: Signal<false>;
|
|
702
|
+
cancel(): boolean;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* A RequestState which always returns the same value immediately
|
|
707
|
+
* @category RequestState
|
|
708
|
+
*/
|
|
709
|
+
declare class StaticRequestState<T> extends IValueRequestState<T> {
|
|
710
|
+
private readonly value;
|
|
711
|
+
readonly request$: Observable<T>;
|
|
712
|
+
readonly result$: Observable<T>;
|
|
713
|
+
readonly result: Signal<T>;
|
|
714
|
+
readonly loading: Signal<false>;
|
|
715
|
+
readonly error: Signal<undefined>;
|
|
716
|
+
readonly failed: Signal<false>;
|
|
717
|
+
constructor(value: T);
|
|
718
|
+
cancel(): boolean;
|
|
719
|
+
then(next: (value: T) => void, _error?: (error: Error) => void): this;
|
|
720
|
+
catch(_onError: (error: Error) => void): this;
|
|
721
|
+
finally(onComplete: () => void): this;
|
|
285
722
|
}
|
|
286
723
|
|
|
287
|
-
|
|
724
|
+
/**
|
|
725
|
+
* A container which keeps track of a replaceable RequestState
|
|
726
|
+
* @category RequestState
|
|
727
|
+
*/
|
|
728
|
+
declare class RequestTracker {
|
|
729
|
+
private readonly _request;
|
|
730
|
+
/** The currently active RequestState */
|
|
731
|
+
readonly request: Signal<IRequestState>;
|
|
732
|
+
/** The currently active RequestState */
|
|
733
|
+
set current(req: IRequestState);
|
|
734
|
+
get current(): IRequestState;
|
|
735
|
+
/** Emits true if the current RequestState is loading */
|
|
736
|
+
readonly loading: Signal<boolean>;
|
|
737
|
+
/** Emits an error if the current RequestState is in an error state */
|
|
738
|
+
readonly error: Signal<Error | undefined>;
|
|
739
|
+
/** Emits true if the current RequestState has failed */
|
|
740
|
+
readonly failed: Signal<boolean>;
|
|
741
|
+
constructor(request?: IRequestState);
|
|
742
|
+
/**
|
|
743
|
+
* Set a new current RequestState
|
|
744
|
+
* @param state - The new RequestState
|
|
745
|
+
*/
|
|
746
|
+
set(state: IRequestState): void;
|
|
747
|
+
/**
|
|
748
|
+
* Resets the Tracker to an Empty state
|
|
749
|
+
*/
|
|
750
|
+
reset(): void;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* A container which keeps track of a replaceable RequestState of a known return type
|
|
754
|
+
* @category RequestState
|
|
755
|
+
*/
|
|
756
|
+
declare class ValueRequestTracker<T> extends RequestTracker {
|
|
757
|
+
private readonly _valueRequest;
|
|
758
|
+
request: Signal<IValueRequestState<T>>;
|
|
759
|
+
set current(req: IValueRequestState<T>);
|
|
760
|
+
get current(): IValueRequestState<T>;
|
|
761
|
+
constructor(request?: IValueRequestState<T>);
|
|
762
|
+
set(req: IValueRequestState<T>): void;
|
|
763
|
+
reset(): void;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Create an empty and untyped RequestTracker
|
|
767
|
+
* @category RequestState
|
|
768
|
+
*/
|
|
769
|
+
declare function requestTracker(): RequestTracker;
|
|
770
|
+
/**
|
|
771
|
+
* Create an empty RequestTracker with a known RequestState return type
|
|
772
|
+
* @category RequestState
|
|
773
|
+
*/
|
|
774
|
+
declare function requestTracker<T>(): ValueRequestTracker<T>;
|
|
775
|
+
/**
|
|
776
|
+
* Create a typed RequestTracker from a types RequestState
|
|
777
|
+
* @param request - The initial RequestState
|
|
778
|
+
* @category RequestState
|
|
779
|
+
*/
|
|
780
|
+
declare function requestTracker<T>(request: IValueRequestState<T>): ValueRequestTracker<T>;
|
|
781
|
+
/**
|
|
782
|
+
* Create an untyped RequestTracker from an untyped RequestState
|
|
783
|
+
* @param request - The initial RequestState
|
|
784
|
+
* @category RequestState
|
|
785
|
+
*/
|
|
786
|
+
declare function requestTracker(request: IRequestState): RequestTracker;
|
|
787
|
+
|
|
788
|
+
export { AsyncRequestState, type CreateLoadingSignalOptions, EmptyRequestState, ErrorRequestState, IRequestState, IValueRequestState, LoadingRequestState, type LoadingSignal, type LoadingSignalOptions, type ReadonlySignalMap, type ReadonlySignalSet, RequestTracker, SignalMap, SignalQueue, type SignalQueueDelta, type SignalQueueOptions, SignalSet, SignalStack, type SignalStackDelta, StaticRequestState, ValueRequestTracker, WritableRequestState, debouncedSignal, debouncedThrottledSignal, loadingSignal, requestState, requestTracker, searchSignal, subjectToSignal, throttledSignal, toLoadingSignal };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
export { debouncedSignal, debouncedThrottledSignal, searchSignal, throttledSignal } from './async.js';
|
|
2
2
|
export { subjectToSignal } from './interop.js';
|
|
3
3
|
export { loadingSignal, toLoadingSignal } from './loading-signal.js';
|
|
4
|
-
export { SignalSet } from './data-structures/signal-set.js';
|
|
5
4
|
export { SignalMap } from './data-structures/signal-map.js';
|
|
5
|
+
export { SignalSet } from './data-structures/signal-set.js';
|
|
6
|
+
export { SignalQueue } from './data-structures/signal-queue.js';
|
|
7
|
+
export { SignalStack } from './data-structures/signal-stack.js';
|
|
8
|
+
export { requestState } from './request-state/constructors.js';
|
|
9
|
+
export { IRequestState, IValueRequestState } from './request-state/request-state.js';
|
|
10
|
+
export { AsyncRequestState } from './request-state/async-request-state.js';
|
|
11
|
+
export { EmptyRequestState } from './request-state/empty-request-state.js';
|
|
12
|
+
export { ErrorRequestState } from './request-state/error-request-state.js';
|
|
13
|
+
export { LoadingRequestState } from './request-state/loading-request-state.js';
|
|
14
|
+
export { StaticRequestState } from './request-state/static-request-state.js';
|
|
15
|
+
export { WritableRequestState } from './request-state/writable-request-state.js';
|
|
16
|
+
export { RequestTracker, ValueRequestTracker, requestTracker } from './request-state/request-tracker.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computed } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
function pairwiseSignal(inputSignal, firstValue) {
|
|
4
|
+
let prevValue = firstValue;
|
|
5
|
+
return computed(() => {
|
|
6
|
+
const nextValue = inputSignal();
|
|
7
|
+
const tuple = [prevValue, nextValue];
|
|
8
|
+
prevValue = nextValue;
|
|
9
|
+
return tuple;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { pairwiseSignal };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ReplaySubject, isObservable, take, from } from 'rxjs';
|
|
2
|
+
import { signal, computed } from '@angular/core';
|
|
3
|
+
import { parseError, isString, isFunction } from '@juulsgaard/ts-tools';
|
|
4
|
+
import { CancelledError } from '@juulsgaard/rxjs-tools';
|
|
5
|
+
import { IValueRequestState } from './request-state.js';
|
|
6
|
+
|
|
7
|
+
class AsyncRequestState extends IValueRequestState {
|
|
8
|
+
constructor(request) {
|
|
9
|
+
super();
|
|
10
|
+
this._request$ = new ReplaySubject();
|
|
11
|
+
this.request$ = this._request$.asObservable();
|
|
12
|
+
this._result$ = new ReplaySubject();
|
|
13
|
+
this.result$ = this._result$.asObservable();
|
|
14
|
+
this._result = signal(void 0);
|
|
15
|
+
this.result = this._result.asReadonly();
|
|
16
|
+
this._loading = signal(true);
|
|
17
|
+
this.loading = this._loading.asReadonly();
|
|
18
|
+
this._error = signal(void 0);
|
|
19
|
+
this.error = this._error.asReadonly();
|
|
20
|
+
this.failed = computed(() => this._error() !== void 0);
|
|
21
|
+
const request$ = isObservable(request) ? request.pipe(take(1)) : from(request);
|
|
22
|
+
this.sub = request$.subscribe(this._request$);
|
|
23
|
+
this.request$.subscribe({
|
|
24
|
+
next: (value) => {
|
|
25
|
+
this._result$.next(value);
|
|
26
|
+
this._result.set(value);
|
|
27
|
+
},
|
|
28
|
+
error: (error) => {
|
|
29
|
+
this._error.set(parseError(error));
|
|
30
|
+
this._result$.complete();
|
|
31
|
+
this._loading.set(false);
|
|
32
|
+
},
|
|
33
|
+
complete: () => {
|
|
34
|
+
this._result$.complete();
|
|
35
|
+
this._loading.set(false);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
cancel(error) {
|
|
40
|
+
if (this.sub.closed) return false;
|
|
41
|
+
this.sub.unsubscribe();
|
|
42
|
+
const fullError = isString(error) ? new CancelledError(error) : isFunction(error) ? error() : error;
|
|
43
|
+
this._request$.error(fullError);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { AsyncRequestState };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isObservable } from 'rxjs';
|
|
2
|
+
import { WritableRequestState } from './writable-request-state.js';
|
|
3
|
+
import { AsyncRequestState } from './async-request-state.js';
|
|
4
|
+
import { StaticRequestState } from './static-request-state.js';
|
|
5
|
+
import { ErrorRequestState } from './error-request-state.js';
|
|
6
|
+
import '@angular/core';
|
|
7
|
+
import '@juulsgaard/ts-tools';
|
|
8
|
+
|
|
9
|
+
function valueRequestState(data) {
|
|
10
|
+
if (isObservable(data)) return new AsyncRequestState(data);
|
|
11
|
+
if (data instanceof Promise) return new AsyncRequestState(data);
|
|
12
|
+
return new StaticRequestState(data);
|
|
13
|
+
}
|
|
14
|
+
function errorRequestState(error) {
|
|
15
|
+
return new ErrorRequestState(error instanceof Error ? () => error : error);
|
|
16
|
+
}
|
|
17
|
+
function writableRequestState(onCancel) {
|
|
18
|
+
return new WritableRequestState(onCancel);
|
|
19
|
+
}
|
|
20
|
+
const compiled = valueRequestState;
|
|
21
|
+
compiled.writable = writableRequestState;
|
|
22
|
+
compiled.error = errorRequestState;
|
|
23
|
+
const requestState = compiled;
|
|
24
|
+
|
|
25
|
+
export { requestState };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EMPTY } from 'rxjs';
|
|
2
|
+
import { signal } from '@angular/core';
|
|
3
|
+
import { IValueRequestState } from './request-state.js';
|
|
4
|
+
|
|
5
|
+
class EmptyRequestState extends IValueRequestState {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.request$ = EMPTY;
|
|
9
|
+
this.result$ = this.request$;
|
|
10
|
+
this.result = signal(void 0).asReadonly();
|
|
11
|
+
this.loading = signal(false);
|
|
12
|
+
this.error = signal(void 0);
|
|
13
|
+
this.failed = signal(false);
|
|
14
|
+
}
|
|
15
|
+
cancel() {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { EmptyRequestState };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EMPTY, throwError } from 'rxjs';
|
|
2
|
+
import { signal } from '@angular/core';
|
|
3
|
+
import { IValueRequestState } from './request-state.js';
|
|
4
|
+
|
|
5
|
+
class ErrorRequestState extends IValueRequestState {
|
|
6
|
+
constructor(getError) {
|
|
7
|
+
super();
|
|
8
|
+
this.result$ = EMPTY;
|
|
9
|
+
this.result = signal(void 0).asReadonly();
|
|
10
|
+
this.loading = signal(false);
|
|
11
|
+
this.failed = signal(true);
|
|
12
|
+
this.error = signal(getError());
|
|
13
|
+
this.request$ = throwError(getError);
|
|
14
|
+
}
|
|
15
|
+
cancel() {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { ErrorRequestState };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
import { signal } from '@angular/core';
|
|
3
|
+
import { IValueRequestState } from './request-state.js';
|
|
4
|
+
|
|
5
|
+
class LoadingRequestState extends IValueRequestState {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this._request$ = new Subject();
|
|
9
|
+
this.request$ = this._request$.asObservable();
|
|
10
|
+
this.result$ = this.request$;
|
|
11
|
+
this.result = signal(void 0).asReadonly();
|
|
12
|
+
this._loading = signal(true);
|
|
13
|
+
this.loading = this._loading.asReadonly();
|
|
14
|
+
this.error = signal(void 0);
|
|
15
|
+
this.failed = signal(false);
|
|
16
|
+
}
|
|
17
|
+
cancel() {
|
|
18
|
+
if (this._request$.closed) return false;
|
|
19
|
+
this._request$.complete();
|
|
20
|
+
this._loading.set(false);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { LoadingRequestState };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { first } from 'rxjs';
|
|
2
|
+
import { parseError } from '@juulsgaard/ts-tools';
|
|
3
|
+
|
|
4
|
+
class IRequestState {
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe to the underlying request Observable
|
|
7
|
+
* @param observer
|
|
8
|
+
*/
|
|
9
|
+
subscribe(observer) {
|
|
10
|
+
return this.request$.subscribe(observer);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class IValueRequestState extends IRequestState {
|
|
14
|
+
subscribe(observer) {
|
|
15
|
+
return this.request$.subscribe(observer);
|
|
16
|
+
}
|
|
17
|
+
then(onValue, onError) {
|
|
18
|
+
this.request$.pipe(first()).subscribe({
|
|
19
|
+
next: (v) => onValue(v),
|
|
20
|
+
error: (e) => onError?.(parseError(e))
|
|
21
|
+
});
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
catch(onError) {
|
|
25
|
+
this.request$.subscribe({
|
|
26
|
+
error: (e) => onError(parseError(e))
|
|
27
|
+
});
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
finally(onComplete) {
|
|
31
|
+
this.request$.subscribe({
|
|
32
|
+
error: (e) => onComplete(parseError(e)),
|
|
33
|
+
complete: () => onComplete()
|
|
34
|
+
});
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { IRequestState, IValueRequestState };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { computed, signal, untracked } from '@angular/core';
|
|
2
|
+
import { IValueRequestState } from './request-state.js';
|
|
3
|
+
import { requestState } from './constructors.js';
|
|
4
|
+
|
|
5
|
+
class RequestTracker {
|
|
6
|
+
constructor(request) {
|
|
7
|
+
/** Emits true if the current RequestState is loading */
|
|
8
|
+
this.loading = computed(() => this._request().loading());
|
|
9
|
+
/** Emits an error if the current RequestState is in an error state */
|
|
10
|
+
this.error = computed(() => this._request().error());
|
|
11
|
+
/** Emits true if the current RequestState has failed */
|
|
12
|
+
this.failed = computed(() => this._request().failed());
|
|
13
|
+
this._request = signal(request ?? requestState.empty());
|
|
14
|
+
this.request = this._request.asReadonly();
|
|
15
|
+
}
|
|
16
|
+
/** The currently active RequestState */
|
|
17
|
+
set current(req) {
|
|
18
|
+
this.set(req);
|
|
19
|
+
}
|
|
20
|
+
get current() {
|
|
21
|
+
return untracked(this.request);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set a new current RequestState
|
|
25
|
+
* @param state - The new RequestState
|
|
26
|
+
*/
|
|
27
|
+
set(state) {
|
|
28
|
+
this._request.set(state);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resets the Tracker to an Empty state
|
|
32
|
+
*/
|
|
33
|
+
reset() {
|
|
34
|
+
this._request.set(requestState.empty());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
class ValueRequestTracker extends RequestTracker {
|
|
38
|
+
set current(req) {
|
|
39
|
+
this.set(req);
|
|
40
|
+
}
|
|
41
|
+
get current() {
|
|
42
|
+
return untracked(this.request);
|
|
43
|
+
}
|
|
44
|
+
constructor(request) {
|
|
45
|
+
super();
|
|
46
|
+
this._valueRequest = signal(request ?? requestState.empty());
|
|
47
|
+
this.request = this._valueRequest.asReadonly();
|
|
48
|
+
}
|
|
49
|
+
set(req) {
|
|
50
|
+
super.set(req);
|
|
51
|
+
this._valueRequest.set(req);
|
|
52
|
+
}
|
|
53
|
+
reset() {
|
|
54
|
+
super.reset();
|
|
55
|
+
this.set(requestState.empty());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function requestTracker(request) {
|
|
59
|
+
if (!request) return new ValueRequestTracker();
|
|
60
|
+
if (request instanceof IValueRequestState) return new ValueRequestTracker(request);
|
|
61
|
+
return new RequestTracker(request);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { RequestTracker, ValueRequestTracker, requestTracker };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { signal } from '@angular/core';
|
|
3
|
+
import { IValueRequestState } from './request-state.js';
|
|
4
|
+
|
|
5
|
+
class StaticRequestState extends IValueRequestState {
|
|
6
|
+
constructor(value) {
|
|
7
|
+
super();
|
|
8
|
+
this.value = value;
|
|
9
|
+
this.loading = signal(false);
|
|
10
|
+
this.error = signal(void 0);
|
|
11
|
+
this.failed = signal(false);
|
|
12
|
+
this.request$ = of(value);
|
|
13
|
+
this.result$ = this.request$;
|
|
14
|
+
this.result = signal(value);
|
|
15
|
+
}
|
|
16
|
+
cancel() {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
then(next, _error) {
|
|
20
|
+
next(this.value);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
catch(_onError) {
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
finally(onComplete) {
|
|
27
|
+
onComplete();
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { StaticRequestState };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ReplaySubject } from 'rxjs';
|
|
2
|
+
import { signal, computed } from '@angular/core';
|
|
3
|
+
import { parseError, isString, isFunction } from '@juulsgaard/ts-tools';
|
|
4
|
+
import { CancelledError } from '@juulsgaard/rxjs-tools';
|
|
5
|
+
import { IValueRequestState } from './request-state.js';
|
|
6
|
+
|
|
7
|
+
class WritableRequestState extends IValueRequestState {
|
|
8
|
+
constructor(onCancel) {
|
|
9
|
+
super();
|
|
10
|
+
this.onCancel = onCancel;
|
|
11
|
+
this._completed = false;
|
|
12
|
+
this._request$ = new ReplaySubject();
|
|
13
|
+
this.request$ = this._request$.asObservable();
|
|
14
|
+
this._result$ = new ReplaySubject();
|
|
15
|
+
this.result$ = this._result$.asObservable();
|
|
16
|
+
this._result = signal(void 0);
|
|
17
|
+
this.result = this._result.asReadonly();
|
|
18
|
+
this._loading = signal(true);
|
|
19
|
+
this.loading = this._loading.asReadonly();
|
|
20
|
+
this._error = signal(void 0);
|
|
21
|
+
this.error = this._error.asReadonly();
|
|
22
|
+
this.failed = computed(() => this._error() !== void 0);
|
|
23
|
+
this._cancelled = signal(false);
|
|
24
|
+
/** Emits true when the RequestState has been cancelled by a consumer, otherwise emits false */
|
|
25
|
+
this.cancelled = this._cancelled.asReadonly();
|
|
26
|
+
this._cancelled$ = new ReplaySubject();
|
|
27
|
+
/** Emits true when the RequestState has been cancelled by a consumer */
|
|
28
|
+
this.cancelled$ = this._cancelled$.asObservable();
|
|
29
|
+
}
|
|
30
|
+
get completed() {
|
|
31
|
+
return this._completed;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a Readonly version of the RequestState
|
|
35
|
+
*/
|
|
36
|
+
asReadonly() {
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Set the RequestState to an Error state
|
|
41
|
+
* @param error - The error for the RequestState
|
|
42
|
+
*/
|
|
43
|
+
setError(error) {
|
|
44
|
+
if (this.completed) return;
|
|
45
|
+
this._completed = true;
|
|
46
|
+
this._request$.error(error);
|
|
47
|
+
this._result$.complete();
|
|
48
|
+
this._loading.set(false);
|
|
49
|
+
this._error.set(parseError(error));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Set the RequestState to a completed state with a given value
|
|
53
|
+
* @param value - The value to emit
|
|
54
|
+
*/
|
|
55
|
+
setValue(value) {
|
|
56
|
+
if (this.completed) return;
|
|
57
|
+
this._completed = true;
|
|
58
|
+
this._request$.next(value);
|
|
59
|
+
this._request$.complete();
|
|
60
|
+
this._loading.set(false);
|
|
61
|
+
this._result.set(value);
|
|
62
|
+
}
|
|
63
|
+
cancel(error) {
|
|
64
|
+
if (this.completed) return false;
|
|
65
|
+
const fullError = isString(error) ? new CancelledError(error) : isFunction(error) ? error() : error;
|
|
66
|
+
this.setError(fullError);
|
|
67
|
+
this._cancelled.set(true);
|
|
68
|
+
this._cancelled$.next(true);
|
|
69
|
+
this._cancelled$.complete();
|
|
70
|
+
this.onCancel?.();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { WritableRequestState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juulsgaard/signal-tools",
|
|
3
|
-
"version": "18.0.
|
|
3
|
+
"version": "18.0.1",
|
|
4
4
|
"description": "Tools for working with Angular signals",
|
|
5
5
|
"repository": "git@github.com:Juulsgaard/signal-tools.git",
|
|
6
6
|
"homepage": "https://juulsgaard.github.io/signal-tools/",
|
|
@@ -14,24 +14,24 @@
|
|
|
14
14
|
"dist/**"
|
|
15
15
|
],
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@angular/core": "^18.2.
|
|
17
|
+
"@angular/core": "^18.2.8",
|
|
18
18
|
"@juulsgaard/ts-tools": "^1.6.4",
|
|
19
|
-
"@juulsgaard/rxjs-tools": "^1.7.
|
|
19
|
+
"@juulsgaard/rxjs-tools": "^1.7.6",
|
|
20
20
|
"rxjs": "^7.8.1",
|
|
21
21
|
"tslib": "^2.7.0",
|
|
22
22
|
"typescript": "~5.5.4"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@angular/core": "18.2.
|
|
26
|
-
"@juulsgaard/rxjs-tools": "1.7.
|
|
27
|
-
"@types/jest": "^29.5.
|
|
25
|
+
"@angular/core": "18.2.8",
|
|
26
|
+
"@juulsgaard/rxjs-tools": "1.7.6",
|
|
27
|
+
"@types/jest": "^29.5.13",
|
|
28
28
|
"jest": "^29.7.0",
|
|
29
|
-
"rollup": "^4.
|
|
29
|
+
"rollup": "^4.24.0",
|
|
30
30
|
"rollup-plugin-dts": "^6.1.1",
|
|
31
31
|
"rollup-plugin-esbuild": "^6.1.1",
|
|
32
32
|
"rxjs": "7.8.1",
|
|
33
|
-
"ts-jest": "^29.2.
|
|
34
|
-
"typedoc": "^0.26.
|
|
33
|
+
"ts-jest": "^29.2.5",
|
|
34
|
+
"typedoc": "^0.26.8",
|
|
35
35
|
"typescript": "~5.5.4",
|
|
36
36
|
"tslib": "2.7.0"
|
|
37
37
|
},
|