shelving 1.194.0 → 1.195.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { AnyCaller } from "../../util/function.js";
1
2
  import type { Endpoint } from "../endpoint/Endpoint.js";
2
3
  import type { APIProvider } from "../provider/APIProvider.js";
3
4
  import { EndpointCache } from "./EndpointCache.js";
@@ -12,13 +13,20 @@ export declare class APICache<P, R> implements AsyncDisposable {
12
13
  private _get;
13
14
  /** Get (or create) the `EndpointCache` for the given endpoint. */
14
15
  get<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>): EndpointCache<PP, RR>;
16
+ /**
17
+ * Fetch (or return a cached result) for the given endpoint and payload.
18
+ * - Returns the cached value immediately if one exists.
19
+ * - Waits for the in-flight fetch if the store is loading.
20
+ * - Throws if the fetch fails, matching `APIProvider.call` behaviour.
21
+ */
22
+ call<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, maxAge?: number, caller?: AnyCaller): Promise<RR>;
15
23
  /** Invalidate a specific store for an endpoint. */
16
24
  invalidate<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP): void;
17
25
  /** Invalidate all stores for an endpoint. */
18
26
  invalidateAll<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>): void;
19
27
  /** Trigger a refetch on a specific store for an endpoint. */
20
- refresh<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP): void;
28
+ refresh<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, maxAge?: number): void;
21
29
  /** Trigger a refetch on all stores for an endpoint. */
22
- refreshAll<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>): void;
30
+ refreshAll<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, maxAge?: number): void;
23
31
  [Symbol.asyncDispose](): Promise<void>;
24
32
  }
