shelving 1.189.0 → 1.189.2

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.0",
3
+ "version": "1.189.2",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
3
  import { Store } from "./Store.js";
5
4
  /** Store an array. */
6
5
  export declare class ArrayStore<T> extends Store<PossibleArray<T>, ImmutableArray<T>> implements Iterable<T> {
7
- constructor(value?: ImmutableArray<T> | typeof NONE);
8
- convert(value: PossibleArray<T>, caller?: AnyCaller): ImmutableArray<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. */
@@ -2,12 +2,13 @@ import { getFirst, getLast, omitArrayItems, requireArray, requireFirst, requireL
2
2
  import { Store } from "./Store.js";
3
3
  /** Store an array. */
4
4
  export class ArrayStore extends Store {
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 automatically convert `PossibleArray`
9
- convert(value, caller = this.convert) {
10
- return requireArray(value, undefined, undefined, caller);
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() {
@@ -1,8 +1,9 @@
1
1
  import { Store } from "./Store.js";
2
2
  /** Store a boolean. */
3
3
  export declare class BooleanStore extends Store<unknown, boolean> {
4
- convert(value: unknown): boolean;
4
+ constructor(value?: boolean);
5
+ protected _convert(input: unknown): boolean;
6
+ protected _equal(a: boolean, b: boolean): boolean;
5
7
  /** Toggle the current boolean value. */
6
8
  toggle(): void;
7
- isEqual(a: boolean, b: boolean): boolean;
8
9
  }
@@ -1,16 +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`
5
+ constructor(value = false) {
6
+ super(value);
7
+ }
4
8
  // Override to automatically convert to boolean.
5
- convert(value) {
6
- return !!value;
9
+ _convert(input) {
10
+ return !!input;
11
+ }
12
+ // Override for fast equality.
13
+ _equal(a, b) {
14
+ return a === b;
7
15
  }
8
16
  /** Toggle the current boolean value. */
9
17
  toggle() {
10
18
  this.value = !this.value;
11
19
  }
12
- // Override for fast equality.
13
- isEqual(a, b) {
14
- return a === b;
15
- }
16
20
  }
@@ -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 { Store } from "./Store.js";
4
+ import { ValueStore } from "./ValueStore.js";
5
5
  /** Store a data object. */
6
- export declare class DataStore<T extends Data> extends Store<T, T> {
6
+ export declare class DataStore<T extends Data> extends ValueStore<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 Store<T | undefined, T | undefined> {
19
+ export declare class OptionalDataStore<T extends Data> extends ValueStore<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
  }
@@ -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 { Store } from "./Store.js";
5
+ import { ValueStore } from "./ValueStore.js";
6
6
  /** Store a data object. */
7
- export class DataStore extends Store {
7
+ export class DataStore extends ValueStore {
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 Store {
30
+ export class OptionalDataStore extends ValueStore {
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
  }
@@ -4,7 +4,7 @@ import { Store } from "./Store.js";
4
4
  /** Store a dictionary object. */
5
5
  export declare class DictionaryStore<T> extends Store<PossibleDictionary<T>, ImmutableDictionary<T>> implements Iterable<DictionaryItem<T>> {
6
6
  constructor(value?: PossibleDictionary<T>);
7
- convert(possible: PossibleDictionary<T>): ImmutableDictionary<T>;
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. */
@@ -9,7 +9,7 @@ export class DictionaryStore extends Store {
9
9
  super(requireDictionary(value));
10
10
  }
11
11
  // Override to convert a possible dictionary to a dictionary on set.
12
- convert(possible) {
12
+ _convert(possible) {
13
13
  return requireDictionary(possible);
14
14
  }
15
15
  /** Get the length of the current value of this store. */
@@ -17,28 +17,25 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
17
17
  */
18
18
  readonly busy: BooleanStore;
19
19
  get loading(): boolean;
20
- get value(): T;
21
- set value(value: T | typeof NONE | PromiseLike<T | typeof NONE>);
20
+ protected _convert(value: T | typeof NONE): T | typeof NONE;
21
+ protected _read(value: T | typeof NONE): T;
22
22
  constructor(value: T | typeof NONE, callback?: FetchCallback<T>);
23
- convert(value: T | typeof NONE): T | typeof NONE;
24
23
  /**
25
24
  * Fetch the result for this store now.
26
25
  * - Triggered automatically when someone reads `value` or `loading`.
27
- * - Concurrent calls while a fetch is in-flight return the same promise (deduplication).
26
+ * - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
28
27
  * - 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
28
  */
33
29
  refresh(): Promise<boolean> | boolean;
34
- private _inflight;
35
- private _refresh;
30
+ private _pendingRefresh;
31
+ await(pending: PromiseLike<T>): Promise<boolean>;
36
32
  /**
37
33
  * Current `AbortSignal` for this store's in-flight fetch.
38
34
  * - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
35
+ * - Triggers `abort()` so any current awaits are cancelled.
39
36
  */
40
37
  get signal(): AbortSignal;
41
- private _controller;
38
+ private _aborter;
42
39
  /**
43
40
  * Call the fetch callback to get the next value.
44
41
  * @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
@@ -47,7 +44,7 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
47
44
  private _callback;
48
45
  /**
49
46
  * 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.
47
+ * - Triggers `abort()` so any current awaits are cancelled.
51
48
  */
52
49
  invalidate(): void;
53
50
  private _invalidation;
@@ -55,8 +52,8 @@ export declare class FetchStore<T> extends Store<T | typeof NONE, T> {
55
52
  refreshStale(maxAge: number): Promise<void>;
56
53
  /**
57
54
  * 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.
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.
60
57
  * - Any pending `await()` result will be silently discarded.
61
58
  */
62
59
  abort(): void;
@@ -16,55 +16,47 @@ export class FetchStore extends Store {
16
16
  * - `true` while a refresh is in-flight, `false` otherwise.
17
17
  * - Can be subscribed to for e.g. loading spinners.
18
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.
19
+ busy = new BooleanStore(false);
20
+ // 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.
22
22
  get loading() {
23
23
  const loading = super.loading;
24
24
  if (loading || this._invalidation)
25
25
  void this.refresh();
26
26
  return loading;
27
27
  }
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.
30
- get value() {
31
- if (super.loading)
32
- void this.refresh();
33
- return super.value;
28
+ // Override to reset the invalidation state.
29
+ // Setting a value means this store is no longer invalid.
30
+ _convert(value) {
31
+ if (!isAsync(value))
32
+ this._invalidation = 0;
33
+ return value;
34
34
  }
35
- set value(value) {
36
- super.value = value; // calls Store.set value() which calls this.abort() then _applyValue()
37
- // Setting a value resets the invalid state.
38
- this._invalidation = 0;
35
+ // 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);
39
41
  }
42
+ // Override to create to save `callback`
40
43
  constructor(value, callback) {
41
44
  super(value);
42
- this.busy = new BooleanStore(value === NONE);
43
45
  this._callback = callback;
44
46
  }
45
- // Implement to allow `NONE`
46
- convert(value) {
47
- return value;
48
- }
49
47
  /**
50
48
  * Fetch the result for this store now.
51
49
  * - Triggered automatically when someone reads `value` or `loading`.
52
- * - Concurrent calls while a fetch is in-flight return the same promise (deduplication).
50
+ * - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
53
51
  * - 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
52
  */
58
53
  refresh() {
59
- if (this._inflight)
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();
54
+ if (this._pendingRefresh)
55
+ return this._pendingRefresh;
64
56
  try {
65
- const value = this._fetch(this._controller.signal);
57
+ const value = this._fetch(this.signal); // Retrieving a new signal calls `abort()` which cancels the previous one.
66
58
  if (isAsync(value))
67
- return (this._inflight = this._refresh(value));
59
+ return (this._pendingRefresh = this.await(value));
68
60
  this.value = value;
69
61
  return true;
70
62
  }
@@ -73,28 +65,24 @@ export class FetchStore extends Store {
73
65
  return false;
74
66
  }
75
67
  }
76
- _inflight = undefined;
77
- async _refresh(asyncValue) {
68
+ _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) {
78
72
  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
- }
73
+ return super.await(pending);
89
74
  }
90
75
  /**
91
76
  * Current `AbortSignal` for this store's in-flight fetch.
92
77
  * - Created lazily; a new signal is issued each time `refresh()` starts a new fetch or `abort()` is called.
78
+ * - Triggers `abort()` so any current awaits are cancelled.
93
79
  */
94
80
  get signal() {
95
- return (this._controller ||= new AbortController()).signal;
81
+ this.abort();
82
+ this._aborter = new AbortController();
83
+ return this._aborter.signal;
96
84
  }
97
- _controller;
85
+ _aborter;
98
86
  /**
99
87
  * Call the fetch callback to get the next value.
100
88
  * @param signal `AbortSignal` for the current fetch — passed through to the callback so it can cancel HTTP requests etc.
@@ -107,7 +95,7 @@ export class FetchStore extends Store {
107
95
  _callback;
108
96
  /**
109
97
  * 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.
98
+ * - Triggers `abort()` so any current awaits are cancelled.
111
99
  */
112
100
  invalidate() {
113
101
  this.abort();
@@ -121,14 +109,14 @@ export class FetchStore extends Store {
121
109
  }
122
110
  /**
123
111
  * 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.
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.
126
114
  * - Any pending `await()` result will be silently discarded.
127
115
  */
128
116
  abort() {
129
- this._controller?.abort(ABORT);
130
- this._controller = undefined;
131
- this._inflight = undefined;
117
+ this._aborter?.abort(ABORT);
118
+ this._aborter = undefined;
119
+ this._pendingRefresh = undefined;
132
120
  this.busy.value = false;
133
121
  super.abort(); // clears _pendingValue
134
122
  }
@@ -2,9 +2,12 @@ import { NONE } from "../util/constants.js";
2
2
  import { Store } from "./Store.js";
3
3
  /**
4
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.
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.
6
9
  */
7
- export declare class LoadingStore<T> extends Store<T, T | typeof NONE> {
10
+ export declare class LoadingStore<T> extends Store<T | typeof NONE, T> {
8
11
  constructor(value?: T | typeof NONE);
9
- convert(value: T | typeof NONE): T | typeof NONE;
12
+ protected _convert(value: T | typeof NONE): T | typeof NONE;
10
13
  }
@@ -2,15 +2,18 @@ import { NONE } from "../util/constants.js";
2
2
  import { Store } from "./Store.js";
3
3
  /**
4
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.
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.
6
9
  */
7
10
  export class LoadingStore extends Store {
8
11
  // Override to default to `NONE`
9
12
  constructor(value = NONE) {
10
13
  super(value);
11
14
  }
12
- // Implement to allow `NONE`
13
- convert(value) {
15
+ // Override to passthrough.
16
+ _convert(value) {
14
17
  return value;
15
18
  }
16
19
  }
@@ -1,11 +1,17 @@
1
1
  import type { AnyCaller } from "../util/function.js";
2
2
  import type { AbsolutePath, PossiblePath } from "../util/path.js";
3
3
  import { Store } from "./Store.js";
4
- /** Store an absolute path, e.g. `/a/b/c` */
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
+ */
5
10
  export declare class PathStore extends Store<PossiblePath, AbsolutePath> {
6
11
  readonly base: AbsolutePath;
7
12
  constructor(path?: string, base?: AbsolutePath);
8
- convert(possible: PossiblePath, caller: AnyCaller): AbsolutePath;
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)? */
@@ -1,6 +1,11 @@
1
1
  import { isPathActive, isPathProud, requirePath } from "../util/path.js";
2
2
  import { Store } from "./Store.js";
3
- /** Store an absolute path, e.g. `/a/b/c` */
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
+ */
4
9
  export class PathStore extends Store {
5
10
  base;
6
11
  // Override to set default path to `.` and base to `/`
@@ -8,10 +13,14 @@ export class PathStore extends Store {
8
13
  super(requirePath(path, base, PathStore));
9
14
  this.base = base;
10
15
  }
11
- // Implement to convert a possible path to an absolute path (relative to `this.base`).
12
- convert(possible, caller) {
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);
package/store/Store.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DeferredSequence } from "../sequence/DeferredSequence.js";
2
- import { NONE, SKIP } from "../util/constants.js";
2
+ import { NONE } 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. */
@@ -35,13 +35,6 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
35
35
  * - Calling `this.loading` is a way to check if this store has a value without triggering those throws.
36
36
  */
37
37
  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
38
  /**
46
39
  * Set the value of this store.
47
40
  * - Sets any sync values.
@@ -51,10 +44,29 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
51
44
  * - Setting value to the same as the existing value
52
45
  * - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
53
46
  */
54
- set value(value: I | PromiseLike<I>);
47
+ set value(input: I | PromiseLike<I>);
48
+ /**
49
+ * Convert input type to internal storage type.
50
+ * - Called only while getting values.
51
+ * - Override in subclasses to change getting behaviour.
52
+ */
53
+ protected abstract _convert(value: I, caller?: AnyCaller): O | typeof NONE;
54
+ /** Internal storage for current value. */
55
55
  private _value;
56
- /** Convert a possible value for this store into an actual value of this store. */
57
- abstract convert(possible: I, _caller?: AnyCaller): O | typeof NONE | typeof SKIP;
56
+ /** Compare two values for this store and return whether they are equal. */
57
+ protected _equal(a: O, b: O): boolean;
58
+ /**
59
+ * Get the current value of this store.
60
+ *
61
+ * @throws {Promise} if this store currently is in a "loading" state (resolves when a value is set).
62
+ * @throws {unknown} if this store currently has an error.
63
+ */
64
+ get value(): O;
65
+ /**
66
+ * Called while reading values. Can be used to override get behaviour.
67
+ * - Override in subclasses to change getting behaviour.
68
+ */
69
+ protected _read(value: O | typeof NONE): O;
58
70
  /**
59
71
  * Time (in milliseconds) this store was last updated with a new value.
60
72
  * - Will be `undefined` if the value is still loading.
@@ -76,10 +88,11 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
76
88
  * Set a starter for this store to allow a function to execute when this store has subscribers or not.
77
89
  *
78
90
  * @todo DH: Change this significantly. Not happy with how it's settable like this. It should be set in `constructor()`?
91
+ * - Also would love some internal hooks
79
92
  */
80
93
  protected set starter(start: PossibleStarter<[this]>);
81
94
  private _starter;
82
- /** Store is initiated with an initial store. */
95
+ /** Store is initiated with a value, or `NONE` to put it in a "loading" state. */
83
96
  constructor(value: O | typeof NONE);
84
97
  /** Set the value of this store as values are pulled from a sequence. */
85
98
  through(sequence: AsyncIterable<I>): AsyncIterable<I>;
@@ -87,7 +100,17 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
87
100
  * Call a callback that returns a new value (possibly async) for this store.
88
101
  * - Errors are stored as `reason`; never throws.
89
102
  *
90
- * @returns `true` if the value was applied, `false` if an error occurred or the result was superseded.
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.
91
114
  */
92
115
  call<A extends Arguments>(callback: StoreCallback<I, A>, ...args: A): Promise<boolean> | boolean;
93
116
  /**
@@ -97,10 +120,14 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
97
120
  * @param value The current value of this store.
98
121
  * @param args Any additional input values for the reducer.
99
122
  * @returns New value for this store (possibly async).
100
- * @oaram args Any arguments to pass to the callback.
123
+ * @param args Any arguments to pass to the callback.
101
124
  *
102
- * @throws {Promise} if this store currently has no value (resolves when this store receives its next value or error).
103
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.
104
131
  */
105
132
  reduce<A extends Arguments>(reducer: StoreReducer<I, O, A>, ...args: A): Promise<boolean> | boolean;
106
133
  /**
@@ -111,11 +138,17 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
111
138
  * - Silently discarded if `await()` is called again.
112
139
  * - Silently discarded if `abort()` is called.
113
140
  *
114
- * @returns `true` if the value was applied, `false` if superseded, aborted, or errored.
141
+ * @param pending The pending value to await.
142
+ *
143
+ * @returns {true} If the callback returned a value and it was set.
144
+ * @returns {false} If the callback threw.
145
+ * @returns {Promise<true>} If the callback returned a promise and it resolved.
146
+ * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
147
+ *
115
148
  * @throws {never} Never throws — safe to call without handling the return value.
116
149
  */
117
150
  await(pending: PromiseLike<I>): Promise<boolean>;
118
- private _pending;
151
+ private _pendingValue;
119
152
  /**
120
153
  * Abort any current pending `await()` call.
121
154
  * - The pending call's result will be silently discarded and its error will not be stored.
@@ -123,7 +156,5 @@ export declare abstract class Store<I, O> implements AsyncIterable<O, void, void
123
156
  abort(): void;
124
157
  [Symbol.asyncIterator](): AsyncIterator<O, void, void>;
125
158
  private _iterating;
126
- /** Compare two values for this store and return whether they are equal. */
127
- isEqual(a: O, b: O): boolean;
128
159
  [Symbol.asyncDispose](): Promise<void>;
129
160
  }
package/store/Store.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { DeferredSequence } from "../sequence/DeferredSequence.js";
2
2
  import { isAsync } from "../util/async.js";
3
- import { getSetter } from "../util/class.js";
4
- import { NONE, SKIP } from "../util/constants.js";
3
+ import { NONE } from "../util/constants.js";
5
4
  import { awaitDispose } from "../util/dispose.js";
6
5
  import { isDeepEqual } from "../util/equal.js";
7
6
  import { getStarter } from "../util/start.js";
@@ -32,20 +31,7 @@ export class Store {
32
31
  * - Calling `this.loading` is a way to check if this store has a value without triggering those throws.
33
32
  */
34
33
  get loading() {
35
- return this._value === NONE && this._reason === undefined;
36
- }
37
- /**
38
- * Get the current value of this store.
39
- *
40
- * @throws {Promise} if this store currently has no value (resolves when this store receives its next value or error).
41
- * @throws {unknown} if this store currently has an error.
42
- */
43
- get value() {
44
- if (this._reason !== undefined)
45
- throw this._reason;
46
- if (this._value === NONE)
47
- throw this.next;
48
- return this._value;
34
+ return this._value === NONE && this.reason === undefined;
49
35
  }
50
36
  /**
51
37
  * Set the value of this store.
@@ -56,34 +42,54 @@ export class Store {
56
42
  * - Setting value to the same as the existing value
57
43
  * - If this store has any pending `await()` calls they are aborted and their results are silently discarded.
58
44
  */
59
- set value(value) {
60
- if (isAsync(value)) {
61
- void this.await(value);
45
+ set value(input) {
46
+ if (isAsync(input)) {
47
+ void this.await(input);
62
48
  }
63
49
  else {
64
50
  this.abort();
65
51
  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.
52
+ const storage = this._convert(input);
53
+ if (storage === NONE) {
54
+ // Put the store into a loading state.
71
55
  this._time = undefined;
72
- this._value = v;
56
+ this._value = storage;
73
57
  this.next.cancel();
74
58
  }
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.
59
+ else if (this._value === NONE || !this._equal(storage, this._value)) {
60
+ // Set this changed value.
80
61
  this._time = Date.now();
81
- this._value = v;
82
- this.next.resolve(v);
62
+ this._value = storage;
63
+ this.next.resolve(this._read(storage));
83
64
  }
84
65
  }
85
66
  }
67
+ /** Internal storage for current value. */
86
68
  _value;
69
+ /** Compare two values for this store and return whether they are equal. */
70
+ _equal(a, b) {
71
+ return isDeepEqual(a, b);
72
+ }
73
+ /**
74
+ * Get the current value of this store.
75
+ *
76
+ * @throws {Promise} if this store currently is in a "loading" state (resolves when a value is set).
77
+ * @throws {unknown} if this store currently has an error.
78
+ */
79
+ get value() {
80
+ if (this._reason !== undefined)
81
+ throw this._reason;
82
+ return this._read(this._value);
83
+ }
84
+ /**
85
+ * Called while reading values. Can be used to override get behaviour.
86
+ * - Override in subclasses to change getting behaviour.
87
+ */
88
+ _read(value) {
89
+ if (value === NONE)
90
+ throw this.next;
91
+ return value;
92
+ }
87
93
  /**
88
94
  * Time (in milliseconds) this store was last updated with a new value.
89
95
  * - Will be `undefined` if the value is still loading.
@@ -117,6 +123,7 @@ export class Store {
117
123
  * Set a starter for this store to allow a function to execute when this store has subscribers or not.
118
124
  *
119
125
  * @todo DH: Change this significantly. Not happy with how it's settable like this. It should be set in `constructor()`?
126
+ * - Also would love some internal hooks
120
127
  */
121
128
  set starter(start) {
122
129
  if (this._starter)
@@ -126,7 +133,7 @@ export class Store {
126
133
  this._starter.start(this); // Start the new starter if we're already iterating.
127
134
  }
128
135
  _starter;
129
- /** Store is initiated with an initial store. */
136
+ /** Store is initiated with a value, or `NONE` to put it in a "loading" state. */
130
137
  constructor(value) {
131
138
  this._value = value;
132
139
  this._time = value === NONE ? -Infinity : Date.now();
@@ -142,7 +149,17 @@ export class Store {
142
149
  * Call a callback that returns a new value (possibly async) for this store.
143
150
  * - Errors are stored as `reason`; never throws.
144
151
  *
145
- * @returns `true` if the value was applied, `false` if an error occurred or the result was superseded.
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.
146
163
  */
147
164
  call(callback, ...args) {
148
165
  try {
@@ -164,10 +181,14 @@ export class Store {
164
181
  * @param value The current value of this store.
165
182
  * @param args Any additional input values for the reducer.
166
183
  * @returns New value for this store (possibly async).
167
- * @oaram args Any arguments to pass to the callback.
184
+ * @param args Any arguments to pass to the callback.
168
185
  *
169
- * @throws {Promise} if this store currently has no value (resolves when this store receives its next value or error).
170
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.
171
192
  */
172
193
  reduce(reducer, ...args) {
173
194
  return this.call(reducer, this.value, ...args);
@@ -180,36 +201,42 @@ export class Store {
180
201
  * - Silently discarded if `await()` is called again.
181
202
  * - Silently discarded if `abort()` is called.
182
203
  *
183
- * @returns `true` if the value was applied, `false` if superseded, aborted, or errored.
204
+ * @param pending The pending value to await.
205
+ *
206
+ * @returns {true} If the callback returned a value and it was set.
207
+ * @returns {false} If the callback threw.
208
+ * @returns {Promise<true>} If the callback returned a promise and it resolved.
209
+ * @returns {Promise<false>} If the callback returned a promise and it rejected, or `abort()` was called before it resolved.
210
+ *
184
211
  * @throws {never} Never throws — safe to call without handling the return value.
185
212
  */
186
213
  async await(pending) {
187
214
  // Keep track of the value that is being awaited.
188
215
  // 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
216
  // If that happens we silently discard the resolved value/reason of this await call.
190
- this._pending = pending;
217
+ this._pendingValue = pending;
191
218
  try {
192
219
  const value = await pending;
193
- if (this._pending === pending) {
220
+ if (this._pendingValue === pending) {
194
221
  this.value = value;
195
222
  return true;
196
223
  }
197
224
  return false;
198
225
  }
199
226
  catch (reason) {
200
- if (this._pending === pending) {
227
+ if (this._pendingValue === pending) {
201
228
  this.reason = reason;
202
229
  }
203
230
  return false;
204
231
  }
205
232
  }
206
- _pending = undefined;
233
+ _pendingValue = undefined;
207
234
  /**
208
235
  * Abort any current pending `await()` call.
209
236
  * - The pending call's result will be silently discarded and its error will not be stored.
210
237
  */
211
238
  abort() {
212
- this._pending = undefined;
239
+ this._pendingValue = undefined;
213
240
  }
214
241
  // Implement `AsyncIterator`
215
242
  // Issues the current value of this store first, then any subsequent values that are issued.
@@ -218,10 +245,11 @@ export class Store {
218
245
  this._starter?.start(this);
219
246
  this._iterating++;
220
247
  try {
221
- if (this._reason !== undefined)
222
- throw this._reason;
223
- if (this._value !== NONE)
224
- yield this._value;
248
+ const reason = this.reason;
249
+ if (reason !== undefined)
250
+ throw reason;
251
+ if (!this.loading)
252
+ yield this.value;
225
253
  yield* this.next;
226
254
  }
227
255
  finally {
@@ -231,10 +259,6 @@ export class Store {
231
259
  }
232
260
  }
233
261
  _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
262
  // Implement `AsyncDisposable`
239
263
  async [Symbol.asyncDispose]() {
240
264
  await awaitDispose(this._starter, // Stop the starter.
@@ -6,7 +6,8 @@ import { Store } from "./Store.js";
6
6
  export declare class URLStore extends Store<PossibleURL, URL> {
7
7
  readonly base: URL | undefined;
8
8
  constructor(url: PossibleURL, base?: PossibleURL);
9
- convert(value: PossibleURL, caller?: AnyCaller): URL;
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
@@ -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
- convert(value, caller = this.convert) {
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
  }
@@ -1,5 +1,5 @@
1
1
  import { Store } from "./Store.js";
2
- /** Store that just stores a simple value. */
2
+ /** Store that just stores a simple value and does no conversion. */
3
3
  export declare class ValueStore<T> extends Store<T, T> {
4
- convert(value: T): T;
4
+ protected _convert(value: T): T;
5
5
  }
@@ -1,8 +1,8 @@
1
1
  import { Store } from "./Store.js";
2
- /** Store that just stores a simple value. */
2
+ /** Store that just stores a simple value and does no conversion. */
3
3
  export class ValueStore extends Store {
4
- // Implement to passthrough.
5
- convert(value) {
4
+ // Override to passthrough.
5
+ _convert(value) {
6
6
  return value;
7
7
  }
8
8
  }