UNPKG

elhala-query

Version:

A lightweight, educational implementation of React Query concepts with Suspense support.

435 lines (423 loc) 11.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ElhalaClient: () => ElhalaClient, ElhalaProvider: () => ElhalaProvider, useElhalaClient: () => useElhalaClient, useMutation: () => useMutation, useQuery: () => useQuery, useSuspenseQuery: () => useSuspenseQuery }); module.exports = __toCommonJS(index_exports); // src/useQuery.tsx var import_react2 = require("react"); // src/client-provider.tsx var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var elhalaClientContext = (0, import_react.createContext)(null); var useElhalaClient = () => { const client = (0, import_react.useContext)(elhalaClientContext); if (!client) { throw new Error( "useElhalaClient must be used within an ElhalaClientProvider" ); } return client; }; var ElhalaProvider = ({ client, children }) => { const onFocus = (0, import_react.useCallback)(() => { client.queries.forEach((query) => { query.subscribers.forEach((sub) => sub.notify()); }); }, [client]); (0, import_react.useEffect)(() => { const controller = new AbortController(); window.addEventListener("focus", onFocus, { signal: controller.signal }); window.addEventListener("visibilitychange", onFocus, { signal: controller.signal }); return () => controller.abort(); }, [onFocus]); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(elhalaClientContext.Provider, { value: client, children }); }; // src/queryObserver.ts function createQueryObserver(client, { queryFn, queryKey, cacheTime, staleTime = 0 }) { const query = client.getQuery({ queryFn, queryKey, cacheTime }); let staleCheckTimer = null; const setupStaleCheck = () => { if (staleTime <= 0 || !query.state.lastUpdated) return; if (staleCheckTimer) { clearTimeout(staleCheckTimer); staleCheckTimer = null; } const timeUntilStale = staleTime - (Date.now() - query.state.lastUpdated); if (timeUntilStale > 0) { staleCheckTimer = setTimeout(() => { query.fetch(); }, timeUntilStale); } }; const observer = { notify: () => { setupStaleCheck(); return query.state; }, getResults: () => query.state, fetch: () => { if (!query.state.lastUpdated || Date.now() - query.state.lastUpdated > staleTime) { query.fetch(); } }, subscribe: (callback) => { observer.notify = () => { setupStaleCheck(); callback(); return query.state; }; const unsub = query.subscribe(observer); observer.fetch(); setupStaleCheck(); return () => { unsub(); if (staleCheckTimer) { clearTimeout(staleCheckTimer); staleCheckTimer = null; } }; }, fetchOptimistic: () => { return query.fetch(); } }; return observer; } // src/useQuery.tsx function useQuery(options) { const client = useElhalaClient(); const observer = (0, import_react2.useRef)(null); if (!observer.current) { observer.current = createQueryObserver(client, options); } (0, import_react2.useSyncExternalStore)( (0, import_react2.useCallback)((onStoreChange) => { return observer.current.subscribe(onStoreChange); }, []), () => observer.current?.getResults(), () => observer.current?.getResults() ); return observer.current.getResults(); } // src/useMutation.ts var import_react3 = require("react"); // src/mutationObserver.ts function createMutationObserver(client, options) { const mutation = client.getMutation(options); const observer = { notify: () => mutation.state, getResults: () => mutation.state, subscribe: (callback) => { observer.notify = callback; const unsub = mutation.subscribe(observer); return unsub; } }; return observer; } // src/useMutation.ts function useMutation(options) { const client = useElhalaClient(); const observer = (0, import_react3.useRef)(null); if (!observer.current) { observer.current = createMutationObserver(client, options); } (0, import_react3.useSyncExternalStore)( (0, import_react3.useCallback)((onStoreChange) => { return observer.current.subscribe(onStoreChange); }, []), () => observer.current?.getResults(), () => observer.current?.getResults() ); return observer.current.getResults(); } // src/mutation.ts function createMutation({ mutationFn, mutationKey, onError, onMutate, onSettled, onSuccess }) { let mutation = { subscribers: [], subscribe: (subscriber) => { mutation.subscribers.push(subscriber); return () => { mutation.subscribers = mutation.subscribers.filter( (sub) => sub !== subscriber ); }; }, setState: (updater) => { mutation.state = updater(mutation.state); mutation.subscribers.forEach((sub) => sub.notify()); }, options: { mutationFn, mutationKey, onError, onMutate, onSettled, onSuccess, mutationHash: JSON.stringify(mutationKey) }, state: { data: void 0, error: void 0, isLoading: false, isSuccess: false, isError: false, status: "idle", mutate: async (variables) => { mutation.setState((state) => ({ ...state, isLoading: true, isSuccess: false, isError: false, status: "loading" })); mutation.options.onMutate?.(); try { const data = await mutation.options.mutationFn(variables); mutation.setState((state) => ({ ...state, data, isSuccess: true, isError: false, status: "success" })); mutation.options.onSuccess?.(data); } catch (error) { mutation.setState((state) => ({ ...state, isSuccess: false, isError: true, status: "error", error })); mutation.options.onError?.(error); } finally { mutation.setState((state) => ({ ...state, isLoading: false, status: "idle" })); mutation.options.onSettled?.(); } } } }; return mutation; } // src/query.ts var DEFAULT_CACHE_TIME = 1e3 * 60 * 5; function createQuery(client, { queryFn, queryKey, cacheTime = DEFAULT_CACHE_TIME }) { let queryHash = JSON.stringify(queryKey); let query = { subscribers: [], state: { data: void 0, error: void 0, isLoading: true, isFetching: true, isError: false, status: "loading", lastUpdated: void 0, refetch: () => { query.fetch(); } }, promise: void 0, options: { queryFn, queryHash, queryKey }, gcTimeout: void 0, scheduleGC: () => { query.gcTimeout = setTimeout(() => { client.removeQuery(query); }, cacheTime); }, unscheduleGC: () => { clearTimeout(query.gcTimeout); }, subscribe: (subscriber) => { query.subscribers.push(subscriber); query.unscheduleGC(); return () => { query.subscribers = query.subscribers.filter( (sub) => sub !== subscriber ); if (query.subscribers.length === 0) { query.scheduleGC(); } }; }, setState: (updater) => { query.state = updater(query.state); query.subscribers.forEach((sub) => sub.notify()); }, fetch: () => { if (!query.promise) { query.promise = (async () => { query.setState((state) => ({ ...state, isFetching: true })); try { let data = await query.options.queryFn({ queryKey: query.options.queryKey }); query.setState((state) => ({ ...state, data, status: "success", isError: false, lastUpdated: Date.now() })); } catch (error) { query.setState((state) => ({ ...state, error, isError: true, status: "error" })); } finally { query.promise = void 0; query.setState((state) => ({ ...state, isFetching: false, isLoading: false })); } })(); } return query.promise; } }; return query; } // src/elhalaClient.ts var ElhalaClient = class { constructor() { this.queries = /* @__PURE__ */ new Map(); this.mutations = /* @__PURE__ */ new Map(); } getQuery(options) { let queryHash = JSON.stringify(options.queryKey); if (this.queries.has(queryHash)) { return this.queries.get(queryHash); } else { let query = createQuery(this, options); this.queries.set(queryHash, query); return query; } } getQueryData(options) { return this.getQuery(options).state.data; } setQueryData(options, data) { let query = this.getQuery(options); query.setState((state) => ({ ...state, data })); } removeQuery(query) { this.queries.delete(query.options.queryHash); } invalidateQueries({ queryKey }) { this.queries.forEach((query) => { if (queryKey.length === 0 || queryKey.some((key) => query.options.queryKey.includes(key))) { query.setState((state) => ({ ...state, isLoading: true, isFetching: true })); query.fetch(); } }); } clearQueriesCache() { this.queries.clear(); } getMutation(options) { let mutationHash = JSON.stringify(options.mutationKey); if (this.mutations.has(mutationHash)) { return this.mutations.get(mutationHash); } else { let mutation = createMutation(options); this.mutations.set(mutationHash, mutation); return mutation; } } clearMutationsCache() { this.mutations.clear(); } }; // src/useSuspenseQuery.ts var import_react4 = require("react"); function useSuspenseQuery(options) { const client = useElhalaClient(); const observer = (0, import_react4.useRef)(null); if (!observer.current) { observer.current = createQueryObserver(client, options); } const state = (0, import_react4.useSyncExternalStore)( (0, import_react4.useCallback)( (onStoreChange) => observer.current.subscribe(onStoreChange), [] ), () => observer.current?.getResults(), () => observer.current?.getResults() ); if (state?.isFetching) { const promise = observer.current.fetchOptimistic(); if (promise) throw promise; throw new Promise(() => { }); } if (state?.status === "error") { throw state.error; } return state; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ElhalaClient, ElhalaProvider, useElhalaClient, useMutation, useQuery, useSuspenseQuery }); //# sourceMappingURL=index.js.map