shelving 1.188.5 → 1.189.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/api/endpoint/util.js +2 -2
- package/api/store/EndpointStore.d.ts +1 -1
- package/api/store/EndpointStore.js +2 -2
- package/db/store/ItemStore.d.ts +1 -1
- package/db/store/ItemStore.js +1 -1
- package/db/store/QueryStore.d.ts +1 -1
- package/db/store/QueryStore.js +1 -1
- package/package.json +5 -5
- package/store/ArrayStore.d.ts +4 -2
- package/store/ArrayStore.js +5 -1
- package/store/BooleanStore.d.ts +2 -3
- package/store/BooleanStore.js +3 -2
- package/store/DataStore.d.ts +4 -2
- package/store/DataStore.js +8 -0
- package/store/DictionaryStore.d.ts +4 -3
- package/store/DictionaryStore.js +8 -3
- package/store/FetchStore.d.ts +35 -25
- package/store/FetchStore.js +61 -58
- package/store/LoadingStore.d.ts +10 -0
- package/store/LoadingStore.js +16 -0
- package/store/PathStore.d.ts +6 -5
- package/store/PathStore.js +8 -8
- package/store/PayloadFetchStore.d.ts +2 -2
- package/store/PayloadFetchStore.js +4 -5
- package/store/Store.d.ts +72 -25
- package/store/Store.js +105 -32
- package/store/URLStore.d.ts +3 -3
- package/store/URLStore.js +6 -7
- package/store/ValueStore.d.ts +5 -0
- package/store/ValueStore.js +8 -0
- package/store/index.d.ts +2 -0
- package/store/index.js +2 -0
- package/util/constants.d.ts +4 -2
- package/util/constants.js +4 -2
- package/util/data.d.ts +0 -3
- package/util/data.js +0 -4
- package/util/dictionary.d.ts +1 -1
- package/util/dictionary.js +1 -1
- package/util/path.d.ts +6 -4
- package/util/path.js +1 -1
- package/util/sequence.d.ts +2 -2
- package/util/sequence.js +5 -5
package/api/endpoint/util.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MethodNotAllowedError, NotFoundError } from "../../error/RequestError.js";
|
|
2
2
|
import { ValueError } from "../../error/ValueError.js";
|
|
3
|
-
import {
|
|
3
|
+
import { requireDictionary } from "../../util/dictionary.js";
|
|
4
4
|
import { getResponse, isRequestMethod, parseRequestBody } from "../../util/http.js";
|
|
5
5
|
import { isPlainObject } from "../../util/object.js";
|
|
6
6
|
import { requireURL } from "../../util/url.js";
|
|
@@ -19,7 +19,7 @@ export function handleEndpoints(base, handlers, request, context, caller = handl
|
|
|
19
19
|
const pathParams = handler.endpoint.match(method, targetPath, caller);
|
|
20
20
|
if (!pathParams)
|
|
21
21
|
continue;
|
|
22
|
-
const params = searchParams.size ? { ...
|
|
22
|
+
const params = searchParams.size ? { ...requireDictionary(searchParams), ...pathParams } : pathParams;
|
|
23
23
|
return _handleEndpoint(handler, params, request, context, handleEndpoints);
|
|
24
24
|
}
|
|
25
25
|
throw new NotFoundError("No matching endpoint", { received: targetPath, caller });
|
|
@@ -6,5 +6,5 @@ export declare class EndpointStore<P, R> extends PayloadFetchStore<P, R> {
|
|
|
6
6
|
readonly provider: APIProvider<P, R>;
|
|
7
7
|
readonly endpoint: Endpoint<P, R>;
|
|
8
8
|
constructor(endpoint: Endpoint<P, R>, payload: P, provider: APIProvider<P, R>);
|
|
9
|
-
protected _fetch(): Promise<R>;
|
|
9
|
+
protected _fetch(signal: AbortSignal): Promise<R>;
|
|
10
10
|
}
|
|
@@ -10,7 +10,7 @@ export class EndpointStore extends PayloadFetchStore {
|
|
|
10
10
|
this.provider = provider;
|
|
11
11
|
}
|
|
12
12
|
// Override to fetch the value using the provider and endpoint.
|
|
13
|
-
_fetch() {
|
|
14
|
-
return this.provider.call(this.endpoint, this.payload.value, { signal
|
|
13
|
+
_fetch(signal) {
|
|
14
|
+
return this.provider.call(this.endpoint, this.payload.value, { signal });
|
|
15
15
|
}
|
|
16
16
|
}
|
package/db/store/ItemStore.d.ts
CHANGED
|
@@ -16,5 +16,5 @@ export declare class ItemStore<I extends Identifier, T extends Data> extends Fet
|
|
|
16
16
|
get item(): Item<I, T>;
|
|
17
17
|
set item(data: T | Item<I, T>);
|
|
18
18
|
constructor(collection: Collection<string, I, T>, id: I, provider: DBProvider<I>, memory?: MemoryDBProvider<I>);
|
|
19
|
-
_fetch(): Promise<OptionalItem<I, T>>;
|
|
19
|
+
_fetch(_signal: AbortSignal): Promise<OptionalItem<I, T>>;
|
|
20
20
|
}
|
package/db/store/ItemStore.js
CHANGED
package/db/store/QueryStore.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class QueryStore<I extends Identifier, T extends Data> extends Fe
|
|
|
23
23
|
/** Get the last item in this store. */
|
|
24
24
|
get last(): Item<I, T>;
|
|
25
25
|
constructor(collection: Collection<string, I, T>, query: Query<Item<I, T>>, provider: DBProvider<I>, memory?: MemoryDBProvider<I>);
|
|
26
|
-
_fetch(): Promise<Items<I, T>>;
|
|
26
|
+
_fetch(_signal: AbortSignal): Promise<Items<I, T>>;
|
|
27
27
|
/**
|
|
28
28
|
* Load more items after the last once.
|
|
29
29
|
* - Promise that needs to be handled.
|
package/db/store/QueryStore.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelving",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.189.0",
|
|
4
4
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"main": "./index.js",
|
|
10
10
|
"module": "./index.js",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@biomejs/biome": "^2.4.
|
|
12
|
+
"@biomejs/biome": "^2.4.13",
|
|
13
13
|
"@google-cloud/firestore": "^8.5.0",
|
|
14
|
-
"@types/bun": "^1.3.
|
|
14
|
+
"@types/bun": "^1.3.13",
|
|
15
15
|
"@types/react": "^19.2.14",
|
|
16
16
|
"@types/react-dom": "^19.2.3",
|
|
17
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
18
|
-
"firebase": "^12.12.
|
|
17
|
+
"@typescript/native-preview": "^7.0.0-dev.20260425.1",
|
|
18
|
+
"firebase": "^12.12.1",
|
|
19
19
|
"react": "^19.2.5",
|
|
20
20
|
"react-dom": "^19.2.5",
|
|
21
21
|
"typescript": "^5.9.3"
|
package/store/ArrayStore.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ImmutableArray } from "../util/array.js";
|
|
1
|
+
import type { ImmutableArray, PossibleArray } from "../util/array.js";
|
|
2
2
|
import type { NONE } from "../util/constants.js";
|
|
3
|
+
import type { AnyCaller } from "../util/function.js";
|
|
3
4
|
import { Store } from "./Store.js";
|
|
4
5
|
/** Store an array. */
|
|
5
|
-
export declare class ArrayStore<T> extends Store<ImmutableArray<T>> implements Iterable<T> {
|
|
6
|
+
export declare class ArrayStore<T> extends Store<PossibleArray<T>, ImmutableArray<T>> implements Iterable<T> {
|
|
6
7
|
constructor(value?: ImmutableArray<T> | typeof NONE);
|
|
8
|
+
convert(value: PossibleArray<T>, caller?: AnyCaller): ImmutableArray<T>;
|
|
7
9
|
/** Get the first item in this store or `null` if this query has no items. */
|
|
8
10
|
get optionalFirst(): T | undefined;
|
|
9
11
|
/** Get the last item in this store or `null` if this query has no items. */
|
package/store/ArrayStore.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { getFirst, getLast, omitArrayItems, requireFirst, requireLast, toggleArrayItems, withArrayItems } from "../util/array.js";
|
|
1
|
+
import { getFirst, getLast, omitArrayItems, requireArray, requireFirst, requireLast, toggleArrayItems, withArrayItems, } from "../util/array.js";
|
|
2
2
|
import { Store } from "./Store.js";
|
|
3
3
|
/** Store an array. */
|
|
4
4
|
export class ArrayStore extends Store {
|
|
5
5
|
constructor(value = []) {
|
|
6
6
|
super(value);
|
|
7
7
|
}
|
|
8
|
+
// Implement to automatically convert `PossibleArray`
|
|
9
|
+
convert(value, caller = this.convert) {
|
|
10
|
+
return requireArray(value, undefined, undefined, caller);
|
|
11
|
+
}
|
|
8
12
|
/** Get the first item in this store or `null` if this query has no items. */
|
|
9
13
|
get optionalFirst() {
|
|
10
14
|
return getFirst(this.value);
|
package/store/BooleanStore.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { NONE } from "../util/constants.js";
|
|
2
1
|
import { Store } from "./Store.js";
|
|
3
2
|
/** Store a boolean. */
|
|
4
|
-
export declare class BooleanStore extends Store<boolean> {
|
|
5
|
-
|
|
3
|
+
export declare class BooleanStore extends Store<unknown, boolean> {
|
|
4
|
+
convert(value: unknown): boolean;
|
|
6
5
|
/** Toggle the current boolean value. */
|
|
7
6
|
toggle(): void;
|
|
8
7
|
isEqual(a: boolean, b: boolean): boolean;
|
package/store/BooleanStore.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Store } from "./Store.js";
|
|
2
2
|
/** Store a boolean. */
|
|
3
3
|
export class BooleanStore extends Store {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Override to automatically convert to boolean.
|
|
5
|
+
convert(value) {
|
|
6
|
+
return !!value;
|
|
6
7
|
}
|
|
7
8
|
/** Toggle the current boolean value. */
|
|
8
9
|
toggle() {
|
package/store/DataStore.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AnyCaller } from "../util/function.js";
|
|
|
3
3
|
import type { Updates } from "../util/update.js";
|
|
4
4
|
import { Store } from "./Store.js";
|
|
5
5
|
/** Store a data object. */
|
|
6
|
-
export declare class DataStore<T extends Data> extends Store<T> {
|
|
6
|
+
export declare class DataStore<T extends Data> extends Store<T, T> {
|
|
7
7
|
/** Get the data of this store. */
|
|
8
8
|
get data(): T;
|
|
9
9
|
/** Set the data of this store. */
|
|
@@ -14,9 +14,10 @@ export declare class DataStore<T extends Data> extends Store<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;
|
|
17
18
|
}
|
|
18
19
|
/** Store an optional data object. */
|
|
19
|
-
export declare class OptionalDataStore<T extends Data> extends Store<T | undefined> {
|
|
20
|
+
export declare class OptionalDataStore<T extends Data> extends Store<T | undefined, T | undefined> {
|
|
20
21
|
/** Get current data value of this store (or throw `Promise` that resolves to the next required value). */
|
|
21
22
|
get data(): T;
|
|
22
23
|
/** Set the data of this store. */
|
|
@@ -33,4 +34,5 @@ export declare class OptionalDataStore<T extends Data> extends Store<T | undefin
|
|
|
33
34
|
set<K extends DataKey<T>>(name: K, value: T[K]): void;
|
|
34
35
|
/** Set the data to `undefined`. */
|
|
35
36
|
delete(): void;
|
|
37
|
+
convert(value: T): T;
|
|
36
38
|
}
|
package/store/DataStore.js
CHANGED
|
@@ -25,6 +25,10 @@ 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
|
+
}
|
|
28
32
|
}
|
|
29
33
|
/** Store an optional data object. */
|
|
30
34
|
export class OptionalDataStore extends Store {
|
|
@@ -63,4 +67,8 @@ export class OptionalDataStore extends Store {
|
|
|
63
67
|
delete() {
|
|
64
68
|
this.value = undefined;
|
|
65
69
|
}
|
|
70
|
+
// Implement to passthrough.
|
|
71
|
+
convert(value) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
66
74
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { DictionaryItem, ImmutableDictionary } from "../util/dictionary.js";
|
|
1
|
+
import type { DictionaryItem, ImmutableDictionary, PossibleDictionary } from "../util/dictionary.js";
|
|
2
2
|
import type { Updates } from "../util/update.js";
|
|
3
3
|
import { Store } from "./Store.js";
|
|
4
4
|
/** Store a dictionary object. */
|
|
5
|
-
export declare class DictionaryStore<T> extends Store<ImmutableDictionary<T>> implements Iterable<DictionaryItem<T>> {
|
|
6
|
-
constructor(value?:
|
|
5
|
+
export declare class DictionaryStore<T> extends Store<PossibleDictionary<T>, ImmutableDictionary<T>> implements Iterable<DictionaryItem<T>> {
|
|
6
|
+
constructor(value?: PossibleDictionary<T>);
|
|
7
|
+
convert(possible: PossibleDictionary<T>): ImmutableDictionary<T>;
|
|
7
8
|
/** Get the length of the current value of this store. */
|
|
8
9
|
get count(): number;
|
|
9
10
|
/** Set a named entry in this object with a different value. */
|
package/store/DictionaryStore.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { getDictionaryItems, omitDictionaryItems } from "../util/dictionary.js";
|
|
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
4
|
import { Store } from "./Store.js";
|
|
5
5
|
/** Store a dictionary object. */
|
|
6
6
|
export class DictionaryStore extends Store {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Override to set default value to empty dictionary.
|
|
8
|
+
constructor(value = EMPTY_DICTIONARY) {
|
|
9
|
+
super(requireDictionary(value));
|
|
10
|
+
}
|
|
11
|
+
// Override to convert a possible dictionary to a dictionary on set.
|
|
12
|
+
convert(possible) {
|
|
13
|
+
return requireDictionary(possible);
|
|
9
14
|
}
|
|
10
15
|
/** Get the length of the current value of this store. */
|
|
11
16
|
get count() {
|
package/store/FetchStore.d.ts
CHANGED
|
@@ -2,53 +2,63 @@ import { NONE } from "../util/constants.js";
|
|
|
2
2
|
import { BooleanStore } from "./BooleanStore.js";
|
|
3
3
|
import { Store } from "./Store.js";
|
|
4
4
|
/** Callback for a callback fetch store. */
|
|
5
|
-
export type FetchCallback<T> = () => T | PromiseLike<T>;
|
|
5
|
+
export type FetchCallback<T> = (signal: AbortSignal) => T | PromiseLike<T>;
|
|
6
6
|
/**
|
|
7
7
|
* Store that fetches its values from a remote source.
|
|
8
8
|
*
|
|
9
9
|
* @param value The initial value for the store, or `NONE` if it does not have one yet.
|
|
10
|
-
* @param callback An optional callback that, if set, will be called when the `
|
|
10
|
+
* @param callback An optional callback that, if set, will be called when the `refresh()` method is invoked to fetch the next value.
|
|
11
11
|
*/
|
|
12
|
-
export declare class FetchStore<T> extends Store<T> {
|
|
12
|
+
export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
|
|
13
13
|
/**
|
|
14
14
|
* Store that indicates the busy state of this store.
|
|
15
|
-
* -
|
|
15
|
+
* - `true` while a refresh is in-flight, `false` otherwise.
|
|
16
|
+
* - Can be subscribed to for e.g. loading spinners.
|
|
16
17
|
*/
|
|
17
18
|
readonly busy: BooleanStore;
|
|
18
19
|
get loading(): boolean;
|
|
19
20
|
get value(): T;
|
|
20
|
-
set value(value: T | typeof NONE);
|
|
21
|
+
set value(value: T | typeof NONE | PromiseLike<T | typeof NONE>);
|
|
21
22
|
constructor(value: T | typeof NONE, callback?: FetchCallback<T>);
|
|
23
|
+
convert(value: T | typeof NONE): T | typeof NONE;
|
|
22
24
|
/**
|
|
23
|
-
* Fetch the result for this
|
|
24
|
-
* - Triggered automatically when someone reads `value` or `loading
|
|
25
|
-
* -
|
|
25
|
+
* Fetch the result for this store now.
|
|
26
|
+
* - Triggered automatically when someone reads `value` or `loading`.
|
|
27
|
+
* - Concurrent calls while a fetch is in-flight return the same promise (deduplication).
|
|
28
|
+
* - Never throws — errors are stored as `reason`.
|
|
26
29
|
*
|
|
27
|
-
* @returns
|
|
28
|
-
* @returns
|
|
29
|
-
* @throws {never} Never throws so safe to call unhandled.
|
|
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.
|
|
30
32
|
*/
|
|
31
|
-
refresh(): Promise<
|
|
32
|
-
/** An in-flight refresh, so we don't de-duplicate these. */
|
|
33
|
+
refresh(): Promise<boolean> | boolean;
|
|
33
34
|
private _inflight;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
private _refresh;
|
|
36
|
+
/**
|
|
37
|
+
* Current `AbortSignal` for this store's in-flight fetch.
|
|
38
|
+
* - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
|
|
39
|
+
*/
|
|
40
|
+
get signal(): AbortSignal;
|
|
41
|
+
private _controller;
|
|
42
|
+
/**
|
|
43
|
+
* Call the fetch callback to get the next value.
|
|
44
|
+
* @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
|
|
45
|
+
*/
|
|
46
|
+
protected _fetch(signal: AbortSignal): T | PromiseLike<T>;
|
|
38
47
|
private _callback;
|
|
39
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
|
|
50
|
+
* - Also aborts any current in-flight fetch.
|
|
51
|
+
*/
|
|
40
52
|
invalidate(): void;
|
|
41
53
|
private _invalidation;
|
|
42
|
-
/** Re-fetch
|
|
54
|
+
/** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
|
|
43
55
|
refreshStale(maxAge: number): Promise<void>;
|
|
44
56
|
/**
|
|
45
|
-
*
|
|
46
|
-
* -
|
|
47
|
-
* -
|
|
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.
|
|
48
61
|
*/
|
|
49
|
-
get signal(): AbortSignal;
|
|
50
|
-
private _controller;
|
|
51
|
-
/** Abort the current signal now. */
|
|
52
62
|
abort(): void;
|
|
53
63
|
[Symbol.asyncDispose](): Promise<void>;
|
|
54
64
|
}
|
package/store/FetchStore.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
import { isAsync } from "../util/async.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ABORT, NONE } from "../util/constants.js";
|
|
4
4
|
import { awaitDispose } from "../util/dispose.js";
|
|
5
5
|
import { BooleanStore } from "./BooleanStore.js";
|
|
6
6
|
import { Store } from "./Store.js";
|
|
@@ -8,126 +8,129 @@ import { Store } from "./Store.js";
|
|
|
8
8
|
* Store that fetches its values from a remote source.
|
|
9
9
|
*
|
|
10
10
|
* @param value The initial value for the store, or `NONE` if it does not have one yet.
|
|
11
|
-
* @param callback An optional callback that, if set, will be called when the `
|
|
11
|
+
* @param callback An optional callback that, if set, will be called when the `refresh()` method is invoked to fetch the next value.
|
|
12
12
|
*/
|
|
13
13
|
export class FetchStore extends Store {
|
|
14
14
|
/**
|
|
15
15
|
* Store that indicates the busy state of this store.
|
|
16
|
-
* -
|
|
16
|
+
* - `true` while a refresh is in-flight, `false` otherwise.
|
|
17
|
+
* - Can be subscribed to for e.g. loading spinners.
|
|
17
18
|
*/
|
|
18
19
|
busy;
|
|
19
20
|
// Override to possibly trigger a fetch when `this.loading` is read.
|
|
20
|
-
//
|
|
21
|
+
// Reading `loading` signals intent to use the value, so we start a fetch if needed.
|
|
21
22
|
get loading() {
|
|
22
23
|
const loading = super.loading;
|
|
23
24
|
if (loading || this._invalidation)
|
|
24
25
|
void this.refresh();
|
|
25
26
|
return loading;
|
|
26
27
|
}
|
|
27
|
-
// Override to possibly trigger a fetch if `this.value` is still
|
|
28
|
-
//
|
|
28
|
+
// Override to possibly trigger a fetch if `this.value` is still loading.
|
|
29
|
+
// Reading `value` signals intent to use the value, so we start a fetch if needed.
|
|
29
30
|
get value() {
|
|
30
31
|
if (super.loading)
|
|
31
32
|
void this.refresh();
|
|
32
33
|
return super.value;
|
|
33
34
|
}
|
|
34
35
|
set value(value) {
|
|
35
|
-
super.value = value;
|
|
36
|
-
// Setting a value resets
|
|
36
|
+
super.value = value; // calls Store.set value() which calls this.abort() then _applyValue()
|
|
37
|
+
// Setting a value resets the invalid state.
|
|
37
38
|
this._invalidation = 0;
|
|
38
39
|
}
|
|
39
|
-
// Override to save callback.
|
|
40
40
|
constructor(value, callback) {
|
|
41
41
|
super(value);
|
|
42
42
|
this.busy = new BooleanStore(value === NONE);
|
|
43
43
|
this._callback = callback;
|
|
44
44
|
}
|
|
45
|
+
// Implement to allow `NONE`
|
|
46
|
+
convert(value) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
45
49
|
/**
|
|
46
|
-
* Fetch the result for this
|
|
47
|
-
* - Triggered automatically when someone reads `value` or `loading
|
|
48
|
-
* -
|
|
50
|
+
* Fetch the result for this store now.
|
|
51
|
+
* - Triggered automatically when someone reads `value` or `loading`.
|
|
52
|
+
* - Concurrent calls while a fetch is in-flight return the same promise (deduplication).
|
|
53
|
+
* - Never throws — errors are stored as `reason`.
|
|
49
54
|
*
|
|
50
|
-
* @returns
|
|
51
|
-
* @returns
|
|
52
|
-
* @throws {never} Never throws so safe to call unhandled.
|
|
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.
|
|
53
57
|
*/
|
|
54
58
|
refresh() {
|
|
55
59
|
if (this._inflight)
|
|
56
60
|
return this._inflight;
|
|
61
|
+
// Cancel any existing controller and create a fresh one for this fetch.
|
|
62
|
+
this._controller?.abort(ABORT);
|
|
63
|
+
this._controller = new AbortController();
|
|
57
64
|
try {
|
|
58
|
-
const value = this._fetch();
|
|
65
|
+
const value = this._fetch(this._controller.signal);
|
|
59
66
|
if (isAsync(value))
|
|
60
|
-
return (this._inflight = this.
|
|
67
|
+
return (this._inflight = this._refresh(value));
|
|
61
68
|
this.value = value;
|
|
69
|
+
return true;
|
|
62
70
|
}
|
|
63
71
|
catch (thrown) {
|
|
64
72
|
this.reason = thrown;
|
|
73
|
+
return false;
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
|
-
/** An in-flight refresh, so we don't de-duplicate these. */
|
|
68
76
|
_inflight = undefined;
|
|
69
|
-
|
|
70
|
-
async await(value) {
|
|
77
|
+
async _refresh(asyncValue) {
|
|
71
78
|
this.busy.value = true;
|
|
72
|
-
this._awaits.add(value);
|
|
73
79
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Use super.value to set the value directly without resetting the invalidation number.
|
|
77
|
-
super.value = await value;
|
|
78
|
-
// If this store was not invalidated while awaiting the value (i.e. invalidation number did not change) then reset the invalidation number.
|
|
79
|
-
if (invalidation === this._invalidation)
|
|
80
|
+
const refreshed = await this.await(asyncValue);
|
|
81
|
+
if (refreshed)
|
|
80
82
|
this._invalidation = 0;
|
|
81
|
-
|
|
82
|
-
catch (thrown) {
|
|
83
|
-
// If the throw was not an on-purpose abort, save it as the reason.
|
|
84
|
-
if (thrown !== ABORTED)
|
|
85
|
-
this.reason = thrown;
|
|
83
|
+
return refreshed;
|
|
86
84
|
}
|
|
87
85
|
finally {
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
this.busy.value = false;
|
|
91
|
-
this._inflight = undefined; // Clear any inflight refresh if this was the last await.
|
|
92
|
-
}
|
|
93
|
-
this._controller = undefined;
|
|
86
|
+
this.busy.value = false;
|
|
87
|
+
this._inflight = undefined;
|
|
94
88
|
}
|
|
95
89
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Current `AbortSignal` for this store's in-flight fetch.
|
|
92
|
+
* - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
|
|
93
|
+
*/
|
|
94
|
+
get signal() {
|
|
95
|
+
return (this._controller ||= new AbortController()).signal;
|
|
96
|
+
}
|
|
97
|
+
_controller;
|
|
98
|
+
/**
|
|
99
|
+
* Call the fetch callback to get the next value.
|
|
100
|
+
* @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
|
|
101
|
+
*/
|
|
102
|
+
_fetch(signal) {
|
|
99
103
|
if (!this._callback)
|
|
100
104
|
throw new RequiredError("FetchStore has no callback() function", { store: this, caller: this.refresh });
|
|
101
|
-
return this._callback();
|
|
105
|
+
return this._callback(signal);
|
|
102
106
|
}
|
|
103
107
|
_callback;
|
|
104
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
|
|
110
|
+
* - Also aborts any current in-flight fetch.
|
|
111
|
+
*/
|
|
105
112
|
invalidate() {
|
|
106
113
|
this.abort();
|
|
107
114
|
this._invalidation++;
|
|
108
115
|
}
|
|
109
|
-
_invalidation = 0;
|
|
110
|
-
/** Re-fetch
|
|
116
|
+
_invalidation = 0;
|
|
117
|
+
/** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
|
|
111
118
|
async refreshStale(maxAge) {
|
|
112
119
|
if (this._invalidation || this.age > maxAge)
|
|
113
120
|
await this.refresh();
|
|
114
121
|
}
|
|
115
122
|
/**
|
|
116
|
-
*
|
|
117
|
-
* -
|
|
118
|
-
* -
|
|
123
|
+
* Abort any current in-flight fetch and pending async operation.
|
|
124
|
+
* - Aborts the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
|
|
125
|
+
* - Clears the in-flight promise and resets busy state.
|
|
126
|
+
* - Any pending `await()` result will be silently discarded.
|
|
119
127
|
*/
|
|
120
|
-
get signal() {
|
|
121
|
-
return (this._controller ||= new AbortController()).signal;
|
|
122
|
-
}
|
|
123
|
-
_controller;
|
|
124
|
-
/** Abort the current signal now. */
|
|
125
128
|
abort() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
this._controller?.abort(ABORT);
|
|
130
|
+
this._controller = undefined;
|
|
131
|
+
this._inflight = undefined;
|
|
132
|
+
this.busy.value = false;
|
|
133
|
+
super.abort(); // clears _pendingValue
|
|
131
134
|
}
|
|
132
135
|
// Implement `AsyncDisposable`.
|
|
133
136
|
async [Symbol.asyncDispose]() {
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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/PathStore.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyCaller } from "../util/function.js";
|
|
2
|
+
import type { AbsolutePath, PossiblePath } from "../util/path.js";
|
|
2
3
|
import { Store } from "./Store.js";
|
|
3
4
|
/** Store an absolute path, e.g. `/a/b/c` */
|
|
4
|
-
export declare class PathStore extends Store<AbsolutePath> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export declare class PathStore extends Store<PossiblePath, AbsolutePath> {
|
|
6
|
+
readonly base: AbsolutePath;
|
|
7
|
+
constructor(path?: string, base?: AbsolutePath);
|
|
8
|
+
convert(possible: PossiblePath, caller: AnyCaller): AbsolutePath;
|
|
8
9
|
/** Based on the current store path, is a path active? */
|
|
9
10
|
isActive(path: AbsolutePath): boolean;
|
|
10
11
|
/** Based on the current store path, is a path proud (i.e. a child of the current store path)? */
|
package/store/PathStore.js
CHANGED
|
@@ -2,15 +2,15 @@ import { isPathActive, isPathProud, requirePath } from "../util/path.js";
|
|
|
2
2
|
import { Store } from "./Store.js";
|
|
3
3
|
/** Store an absolute path, e.g. `/a/b/c` */
|
|
4
4
|
export class PathStore extends Store {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
base;
|
|
6
|
+
// Override to set default path to `.` and base to `/`
|
|
7
|
+
constructor(path = ".", base = "/") {
|
|
8
|
+
super(requirePath(path, base, PathStore));
|
|
9
|
+
this.base = base;
|
|
7
10
|
}
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
set value(path) {
|
|
13
|
-
super.value = requirePath(path, super.value);
|
|
11
|
+
// Implement to convert a possible path to an absolute path (relative to `this.base`).
|
|
12
|
+
convert(possible, caller) {
|
|
13
|
+
return requirePath(possible, this.base, caller);
|
|
14
14
|
}
|
|
15
15
|
/** Based on the current store path, is a path active? */
|
|
16
16
|
isActive(path) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NONE } from "../util/constants.js";
|
|
2
2
|
import { FetchStore } from "./FetchStore.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ValueStore } from "./ValueStore.js";
|
|
4
4
|
export type PayloadFetchCallback<P, R> = (payload: P) => R | PromiseLike<R>;
|
|
5
5
|
/**
|
|
6
6
|
* Store that fetches its values from a remote source by sending a payload to them.
|
|
@@ -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: ValueStore<P>;
|
|
18
18
|
constructor(payload: P, value: R | typeof NONE, callback?: PayloadFetchCallback<P, R>);
|
|
19
19
|
[Symbol.asyncDispose](): Promise<void>;
|
|
20
20
|
}
|