UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

119 lines (118 loc) 4.5 kB
import { RequiredError } from "../error/RequiredError.js"; import { ArrayStore } from "../store/ArrayStore.js"; import { BooleanStore } from "../store/BooleanStore.js"; import { getGetter } from "../util/class.js"; import { NONE } from "../util/constants.js"; import { getAfterQuery, getLimit } from "../util/query.js"; import { runSequence } from "../util/sequence.js"; /** Store a set of multiple items. */ export class QueryStore extends ArrayStore { provider; collection; query; busy = new BooleanStore(); limit; /** Can more items be loaded after the current result. */ get hasMore() { return this._hasMore; } _hasMore = false; /** Get the first item in this store. */ get first() { const first = this.optionalFirst; if (!first) throw new RequiredError(`First item does not exist in collection "${this.collection}"`, { store: this, provider: this.provider, collection: this.collection, query: this.query, caller: getGetter(this, "first"), }); return first; } /** Get the last item in this store. */ get last() { const last = this.optionalLast; if (!last) throw new RequiredError(`Last item does not exist in collection "${this.collection}"`, { store: this, provider: this.provider, collection: this.collection, query: this.query, caller: getGetter(this, "first"), }); return last; } constructor(collection, query, provider, memory) { const time = memory?.getQueryTime(collection, query); const items = memory?.getQuery(collection, query) || []; super(typeof time === "number" || items.length ? items : NONE, time); // Use the value if it was definitely cached or is not empty. if (memory) this.starter = store => runSequence(store.through(memory.getCachedQuerySequence(collection, query))); this.provider = provider; this.collection = collection; this.query = query; this.limit = getLimit(query) ?? Number.POSITIVE_INFINITY; // Start loading the value from the provider if it is not definitely cached. if (typeof time !== "number") this.refresh(); } /** Refresh this store from the source provider. */ refresh(provider = this.provider) { if (!this.busy.value) void this._refresh(provider); } async _refresh(provider) { this.busy.value = true; this.reason = undefined; // Optimistically clear the error. try { const items = await provider.getQuery(this.collection, this.query); this._hasMore = items.length >= this.limit; // If the query returned {limit} or more items, we can assume there are more items waiting to be queried. this.value = items; } catch (thrown) { this.reason = thrown; } finally { this.busy.value = false; } } /** Refresh this store if data in the cache is older than `maxAge` (in milliseconds). */ refreshStale(maxAge) { if (this.age > maxAge) this.refresh(); } /** Subscribe this store to a provider. */ connect(provider = this.provider) { return runSequence(this.through(provider.getQuerySequence(this.collection, this.query))); } /** * Load more items after the last once. * - Promise that needs to be handled. */ loadMore() { if (!this.busy.value) void this._loadMore(); } async _loadMore() { this.busy.value = true; this.reason = undefined; // Optimistically clear the error. try { const last = this.last; const query = last ? getAfterQuery(this.query, last) : this.query; const items = await this.provider.getQuery(this.collection, query); this.value = [...this.value, ...items]; this._hasMore = items.length >= this.limit; // If the query returned {limit} or more items, we can assume there are more items waiting to be queried. } catch (thrown) { this.reason = thrown; } finally { this.busy.value = false; } } // Implement `Iteratable` [Symbol.iterator]() { return this.value.values(); } }