/** * @template T * @typedef {Object} Resource * @property {Signal} data * @property {Signal} loading * @property {Signal} error * @property {(...args: any[]) => Promise} fetch */ /** * @template T * @typedef {Object} RangeState * @property {Signal | null>} response * @property {Signal} loading */ /** @typedef {RangeState} AnyRangeState */ /** * @template T * @typedef {Object} MetricResource * @property {string} path * @property {(from?: number, to?: number) => RangeState} range * @property {(from?: number, to?: number) => Promise | null>} fetch */ /** @typedef {MetricResource} AnyMetricResource */ /** * @typedef {{ createResource: typeof createResource, useMetricEndpoint: typeof useMetricEndpoint }} Resources */ import signals from "./signals.js"; /** * Create a generic reactive resource wrapper for any async fetcher * @template T * @template {any[]} Args * @param {(...args: Args) => Promise} fetcher * @returns {Resource} */ function createResource(fetcher) { const owner = signals.getOwner(); return signals.runWithOwner(owner, () => { const data = signals.createSignal(/** @type {T | null} */ (null)); const loading = signals.createSignal(false); const error = signals.createSignal(/** @type {Error | null} */ (null)); return { data, loading, error, /** * @param {Args} args */ async fetch(...args) { loading.set(true); error.set(null); try { const result = await fetcher(...args); data.set(() => result); return result; } catch (e) { error.set(e instanceof Error ? e : new Error(String(e))); return null; } finally { loading.set(false); } }, }; }); } /** * Create a reactive resource wrapper for a MetricEndpoint with multi-range support * @template T * @param {MetricEndpoint} endpoint * @returns {MetricResource} */ function useMetricEndpoint(endpoint) { const owner = signals.getOwner(); return signals.runWithOwner(owner, () => { /** @type {Map>} */ const ranges = new Map(); /** * Get or create range state * @param {number} [from=-10000] * @param {number} [to] * @returns {RangeState} */ function range(from = -10000, to) { const key = `${from}-${to ?? ""}`; const existing = ranges.get(key); if (existing) return existing; /** @type {RangeState} */ const state = { response: signals.createSignal( /** @type {MetricData | null} */ (null), ), loading: signals.createSignal(false), }; ranges.set(key, state); return state; } return { path: endpoint.path, range, /** * Fetch data for a range * @param {number} [start=-10000] * @param {number} [end] */ async fetch(start = -10000, end) { const r = range(start, end); r.loading.set(true); try { const result = await endpoint.slice(start, end).fetch(r.response.set); return result; } finally { r.loading.set(false); } }, }; }); } export const resources = { createResource, useMetricEndpoint };