shelving 1.189.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.189.2",
3
+ "version": "1.190.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,8 +1,8 @@
1
1
  import type { ImmutableArray, PossibleArray } from "../util/array.js";
2
2
  import type { AnyCaller } from "../util/function.js";
3
- import { Store } from "./Store.js";
3
+ import { BusyStore } from "./BusyStore.js";
4
4
  /** Store an array. */
5
- export declare class ArrayStore<T> extends Store<PossibleArray<T>, ImmutableArray<T>> implements Iterable<T> {
5
+ export declare class ArrayStore<T> extends BusyStore<ImmutableArray<T>, PossibleArray<T>> implements Iterable<T> {
6
6
  constructor(value?: PossibleArray<T>);
7
7
  protected _convert(input: PossibleArray<T>, caller: AnyCaller): ImmutableArray<T>;
8
8
  /** Get the first item in this store or `null` if this query has no items. */
@@ -1,7 +1,7 @@
1
1
  import { getFirst, getLast, omitArrayItems, requireArray, requireFirst, requireLast, toggleArrayItems, withArrayItems, } from "../util/array.js";
2
- import { Store } from "./Store.js";
2
+ import { BusyStore } from "./BusyStore.js";
3
3
  /** Store an array. */
4
- export class ArrayStore extends Store {
4
+ export class ArrayStore extends BusyStore {
5
5
  // Override to set default value to `[]` and convert possible arrays.
6
6
  constructor(value = []) {
7
7
  super(requireArray(value, undefined, undefined, ArrayStore));
@@ -1,6 +1,6 @@
1
1
  import { Store } from "./Store.js";
2
2
  /** Store a boolean. */
3
- export declare class BooleanStore extends Store<unknown, boolean> {
3
+ export declare class BooleanStore extends Store<boolean, unknown> {
4
4
  constructor(value?: boolean);
5
5
  protected _convert(input: unknown): boolean;
6
6
  protected _equal(a: boolean, b: boolean): boolean;
@@ -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
+ }
@@ -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 { ValueStore } from "./ValueStore.js";
4
+ import { BusyStore } from "./BusyStore.js";
5
5
  /** Store a data object. */
6
- export declare class DataStore<T extends Data> extends ValueStore<T> {
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. */
@@ -16,7 +16,7 @@ export declare class DataStore<T extends Data> extends ValueStore<T> {
16
16
  set<K extends DataKey<T>>(name: K, value: T[K]): void;
17
17
  }
18
18
  /** Store an optional data object. */
19
- export declare class OptionalDataStore<T extends Data> extends ValueStore<T | undefined> {
19
+ export declare class OptionalDataStore<T extends Data> extends BusyStore<T | undefined> {
20
20
  /** Get current data value of this store (or throw `Promise` that resolves to the next required value). */
21
21
  get data(): T;
22
22
  /** Set the data of this store. */
@@ -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 { ValueStore } from "./ValueStore.js";
5
+ import { BusyStore } from "./BusyStore.js";
6
6
  /** Store a data object. */
7
- export class DataStore extends ValueStore {
7
+ export class DataStore extends BusyStore {
8
8
  /** Get the data of this store. */
9
9
  get data() {
10
10
  return this.value;
@@ -27,7 +27,7 @@ export class DataStore extends ValueStore {
27
27
  }
28
28
  }
29
29
  /** Store an optional data object. */
30
- export class OptionalDataStore extends ValueStore {
30
+ export class OptionalDataStore extends BusyStore {
31
31
  /** Get current data value of this store (or throw `Promise` that resolves to the next required value). */
32
32
  get data() {
33
33
  return this.require(getGetter(this, "data"));
@@ -1,8 +1,8 @@
1
1
  import type { DictionaryItem, ImmutableDictionary, PossibleDictionary } from "../util/dictionary.js";
2
2
  import type { Updates } from "../util/update.js";
3
- import { Store } from "./Store.js";
3
+ import { BusyStore } from "./BusyStore.js";
4
4
  /** Store a dictionary object. */
5
- export declare class DictionaryStore<T> extends Store<PossibleDictionary<T>, ImmutableDictionary<T>> implements Iterable<DictionaryItem<T>> {
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. */
@@ -1,9 +1,9 @@
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 { Store } from "./Store.js";
4
+ import { BusyStore } from "./BusyStore.js";
5
5
  /** Store a dictionary object. */
6
- export class DictionaryStore extends Store {
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));
@@ -1,6 +1,6 @@
1
- import { NONE } from "../util/constants.js";
2
- import { BooleanStore } from "./BooleanStore.js";
3
- import { Store } from "./Store.js";
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,18 +8,13 @@ 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 Store<T | typeof NONE, T> {
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
- protected _convert(value: T | typeof NONE): T | typeof NONE;
21
- protected _read(value: T | typeof NONE): T;
22
- constructor(value: T | typeof NONE, callback?: FetchCallback<T>);
15
+ write(input: StoreInput<TT>): void;
16
+ read(): import("./Store.js").StoreInternal<T>;
17
+ constructor(value: T | typeof NONE, callback?: FetchCallback<TT>);
23
18
  /**
24
19
  * Fetch the result for this store now.
25
20
  * - Triggered automatically when someone reads `value` or `loading`.
@@ -28,7 +23,6 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
28
23
  */
29
24
  refresh(): Promise<boolean> | boolean;
30
25
  private _pendingRefresh;
31
- await(pending: PromiseLike<T>): Promise<boolean>;
32
26
  /**
33
27
  * Current `AbortSignal` for this store's in-flight fetch.
34
28
  * - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
@@ -40,7 +34,7 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
40
34
  * Call the fetch callback to get the next value.
41
35
  * @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
42
36
  */
43
- protected _fetch(signal: AbortSignal): T | PromiseLike<T>;
37
+ protected _fetch(signal: AbortSignal): TT | PromiseLike<TT>;
44
38
  private _callback;
45
39
  /**
46
40
  * Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
@@ -49,13 +43,6 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
49
43
  invalidate(): void;
50
44
  private _invalidation;
51
45
  /** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
52
- refreshStale(maxAge: number): Promise<void>;
53
- /**
54
- * Abort any current in-flight fetch and pending async operation.
55
- * - Sends `ABORT` to the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
56
- * - Clears the inflight promise or await and resets busy state.
57
- * - Any pending `await()` result will be silently discarded.
58
- */
46
+ refreshStale(maxAge: number): Promise<boolean> | boolean;
59
47
  abort(): void;
60
- [Symbol.asyncDispose](): Promise<void>;
61
48
  }
@@ -1,43 +1,37 @@
1
1
  import { RequiredError } from "../error/RequiredError.js";
2
2
  import { isAsync } from "../util/async.js";
3
- import { ABORT, NONE } from "../util/constants.js";
4
- import { awaitDispose } from "../util/dispose.js";
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 Store {
14
- /**
15
- * Store that indicates the busy state of this store.
16
- * - `true` while a refresh is in-flight, `false` otherwise.
17
- * - Can be subscribed to for e.g. loading spinners.
18
- */
19
- busy = new BooleanStore(false);
12
+ export class FetchStore extends BusyStore {
20
13
  // Override to trigger a refresh when `this.loading` is read.
21
- // Reading `loading` signals intent to use the value, so we optimistically start fetching so the value is available.
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 (loading || this._invalidation)
18
+ if (this._invalidation || loading)
25
19
  void this.refresh();
26
20
  return loading;
27
21
  }
28
22
  // Override to reset the invalidation state.
29
23
  // Setting a value means this store is no longer invalid.
30
- _convert(value) {
31
- if (!isAsync(value))
24
+ write(input) {
25
+ if (input !== SKIP)
32
26
  this._invalidation = 0;
33
- return value;
27
+ super.write(input);
34
28
  }
35
29
  // Override to trigger refresh on `NONE`
36
- // Reading `value` signals intent to use the value, so we optimistically start fetching so the value is available.
37
- _read(value) {
38
- if (value === NONE)
39
- void this.refresh();
40
- return super._read(value);
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();
41
35
  }
42
36
  // Override to create to save `callback`
43
37
  constructor(value, callback) {
@@ -66,12 +60,6 @@ export class FetchStore extends Store {
66
60
  }
67
61
  }
68
62
  _pendingRefresh = undefined;
69
- // Overload to set `this.busy` to `true` when we start awaiting a value.
70
- // Gets set back to `false` when `abort()` is called.
71
- await(pending) {
72
- this.busy.value = true;
73
- return super.await(pending);
74
- }
75
63
  /**
76
64
  * Current `AbortSignal` for this store's in-flight fetch.
77
65
  * - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
@@ -103,26 +91,18 @@ export class FetchStore extends Store {
103
91
  }
104
92
  _invalidation = 0;
105
93
  /** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
106
- async refreshStale(maxAge) {
94
+ refreshStale(maxAge) {
107
95
  if (this._invalidation || this.age > maxAge)
108
- await this.refresh();
96
+ return this.refresh();
97
+ return true;
109
98
  }
110
- /**
111
- * Abort any current in-flight fetch and pending async operation.
112
- * - Sends `ABORT` to the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
113
- * - Clears the inflight promise or await and resets busy state.
114
- * - Any pending `await()` result will be silently discarded.
115
- */
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.
116
102
  abort() {
117
103
  this._aborter?.abort(ABORT);
118
104
  this._aborter = undefined;
119
105
  this._pendingRefresh = undefined;
120
- this.busy.value = false;
121
106
  super.abort(); // clears _pendingValue
122
107
  }
123
- // Implement `AsyncDisposable`.
124
- async [Symbol.asyncDispose]() {
125
- await awaitDispose(() => this.abort(), this.busy, // Send `done: true` to any iterators of the busy store.
126
- super[Symbol.asyncDispose]());
127
- }
128
108
  }
@@ -1,13 +1,13 @@
1
1
  import type { AnyCaller } from "../util/function.js";
2
2
  import type { AbsolutePath, PossiblePath } from "../util/path.js";
3
- import { Store } from "./Store.js";
3
+ import { BusyStore } from "./BusyStore.js";
4
4
  /**
5
5
  * Store an absolute path, e.g. `/a/b/c`
6
6
  *
7
7
  * @param path: The initial value for the store.
8
8
  * @param base: The base path to resolve relative paths against.
9
9
  */
10
- export declare class PathStore extends Store<PossiblePath, AbsolutePath> {
10
+ export declare class PathStore extends BusyStore<AbsolutePath, PossiblePath> {
11
11
  readonly base: AbsolutePath;
12
12
  constructor(path?: string, base?: AbsolutePath);
13
13
  protected _convert(possible: PossiblePath, caller: AnyCaller): AbsolutePath;
@@ -1,12 +1,12 @@
1
1
  import { isPathActive, isPathProud, requirePath } from "../util/path.js";
2
- import { Store } from "./Store.js";
2
+ import { BusyStore } from "./BusyStore.js";
3
3
  /**
4
4
  * Store an absolute path, e.g. `/a/b/c`
5
5
  *
6
6
  * @param path: The initial value for the store.
7
7
  * @param base: The base path to resolve relative paths against.
8
8
  */
9
- export class PathStore extends Store {
9
+ export class PathStore extends BusyStore {
10
10
  base;
11
11
  // Override to set default path to `.` and base to `/`
12
12
  constructor(path = ".", base = "/") {
@@ -1,7 +1,7 @@
1
1
  import type { NONE } from "../util/constants.js";
2
2
  import { FetchStore } from "./FetchStore.js";
3
- import { ValueStore } from "./ValueStore.js";
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: ValueStore<P>;
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 { ValueStore } from "./ValueStore.js";
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 ValueStore(payload);
20
- super(value, callback && (() => callback(payloadStore.value)));
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
@@ -1,13 +1,17 @@
1
1
  import { DeferredSequence } from "../sequence/DeferredSequence.js";
2
- import { NONE } from "../util/constants.js";
2
+ import { NONE, SKIP } from "../util/constants.js";
3
3
  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,17 +22,18 @@ 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 I The "input type" for this store.
22
- * - Indicates what values `this.value` can be set to.
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 O The "output type" for this store.
25
- * - Indicates what value `this.value` will return.
26
- * - Must extend the input type `I`.
27
- * - Defaults to I.
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 abstract class Store<I, O> implements AsyncIterable<O, void, void>, AsyncDisposable {
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<O, void, void>;
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.
@@ -40,33 +45,36 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
40
45
  * - Sets any sync values.
41
46
  * - Awaits any async values.
42
47
  * - Setting value the `NONE` symbol indicates the store has no value so should be in a "loading" state.
43
- * - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip setting entirely).
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).
44
49
  * - Setting value to the same as the existing value
45
50
  * - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
46
51
  */
47
- set value(input: I | PromiseLike<I>);
52
+ set value(input: StoreInput<TT> | PromiseLike<StoreInput<TT>>);
53
+ /** Write a synchronous value to this store. */
54
+ write(input: StoreInput<TT>): void;
48
55
  /**
49
56
  * Convert input type to internal storage type.
50
- * - Called only while getting values.
51
- * - Override in subclasses to change getting behaviour.
57
+ * - Override in subclasses to change conversion behaviour.
58
+ * - Warning: With no override, default behaviour is to just assert TT is T (unsafe).
52
59
  */
53
- protected abstract _convert(value: I, caller?: AnyCaller): O | typeof NONE;
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;
54
63
  /** Internal storage for current value. */
55
64
  private _value;
56
- /** Compare two values for this store and return whether they are equal. */
57
- protected _equal(a: O, b: O): boolean;
58
65
  /**
59
66
  * Get the current value of this store.
60
67
  *
61
68
  * @throws {Promise} if this store currently is in a "loading" state (resolves when a value is set).
62
69
  * @throws {unknown} if this store currently has an error.
63
70
  */
64
- get value(): O;
71
+ get value(): T;
65
72
  /**
66
- * Called while reading values. Can be used to override get behaviour.
73
+ * Called to read values. Can be used to override get behaviour.
67
74
  * - Override in subclasses to change getting behaviour.
75
+ * - Note: doesn't throw `reason` if there is one!
68
76
  */
69
- protected _read(value: O | typeof NONE): O;
77
+ read(): StoreInternal<T>;
70
78
  /**
71
79
  * Time (in milliseconds) this store was last updated with a new value.
72
80
  * - Will be `undefined` if the value is still loading.
@@ -93,43 +101,25 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
93
101
  protected set starter(start: PossibleStarter<[this]>);
94
102
  private _starter;
95
103
  /** Store is initiated with a value, or `NONE` to put it in a "loading" state. */
96
- constructor(value: O | typeof NONE);
104
+ constructor(value: StoreInternal<T>);
97
105
  /** Set the value of this store as values are pulled from a sequence. */
98
- through(sequence: AsyncIterable<I>): AsyncIterable<I>;
106
+ through(sequence: AsyncIterable<TT>): AsyncIterable<TT>;
99
107
  /**
100
- * Call a callback that returns a new value (possibly async) for this store.
101
- * - Errors are stored as `reason`; never throws.
102
- *
103
- * @param callback The callback function to call that should return a value to set on this store.
104
- * @param args Any additional input values for the reducer.
105
- * @returns New value for this store (possibly async).
106
- * @param args Any arguments to pass to the callback.
107
- *
108
- * @returns {true} If the callback returned a value and it was set.
109
- * @returns {false} If the callback threw.
110
- * @returns {Promise<true>} If the callback returned a promise and it resolved.
111
- * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
112
- *
113
- * @throws {never} Never throws — safe to call without handling the return value.
108
+ * Call a callback and save the returned value to this store.
114
109
  */
115
- call<A extends Arguments>(callback: StoreCallback<I, A>, ...args: A): Promise<boolean> | boolean;
110
+ call<A extends Arguments>(callback: StoreCallback<TT, A>, ...args: A): Promise<boolean> | boolean;
116
111
  /**
117
- * Reduce the current value using a reducer callback that receives the current value.
118
- *
119
- * @param reducer The callback function to call that should return a value to set on this store.
120
- * @param value The current value of this store.
121
- * @param args Any additional input values for the reducer.
122
- * @returns New value for this store (possibly async).
123
- * @param args Any arguments to pass to the callback.
124
- *
125
- * @throws {unknown} if this store currently has an error reason set.
126
- *
127
- * @returns {true} If the callback returned a value and it was set.
128
- * @returns {false} If the callback threw.
129
- * @returns {Promise<true>} If the callback returned a promise and it resolved.
130
- * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
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.
131
121
  */
132
- reduce<A extends Arguments>(reducer: StoreReducer<I, O, A>, ...args: A): Promise<boolean> | boolean;
122
+ send<A extends Arguments>(callback: (value: T, ...args: A) => void | PromiseLike<void>, ...args: A): Promise<boolean> | boolean;
133
123
  /**
134
124
  * Await an async value and save it to this store.
135
125
  * - Saves the resolved value.
@@ -147,14 +137,14 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
147
137
  *
148
138
  * @throws {never} Never throws — safe to call without handling the return value.
149
139
  */
150
- await(pending: PromiseLike<I>): Promise<boolean>;
140
+ await(pending: PromiseLike<StoreInput<TT>>): Promise<boolean>;
151
141
  private _pendingValue;
152
142
  /**
153
143
  * Abort any current pending `await()` call.
154
144
  * - The pending call's result will be silently discarded and its error will not be stored.
155
145
  */
156
146
  abort(): void;
157
- [Symbol.asyncIterator](): AsyncIterator<O, void, void>;
147
+ [Symbol.asyncIterator](): AsyncIterator<T, void, void>;
158
148
  private _iterating;
159
149
  [Symbol.asyncDispose](): Promise<void>;
160
150
  }
package/store/Store.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DeferredSequence } from "../sequence/DeferredSequence.js";
2
2
  import { isAsync } from "../util/async.js";
3
- import { NONE } from "../util/constants.js";
3
+ import { NONE, SKIP } from "../util/constants.js";
4
4
  import { awaitDispose } from "../util/dispose.js";
5
5
  import { isDeepEqual } from "../util/equal.js";
6
6
  import { getStarter } from "../util/start.js";
@@ -14,13 +14,14 @@ import { getStarter } from "../util/start.js";
14
14
  * - To set this store to be loading, use the `NONE` constant or a `Promise` value.
15
15
  * - To set this store to an explicit value, use that value or another `Store` instance with a value.
16
16
  *
17
- * @param I The "input type" for this store.
18
- * - Indicates what values `this.value` can be set to.
17
+ * @param T The "main type" for this store.
18
+ * - Indicates what values `this.value` will return.
19
19
  * - Methods that set values like `this.call()` and `this.await()` can also accept these values.
20
- * @param O The "output type" for this store.
21
- * - Indicates what value `this.value` will return.
22
- * - Must extend the input type `I`.
23
- * - Defaults to I.
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).
24
25
  */
25
26
  export class Store {
26
27
  /** Deferred sequence this store uses to issue values as they change. */
@@ -38,38 +39,53 @@ export class Store {
38
39
  * - Sets any sync values.
39
40
  * - Awaits any async values.
40
41
  * - Setting value the `NONE` symbol indicates the store has no value so should be in a "loading" state.
41
- * - Setting value to `SKIP` indicates the value should be silently ignored (sometimes it's helpful to have a way to skip setting entirely).
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).
42
43
  * - Setting value to the same as the existing value
43
44
  * - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
44
45
  */
45
46
  set value(input) {
46
- if (isAsync(input)) {
47
+ if (isAsync(input))
47
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;
48
59
  }
49
- else {
50
- this.abort();
51
- this._reason = undefined;
52
- const storage = this._convert(input);
53
- if (storage === NONE) {
54
- // Put the store into a loading state.
55
- this._time = undefined;
56
- this._value = storage;
57
- this.next.cancel();
58
- }
59
- else if (this._value === NONE || !this._equal(storage, this._value)) {
60
- // Set this changed value.
61
- this._time = Date.now();
62
- this._value = storage;
63
- this.next.resolve(this._read(storage));
64
- }
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);
65
73
  }
66
74
  }
67
- /** Internal storage for current value. */
68
- _value;
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
+ }
69
83
  /** Compare two values for this store and return whether they are equal. */
70
84
  _equal(a, b) {
71
85
  return isDeepEqual(a, b);
72
86
  }
87
+ /** Internal storage for current value. */
88
+ _value;
73
89
  /**
74
90
  * Get the current value of this store.
75
91
  *
@@ -79,16 +95,18 @@ export class Store {
79
95
  get value() {
80
96
  if (this._reason !== undefined)
81
97
  throw this._reason;
82
- return this._read(this._value);
98
+ const value = this.read();
99
+ if (value === NONE)
100
+ throw this.next;
101
+ return value;
83
102
  }
84
103
  /**
85
- * Called while reading values. Can be used to override get behaviour.
104
+ * Called to read values. Can be used to override get behaviour.
86
105
  * - Override in subclasses to change getting behaviour.
106
+ * - Note: doesn't throw `reason` if there is one!
87
107
  */
88
- _read(value) {
89
- if (value === NONE)
90
- throw this.next;
91
- return value;
108
+ read() {
109
+ return this._value;
92
110
  }
93
111
  /**
94
112
  * Time (in milliseconds) this store was last updated with a new value.
@@ -141,32 +159,19 @@ export class Store {
141
159
  /** Set the value of this store as values are pulled from a sequence. */
142
160
  async *through(sequence) {
143
161
  for await (const value of sequence) {
144
- this.value = value;
162
+ this.write(value);
145
163
  yield value;
146
164
  }
147
165
  }
148
166
  /**
149
- * Call a callback that returns a new value (possibly async) for this store.
150
- * - Errors are stored as `reason`; never throws.
151
- *
152
- * @param callback The callback function to call that should return a value to set on this store.
153
- * @param args Any additional input values for the reducer.
154
- * @returns New value for this store (possibly async).
155
- * @param args Any arguments to pass to the callback.
156
- *
157
- * @returns {true} If the callback returned a value and it was set.
158
- * @returns {false} If the callback threw.
159
- * @returns {Promise<true>} If the callback returned a promise and it resolved.
160
- * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
161
- *
162
- * @throws {never} Never throws — safe to call without handling the return value.
167
+ * Call a callback and save the returned value to this store.
163
168
  */
164
169
  call(callback, ...args) {
165
170
  try {
166
171
  const value = callback(...args);
167
172
  if (isAsync(value))
168
173
  return this.await(value);
169
- this.value = value;
174
+ this.write(value);
170
175
  return true;
171
176
  }
172
177
  catch (thrown) {
@@ -175,24 +180,23 @@ export class Store {
175
180
  }
176
181
  }
177
182
  /**
178
- * Reduce the current value using a reducer callback that receives the current value.
179
- *
180
- * @param reducer The callback function to call that should return a value to set on this store.
181
- * @param value The current value of this store.
182
- * @param args Any additional input values for the reducer.
183
- * @returns New value for this store (possibly async).
184
- * @param args Any arguments to pass to the callback.
185
- *
186
- * @throws {unknown} if this store currently has an error reason set.
187
- *
188
- * @returns {true} If the callback returned a value and it was set.
189
- * @returns {false} If the callback threw.
190
- * @returns {Promise<true>} If the callback returned a promise and it resolved.
191
- * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
183
+ * Send the current value to a callback and save the returned value to this store.
192
184
  */
193
185
  reduce(reducer, ...args) {
194
186
  return this.call(reducer, this.value, ...args);
195
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
+ }
196
200
  /**
197
201
  * Await an async value and save it to this store.
198
202
  * - Saves the resolved value.
@@ -218,7 +222,7 @@ export class Store {
218
222
  try {
219
223
  const value = await pending;
220
224
  if (this._pendingValue === pending) {
221
- this.value = value;
225
+ this.write(value);
222
226
  return true;
223
227
  }
224
228
  return false;
@@ -265,3 +269,15 @@ export class Store {
265
269
  this.next);
266
270
  }
267
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
+ }
@@ -1,9 +1,9 @@
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 { Store } from "./Store.js";
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 Store<PossibleURL, URL> {
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;
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 { Store } from "./Store.js";
4
+ import { BusyStore } from "./BusyStore.js";
5
5
  /** Store a URL, e.g. `https://top.com/a/b/c` */
6
- export class URLStore extends Store {
6
+ export class URLStore extends BusyStore {
7
7
  base;
8
8
  // Override to convert possible URL to URL.
9
9
  constructor(url, base) {
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";
@@ -1,13 +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
- * - Throws a `Promise` if the user attempts to read a store that is still loading (i.e. its value is still `NONE`).
6
- *
7
- * @example const v = this.value; // Might throw `Promise` if internal storage value is `NONE`
8
- * @example if (!this.loading) const v = this.value; // Won't throw because we know the value is loaded.
9
- */
10
- export declare class LoadingStore<T> extends Store<T | typeof NONE, T> {
11
- constructor(value?: T | typeof NONE);
12
- protected _convert(value: T | typeof NONE): T | typeof NONE;
13
- }
@@ -1,19 +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
- * - Throws a `Promise` if the user attempts to read a store that is still loading (i.e. its value is still `NONE`).
6
- *
7
- * @example const v = this.value; // Might throw `Promise` if internal storage value is `NONE`
8
- * @example if (!this.loading) const v = this.value; // Won't throw because we know the value is loaded.
9
- */
10
- export class LoadingStore extends Store {
11
- // Override to default to `NONE`
12
- constructor(value = NONE) {
13
- super(value);
14
- }
15
- // Override to passthrough.
16
- _convert(value) {
17
- return value;
18
- }
19
- }
@@ -1,5 +0,0 @@
1
- import { Store } from "./Store.js";
2
- /** Store that just stores a simple value and does no conversion. */
3
- export declare class ValueStore<T> extends Store<T, T> {
4
- protected _convert(value: T): T;
5
- }
@@ -1,8 +0,0 @@
1
- import { Store } from "./Store.js";
2
- /** Store that just stores a simple value and does no conversion. */
3
- export class ValueStore extends Store {
4
- // Override to passthrough.
5
- _convert(value) {
6
- return value;
7
- }
8
- }