elhala-query
Version:
A lightweight, educational implementation of React Query concepts with Suspense support.
435 lines (423 loc) • 11.9 kB
JavaScript
;
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