@@ -17,6 +17,15 @@ export class APICache {
17
17
  get(endpoint) {
18
18
  return this._get(endpoint) || setMapItem(this._endpoints, endpoint, new EndpointCache(endpoint, this.provider));
19
19
  }
20
+ /**
21
+ * Fetch (or return a cached result) for the given endpoint and payload.
22
+ * - Returns the cached value immediately if one exists.
23
+ * - Waits for the in-flight fetch if the store is loading.
24
+ * - Throws if the fetch fails, matching `APIProvider.call` behaviour.
25
+ */
26
+ async call(endpoint, payload, maxAge, caller = this.call) {
27
+ return this.get(endpoint).call(payload, maxAge, caller);
28
+ }
20
29
  /** Invalidate a specific store for an endpoint. */
21
30
  invalidate(endpoint, payload) {
22
31
  this._get(endpoint)?.invalidate(payload);
@@ -26,12 +35,12 @@ export class APICache {
26
35
  this._get(endpoint)?.invalidateAll();
27
36
  }
28
37
  /** Trigger a refetch on a specific store for an endpoint. */
29
- refresh(endpoint, payload) {
30
- this._get(endpoint)?.refresh(payload);
38
+ refresh(endpoint, payload, maxAge) {
39
+ this._get(endpoint)?.refresh(payload, maxAge);
31
40
  }
32
41
  /** Trigger a refetch on all stores for an endpoint. */
33
- refreshAll(endpoint) {
34
- this._get(endpoint)?.refreshAll();
42
+ refreshAll(endpoint, maxAge) {
43
+ this._get(endpoint)?.refreshAll(maxAge);
35
44
  }
36
45
  // Implement `AsyncDisposable`
37
46
  [Symbol.asyncDispose]() {
@@ -13,13 +13,20 @@ export declare class EndpointCache<P = unknown, R = unknown> implements AsyncDis
13
13
  constructor(endpoint: Endpoint<P, R>, provider: APIProvider<P, R>);
14
14
  /** Get (or create) the `EndpointStore` for the given payload. */
15
15
  get(payload: P, caller?: AnyCaller): EndpointStore<P, R>;
16
+ /**
17
+ * Fetch (or return a cached result) for the given payload.
18
+ * - Returns the cached value immediately if one exists.
19
+ * - Waits for the in-flight fetch if the store is loading.
20
+ * - Throws if the fetch fails, matching `APIProvider.call` behaviour.
21
+ */
22
+ call(payload: P, maxAge?: number, caller?: AnyCaller): Promise<R>;
16
23
  /** Invalidate a specific store. */
17
24
  invalidate(payload: P, caller?: AnyCaller): void;
18
25
  /** Invalidate all stores. */
19
26
  invalidateAll(): void;
20
27
  /** Trigger a refetch on a specific store. */
21
- refresh(payload: P, caller?: AnyCaller): Promise<void>;
28
+ refresh(payload: P, maxAge?: number, caller?: AnyCaller): Promise<void>;
22
29
  /** Trigger a refetch on all stores. */
23
- refreshAll(): Promise<void>;
30
+ refreshAll(maxAge?: number): Promise<void>;
24
31
  [Symbol.asyncDispose](): Promise<void>;
25
32
  }
@@ -19,6 +19,17 @@ export class EndpointCache {
19
19
  const url = this.provider.renderURL(this.endpoint, payload, caller).href;
20
20
  return this._endpoints.get(url) || setMapItem(this._endpoints, url, new EndpointStore(this.endpoint, payload, this.provider));
21
21
  }
22
+ /**
23
+ * Fetch (or return a cached result) for the given payload.
24
+ * - Returns the cached value immediately if one exists.
25
+ * - Waits for the in-flight fetch if the store is loading.
26
+ * - Throws if the fetch fails, matching `APIProvider.call` behaviour.
27
+ */
28
+ async call(payload, maxAge, caller = this.call) {
29
+ const store = this.get(payload, caller);
30
+ await store.refresh(maxAge);
31
+ return store.value;
32
+ }
22
33
  /** Invalidate a specific store. */
23
34
  invalidate(payload, caller = this.invalidate) {
24
35
  this.get(payload, caller)?.invalidate();
@@ -29,12 +40,12 @@ export class EndpointCache {
29
40
  store.invalidate();
30
41
  }
31
42
  /** Trigger a refetch on a specific store. */
32
- async refresh(payload, caller = this.invalidate) {
33
- await this.get(payload, caller)?.refresh();
43
+ async refresh(payload, maxAge, caller = this.invalidate) {
44
+ await this.get(payload, caller)?.refresh(maxAge);
34
45
  }
35
46
  /** Trigger a refetch on all stores. */
36
- async refreshAll() {
37
- await awaitValues(...this._endpoints.values().map(store => store.refresh()));
47
+ async refreshAll(maxAge) {
48
+ await awaitValues(...this._endpoints.values().map(store => store.refresh(maxAge)));
38
49
  }
39
50
  // Implement `AsyncDisposable`
40
51
  [Symbol.asyncDispose]() {
@@ -2,7 +2,9 @@ import type { AnyCaller } from "../../util/function.js";
2
2
  import type { RequestOptions } from "../../util/http.js";
3
3
  import type { Endpoint } from "../endpoint/Endpoint.js";
4
4
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
5
- /** Provider that logs API operations to the console. */
5
+ /** Provider that logs operations to the console. */
6
6
  export declare class DebugAPIProvider<P, R> extends ThroughAPIProvider<P, R> {
7
- call<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Promise<RR>;
7
+ getRequest<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, payload: PP, options?: RequestOptions, caller?: AnyCaller): Request;
8
+ fetch(request: Request): Promise<Response>;
9
+ parseResponse<PP extends P, RR extends R>(endpoint: Endpoint<PP, RR>, response: Response, caller?: AnyCaller): Promise<RR>;
8
10
  }
@@ -1,37 +1,43 @@
1
1
  import { debugFullRequest, debugFullResponse } from "../../util/debug.js";
2
2
  import { ThroughAPIProvider } from "./ThroughAPIProvider.js";
3
- /** Provider that logs API operations to the console. */
3
+ /** Provider that logs operations to the console. */
4
4
  export class DebugAPIProvider extends ThroughAPIProvider {
5
- async call(endpoint, payload, options, caller = this.call) {
6
- // Turn the payload into a request and debug it before sending.
7
- let request;
5
+ getRequest(endpoint, payload, options, caller = this.getRequest) {
6
+ const url = this.url.toString();
7
+ const ep = endpoint.toString();
8
8
  try {
9
- request = this.getRequest(endpoint, payload, options, caller);
9
+ const request = super.getRequest(endpoint, payload, options, caller);
10
+ console.debug("✔ REQUEST", url, ep, payload);
11
+ return request;
10
12
  }
11
13
  catch (reason) {
12
- console.error("✘ FETCH", this.url.toString(), endpoint.toString(), payload, reason);
14
+ console.error("✘ REQUEST", url, ep, payload, reason);
13
15
  throw reason;
14
16
  }
15
- const debuggedRequest = await debugFullRequest(request);
16
- console.debug("… FETCH", this.url.toString(), endpoint.toString(), payload, debuggedRequest);
17
- // Fetch the response and debug if it throws.
18
- let response;
17
+ }
18
+ async fetch(request) {
19
+ const url = this.url.toString();
19
20
  try {
20
- response = await this.fetch(request);
21
+ console.error("→ FETCH", url, await debugFullRequest(request));
22
+ const response = await super.fetch(request);
23
+ console.error("← FETCH", url, await debugFullResponse(response));
24
+ return response;
21
25
  }
22
26
  catch (reason) {
23
- console.error("✘ FETCH", this.url.toString(), endpoint.toString(), payload, debuggedRequest, reason);
27
+ console.error("✘ FETCH", url, reason);
24
28
  throw reason;
25
29
  }
26
- const debuggedResponse = await debugFullResponse(response);
27
- // Convert the result or any parsing error.
30
+ }
31
+ async parseResponse(endpoint, response, caller = this.parseResponse) {
32
+ const url = this.url.toString();
33
+ const ep = endpoint.toString();
28
34
  try {
29
- const result = await this.parseResponse(endpoint, response, caller);
30
- console.debug("✔ FETCH", this.url.toString(), endpoint.toString(), payload, debuggedRequest, debuggedResponse, result);
35
+ const result = await super.parseResponse(endpoint, response, caller);
36
+ console.debug("✔ RESPONSE", url, ep, result);
31
37
  return result;
32
38
  }
33
39
  catch (reason) {
34
- console.error("✘ FETCH", this.url.toString(), endpoint.toString(), payload, debuggedRequest, debuggedResponse, reason);
40
+ console.error("✘ RESPONSE", url, ep, reason);
35
41
  throw reason;
36
42
  }
37
43
  }
@@ -23,15 +23,15 @@ export declare class CollectionCache<I extends Identifier, T extends Data> imple
23
23
  /** Get (or create) the `QueryStore` for the given query. */
24
24
  getQuery(query: Query<Item<I, T>>): QueryStore<I, T>;
25
25
  /** Refresh a specific item store. */
26
- refreshItem(id: I): Promise<void>;
26
+ refreshItem(id: I, maxAge?: number): Promise<void>;
27
27
  /** Refresh every cached item store. */
28
- refreshItems(): Promise<void>;
28
+ refreshItems(maxAge?: number): Promise<void>;
29
29
  /** Refresh a specific query store. */
30
- refreshQuery(query: Query<Item<I, T>>): Promise<void>;
30
+ refreshQuery(query: Query<Item<I, T>>, maxAge?: number): Promise<void>;
31
31
  /** Refresh every cached query store. */
32
- refreshQueries(): Promise<void>;
32
+ refreshQueries(maxAge?: number): Promise<void>;
33
33
  /** Refresh every cached store (items and queries). */
34
- refreshAll(): Promise<void>;
34
+ refreshAll(maxAge?: number): Promise<void>;
35
35
  private _queryKey;
36
36
  [Symbol.asyncDispose](): Promise<void>;
37
37
  }
@@ -29,24 +29,24 @@ export class CollectionCache {
29
29
  return this._queries.get(key) || setMapItem(this._queries, key, new QueryStore(this.collection, query, this.provider, this.memory));
30
30
  }
31
31
  /** Refresh a specific item store. */
32
- async refreshItem(id) {
33
- await this._items.get(id)?.refresh();
32
+ async refreshItem(id, maxAge) {
33
+ await this._items.get(id)?.refresh(maxAge);
34
34
  }
35
35
  /** Refresh every cached item store. */
36
- async refreshItems() {
37
- await awaitValues(...this._items.values().map(store => store.refresh()));
36
+ async refreshItems(maxAge) {
37
+ await awaitValues(...this._items.values().map(store => store.refresh(maxAge)));
38
38
  }
39
39
  /** Refresh a specific query store. */
40
- async refreshQuery(query) {
41
- await this._queries.get(this._queryKey(query))?.refresh();
40
+ async refreshQuery(query, maxAge) {
41
+ await this._queries.get(this._queryKey(query))?.refresh(maxAge);
42
42
  }
43
43
  /** Refresh every cached query store. */
44
- async refreshQueries() {
45
- await awaitValues(...this._queries.values().map(store => store.refresh()));
44
+ async refreshQueries(maxAge) {
45
+ await awaitValues(...this._queries.values().map(store => store.refresh(maxAge)));
46
46
  }
47
47
  /** Refresh every cached store (items and queries). */
48
- async refreshAll() {
49
- await awaitValues(this.refreshItems(), this.refreshQueries());
48
+ async refreshAll(maxAge) {
49
+ await awaitValues(this.refreshItems(maxAge), this.refreshQueries(maxAge));
50
50
  }
51
51
  _queryKey(query) {
52
52
  return JSON.stringify(query);
@@ -25,14 +25,14 @@ export declare class DBCache<I extends Identifier = Identifier, T extends Data =
25
25
  /** Get (or create) a `QueryStore` for a collection/query in one hop. */
26
26
  getQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): QueryStore<II, TT>;
27
27
  /** Refresh a specific item store for a collection. */
28
- refreshItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<void>;
28
+ refreshItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II, maxAge?: number): Promise<void>;
29
29
  /** Refresh every cached item store for a collection. */
30
- refreshItems<II extends I, TT extends T>(collection: Collection<string, II, TT>): Promise<void>;
30
+ refreshItems<II extends I, TT extends T>(collection: Collection<string, II, TT>, maxAge?: number): Promise<void>;
31
31
  /** Refresh a specific query store for a collection. */
32
- refreshQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>): Promise<void>;
32
+ refreshQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, query: Query<Item<II, TT>>, maxAge?: number): Promise<void>;
33
33
  /** Refresh every cached query store for a collection. */
34
- refreshQueries<II extends I, TT extends T>(collection: Collection<string, II, TT>): Promise<void>;
34
+ refreshQueries<II extends I, TT extends T>(collection: Collection<string, II, TT>, maxAge?: number): Promise<void>;
35
35
  /** Refresh every cached store (items and queries) for a collection. */
36
- refreshAll<II extends I, TT extends T>(collection: Collection<string, II, TT>): Promise<void>;
36
+ refreshAll<II extends I, TT extends T>(collection: Collection<string, II, TT>, maxAge?: number): Promise<void>;
37
37
  [Symbol.asyncDispose](): Promise<void>;
38
38
  }
@@ -32,24 +32,24 @@ export class DBCache {
32
32
  return this.get(collection).getQuery(query);
33
33
  }
34
34
  /** Refresh a specific item store for a collection. */
35
- async refreshItem(collection, id) {
36
- await this._get(collection)?.refreshItem(id);
35
+ async refreshItem(collection, id, maxAge) {
36
+ await this._get(collection)?.refreshItem(id, maxAge);
37
37
  }
38
38
  /** Refresh every cached item store for a collection. */
39
- async refreshItems(collection) {
40
- await this._get(collection)?.refreshItems();
39
+ async refreshItems(collection, maxAge) {
40
+ await this._get(collection)?.refreshItems(maxAge);
41
41
  }
42
42
  /** Refresh a specific query store for a collection. */
43
- async refreshQuery(collection, query) {
44
- await this._get(collection)?.refreshQuery(query);
43
+ async refreshQuery(collection, query, maxAge) {
44
+ await this._get(collection)?.refreshQuery(query, maxAge);
45
45
  }
46
46
  /** Refresh every cached query store for a collection. */
47
- async refreshQueries(collection) {
48
- await this._get(collection)?.refreshQueries();
47
+ async refreshQueries(collection, maxAge) {
48
+ await this._get(collection)?.refreshQueries(maxAge);
49
49
  }
50
50
  /** Refresh every cached store (items and queries) for a collection. */
51
- async refreshAll(collection) {
52
- await this._get(collection)?.refreshAll();
51
+ async refreshAll(collection, maxAge) {
52
+ await this._get(collection)?.refreshAll(maxAge);
53
53
  }
54
54
  // Implement `AsyncDisposable`
55
55
  async [Symbol.asyncDispose]() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.194.0",
3
+ "version": "1.195.1",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,12 +9,12 @@
9
9
  "main": "./index.js",
10
10
  "module": "./index.js",
11
11
  "devDependencies": {
12
- "@biomejs/biome": "^2.4.13",
12
+ "@biomejs/biome": "^2.4.14",
13
13
  "@google-cloud/firestore": "^8.5.0",
14
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.20260421.2",
17
+ "@typescript/native-preview": "^7.0.0-dev.20260502.1",
18
18
  "firebase": "^12.12.1",
19
19
  "react": "^19.2.5",
20
20
  "react-dom": "^19.2.5"
@@ -14,6 +14,7 @@ export declare class FetchStore<T, TT = T> extends BusyStore<T, TT> {
14
14
  get loading(): boolean;
15
15
  write(input: StoreInput<TT>): void;
16
16
  read(): import("./Store.js").StoreInternal<T>;
17
+ get age(): number;
17
18
  constructor(value: T | typeof NONE, callback?: FetchCallback<TT>);
18
19
  /**
19
20
  * Fetch the result for this store now.
@@ -21,7 +22,7 @@ export declare class FetchStore<T, TT = T> extends BusyStore<T, TT> {
21
22
  * - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
22
23
  * - Never throws — errors are stored as `reason`.
23
24
  */
24
- refresh(): Promise<boolean> | boolean;
25
+ refresh(maxAge?: number): Promise<boolean> | boolean;
25
26
  private _pendingRefresh;
26
27
  /**
27
28
  * Current `AbortSignal` for this store's in-flight fetch.
@@ -36,13 +37,13 @@ export declare class FetchStore<T, TT = T> extends BusyStore<T, TT> {
36
37
  */
37
38
  protected _fetch(signal: AbortSignal): TT | PromiseLike<TT>;
38
39
  private _callback;
40
+ /** Whether this store is has currently been invalidated and needs a refresh. */
41
+ get invalidated(): boolean;
39
42
  /**
40
43
  * Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
41
44
  * - Triggers `abort()` so any current awaits are cancelled.
42
45
  */
43
46
  invalidate(): void;
44
47
  private _invalidation;
45
- /** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
46
- refreshStale(maxAge: number): Promise<boolean> | boolean;
47
48
  abort(): void;
48
49
  }
@@ -15,7 +15,7 @@ export class FetchStore extends BusyStore {
15
15
  // We optimistically refresh so the value is available the next time the user wants it.
16
16
  get loading() {
17
17
  const loading = super.loading;
18
- if (this._invalidation || loading)
18
+ if (this.invalidated || loading)
19
19
  void this.refresh();
20
20
  return loading;
21
21
  }
@@ -33,6 +33,10 @@ export class FetchStore extends BusyStore {
33
33
  this.loading; // Ping loading to possibly trigger the intiial fetch.
34
34
  return super.read();
35
35
  }
36
+ // Override to consider invalid to be really old.
37
+ get age() {
38
+ return this.invalidated ? Infinity : super.age;
39
+ }
36
40
  // Override to create to save `callback`
37
41
  constructor(value, callback) {
38
42
  super(value);
@@ -44,9 +48,11 @@ export class FetchStore extends BusyStore {
44
48
  * - Refreshes are de-duplicated. Concurrent calls while a fetch is in-flight return the same promise.
45
49
  * - Never throws — errors are stored as `reason`.
46
50
  */
47
- refresh() {
51
+ refresh(maxAge) {
48
52
  if (this._pendingRefresh)
49
53
  return this._pendingRefresh;
54
+ if (!this.stale(maxAge))
55
+ return false;
50
56
  try {
51
57
  const value = this._fetch(this.signal); // Retrieving a new signal calls `abort()` which cancels the previous one.
52
58
  if (isAsync(value))
@@ -81,6 +87,10 @@ export class FetchStore extends BusyStore {
81
87
  return this._callback(signal);
82
88
  }
83
89
  _callback;
90
+ /** Whether this store is has currently been invalidated and needs a refresh. */
91
+ get invalidated() {
92
+ return !!this._invalidation;
93
+ }
84
94
  /**
85
95
  * Invalidate this store so a new fetch is triggered on the next read of `loading` or `value`.
86
96
  * - Triggers `abort()` so any current awaits are cancelled.
@@ -90,12 +100,6 @@ export class FetchStore extends BusyStore {
90
100
  this._invalidation++;
91
101
  }
92
102
  _invalidation = 0;
93
- /** Re-fetch now if the current value is older than `maxAge` milliseconds or has been invalidated. */
94
- refreshStale(maxAge) {
95
- if (this._invalidation || this.age > maxAge)
96
- return this.refresh();
97
- return true;
98
- }
99
103
  // Override to abort any current in-flight fetch and pending async operation.
100
104
  // - Sends `ABORT` to the current `AbortSignal` and clears the controller (a new signal will be created on the next read or fetch).
101
105
  // - Any pending `await()` result will be silently discarded.
package/store/Store.d.ts CHANGED
@@ -92,6 +92,15 @@ export declare class Store<T, TT = T> implements AsyncIterable<T, void, void>, A
92
92
  * @example if (store.age > MINUTE) refreshStore(store);
93
93
  */
94
94
  get age(): number;
95
+ /**
96
+ * Whether this store is stale based on a `maxAge` value in milliseconds.
97
+ *
98
+ * @param maxAge The maximum age for the stale check.
99
+ * - `0` zero means "always refresh" (this is the default).
100
+ * - `Infinity` means "refresh only if store is still in a loading state.
101
+ * - Any other value may or may not be stale based on `this.age`
102
+ */
103
+ stale(maxAge?: number): boolean;
95
104
  /** Current error of this store, or `undefined` if there is no error. */
96
105
  get reason(): unknown;
97
106
  set reason(reason: unknown);
package/store/Store.js CHANGED
@@ -132,6 +132,17 @@ export class Store {
132
132
  const time = this.time;
133
133
  return typeof time === "number" ? Date.now() - time : Infinity;
134
134
  }
135
+ /**
136
+ * Whether this store is stale based on a `maxAge` value in milliseconds.
137
+ *
138
+ * @param maxAge The maximum age for the stale check.
139
+ * - `0` zero means "always refresh" (this is the default).
140
+ * - `Infinity` means "refresh only if store is still in a loading state.
141
+ * - Any other value may or may not be stale based on `this.age`
142
+ */
143
+ stale(maxAge = 0) {
144
+ return this.age >= maxAge;
145
+ }
135
146
  /** Current error of this store, or `undefined` if there is no error. */
136
147
  get reason() {
137
148
  return this._reason;
package/util/sequence.js CHANGED
@@ -2,8 +2,12 @@ import { UnexpectedError } from "../error/UnexpectedError.js";
2
2
  import { getDeferred, getDelay } from "./async.js";
3
3
  import { ABORT } from "./constants.js";
4
4
  /** Turn a `Promise<R>` into an `IteratorAbortResult<R>` */
5
- async function _awaitAbort(value) {
6
- return { done: ABORT, value: await value };
5
+ function _awaitAbortResult(value) {
6
+ return value.then(_getAbortResult);
7
+ }
8
+ /** Turn a result `R` into an `IteratorAbortResult<R>` */
9
+ function _getAbortResult(value) {
10
+ return { done: ABORT, value };
7
11
  }
8
12
  /** Call an iterator's `return()` method (if it exists) with an initial value, and return the `value` it returns. */
9
13
  async function _iteratorReturn(iterator, initial, caller) {
@@ -26,13 +30,12 @@ export function isSequence(value) {
26
30
  /** Infinite sequence that yields until a `SIGNAL` is received. */
27
31
  export async function* repeatUntil(source, ...signals) {
28
32
  const iterator = source[Symbol.asyncIterator]();
33
+ const aborts = signals.map(_awaitAbortResult);
29
34
  let n;
30
35
  while (true) {
31
36
  try {
32
- const { done, value } = await Promise.race([
33
- iterator.next(n),
34
- ...signals.map(_awaitAbort),
35
- ]);
37
+ const next = iterator.next(n);
38
+ const { done, value } = await (aborts.length ? Promise.race([next, ...aborts]) : next);
36
39
  if (done) {
37
40
  // For aborts, tell the iterator we're no longer using it.
38
41
  if (done === ABORT)
@@ -81,7 +84,7 @@ export async function* callSequence(sequence, callback) {
81
84
  export function runSequence(sequence, onNext, onError, onReturn) {
82
85
  const { promise, resolve } = getDeferred();
83
86
  void _runSequenceIterator(sequence[Symbol.asyncIterator](), promise, onNext, onError, onReturn);
84
- return (value) => resolve({ done: ABORT, value });
87
+ return (value) => resolve(_getAbortResult(value));
85
88
  }
86
89
  async function _runSequenceIterator(iterator, stopped, onNext, onError, onReturn) {
87
90
  let n;