shelving 1.189.1 → 1.190.0
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/package.json +1 -1
- package/store/ArrayStore.d.ts +4 -5
- package/store/ArrayStore.js +7 -6
- package/store/BooleanStore.d.ts +3 -3
- package/store/BooleanStore.js +7 -7
- package/store/BusyStore.d.ts +12 -0
- package/store/BusyStore.js +27 -0
- package/store/DataStore.d.ts +3 -5
- package/store/DataStore.js +3 -11
- package/store/DictionaryStore.d.ts +3 -3
- package/store/DictionaryStore.js +3 -3
- package/store/FetchStore.d.ts +15 -31
- package/store/FetchStore.js +42 -74
- package/store/PathStore.d.ts +10 -4
- package/store/PathStore.js +14 -5
- package/store/PayloadFetchStore.d.ts +3 -3
- package/store/PayloadFetchStore.js +3 -3
- package/store/Store.d.ts +67 -46
- package/store/Store.js +117 -77
- package/store/URLStore.d.ts +4 -4
- package/store/URLStore.js +7 -7
- package/store/index.d.ts +0 -2
- package/store/index.js +0 -2
- package/store/LoadingStore.d.ts +0 -10
- package/store/LoadingStore.js +0 -16
- package/store/ValueStore.d.ts +0 -5
- package/store/ValueStore.js +0 -8
package/package.json
CHANGED
package/store/ArrayStore.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ImmutableArray, PossibleArray } from "../util/array.js";
|
|
2
|
-
import type { NONE } from "../util/constants.js";
|
|
3
2
|
import type { AnyCaller } from "../util/function.js";
|
|
4
|
-
import {
|
|
3
|
+
import { BusyStore } from "./BusyStore.js";
|
|
5
4
|
/** Store an array. */
|
|
6
|
-
export declare class ArrayStore<T> extends
|
|
7
|
-
constructor(value?:
|
|
8
|
-
|
|
5
|
+
export declare class ArrayStore<T> extends BusyStore<ImmutableArray<T>, PossibleArray<T>> implements Iterable<T> {
|
|
6
|
+
constructor(value?: PossibleArray<T>);
|
|
7
|
+
protected _convert(input: PossibleArray<T>, caller: AnyCaller): ImmutableArray<T>;
|
|
9
8
|
/** Get the first item in this store or `null` if this query has no items. */
|
|
10
9
|
get optionalFirst(): T | undefined;
|
|
11
10
|
/** Get the last item in this store or `null` if this query has no items. */
|
package/store/ArrayStore.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { getFirst, getLast, omitArrayItems, requireArray, requireFirst, requireLast, toggleArrayItems, withArrayItems, } from "../util/array.js";
|
|
2
|
-
import {
|
|
2
|
+
import { BusyStore } from "./BusyStore.js";
|
|
3
3
|
/** Store an array. */
|
|
4
|
-
export class ArrayStore extends
|
|
4
|
+
export class ArrayStore extends BusyStore {
|
|
5
|
+
// Override to set default value to `[]` and convert possible arrays.
|
|
5
6
|
constructor(value = []) {
|
|
6
|
-
super(value);
|
|
7
|
+
super(requireArray(value, undefined, undefined, ArrayStore));
|
|
7
8
|
}
|
|
8
|
-
// Implement to
|
|
9
|
-
|
|
10
|
-
return requireArray(
|
|
9
|
+
// Implement to convert possible arrays.
|
|
10
|
+
_convert(input, caller) {
|
|
11
|
+
return requireArray(input, undefined, undefined, caller);
|
|
11
12
|
}
|
|
12
13
|
/** Get the first item in this store or `null` if this query has no items. */
|
|
13
14
|
get optionalFirst() {
|
package/store/BooleanStore.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Store } from "./Store.js";
|
|
2
2
|
/** Store a boolean. */
|
|
3
|
-
export declare class BooleanStore extends Store<
|
|
3
|
+
export declare class BooleanStore extends Store<boolean, unknown> {
|
|
4
4
|
constructor(value?: boolean);
|
|
5
|
-
|
|
5
|
+
protected _convert(input: unknown): boolean;
|
|
6
|
+
protected _equal(a: boolean, b: boolean): boolean;
|
|
6
7
|
/** Toggle the current boolean value. */
|
|
7
8
|
toggle(): void;
|
|
8
|
-
isEqual(a: boolean, b: boolean): boolean;
|
|
9
9
|
}
|
package/store/BooleanStore.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Store } from "./Store.js";
|
|
2
2
|
/** Store a boolean. */
|
|
3
3
|
export class BooleanStore extends Store {
|
|
4
|
-
// Override to set default value to false
|
|
4
|
+
// Override to set default value to `false`
|
|
5
5
|
constructor(value = false) {
|
|
6
6
|
super(value);
|
|
7
7
|
}
|
|
8
8
|
// Override to automatically convert to boolean.
|
|
9
|
-
|
|
10
|
-
return !!
|
|
9
|
+
_convert(input) {
|
|
10
|
+
return !!input;
|
|
11
|
+
}
|
|
12
|
+
// Override for fast equality.
|
|
13
|
+
_equal(a, b) {
|
|
14
|
+
return a === b;
|
|
11
15
|
}
|
|
12
16
|
/** Toggle the current boolean value. */
|
|
13
17
|
toggle() {
|
|
14
18
|
this.value = !this.value;
|
|
15
19
|
}
|
|
16
|
-
// Override for fast equality.
|
|
17
|
-
isEqual(a, b) {
|
|
18
|
-
return a === b;
|
|
19
|
-
}
|
|
20
20
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BooleanStore } from "./BooleanStore.js";
|
|
2
|
+
import { Store } from "./Store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Store that tracks its busy status via a separate `this.busy` store.
|
|
5
|
+
* - "busy" means the store is awaiting a new value.
|
|
6
|
+
*/
|
|
7
|
+
export declare class BusyStore<T, TT = T> extends Store<T, TT> {
|
|
8
|
+
readonly busy: BooleanStore;
|
|
9
|
+
await(pending: PromiseLike<TT>): Promise<boolean>;
|
|
10
|
+
abort(): void;
|
|
11
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { awaitDispose } from "../util/dispose.js";
|
|
2
|
+
import { BooleanStore } from "./BooleanStore.js";
|
|
3
|
+
import { Store } from "./Store.js";
|
|
4
|
+
/**
|
|
5
|
+
* Store that tracks its busy status via a separate `this.busy` store.
|
|
6
|
+
* - "busy" means the store is awaiting a new value.
|
|
7
|
+
*/
|
|
8
|
+
export class BusyStore extends Store {
|
|
9
|
+
busy = new BooleanStore(false);
|
|
10
|
+
// Overload to set `this.busy` to `true` when we start awaiting a value.
|
|
11
|
+
// Gets set back to `false` when `abort()` is called (this also happens whenever a value or reason is set).
|
|
12
|
+
await(pending) {
|
|
13
|
+
this.busy.value = true;
|
|
14
|
+
return super.await(pending);
|
|
15
|
+
}
|
|
16
|
+
// Override to set busy to false on abort.
|
|
17
|
+
// This also happens whenever a value or reaosn is set.
|
|
18
|
+
abort() {
|
|
19
|
+
this.busy.value = false;
|
|
20
|
+
super.abort();
|
|
21
|
+
}
|
|
22
|
+
// Implement `AsyncDisposable`.
|
|
23
|
+
async [Symbol.asyncDispose]() {
|
|
24
|
+
await awaitDispose(() => this.abort(), this.busy, // Send `done: true` to any iterators of the busy store.
|
|
25
|
+
super[Symbol.asyncDispose]());
|
|
26
|
+
}
|
|
27
|
+
}
|
package/store/DataStore.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Data, DataKey } from "../util/data.js";
|
|
2
2
|
import type { AnyCaller } from "../util/function.js";
|
|
3
3
|
import type { Updates } from "../util/update.js";
|
|
4
|
-
import {
|
|
4
|
+
import { BusyStore } from "./BusyStore.js";
|
|
5
5
|
/** Store a data object. */
|
|
6
|
-
export declare class DataStore<T extends Data> extends
|
|
6
|
+
export declare class DataStore<T extends Data> extends BusyStore<T> {
|
|
7
7
|
/** Get the data of this store. */
|
|
8
8
|
get data(): T;
|
|
9
9
|
/** Set the data of this store. */
|
|
@@ -14,10 +14,9 @@ export declare class DataStore<T extends Data> extends Store<T, T> {
|
|
|
14
14
|
get<K extends DataKey<T>>(name: K): T[K];
|
|
15
15
|
/** Update a single named prop in this data. */
|
|
16
16
|
set<K extends DataKey<T>>(name: K, value: T[K]): void;
|
|
17
|
-
convert(value: T): T;
|
|
18
17
|
}
|
|
19
18
|
/** Store an optional data object. */
|
|
20
|
-
export declare class OptionalDataStore<T extends Data> extends
|
|
19
|
+
export declare class OptionalDataStore<T extends Data> extends BusyStore<T | undefined> {
|
|
21
20
|
/** Get current data value of this store (or throw `Promise` that resolves to the next required value). */
|
|
22
21
|
get data(): T;
|
|
23
22
|
/** Set the data of this store. */
|
|
@@ -34,5 +33,4 @@ export declare class OptionalDataStore<T extends Data> extends Store<T | undefin
|
|
|
34
33
|
set<K extends DataKey<T>>(name: K, value: T[K]): void;
|
|
35
34
|
/** Set the data to `undefined`. */
|
|
36
35
|
delete(): void;
|
|
37
|
-
convert(value: T): T;
|
|
38
36
|
}
|
package/store/DataStore.js
CHANGED
|
@@ -2,9 +2,9 @@ import { RequiredError } from "../error/RequiredError.js";
|
|
|
2
2
|
import { getGetter } from "../util/class.js";
|
|
3
3
|
import { withProp } from "../util/object.js";
|
|
4
4
|
import { updateData } from "../util/update.js";
|
|
5
|
-
import {
|
|
5
|
+
import { BusyStore } from "./BusyStore.js";
|
|
6
6
|
/** Store a data object. */
|
|
7
|
-
export class DataStore extends
|
|
7
|
+
export class DataStore extends BusyStore {
|
|
8
8
|
/** Get the data of this store. */
|
|
9
9
|
get data() {
|
|
10
10
|
return this.value;
|
|
@@ -25,13 +25,9 @@ export class DataStore extends Store {
|
|
|
25
25
|
set(name, value) {
|
|
26
26
|
this.value = withProp(this.data, name, value);
|
|
27
27
|
}
|
|
28
|
-
// Implement to passthrough.
|
|
29
|
-
convert(value) {
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
28
|
}
|
|
33
29
|
/** Store an optional data object. */
|
|
34
|
-
export class OptionalDataStore extends
|
|
30
|
+
export class OptionalDataStore extends BusyStore {
|
|
35
31
|
/** Get current data value of this store (or throw `Promise` that resolves to the next required value). */
|
|
36
32
|
get data() {
|
|
37
33
|
return this.require(getGetter(this, "data"));
|
|
@@ -67,8 +63,4 @@ export class OptionalDataStore extends Store {
|
|
|
67
63
|
delete() {
|
|
68
64
|
this.value = undefined;
|
|
69
65
|
}
|
|
70
|
-
// Implement to passthrough.
|
|
71
|
-
convert(value) {
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
66
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { DictionaryItem, ImmutableDictionary, PossibleDictionary } from "../util/dictionary.js";
|
|
2
2
|
import type { Updates } from "../util/update.js";
|
|
3
|
-
import {
|
|
3
|
+
import { BusyStore } from "./BusyStore.js";
|
|
4
4
|
/** Store a dictionary object. */
|
|
5
|
-
export declare class DictionaryStore<T> extends
|
|
5
|
+
export declare class DictionaryStore<T> extends BusyStore<ImmutableDictionary<T>, PossibleDictionary<T>> implements Iterable<DictionaryItem<T>> {
|
|
6
6
|
constructor(value?: PossibleDictionary<T>);
|
|
7
|
-
|
|
7
|
+
protected _convert(possible: PossibleDictionary<T>): ImmutableDictionary<T>;
|
|
8
8
|
/** Get the length of the current value of this store. */
|
|
9
9
|
get count(): number;
|
|
10
10
|
/** Set a named entry in this object with a different value. */
|
package/store/DictionaryStore.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { EMPTY_DICTIONARY, getDictionaryItems, omitDictionaryItems, requireDictionary } from "../util/dictionary.js";
|
|
2
2
|
import { omitProps, withProp } from "../util/object.js";
|
|
3
3
|
import { updateData } from "../util/update.js";
|
|
4
|
-
import {
|
|
4
|
+
import { BusyStore } from "./BusyStore.js";
|
|
5
5
|
/** Store a dictionary object. */
|
|
6
|
-
export class DictionaryStore extends
|
|
6
|
+
export class DictionaryStore extends BusyStore {
|
|
7
7
|
// Override to set default value to empty dictionary.
|
|
8
8
|
constructor(value = EMPTY_DICTIONARY) {
|
|
9
9
|
super(requireDictionary(value));
|
|
10
10
|
}
|
|
11
11
|
// Override to convert a possible dictionary to a dictionary on set.
|
|
12
|
-
|
|
12
|
+
_convert(possible) {
|
|
13
13
|
return requireDictionary(possible);
|
|
14
14
|
}
|
|
15
15
|
/** Get the length of the current value of this store. */
|
package/store/FetchStore.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NONE } from "../util/constants.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { type NONE } from "../util/constants.js";
|
|
2
|
+
import { BusyStore } from "./BusyStore.js";
|
|
3
|
+
import type { StoreInput } from "./Store.js";
|
|
4
4
|
/** Callback for a callback fetch store. */
|
|
5
5
|
export type FetchCallback<T> = (signal: AbortSignal) => T | PromiseLike<T>;
|
|
6
6
|
/**
|
|
@@ -8,57 +8,41 @@ export type FetchCallback<T> = (signal: AbortSignal) => T | PromiseLike<T>;
|
|
|
8
8
|
*
|
|
9
9
|
* @param value The initial value for the store, or `NONE` if it does not have one yet.
|
|
10
10
|
* @param callback An optional callback that, if set, will be called when the `refresh()` method is invoked to fetch the next value.
|
|
11
|
+
* - Override `this._fetch()` in subclasses to define custom fetching behaviour for a subclass.
|
|
11
12
|
*/
|
|
12
|
-
export declare class FetchStore<T> extends
|
|
13
|
-
/**
|
|
14
|
-
* Store that indicates the busy state of this store.
|
|
15
|
-
* - `true` while a refresh is in-flight, `false` otherwise.
|
|
16
|
-
* - Can be subscribed to for e.g. loading spinners.
|
|
17
|
-
*/
|
|
18
|
-
readonly busy: BooleanStore;
|
|
13
|
+
export declare class FetchStore<T, TT = T> extends BusyStore<T, TT> {
|
|
19
14
|
get loading(): boolean;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
constructor(value: T | typeof NONE, callback?: FetchCallback<
|
|
23
|
-
convert(value: T | typeof NONE): T | typeof NONE;
|
|
15
|
+
write(input: StoreInput<TT>): void;
|
|
16
|
+
read(): import("./Store.js").StoreInternal<T>;
|
|
17
|
+
constructor(value: T | typeof NONE, callback?: FetchCallback<TT>);
|
|
24
18
|
/**
|
|
25
19
|
* Fetch the result for this store now.
|
|
26
20
|
* - Triggered automatically when someone reads `value` or `loading`.
|
|
27
|
-
* - Concurrent calls while a fetch is in-flight return the same promise
|
|
21
|
+
* - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
|
|
28
22
|
* - Never throws — errors are stored as `reason`.
|
|
29
|
-
*
|
|
30
|
-
* @returns `true` if the fetch completed and the value was applied, `false` if aborted or superseded.
|
|
31
|
-
* @returns Synchronous `boolean` if the callback returned a synchronous value.
|
|
32
23
|
*/
|
|
33
24
|
refresh(): Promise<boolean> | boolean;
|
|
34
|
-
private
|
|
35
|
-
private _refresh;
|
|
25
|
+
private _pendingRefresh;
|
|
36
26
|
/**
|
|
37
27
|
* Current `AbortSignal` for this store's in-flight fetch.
|
|
38
28
|
* - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
|
|
29
|
+
* - Triggers `abort()` so any current awaits are cancelled.
|
|
39
30
|
*/
|
|
40
31
|
get signal(): AbortSignal;
|
|
41
|
-
private
|
|
32
|
+
private _aborter;
|
|
42
33
|
/**
|
|
43
34
|
* Call the fetch callback to get the next value.
|
|
44
35
|
* @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
|
|
45
36
|
*/
|
|
46
|
-
protected _fetch(signal: AbortSignal):
|
|
37
|
+
protected _fetch(signal: AbortSignal): TT | PromiseLike<TT>;
|
|
47
38
|
private _callback;
|
|
48
39
|
/**
|
|
49
40
|
* Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
|
|
50
|
-
* -
|
|
41
|
+
* - Triggers `abort()` so any current awaits are cancelled.
|
|
51
42
|
*/
|
|
52
43
|
invalidate(): void;
|
|
53
44
|
private _invalidation;
|
|
54
45
|
/** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
|
|
55
|
-
refreshStale(maxAge: number): Promise<
|
|
56
|
-
/**
|
|
57
|
-
* Abort any current in-flight fetch and pending async operation.
|
|
58
|
-
* - Aborts the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
|
|
59
|
-
* - Clears the in-flight promise and resets busy state.
|
|
60
|
-
* - Any pending `await()` result will be silently discarded.
|
|
61
|
-
*/
|
|
46
|
+
refreshStale(maxAge: number): Promise<boolean> | boolean;
|
|
62
47
|
abort(): void;
|
|
63
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
64
48
|
}
|
package/store/FetchStore.js
CHANGED
|
@@ -1,70 +1,56 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { isAsync } from "../util/async.js";
|
|
3
|
-
import { ABORT,
|
|
4
|
-
import {
|
|
5
|
-
import { BooleanStore } from "./BooleanStore.js";
|
|
6
|
-
import { Store } from "./Store.js";
|
|
3
|
+
import { ABORT, SKIP } from "../util/constants.js";
|
|
4
|
+
import { BusyStore } from "./BusyStore.js";
|
|
7
5
|
/**
|
|
8
6
|
* Store that fetches its values from a remote source.
|
|
9
7
|
*
|
|
10
8
|
* @param value The initial value for the store, or `NONE` if it does not have one yet.
|
|
11
9
|
* @param callback An optional callback that, if set, will be called when the `refresh()` method is invoked to fetch the next value.
|
|
10
|
+
* - Override `this._fetch()` in subclasses to define custom fetching behaviour for a subclass.
|
|
12
11
|
*/
|
|
13
|
-
export class FetchStore extends
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* - Can be subscribed to for e.g. loading spinners.
|
|
18
|
-
*/
|
|
19
|
-
busy;
|
|
20
|
-
// Override to possibly trigger a fetch when `this.loading` is read.
|
|
21
|
-
// Reading `loading` signals intent to use the value, so we start a fetch if needed.
|
|
12
|
+
export class FetchStore extends BusyStore {
|
|
13
|
+
// Override to trigger a refresh when `this.loading` is read.
|
|
14
|
+
// Calling `store.loading` signals intent to use the value.
|
|
15
|
+
// We optimistically refresh so the value is available the next time the user wants it.
|
|
22
16
|
get loading() {
|
|
23
17
|
const loading = super.loading;
|
|
24
|
-
if (
|
|
18
|
+
if (this._invalidation || loading)
|
|
25
19
|
void this.refresh();
|
|
26
20
|
return loading;
|
|
27
21
|
}
|
|
28
|
-
// Override to
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
// Override to reset the invalidation state.
|
|
23
|
+
// Setting a value means this store is no longer invalid.
|
|
24
|
+
write(input) {
|
|
25
|
+
if (input !== SKIP)
|
|
26
|
+
this._invalidation = 0;
|
|
27
|
+
super.write(input);
|
|
34
28
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
// Override to trigger refresh on `NONE`
|
|
30
|
+
// Calling `store.value` signals intent to use the value.
|
|
31
|
+
// We optimistically refresh so the value is available the next time the user wants it.
|
|
32
|
+
read() {
|
|
33
|
+
this.loading; // Ping loading to possibly trigger the intiial fetch.
|
|
34
|
+
return super.read();
|
|
39
35
|
}
|
|
36
|
+
// Override to create to save `callback`
|
|
40
37
|
constructor(value, callback) {
|
|
41
38
|
super(value);
|
|
42
|
-
this.busy = new BooleanStore(value === NONE);
|
|
43
39
|
this._callback = callback;
|
|
44
40
|
}
|
|
45
|
-
// Implement to allow `NONE`
|
|
46
|
-
convert(value) {
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
41
|
/**
|
|
50
42
|
* Fetch the result for this store now.
|
|
51
43
|
* - Triggered automatically when someone reads `value` or `loading`.
|
|
52
|
-
* - Concurrent calls while a fetch is in-flight return the same promise
|
|
44
|
+
* - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
|
|
53
45
|
* - Never throws — errors are stored as `reason`.
|
|
54
|
-
*
|
|
55
|
-
* @returns `true` if the fetch completed and the value was applied, `false` if aborted or superseded.
|
|
56
|
-
* @returns Synchronous `boolean` if the callback returned a synchronous value.
|
|
57
46
|
*/
|
|
58
47
|
refresh() {
|
|
59
|
-
if (this.
|
|
60
|
-
return this.
|
|
61
|
-
// Cancel any existing controller and create a fresh one for this fetch.
|
|
62
|
-
this._controller?.abort(ABORT);
|
|
63
|
-
this._controller = new AbortController();
|
|
48
|
+
if (this._pendingRefresh)
|
|
49
|
+
return this._pendingRefresh;
|
|
64
50
|
try {
|
|
65
|
-
const value = this._fetch(this.
|
|
51
|
+
const value = this._fetch(this.signal); // Retrieving a new signal calls `abort()` which cancels the previous one.
|
|
66
52
|
if (isAsync(value))
|
|
67
|
-
return (this.
|
|
53
|
+
return (this._pendingRefresh = this.await(value));
|
|
68
54
|
this.value = value;
|
|
69
55
|
return true;
|
|
70
56
|
}
|
|
@@ -73,28 +59,18 @@ export class FetchStore extends Store {
|
|
|
73
59
|
return false;
|
|
74
60
|
}
|
|
75
61
|
}
|
|
76
|
-
|
|
77
|
-
async _refresh(asyncValue) {
|
|
78
|
-
this.busy.value = true;
|
|
79
|
-
try {
|
|
80
|
-
const refreshed = await this.await(asyncValue);
|
|
81
|
-
if (refreshed)
|
|
82
|
-
this._invalidation = 0;
|
|
83
|
-
return refreshed;
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
this.busy.value = false;
|
|
87
|
-
this._inflight = undefined;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
62
|
+
_pendingRefresh = undefined;
|
|
90
63
|
/**
|
|
91
64
|
* Current `AbortSignal` for this store's in-flight fetch.
|
|
92
65
|
* - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
|
|
66
|
+
* - Triggers `abort()` so any current awaits are cancelled.
|
|
93
67
|
*/
|
|
94
68
|
get signal() {
|
|
95
|
-
|
|
69
|
+
this.abort();
|
|
70
|
+
this._aborter = new AbortController();
|
|
71
|
+
return this._aborter.signal;
|
|
96
72
|
}
|
|
97
|
-
|
|
73
|
+
_aborter;
|
|
98
74
|
/**
|
|
99
75
|
* Call the fetch callback to get the next value.
|
|
100
76
|
* @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
|
|
@@ -107,7 +83,7 @@ export class FetchStore extends Store {
|
|
|
107
83
|
_callback;
|
|
108
84
|
/**
|
|
109
85
|
* Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
|
|
110
|
-
* -
|
|
86
|
+
* - Triggers `abort()` so any current awaits are cancelled.
|
|
111
87
|
*/
|
|
112
88
|
invalidate() {
|
|
113
89
|
this.abort();
|
|
@@ -115,26 +91,18 @@ export class FetchStore extends Store {
|
|
|
115
91
|
}
|
|
116
92
|
_invalidation = 0;
|
|
117
93
|
/** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
|
|
118
|
-
|
|
94
|
+
refreshStale(maxAge) {
|
|
119
95
|
if (this._invalidation || this.age > maxAge)
|
|
120
|
-
|
|
96
|
+
return this.refresh();
|
|
97
|
+
return true;
|
|
121
98
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
* - Clears the in-flight promise and resets busy state.
|
|
126
|
-
* - Any pending `await()` result will be silently discarded.
|
|
127
|
-
*/
|
|
99
|
+
// Override to abort any current in-flight fetch and pending async operation.
|
|
100
|
+
// - Sends `ABORT` to the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
|
|
101
|
+
// - Any pending `await()` result will be silently discarded.
|
|
128
102
|
abort() {
|
|
129
|
-
this.
|
|
130
|
-
this.
|
|
131
|
-
this.
|
|
132
|
-
this.busy.value = false;
|
|
103
|
+
this._aborter?.abort(ABORT);
|
|
104
|
+
this._aborter = undefined;
|
|
105
|
+
this._pendingRefresh = undefined;
|
|
133
106
|
super.abort(); // clears _pendingValue
|
|
134
107
|
}
|
|
135
|
-
// Implement `AsyncDisposable`.
|
|
136
|
-
async [Symbol.asyncDispose]() {
|
|
137
|
-
await awaitDispose(() => this.abort(), this.busy, // Send `done: true` to any iterators of the busy store.
|
|
138
|
-
super[Symbol.asyncDispose]());
|
|
139
|
-
}
|
|
140
108
|
}
|
package/store/PathStore.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { AnyCaller } from "../util/function.js";
|
|
2
2
|
import type { AbsolutePath, PossiblePath } from "../util/path.js";
|
|
3
|
-
import {
|
|
4
|
-
/**
|
|
5
|
-
|
|
3
|
+
import { BusyStore } from "./BusyStore.js";
|
|
4
|
+
/**
|
|
5
|
+
* Store an absolute path, e.g. `/a/b/c`
|
|
6
|
+
*
|
|
7
|
+
* @param path: The initial value for the store.
|
|
8
|
+
* @param base: The base path to resolve relative paths against.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PathStore extends BusyStore<AbsolutePath, PossiblePath> {
|
|
6
11
|
readonly base: AbsolutePath;
|
|
7
12
|
constructor(path?: string, base?: AbsolutePath);
|
|
8
|
-
|
|
13
|
+
protected _convert(possible: PossiblePath, caller: AnyCaller): AbsolutePath;
|
|
14
|
+
protected _equal(a: AbsolutePath, b: AbsolutePath): boolean;
|
|
9
15
|
/** Based on the current store path, is a path active? */
|
|
10
16
|
isActive(path: AbsolutePath): boolean;
|
|
11
17
|
/** Based on the current store path, is a path proud (i.e. a child of the current store path)? */
|
package/store/PathStore.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { isPathActive, isPathProud, requirePath } from "../util/path.js";
|
|
2
|
-
import {
|
|
3
|
-
/**
|
|
4
|
-
|
|
2
|
+
import { BusyStore } from "./BusyStore.js";
|
|
3
|
+
/**
|
|
4
|
+
* Store an absolute path, e.g. `/a/b/c`
|
|
5
|
+
*
|
|
6
|
+
* @param path: The initial value for the store.
|
|
7
|
+
* @param base: The base path to resolve relative paths against.
|
|
8
|
+
*/
|
|
9
|
+
export class PathStore extends BusyStore {
|
|
5
10
|
base;
|
|
6
11
|
// Override to set default path to `.` and base to `/`
|
|
7
12
|
constructor(path = ".", base = "/") {
|
|
8
13
|
super(requirePath(path, base, PathStore));
|
|
9
14
|
this.base = base;
|
|
10
15
|
}
|
|
11
|
-
//
|
|
12
|
-
|
|
16
|
+
// Override to convert a possible path to an absolute path (relative to `this.base`).
|
|
17
|
+
_convert(possible, caller) {
|
|
13
18
|
return requirePath(possible, this.base, caller);
|
|
14
19
|
}
|
|
20
|
+
// Override for fast equality.
|
|
21
|
+
_equal(a, b) {
|
|
22
|
+
return a === b;
|
|
23
|
+
}
|
|
15
24
|
/** Based on the current store path, is a path active? */
|
|
16
25
|
isActive(path) {
|
|
17
26
|
return isPathActive(this.value, path);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NONE } from "../util/constants.js";
|
|
2
2
|
import { FetchStore } from "./FetchStore.js";
|
|
3
|
-
import {
|
|
4
|
-
export type PayloadFetchCallback<P, R> = (payload: P) => R | PromiseLike<R>;
|
|
3
|
+
import { Store } from "./Store.js";
|
|
4
|
+
export type PayloadFetchCallback<P, R> = (payload: P, signal: AbortSignal) => R | PromiseLike<R>;
|
|
5
5
|
/**
|
|
6
6
|
* Store that fetches its values from a remote source by sending a payload to them.
|
|
7
7
|
*
|
|
@@ -14,7 +14,7 @@ export declare class PayloadFetchStore<P, R> extends FetchStore<R> {
|
|
|
14
14
|
* Store keeping the current payload to send to the fetch on send.
|
|
15
15
|
* - New payloads can be set using `this.payload.value`
|
|
16
16
|
*/
|
|
17
|
-
readonly payload:
|
|
17
|
+
readonly payload: Store<P>;
|
|
18
18
|
constructor(payload: P, value: R | typeof NONE, callback?: PayloadFetchCallback<P, R>);
|
|
19
19
|
[Symbol.asyncDispose](): Promise<void>;
|
|
20
20
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { awaitDispose } from "../util/dispose.js";
|
|
2
2
|
import { FetchStore } from "./FetchStore.js";
|
|
3
|
-
import {
|
|
3
|
+
import { Store } from "./Store.js";
|
|
4
4
|
/**
|
|
5
5
|
* Store that fetches its values from a remote source by sending a payload to them.
|
|
6
6
|
*
|
|
@@ -16,8 +16,8 @@ export class PayloadFetchStore extends FetchStore {
|
|
|
16
16
|
payload;
|
|
17
17
|
// Override to save initial payload and callback.
|
|
18
18
|
constructor(payload, value, callback) {
|
|
19
|
-
const payloadStore = new
|
|
20
|
-
super(value, callback && (
|
|
19
|
+
const payloadStore = new Store(payload);
|
|
20
|
+
super(value, callback && (signal => callback(payloadStore.value, signal)));
|
|
21
21
|
this.payload = payloadStore;
|
|
22
22
|
void _iterate(this);
|
|
23
23
|
}
|
package/store/Store.d.ts
CHANGED
|
@@ -4,10 +4,14 @@ import type { AnyCaller, Arguments } from "../util/function.js";
|
|
|
4
4
|
import { type PossibleStarter } from "../util/start.js";
|
|
5
5
|
/** Any `Store` instance. */
|
|
6
6
|
export type AnyStore = Store<any, any>;
|
|
7
|
+
/** Values that a store natively knows how to process as inputs. */
|
|
8
|
+
export type StoreInput<I> = I | typeof SKIP | typeof NONE;
|
|
9
|
+
/** Internal storage value for a store. */
|
|
10
|
+
export type StoreInternal<O> = O | typeof NONE;
|
|
7
11
|
/** Callback that sets a store's value (possibly asynchronously). */
|
|
8
|
-
export type StoreCallback<I, A extends Arguments = []> = (...args: A) => I | PromiseLike<I
|
|
12
|
+
export type StoreCallback<I, A extends Arguments = []> = (...args: A) => StoreInput<I> | PromiseLike<StoreInput<I>>;
|
|
9
13
|
/** Reducer that receives a store's current value and sets the stores next value (possibly asynchronously). */
|
|
10
|
-
export type StoreReducer<I, O, A extends Arguments = []> = (value: O, ...args: A) => I | PromiseLike<I
|
|
14
|
+
export type StoreReducer<I, O, A extends Arguments = []> = (value: O, ...args: A) => StoreInput<I> | PromiseLike<StoreInput<I>>;
|
|
11
15
|
/**
|
|
12
16
|
* Store that retains its most recent value and is async-iterable to allow values to be observed.
|
|
13
17
|
* - Current value can be read at `store.value` and `store.data`
|
|
@@ -18,43 +22,59 @@ export type StoreReducer<I, O, A extends Arguments = []> = (value: O, ...args: A
|
|
|
18
22
|
* - To set this store to be loading, use the `NONE` constant or a `Promise` value.
|
|
19
23
|
* - To set this store to an explicit value, use that value or another `Store` instance with a value.
|
|
20
24
|
*
|
|
21
|
-
* @param
|
|
22
|
-
* - Indicates what values `this.value`
|
|
25
|
+
* @param T The "main type" for this store.
|
|
26
|
+
* - Indicates what values `this.value` will return.
|
|
23
27
|
* - Methods that set values like `this.call()` and `this.await()` can also accept these values.
|
|
24
|
-
* @param
|
|
25
|
-
* - Indicates what
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
28
|
+
* @param TT The "input type" for this store.
|
|
29
|
+
* - Indicates what additional input types `this.value` can convert to `T`
|
|
30
|
+
* - Defaults to `T` (no conversion).
|
|
31
|
+
* - Override conversion by overriding `this._convert(v: TT): T`
|
|
32
|
+
* - Warning: With no override, default behaviour is to just assert TT is T (unsafe).
|
|
28
33
|
*/
|
|
29
|
-
export declare
|
|
34
|
+
export declare class Store<T, TT = T> implements AsyncIterable<T, void, void>, AsyncDisposable {
|
|
30
35
|
/** Deferred sequence this store uses to issue values as they change. */
|
|
31
|
-
readonly next: DeferredSequence<
|
|
36
|
+
readonly next: DeferredSequence<T, void, void>;
|
|
32
37
|
/**
|
|
33
38
|
* Store is considered to be "loading" if it has no value or error.
|
|
34
39
|
* - Calling `this.value` will throw `this.reason` if there's an error reason set, or a `Promise` if there's no value set.
|
|
35
40
|
* - Calling `this.loading` is a way to check if this store has a value without triggering those throws.
|
|
36
41
|
*/
|
|
37
42
|
get loading(): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Get the current value of this store.
|
|
40
|
-
*
|
|
41
|
-
* @throws {Promise} if this store currently has no value (resolves when this store receives its next value or error).
|
|
42
|
-
* @throws {unknown} if this store currently has an error.
|
|
43
|
-
*/
|
|
44
|
-
get value(): O;
|
|
45
43
|
/**
|
|
46
44
|
* Set the value of this store.
|
|
47
45
|
* - Sets any sync values.
|
|
48
46
|
* - Awaits any async values.
|
|
49
47
|
* - Setting value the `NONE` symbol indicates the store has no value so should be in a "loading" state.
|
|
50
|
-
* - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip
|
|
48
|
+
* - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip a write entirely).
|
|
51
49
|
* - Setting value to the same as the existing value
|
|
52
50
|
* - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
|
|
53
51
|
*/
|
|
54
|
-
set value(
|
|
52
|
+
set value(input: StoreInput<TT> | PromiseLike<StoreInput<TT>>);
|
|
53
|
+
/** Write a synchronous value to this store. */
|
|
54
|
+
write(input: StoreInput<TT>): void;
|
|
55
|
+
/**
|
|
56
|
+
* Convert input type to internal storage type.
|
|
57
|
+
* - Override in subclasses to change conversion behaviour.
|
|
58
|
+
* - Warning: With no override, default behaviour is to just assert TT is T (unsafe).
|
|
59
|
+
*/
|
|
60
|
+
protected _convert(input: TT, _caller?: AnyCaller): T;
|
|
61
|
+
/** Compare two values for this store and return whether they are equal. */
|
|
62
|
+
protected _equal(a: T, b: T): boolean;
|
|
63
|
+
/** Internal storage for current value. */
|
|
55
64
|
private _value;
|
|
56
|
-
/**
|
|
57
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Get the current value of this store.
|
|
67
|
+
*
|
|
68
|
+
* @throws {Promise} if this store currently is in a "loading" state (resolves when a value is set).
|
|
69
|
+
* @throws {unknown} if this store currently has an error.
|
|
70
|
+
*/
|
|
71
|
+
get value(): T;
|
|
72
|
+
/**
|
|
73
|
+
* Called to read values. Can be used to override get behaviour.
|
|
74
|
+
* - Override in subclasses to change getting behaviour.
|
|
75
|
+
* - Note: doesn't throw `reason` if there is one!
|
|
76
|
+
*/
|
|
77
|
+
read(): StoreInternal<T>;
|
|
58
78
|
/**
|
|
59
79
|
* Time (in milliseconds) this store was last updated with a new value.
|
|
60
80
|
* - Will be `undefined` if the value is still loading.
|
|
@@ -76,33 +96,30 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
|
|
|
76
96
|
* Set a starter for this store to allow a function to execute when this store has subscribers or not.
|
|
77
97
|
*
|
|
78
98
|
* @todo DH: Change this significantly. Not happy with how it's settable like this. It should be set in `constructor()`?
|
|
99
|
+
* - Also would love some internal hooks
|
|
79
100
|
*/
|
|
80
101
|
protected set starter(start: PossibleStarter<[this]>);
|
|
81
102
|
private _starter;
|
|
82
|
-
/** Store is initiated with
|
|
83
|
-
constructor(value:
|
|
103
|
+
/** Store is initiated with a value, or `NONE` to put it in a "loading" state. */
|
|
104
|
+
constructor(value: StoreInternal<T>);
|
|
84
105
|
/** Set the value of this store as values are pulled from a sequence. */
|
|
85
|
-
through(sequence: AsyncIterable<
|
|
106
|
+
through(sequence: AsyncIterable<TT>): AsyncIterable<TT>;
|
|
86
107
|
/**
|
|
87
|
-
* Call a callback
|
|
88
|
-
* - Errors are stored as `reason`; never throws.
|
|
89
|
-
*
|
|
90
|
-
* @returns `true` if the value was applied, `false` if an error occurred or the result was superseded.
|
|
108
|
+
* Call a callback and save the returned value to this store.
|
|
91
109
|
*/
|
|
92
|
-
call<A extends Arguments>(callback: StoreCallback<
|
|
110
|
+
call<A extends Arguments>(callback: StoreCallback<TT, A>, ...args: A): Promise<boolean> | boolean;
|
|
93
111
|
/**
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
* @throws {unknown} if this store currently has an error reason set.
|
|
112
|
+
* Send the current value to a callback and save the returned value to this store.
|
|
113
|
+
*/
|
|
114
|
+
reduce<A extends Arguments>(reducer: StoreReducer<TT, T, A>, ...args: A): Promise<boolean> | boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Run a callback and ignore any returned value.
|
|
117
|
+
*/
|
|
118
|
+
run<A extends Arguments>(callback: (...args: A) => void | PromiseLike<void>, ...args: A): Promise<boolean> | boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Send the current value to a callback and ignore any returned value.
|
|
104
121
|
*/
|
|
105
|
-
|
|
122
|
+
send<A extends Arguments>(callback: (value: T, ...args: A) => void | PromiseLike<void>, ...args: A): Promise<boolean> | boolean;
|
|
106
123
|
/**
|
|
107
124
|
* Await an async value and save it to this store.
|
|
108
125
|
* - Saves the resolved value.
|
|
@@ -111,19 +128,23 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
|
|
|
111
128
|
* - Silently discarded if `await()` is called again.
|
|
112
129
|
* - Silently discarded if `abort()` is called.
|
|
113
130
|
*
|
|
114
|
-
* @
|
|
131
|
+
* @param pending The pending value to await.
|
|
132
|
+
*
|
|
133
|
+
* @returns {true} If the callback returned a value and it was set.
|
|
134
|
+
* @returns {false} If the callback threw.
|
|
135
|
+
* @returns {Promise<true>} If the callback returned a promise and it resolved.
|
|
136
|
+
* @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
|
|
137
|
+
*
|
|
115
138
|
* @throws {never} Never throws — safe to call without handling the return value.
|
|
116
139
|
*/
|
|
117
|
-
await(pending: PromiseLike<
|
|
118
|
-
private
|
|
140
|
+
await(pending: PromiseLike<StoreInput<TT>>): Promise<boolean>;
|
|
141
|
+
private _pendingValue;
|
|
119
142
|
/**
|
|
120
143
|
* Abort any current pending `await()` call.
|
|
121
144
|
* - The pending call's result will be silently discarded and its error will not be stored.
|
|
122
145
|
*/
|
|
123
146
|
abort(): void;
|
|
124
|
-
[Symbol.asyncIterator](): AsyncIterator<
|
|
147
|
+
[Symbol.asyncIterator](): AsyncIterator<T, void, void>;
|
|
125
148
|
private _iterating;
|
|
126
|
-
/** Compare two values for this store and return whether they are equal. */
|
|
127
|
-
isEqual(a: O, b: O): boolean;
|
|
128
149
|
[Symbol.asyncDispose](): Promise<void>;
|
|
129
150
|
}
|
package/store/Store.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DeferredSequence } from "../sequence/DeferredSequence.js";
|
|
2
2
|
import { isAsync } from "../util/async.js";
|
|
3
|
-
import { getSetter } from "../util/class.js";
|
|
4
3
|
import { NONE, SKIP } from "../util/constants.js";
|
|
5
4
|
import { awaitDispose } from "../util/dispose.js";
|
|
6
5
|
import { isDeepEqual } from "../util/equal.js";
|
|
@@ -15,13 +14,14 @@ import { getStarter } from "../util/start.js";
|
|
|
15
14
|
* - To set this store to be loading, use the `NONE` constant or a `Promise` value.
|
|
16
15
|
* - To set this store to an explicit value, use that value or another `Store` instance with a value.
|
|
17
16
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* - Indicates what values `this.value`
|
|
17
|
+
* @param T The "main type" for this store.
|
|
18
|
+
* - Indicates what values `this.value` will return.
|
|
20
19
|
* - Methods that set values like `this.call()` and `this.await()` can also accept these values.
|
|
21
|
-
* @param
|
|
22
|
-
* - Indicates what
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
20
|
+
* @param TT The "input type" for this store.
|
|
21
|
+
* - Indicates what additional input types `this.value` can convert to `T`
|
|
22
|
+
* - Defaults to `T` (no conversion).
|
|
23
|
+
* - Override conversion by overriding `this._convert(v: TT): T`
|
|
24
|
+
* - Warning: With no override, default behaviour is to just assert TT is T (unsafe).
|
|
25
25
|
*/
|
|
26
26
|
export class Store {
|
|
27
27
|
/** Deferred sequence this store uses to issue values as they change. */
|
|
@@ -32,58 +32,82 @@ export class Store {
|
|
|
32
32
|
* - Calling `this.loading` is a way to check if this store has a value without triggering those throws.
|
|
33
33
|
*/
|
|
34
34
|
get loading() {
|
|
35
|
-
return this._value === NONE && this.
|
|
35
|
+
return this._value === NONE && this.reason === undefined;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Set the value of this store.
|
|
39
|
+
* - Sets any sync values.
|
|
40
|
+
* - Awaits any async values.
|
|
41
|
+
* - Setting value the `NONE` symbol indicates the store has no value so should be in a "loading" state.
|
|
42
|
+
* - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip a write entirely).
|
|
43
|
+
* - Setting value to the same as the existing value
|
|
44
|
+
* - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
|
|
45
|
+
*/
|
|
46
|
+
set value(input) {
|
|
47
|
+
if (isAsync(input))
|
|
48
|
+
void this.await(input);
|
|
49
|
+
else
|
|
50
|
+
this.write(input);
|
|
51
|
+
}
|
|
52
|
+
/** Write a synchronous value to this store. */
|
|
53
|
+
write(input) {
|
|
54
|
+
this.abort();
|
|
55
|
+
this._reason = undefined;
|
|
56
|
+
if (input === SKIP) {
|
|
57
|
+
// Skip this value entirely.
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
else if (input === NONE) {
|
|
61
|
+
// Put the store into a loading state.
|
|
62
|
+
this._time = undefined;
|
|
63
|
+
this._value = NONE;
|
|
64
|
+
this.next.cancel();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const storage = this._convert(input);
|
|
68
|
+
if (this._value === NONE || !this._equal(storage, this._value)) {
|
|
69
|
+
// Set this changed value.
|
|
70
|
+
this._time = Date.now();
|
|
71
|
+
this._value = storage;
|
|
72
|
+
this.next.resolve(storage);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert input type to internal storage type.
|
|
77
|
+
* - Override in subclasses to change conversion behaviour.
|
|
78
|
+
* - Warning: With no override, default behaviour is to just assert TT is T (unsafe).
|
|
79
|
+
*/
|
|
80
|
+
_convert(input, _caller) {
|
|
81
|
+
return input;
|
|
82
|
+
}
|
|
83
|
+
/** Compare two values for this store and return whether they are equal. */
|
|
84
|
+
_equal(a, b) {
|
|
85
|
+
return isDeepEqual(a, b);
|
|
86
|
+
}
|
|
87
|
+
/** Internal storage for current value. */
|
|
88
|
+
_value;
|
|
37
89
|
/**
|
|
38
90
|
* Get the current value of this store.
|
|
39
91
|
*
|
|
40
|
-
* @throws {Promise} if this store currently
|
|
92
|
+
* @throws {Promise} if this store currently is in a "loading" state (resolves when a value is set).
|
|
41
93
|
* @throws {unknown} if this store currently has an error.
|
|
42
94
|
*/
|
|
43
95
|
get value() {
|
|
44
96
|
if (this._reason !== undefined)
|
|
45
97
|
throw this._reason;
|
|
46
|
-
|
|
98
|
+
const value = this.read();
|
|
99
|
+
if (value === NONE)
|
|
47
100
|
throw this.next;
|
|
48
|
-
return
|
|
101
|
+
return value;
|
|
49
102
|
}
|
|
50
103
|
/**
|
|
51
|
-
*
|
|
52
|
-
* -
|
|
53
|
-
* -
|
|
54
|
-
* - Setting value the `NONE` symbol indicates the store has no value so should be in a "loading" state.
|
|
55
|
-
* - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip setting entirely).
|
|
56
|
-
* - Setting value to the same as the existing value
|
|
57
|
-
* - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
|
|
104
|
+
* Called to read values. Can be used to override get behaviour.
|
|
105
|
+
* - Override in subclasses to change getting behaviour.
|
|
106
|
+
* - Note: doesn't throw `reason` if there is one!
|
|
58
107
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
void this.await(value);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
this.abort();
|
|
65
|
-
this._reason = undefined;
|
|
66
|
-
const v = value === NONE || value === SKIP
|
|
67
|
-
? value || typeof SKIP
|
|
68
|
-
: this.convert(value, getSetter(this, "value"));
|
|
69
|
-
if (v === NONE) {
|
|
70
|
-
// Put the store back into a loading state.
|
|
71
|
-
this._time = undefined;
|
|
72
|
-
this._value = v;
|
|
73
|
-
this.next.cancel();
|
|
74
|
-
}
|
|
75
|
-
else if (v === SKIP) {
|
|
76
|
-
// Silently skip this set value call.
|
|
77
|
-
}
|
|
78
|
-
else if (this._value === NONE || !this.isEqual(v, this._value)) {
|
|
79
|
-
// Set this value.
|
|
80
|
-
this._time = Date.now();
|
|
81
|
-
this._value = v;
|
|
82
|
-
this.next.resolve(v);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
108
|
+
read() {
|
|
109
|
+
return this._value;
|
|
85
110
|
}
|
|
86
|
-
_value;
|
|
87
111
|
/**
|
|
88
112
|
* Time (in milliseconds) this store was last updated with a new value.
|
|
89
113
|
* - Will be `undefined` if the value is still loading.
|
|
@@ -117,6 +141,7 @@ export class Store {
|
|
|
117
141
|
* Set a starter for this store to allow a function to execute when this store has subscribers or not.
|
|
118
142
|
*
|
|
119
143
|
* @todo DH: Change this significantly. Not happy with how it's settable like this. It should be set in `constructor()`?
|
|
144
|
+
* - Also would love some internal hooks
|
|
120
145
|
*/
|
|
121
146
|
set starter(start) {
|
|
122
147
|
if (this._starter)
|
|
@@ -126,7 +151,7 @@ export class Store {
|
|
|
126
151
|
this._starter.start(this); // Start the new starter if we're already iterating.
|
|
127
152
|
}
|
|
128
153
|
_starter;
|
|
129
|
-
/** Store is initiated with
|
|
154
|
+
/** Store is initiated with a value, or `NONE` to put it in a "loading" state. */
|
|
130
155
|
constructor(value) {
|
|
131
156
|
this._value = value;
|
|
132
157
|
this._time = value === NONE ? -Infinity : Date.now();
|
|
@@ -134,22 +159,19 @@ export class Store {
|
|
|
134
159
|
/** Set the value of this store as values are pulled from a sequence. */
|
|
135
160
|
async *through(sequence) {
|
|
136
161
|
for await (const value of sequence) {
|
|
137
|
-
this.value
|
|
162
|
+
this.write(value);
|
|
138
163
|
yield value;
|
|
139
164
|
}
|
|
140
165
|
}
|
|
141
166
|
/**
|
|
142
|
-
* Call a callback
|
|
143
|
-
* - Errors are stored as `reason`; never throws.
|
|
144
|
-
*
|
|
145
|
-
* @returns `true` if the value was applied, `false` if an error occurred or the result was superseded.
|
|
167
|
+
* Call a callback and save the returned value to this store.
|
|
146
168
|
*/
|
|
147
169
|
call(callback, ...args) {
|
|
148
170
|
try {
|
|
149
171
|
const value = callback(...args);
|
|
150
172
|
if (isAsync(value))
|
|
151
173
|
return this.await(value);
|
|
152
|
-
this.value
|
|
174
|
+
this.write(value);
|
|
153
175
|
return true;
|
|
154
176
|
}
|
|
155
177
|
catch (thrown) {
|
|
@@ -158,20 +180,23 @@ export class Store {
|
|
|
158
180
|
}
|
|
159
181
|
}
|
|
160
182
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @param reducer The callback function to call that should return a value to set on this store.
|
|
164
|
-
* @param value The current value of this store.
|
|
165
|
-
* @param args Any additional input values for the reducer.
|
|
166
|
-
* @returns New value for this store (possibly async).
|
|
167
|
-
* @oaram args Any arguments to pass to the callback.
|
|
168
|
-
*
|
|
169
|
-
* @throws {Promise} if this store currently has no value (resolves when this store receives its next value or error).
|
|
170
|
-
* @throws {unknown} if this store currently has an error reason set.
|
|
183
|
+
* Send the current value to a callback and save the returned value to this store.
|
|
171
184
|
*/
|
|
172
185
|
reduce(reducer, ...args) {
|
|
173
186
|
return this.call(reducer, this.value, ...args);
|
|
174
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Run a callback and ignore any returned value.
|
|
190
|
+
*/
|
|
191
|
+
run(callback, ...args) {
|
|
192
|
+
return this.call(_callSkipped, callback, ...args);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Send the current value to a callback and ignore any returned value.
|
|
196
|
+
*/
|
|
197
|
+
send(callback, ...args) {
|
|
198
|
+
return this.call(_callSkipped, callback, this.value, ...args);
|
|
199
|
+
}
|
|
175
200
|
/**
|
|
176
201
|
* Await an async value and save it to this store.
|
|
177
202
|
* - Saves the resolved value.
|
|
@@ -180,36 +205,42 @@ export class Store {
|
|
|
180
205
|
* - Silently discarded if `await()` is called again.
|
|
181
206
|
* - Silently discarded if `abort()` is called.
|
|
182
207
|
*
|
|
183
|
-
* @
|
|
208
|
+
* @param pending The pending value to await.
|
|
209
|
+
*
|
|
210
|
+
* @returns {true} If the callback returned a value and it was set.
|
|
211
|
+
* @returns {false} If the callback threw.
|
|
212
|
+
* @returns {Promise<true>} If the callback returned a promise and it resolved.
|
|
213
|
+
* @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
|
|
214
|
+
*
|
|
184
215
|
* @throws {never} Never throws — safe to call without handling the return value.
|
|
185
216
|
*/
|
|
186
217
|
async await(pending) {
|
|
187
218
|
// Keep track of the value that is being awaited.
|
|
188
219
|
// If `_pendingValue` changes while waiting for `asyncValue` to resolve, another call to `await()` has `set value`, `set reason`, or `abort()` has invalidated this one.
|
|
189
220
|
// If that happens we silently discard the resolved value/reason of this await call.
|
|
190
|
-
this.
|
|
221
|
+
this._pendingValue = pending;
|
|
191
222
|
try {
|
|
192
223
|
const value = await pending;
|
|
193
|
-
if (this.
|
|
194
|
-
this.value
|
|
224
|
+
if (this._pendingValue === pending) {
|
|
225
|
+
this.write(value);
|
|
195
226
|
return true;
|
|
196
227
|
}
|
|
197
228
|
return false;
|
|
198
229
|
}
|
|
199
230
|
catch (reason) {
|
|
200
|
-
if (this.
|
|
231
|
+
if (this._pendingValue === pending) {
|
|
201
232
|
this.reason = reason;
|
|
202
233
|
}
|
|
203
234
|
return false;
|
|
204
235
|
}
|
|
205
236
|
}
|
|
206
|
-
|
|
237
|
+
_pendingValue = undefined;
|
|
207
238
|
/**
|
|
208
239
|
* Abort any current pending `await()` call.
|
|
209
240
|
* - The pending call's result will be silently discarded and its error will not be stored.
|
|
210
241
|
*/
|
|
211
242
|
abort() {
|
|
212
|
-
this.
|
|
243
|
+
this._pendingValue = undefined;
|
|
213
244
|
}
|
|
214
245
|
// Implement `AsyncIterator`
|
|
215
246
|
// Issues the current value of this store first, then any subsequent values that are issued.
|
|
@@ -218,10 +249,11 @@ export class Store {
|
|
|
218
249
|
this._starter?.start(this);
|
|
219
250
|
this._iterating++;
|
|
220
251
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
252
|
+
const reason = this.reason;
|
|
253
|
+
if (reason !== undefined)
|
|
254
|
+
throw reason;
|
|
255
|
+
if (!this.loading)
|
|
256
|
+
yield this.value;
|
|
225
257
|
yield* this.next;
|
|
226
258
|
}
|
|
227
259
|
finally {
|
|
@@ -231,13 +263,21 @@ export class Store {
|
|
|
231
263
|
}
|
|
232
264
|
}
|
|
233
265
|
_iterating = 0;
|
|
234
|
-
/** Compare two values for this store and return whether they are equal. */
|
|
235
|
-
isEqual(a, b) {
|
|
236
|
-
return isDeepEqual(a, b);
|
|
237
|
-
}
|
|
238
266
|
// Implement `AsyncDisposable`
|
|
239
267
|
async [Symbol.asyncDispose]() {
|
|
240
268
|
await awaitDispose(this._starter, // Stop the starter.
|
|
241
269
|
this.next);
|
|
242
270
|
}
|
|
243
271
|
}
|
|
272
|
+
/** Call a callback but always return or resolve to `SKIP` */
|
|
273
|
+
function _callSkipped(callback, ...args) {
|
|
274
|
+
const value = callback(...args);
|
|
275
|
+
if (isAsync(value))
|
|
276
|
+
return _awaitSkipped(value);
|
|
277
|
+
return SKIP;
|
|
278
|
+
}
|
|
279
|
+
/** Await a promise but resolve to `SKIP` */
|
|
280
|
+
async function _awaitSkipped(pending) {
|
|
281
|
+
await pending;
|
|
282
|
+
return SKIP;
|
|
283
|
+
}
|
package/store/URLStore.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { AnyCaller } from "../util/function.js";
|
|
2
2
|
import { type PossibleURIParams, type URIParams, type URIScheme } from "../util/uri.js";
|
|
3
3
|
import { type PossibleURL, type URL, type URLString } from "../util/url.js";
|
|
4
|
-
import {
|
|
4
|
+
import { BusyStore } from "./BusyStore.js";
|
|
5
5
|
/** Store a URL, e.g. `https://top.com/a/b/c` */
|
|
6
|
-
export declare class URLStore extends
|
|
6
|
+
export declare class URLStore extends BusyStore<URL, PossibleURL> {
|
|
7
7
|
readonly base: URL | undefined;
|
|
8
8
|
constructor(url: PossibleURL, base?: PossibleURL);
|
|
9
|
-
|
|
9
|
+
protected _convert(value: PossibleURL, caller: AnyCaller): URL;
|
|
10
|
+
protected _equal(a: URL, b: URL): boolean;
|
|
10
11
|
get href(): URLString;
|
|
11
12
|
set href(href: URLString);
|
|
12
13
|
get origin(): URLString;
|
|
@@ -45,6 +46,5 @@ export declare class URLStore extends Store<PossibleURL, URL> {
|
|
|
45
46
|
omitParams(...keys: string[]): URL;
|
|
46
47
|
/** Return the current URL with an additional param. */
|
|
47
48
|
omitParam(key: string): URL;
|
|
48
|
-
isEqual(a: URL, b: URL): boolean;
|
|
49
49
|
toString(): string;
|
|
50
50
|
}
|
package/store/URLStore.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getGetter, getSetter } from "../util/class.js";
|
|
2
2
|
import { clearURIParams, getURIParam, getURIParams, omitURIParams, requireURIParam, withURIParam, withURIParams, } from "../util/uri.js";
|
|
3
3
|
import { getURL, requireURL } from "../util/url.js";
|
|
4
|
-
import {
|
|
4
|
+
import { BusyStore } from "./BusyStore.js";
|
|
5
5
|
/** Store a URL, e.g. `https://top.com/a/b/c` */
|
|
6
|
-
export class URLStore extends
|
|
6
|
+
export class URLStore extends BusyStore {
|
|
7
7
|
base;
|
|
8
8
|
// Override to convert possible URL to URL.
|
|
9
9
|
constructor(url, base) {
|
|
@@ -12,9 +12,13 @@ export class URLStore extends Store {
|
|
|
12
12
|
this.base = baseURL;
|
|
13
13
|
}
|
|
14
14
|
// Override to convert possible URL to URL (relative to `this.base`).
|
|
15
|
-
|
|
15
|
+
_convert(value, caller) {
|
|
16
16
|
return requireURL(value, this.base, caller);
|
|
17
17
|
}
|
|
18
|
+
// Override for fast equality.
|
|
19
|
+
_equal(a, b) {
|
|
20
|
+
return a.href === b.href;
|
|
21
|
+
}
|
|
18
22
|
get href() {
|
|
19
23
|
return this.value.href;
|
|
20
24
|
}
|
|
@@ -101,10 +105,6 @@ export class URLStore extends Store {
|
|
|
101
105
|
omitParam(key) {
|
|
102
106
|
return omitURIParams(this.value, key);
|
|
103
107
|
}
|
|
104
|
-
// Override `equal()` to just compare the hrefs.
|
|
105
|
-
isEqual(a, b) {
|
|
106
|
-
return a.href === b.href;
|
|
107
|
-
}
|
|
108
108
|
toString() {
|
|
109
109
|
return this.href;
|
|
110
110
|
}
|
package/store/index.d.ts
CHANGED
|
@@ -3,9 +3,7 @@ export * from "./BooleanStore.js";
|
|
|
3
3
|
export * from "./DataStore.js";
|
|
4
4
|
export * from "./DictionaryStore.js";
|
|
5
5
|
export * from "./FetchStore.js";
|
|
6
|
-
export * from "./LoadingStore.js";
|
|
7
6
|
export * from "./PathStore.js";
|
|
8
7
|
export * from "./PayloadFetchStore.js";
|
|
9
8
|
export * from "./Store.js";
|
|
10
9
|
export * from "./URLStore.js";
|
|
11
|
-
export * from "./ValueStore.js";
|
package/store/index.js
CHANGED
|
@@ -3,9 +3,7 @@ export * from "./BooleanStore.js";
|
|
|
3
3
|
export * from "./DataStore.js";
|
|
4
4
|
export * from "./DictionaryStore.js";
|
|
5
5
|
export * from "./FetchStore.js";
|
|
6
|
-
export * from "./LoadingStore.js";
|
|
7
6
|
export * from "./PathStore.js";
|
|
8
7
|
export * from "./PayloadFetchStore.js";
|
|
9
8
|
export * from "./Store.js";
|
|
10
9
|
export * from "./URLStore.js";
|
|
11
|
-
export * from "./ValueStore.js";
|
package/store/LoadingStore.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { NONE } from "../util/constants.js";
|
|
2
|
-
import { Store } from "./Store.js";
|
|
3
|
-
/**
|
|
4
|
-
* Store that is explicitly allows a "loading" state when it has no value (which is also the default value).
|
|
5
|
-
* - Note: All stores have know how to interpret `NONE` — this subclass simply _allows_ that through its types.
|
|
6
|
-
*/
|
|
7
|
-
export declare class LoadingStore<T> extends Store<T, T | typeof NONE> {
|
|
8
|
-
constructor(value?: T | typeof NONE);
|
|
9
|
-
convert(value: T | typeof NONE): T | typeof NONE;
|
|
10
|
-
}
|
package/store/LoadingStore.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { NONE } from "../util/constants.js";
|
|
2
|
-
import { Store } from "./Store.js";
|
|
3
|
-
/**
|
|
4
|
-
* Store that is explicitly allows a "loading" state when it has no value (which is also the default value).
|
|
5
|
-
* - Note: All stores have know how to interpret `NONE` — this subclass simply _allows_ that through its types.
|
|
6
|
-
*/
|
|
7
|
-
export class LoadingStore extends Store {
|
|
8
|
-
// Override to default to `NONE`
|
|
9
|
-
constructor(value = NONE) {
|
|
10
|
-
super(value);
|
|
11
|
-
}
|
|
12
|
-
// Implement to allow `NONE`
|
|
13
|
-
convert(value) {
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
}
|
package/store/ValueStore.d.ts
DELETED