140 lines
4.6 KiB
JavaScript
140 lines
4.6 KiB
JavaScript
import { watch } from "../watch/index.js";
|
|
// Helper functions for debounce and throttle
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function debounce(fn, delay) {
|
|
let timeoutId;
|
|
let lastResolve = null;
|
|
return (...args) => {
|
|
return new Promise((resolve) => {
|
|
if (lastResolve) {
|
|
lastResolve(undefined);
|
|
}
|
|
lastResolve = resolve;
|
|
clearTimeout(timeoutId);
|
|
timeoutId = setTimeout(async () => {
|
|
const result = await fn(...args);
|
|
if (lastResolve) {
|
|
lastResolve(result);
|
|
lastResolve = null;
|
|
}
|
|
}, delay);
|
|
});
|
|
};
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function throttle(fn, delay) {
|
|
let lastRun = 0;
|
|
let lastPromise = null;
|
|
return (...args) => {
|
|
const now = Date.now();
|
|
if (lastRun && now - lastRun < delay) {
|
|
return lastPromise ?? Promise.resolve(undefined);
|
|
}
|
|
lastRun = now;
|
|
lastPromise = fn(...args);
|
|
return lastPromise;
|
|
};
|
|
}
|
|
function runResource(source, fetcher, options = {}, effectFn) {
|
|
const { lazy = false, once = false, initialValue, debounce: debounceTime, throttle: throttleTime, } = options;
|
|
// Create state
|
|
let current = $state(initialValue);
|
|
let loading = $state(false);
|
|
let error = $state(undefined);
|
|
let cleanupFns = $state([]);
|
|
// Helper function to run cleanup functions
|
|
const runCleanup = () => {
|
|
cleanupFns.forEach((fn) => fn());
|
|
cleanupFns = [];
|
|
};
|
|
// Helper function to register cleanup
|
|
const onCleanup = (fn) => {
|
|
cleanupFns = [...cleanupFns, fn];
|
|
};
|
|
// Create the base fetcher function
|
|
const baseFetcher = async (value, previousValue, refetching = false) => {
|
|
try {
|
|
loading = true;
|
|
error = undefined;
|
|
runCleanup();
|
|
// Create new AbortController for this fetch
|
|
const controller = new AbortController();
|
|
onCleanup(() => controller.abort());
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const result = await fetcher(value, previousValue, {
|
|
data: current,
|
|
refetching,
|
|
onCleanup,
|
|
signal: controller.signal,
|
|
});
|
|
current = result;
|
|
return result;
|
|
}
|
|
catch (e) {
|
|
if (!(e instanceof DOMException && e.name === "AbortError")) {
|
|
error = e;
|
|
}
|
|
return undefined;
|
|
}
|
|
finally {
|
|
loading = false;
|
|
}
|
|
};
|
|
// Apply debounce or throttle if specified
|
|
const runFetcher = debounceTime
|
|
? debounce(baseFetcher, debounceTime)
|
|
: throttleTime
|
|
? throttle(baseFetcher, throttleTime)
|
|
: baseFetcher;
|
|
// Setup effect
|
|
const sources = Array.isArray(source) ? source : [source];
|
|
let prevValues;
|
|
effectFn((values, previousValues) => {
|
|
// Skip if once and already ran
|
|
if (once && prevValues) {
|
|
return;
|
|
}
|
|
prevValues = values;
|
|
runFetcher(Array.isArray(source) ? values : values[0], Array.isArray(source) ? previousValues : previousValues?.[0]);
|
|
}, { lazy });
|
|
return {
|
|
get current() {
|
|
return current;
|
|
},
|
|
get loading() {
|
|
return loading;
|
|
},
|
|
get error() {
|
|
return error;
|
|
},
|
|
mutate: (value) => {
|
|
current = value;
|
|
},
|
|
refetch: (info) => {
|
|
const values = sources.map((s) => s());
|
|
return runFetcher(Array.isArray(source) ? values : values[0], Array.isArray(source) ? values : values[0], info ?? true);
|
|
},
|
|
};
|
|
}
|
|
// Implementation
|
|
export function resource(source, fetcher, options) {
|
|
return runResource(source, fetcher, options, (fn, options) => {
|
|
const sources = Array.isArray(source) ? source : [source];
|
|
const getters = () => sources.map((s) => s());
|
|
watch(getters, (values, previousValues) => {
|
|
fn(values, previousValues ?? []);
|
|
}, options);
|
|
});
|
|
}
|
|
// Implementation
|
|
export function resourcePre(source, fetcher, options) {
|
|
return runResource(source, fetcher, options, (fn, options) => {
|
|
const sources = Array.isArray(source) ? source : [source];
|
|
const getter = () => sources.map((s) => s());
|
|
watch.pre(getter, (values, previousValues) => {
|
|
fn(values, previousValues ?? []);
|
|
}, options);
|
|
});
|
|
}
|
|
resource.pre = resourcePre;
|