@tanstack/solid-query
Advanced tools
@@ -6,3 +6,2 @@ import type { Accessor } from 'solid-js'; | ||
| import { Context } from 'solid-js'; | ||
| import { ContextProviderComponent } from 'solid-js'; | ||
| import { DataTag } from '@tanstack/query-core'; | ||
@@ -364,5 +363,5 @@ import { dataTagErrorSymbol } from '@tanstack/query-core'; | ||
| declare const IsRestoringProvider: ContextProviderComponent<Accessor<boolean>>; | ||
| export { IsRestoringProvider } | ||
| export { IsRestoringProvider as IsRestoringProvider_alias_1 } | ||
| declare const IsRestoringContext: Context<Accessor<boolean>>; | ||
| export { IsRestoringContext } | ||
| export { IsRestoringContext as IsRestoringContext_alias_1 } | ||
@@ -469,3 +468,3 @@ export { isServer } | ||
| /** | ||
| * QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param | ||
| * QueriesOptions reducer recursively snapshots function arguments to infer/enforce type param | ||
| */ | ||
@@ -517,3 +516,3 @@ declare type QueriesOptions<T extends Array<any>, TResult extends Array<any> = [], TDepth extends ReadonlyArray<number> = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array<UseQueryOptionsForUseQueries> : T extends [] ? [] : T extends [infer Head] ? [...TResult, GetOptions<Head>] : T extends [infer Head, ...infer Tail] ? QueriesOptions<[ | ||
| declare const QueryClientContext: Context<(() => QueryClient) | undefined>; | ||
| declare const QueryClientContext: Context<(() => QueryClient) | null>; | ||
| export { QueryClientContext } | ||
@@ -685,3 +684,3 @@ export { QueryClientContext as QueryClientContext_alias_1 } | ||
| export declare function useBaseQuery<TQueryFnData, TError, TData, TQueryData, TQueryKey extends QueryKey>(options: Accessor<UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>>, Observer: typeof QueryObserver, queryClient?: Accessor<QueryClient>): QueryObserverResult<TData, TError>; | ||
| export declare function useBaseQuery<TQueryFnData, TError, TData, TQueryData, TQueryKey extends QueryKey>(options: Accessor<UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>>, Observer: typeof QueryObserver, queryClient?: Accessor<QueryClient>): Readonly<QueryObserverResult<TData, TError>>; | ||
@@ -688,0 +687,0 @@ declare interface UseBaseQueryOptions<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> extends OmitKeyof<QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>, 'suspense'> { |
@@ -6,3 +6,2 @@ import type { Accessor } from 'solid-js'; | ||
| import { Context } from 'solid-js'; | ||
| import { ContextProviderComponent } from 'solid-js'; | ||
| import { DataTag } from '@tanstack/query-core'; | ||
@@ -364,5 +363,5 @@ import { dataTagErrorSymbol } from '@tanstack/query-core'; | ||
| declare const IsRestoringProvider: ContextProviderComponent<Accessor<boolean>>; | ||
| export { IsRestoringProvider } | ||
| export { IsRestoringProvider as IsRestoringProvider_alias_1 } | ||
| declare const IsRestoringContext: Context<Accessor<boolean>>; | ||
| export { IsRestoringContext } | ||
| export { IsRestoringContext as IsRestoringContext_alias_1 } | ||
@@ -469,3 +468,3 @@ export { isServer } | ||
| /** | ||
| * QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param | ||
| * QueriesOptions reducer recursively snapshots function arguments to infer/enforce type param | ||
| */ | ||
@@ -517,3 +516,3 @@ declare type QueriesOptions<T extends Array<any>, TResult extends Array<any> = [], TDepth extends ReadonlyArray<number> = []> = TDepth['length'] extends MAXIMUM_DEPTH ? Array<UseQueryOptionsForUseQueries> : T extends [] ? [] : T extends [infer Head] ? [...TResult, GetOptions<Head>] : T extends [infer Head, ...infer Tail] ? QueriesOptions<[ | ||
| declare const QueryClientContext: Context<(() => QueryClient) | undefined>; | ||
| declare const QueryClientContext: Context<(() => QueryClient) | null>; | ||
| export { QueryClientContext } | ||
@@ -685,3 +684,3 @@ export { QueryClientContext as QueryClientContext_alias_1 } | ||
| export declare function useBaseQuery<TQueryFnData, TError, TData, TQueryData, TQueryKey extends QueryKey>(options: Accessor<UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>>, Observer: typeof QueryObserver, queryClient?: Accessor<QueryClient>): QueryObserverResult<TData, TError>; | ||
| export declare function useBaseQuery<TQueryFnData, TError, TData, TQueryData, TQueryKey extends QueryKey>(options: Accessor<UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>>, Observer: typeof QueryObserver, queryClient?: Accessor<QueryClient>): Readonly<QueryObserverResult<TData, TError>>; | ||
@@ -688,0 +687,0 @@ declare interface UseBaseQueryOptions<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> extends OmitKeyof<QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>, 'suspense'> { |
+161
-258
@@ -5,7 +5,6 @@ 'use strict'; | ||
| var solidJs = require('solid-js'); | ||
| var web = require('solid-js/web'); | ||
| var store = require('solid-js/store'); | ||
| var web = require('@solidjs/web'); | ||
| // src/useQuery.ts | ||
| exports.QueryClientContext = solidJs.createContext(void 0); | ||
| exports.QueryClientContext = solidJs.createContext(null); | ||
| exports.useQueryClient = (queryClient) => { | ||
@@ -22,9 +21,5 @@ if (queryClient) { | ||
| exports.QueryClientProvider = (props) => { | ||
| solidJs.createRenderEffect((unmount) => { | ||
| unmount?.(); | ||
| props.client.mount(); | ||
| return props.client.unmount.bind(props.client); | ||
| }); | ||
| props.client.mount(); | ||
| solidJs.onCleanup(() => props.client.unmount()); | ||
| return web.createComponent(exports.QueryClientContext.Provider, { | ||
| return web.createComponent(exports.QueryClientContext, { | ||
| value: () => props.client, | ||
@@ -36,15 +31,28 @@ get children() { | ||
| }; | ||
| var IsRestoringContext = solidJs.createContext(() => false); | ||
| exports.useIsRestoring = () => solidJs.useContext(IsRestoringContext); | ||
| exports.IsRestoringProvider = IsRestoringContext.Provider; | ||
| exports.IsRestoringContext = solidJs.createContext(() => false); | ||
| exports.useIsRestoring = () => solidJs.useContext(exports.IsRestoringContext); | ||
| // src/useBaseQuery.ts | ||
| function reconcileFn(store$1, result, reconcileOption, queryHash) { | ||
| if (reconcileOption === false) return result; | ||
| var isServer = typeof window === "undefined"; | ||
| function _stripFnsForSSR(obj) { | ||
| if (!isServer) return obj; | ||
| const out = {}; | ||
| for (const k of Object.keys(obj)) { | ||
| if (k === "refetch" || k === "fetchNextPage" || k === "fetchPreviousPage") { | ||
| out[k] = void 0; | ||
| } else { | ||
| out[k] = obj[k]; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function reconcileFn(store, result, reconcileOption, queryHash) { | ||
| if (typeof reconcileOption === "function") { | ||
| const newData2 = reconcileOption(store$1.data, result.data); | ||
| return { ...result, data: newData2 }; | ||
| const newData = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData }; | ||
| } | ||
| if (reconcileOption === false) return result; | ||
| const key = reconcileOption; | ||
| let data = result.data; | ||
| if (store$1.data === void 0) { | ||
| if (store.data === void 0) { | ||
| try { | ||
@@ -64,9 +72,12 @@ data = structuredClone(data); | ||
| } | ||
| const newData = store.reconcile(data, { key: reconcileOption })(store$1.data); | ||
| return { ...result, data: newData }; | ||
| if (store.data !== void 0 && data !== void 0) { | ||
| solidJs.reconcile(data, key)(store.data); | ||
| return { ...result, data: store.data }; | ||
| } | ||
| return { ...result, data }; | ||
| } | ||
| var hydratableObserverResult = (query, result) => { | ||
| if (!web.isServer) return result; | ||
| if (!isServer) return result; | ||
| const obj = { | ||
| ...store.unwrap(result), | ||
| ...solidJs.snapshot(result), | ||
| // During SSR, functions cannot be serialized, so we need to remove them | ||
@@ -96,3 +107,3 @@ // This is safe because we will add these functions back when the query is hydrated | ||
| defaultOptions.structuralSharing = false; | ||
| if (web.isServer) { | ||
| if (isServer) { | ||
| defaultOptions.retry = false; | ||
@@ -104,12 +115,18 @@ defaultOptions.throwOnError = true; | ||
| }); | ||
| const initialOptions = defaultedOptions(); | ||
| const [observer, setObserver] = solidJs.createSignal( | ||
| new Observer(client(), defaultedOptions()) | ||
| const observer = new Observer(client(), defaultedOptions()); | ||
| const trackedDefaultedOptions = solidJs.createMemo(() => defaultedOptions()); | ||
| solidJs.createRenderEffect( | ||
| () => trackedDefaultedOptions(), | ||
| (opts) => { | ||
| observer.setOptions(opts); | ||
| } | ||
| ); | ||
| let observerResult = observer().getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = store.createStore(observerResult); | ||
| let observerResult = observer.getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = solidJs.createStore( | ||
| _stripFnsForSSR(observerResult) | ||
| ); | ||
| const createServerSubscriber = (resolve, reject) => { | ||
| return observer().subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| queryCore.notifyManager.batchCalls(() => { | ||
| const query = observer().getCurrentQuery(); | ||
| const query = observer.getCurrentQuery(); | ||
| const unwrappedResult = hydratableObserverResult(query, result); | ||
@@ -133,8 +150,12 @@ if (result.data !== void 0 && unwrappedResult.isError) { | ||
| const createClientSubscriber = () => { | ||
| const obs = observer(); | ||
| return obs.subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| const previousResult = observerResult; | ||
| observerResult = result; | ||
| setStateWithReconciliation(result); | ||
| queueMicrotask(() => { | ||
| if (unsubscribe) { | ||
| refetch(); | ||
| if (unsubscribe && !disposed && (previousResult.isLoading !== result.isLoading || previousResult.isError !== result.isError)) { | ||
| try { | ||
| solidJs.refresh(queryResource); | ||
| } catch { | ||
| } | ||
| } | ||
@@ -145,8 +166,9 @@ }); | ||
| function setStateWithReconciliation(res) { | ||
| const opts = observer().options; | ||
| const opts = observer.options; | ||
| const reconcileOptions = opts.reconcile; | ||
| const sanitized = _stripFnsForSSR(res); | ||
| setState((store) => { | ||
| return reconcileFn( | ||
| store, | ||
| res, | ||
| sanitized, | ||
| reconcileOptions === void 0 ? false : reconcileOptions, | ||
@@ -157,107 +179,43 @@ opts.queryHash | ||
| } | ||
| function createDeepSignal() { | ||
| return [ | ||
| () => state, | ||
| (v) => { | ||
| const unwrapped = store.unwrap(state); | ||
| if (typeof v === "function") { | ||
| v = v(unwrapped); | ||
| } | ||
| if (v?.hydrationData) { | ||
| const { hydrationData, ...rest } = v; | ||
| v = rest; | ||
| } | ||
| setStateWithReconciliation(v); | ||
| } | ||
| ]; | ||
| } | ||
| let unsubscribe = null; | ||
| let disposed = false; | ||
| let resolver = null; | ||
| const [queryResource, { refetch }] = solidJs.createResource( | ||
| const queryResource = solidJs.createMemo( | ||
| () => { | ||
| const obs = observer(); | ||
| const opts = trackedDefaultedOptions(); | ||
| const restoring = isRestoring(); | ||
| return new Promise((resolve, reject) => { | ||
| resolver = resolve; | ||
| if (web.isServer) { | ||
| unsubscribe = createServerSubscriber(resolve, reject); | ||
| } else if (!unsubscribe && !isRestoring()) { | ||
| if (isServer) { | ||
| unsubscribe = createServerSubscriber((data) => { | ||
| resolve(data); | ||
| }, reject); | ||
| } else if (!unsubscribe && !restoring) { | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| obs.updateResult(); | ||
| if (observerResult.isError && !observerResult.isFetching && !isRestoring() && queryCore.shouldThrowError(obs.options.throwOnError, [ | ||
| observerResult.error, | ||
| obs.getCurrentQuery() | ||
| const currentResult = observer.getOptimisticResult(opts); | ||
| observerResult = currentResult; | ||
| if (currentResult.isError && !currentResult.isFetching && !restoring && queryCore.shouldThrowError(opts.throwOnError, [ | ||
| currentResult.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| setStateWithReconciliation(observerResult); | ||
| return reject(observerResult.error); | ||
| setStateWithReconciliation(currentResult); | ||
| return reject(currentResult.error); | ||
| } | ||
| if (!observerResult.isLoading) { | ||
| if (!currentResult.isLoading) { | ||
| resolver = null; | ||
| setStateWithReconciliation(currentResult); | ||
| return resolve( | ||
| hydratableObserverResult(obs.getCurrentQuery(), observerResult) | ||
| hydratableObserverResult(observer.getCurrentQuery(), currentResult) | ||
| ); | ||
| } | ||
| setStateWithReconciliation(observerResult); | ||
| queueMicrotask(() => setStateWithReconciliation(currentResult)); | ||
| }); | ||
| }, | ||
| { | ||
| storage: createDeepSignal, | ||
| get deferStream() { | ||
| return options().deferStream; | ||
| }, | ||
| /** | ||
| * If this resource was populated on the server (either sync render, or streamed in over time), onHydrated | ||
| * will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber. | ||
| * | ||
| * Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support. | ||
| * | ||
| * Note that this is only invoked on the client, for queries that were originally run on the server. | ||
| */ | ||
| onHydrated(_k, info) { | ||
| if (info.value && "hydrationData" in info.value) { | ||
| queryCore.hydrate(client(), { | ||
| // @ts-expect-error - hydrationData is not correctly typed internally | ||
| queries: [{ ...info.value.hydrationData }] | ||
| }); | ||
| } | ||
| if (unsubscribe) return; | ||
| const newOptions = { ...initialOptions }; | ||
| if ((initialOptions.staleTime || !initialOptions.initialData) && info.value) { | ||
| newOptions.refetchOnMount = false; | ||
| } | ||
| observer().setOptions(newOptions); | ||
| setStateWithReconciliation(observer().getOptimisticResult(newOptions)); | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| } | ||
| observerResult, | ||
| { ssrSource: "client" } | ||
| ); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| client, | ||
| (c) => { | ||
| if (unsubscribe) { | ||
| unsubscribe(); | ||
| } | ||
| const newObserver = new Observer(c, defaultedOptions()); | ||
| unsubscribe = createClientSubscriber(); | ||
| setObserver(newObserver); | ||
| }, | ||
| { | ||
| defer: true | ||
| } | ||
| ) | ||
| ); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| isRestoring, | ||
| (restoring) => { | ||
| if (!restoring && !web.isServer) { | ||
| refetch(); | ||
| } | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| solidJs.onCleanup(() => { | ||
| if (web.isServer && queryResource.loading) { | ||
| disposed = true; | ||
| if (isServer && solidJs.isPending(queryResource)) { | ||
| unsubscribeQueued = true; | ||
@@ -270,3 +228,3 @@ return; | ||
| } | ||
| if (resolver && !web.isServer) { | ||
| if (resolver && !isServer) { | ||
| resolver(observerResult); | ||
@@ -276,25 +234,27 @@ resolver = null; | ||
| }); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| [observer, defaultedOptions], | ||
| ([obs, opts]) => { | ||
| obs.setOptions(opts); | ||
| setStateWithReconciliation(obs.getOptimisticResult(opts)); | ||
| refetch(); | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| const handler = { | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| if (state.data !== void 0) { | ||
| return queryResource.latest?.data; | ||
| } | ||
| return queryResource()?.data; | ||
| const errorPassthroughProps = /* @__PURE__ */ new Set([ | ||
| "error", | ||
| "isError", | ||
| "failureCount", | ||
| "failureReason", | ||
| "errorUpdateCount", | ||
| "errorUpdatedAt" | ||
| ]); | ||
| return new Proxy(state, { | ||
| get(target, prop, receiver) { | ||
| if (typeof prop === "symbol") { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| if (errorPassthroughProps.has(prop)) { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| if (state.isError && !state.isFetching && queryCore.shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| throw state.error; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }; | ||
| return new Proxy(state, handler); | ||
| }); | ||
| } | ||
@@ -320,6 +280,9 @@ | ||
| const observer = new queryCore.MutationObserver(client(), options()); | ||
| solidJs.createMemo(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| const mutate = (variables, mutateOptions) => { | ||
| observer.mutate(variables, mutateOptions).catch(queryCore.noop); | ||
| }; | ||
| const [state, setState] = store.createStore({ | ||
| const [state, setState] = solidJs.createStore({ | ||
| ...observer.getCurrentResult(), | ||
@@ -329,23 +292,21 @@ mutate, | ||
| }); | ||
| solidJs.createComputed(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| () => state.status, | ||
| () => { | ||
| if (state.isError && queryCore.shouldThrowError(observer.options.throwOnError, [state.error])) { | ||
| throw state.error; | ||
| } | ||
| } | ||
| ) | ||
| ); | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| setState({ | ||
| setState(() => ({ | ||
| ...result, | ||
| mutate, | ||
| mutateAsync: result.mutate | ||
| }); | ||
| })); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| solidJs.createRenderEffect( | ||
| () => { | ||
| const isError = state.isError; | ||
| const error = state.error; | ||
| if (isError && queryCore.shouldThrowError(observer.options.throwOnError, [error])) { | ||
| throw error; | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| return state; | ||
@@ -358,10 +319,7 @@ } | ||
| () => queriesOptions().queries.map( | ||
| (options) => solidJs.mergeProps( | ||
| client().defaultQueryOptions(options), | ||
| { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| (options) => solidJs.merge(client().defaultQueryOptions(options), { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| ) | ||
| }) | ||
| ) | ||
@@ -376,69 +334,36 @@ ); | ||
| ); | ||
| const [state, setState] = store.createStore( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| const [, getCombinedResult] = observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| ); | ||
| solidJs.createRenderEffect( | ||
| solidJs.on( | ||
| () => queriesOptions().queries.length, | ||
| () => setState( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| ) | ||
| ) | ||
| const initialResult = getCombinedResult(); | ||
| const [state, setState] = solidJs.createStore( | ||
| Array.isArray(initialResult) ? initialResult : [initialResult] | ||
| ); | ||
| const dataResources = solidJs.createMemo( | ||
| solidJs.on( | ||
| () => state.length, | ||
| () => state.map((queryRes) => { | ||
| const dataPromise = () => new Promise((resolve) => { | ||
| if (queryRes.isFetching && queryRes.isLoading) return; | ||
| resolve(store.unwrap(queryRes.data)); | ||
| let unsubscribe = queryCore.noop; | ||
| solidJs.createEffect( | ||
| () => { | ||
| if (!isRestoring()) { | ||
| unsubscribe = observer.subscribe((result) => { | ||
| setState( | ||
| solidJs.reconcile( | ||
| [...result], | ||
| // Use a key function that returns undefined so reconcile | ||
| // uses positional matching and recursively updates nested properties | ||
| () => void 0 | ||
| ) | ||
| ); | ||
| }); | ||
| return solidJs.createResource(dataPromise); | ||
| }) | ||
| ) | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| solidJs.batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| dataResource[1].mutate(() => store.unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| solidJs.onCleanup(() => { | ||
| unsubscribe(); | ||
| }); | ||
| let taskQueue = []; | ||
| const subscribeToObserver = () => observer.subscribe((result) => { | ||
| taskQueue.push(() => { | ||
| solidJs.batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| const unwrappedResult = { ...store.unwrap(result[index]) }; | ||
| setState(index, store.unwrap(unwrappedResult)); | ||
| dataResource[1].mutate(() => store.unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| }); | ||
| }); | ||
| queueMicrotask(() => { | ||
| const taskToRun = taskQueue.pop(); | ||
| if (taskToRun) taskToRun(); | ||
| taskQueue = []; | ||
| }); | ||
| }); | ||
| let unsubscribe = queryCore.noop; | ||
| solidJs.createComputed((cleanup) => { | ||
| cleanup?.(); | ||
| unsubscribe = isRestoring() ? queryCore.noop : subscribeToObserver(); | ||
| return () => queueMicrotask(unsubscribe); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| solidJs.onMount(() => { | ||
| solidJs.createMemo(() => { | ||
| const queries = defaultedQueries(); | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queries, | ||
| queriesOptions().combine ? { | ||
@@ -448,25 +373,5 @@ combine: queriesOptions().combine | ||
| ); | ||
| return queries; | ||
| }); | ||
| solidJs.createComputed(() => { | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queriesOptions().combine ? { | ||
| combine: queriesOptions().combine | ||
| } : void 0 | ||
| ); | ||
| }); | ||
| const handler = (index) => ({ | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| return dataResources()[index][0](); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| } | ||
| }); | ||
| const getProxies = () => state.map((s, index) => { | ||
| return new Proxy(s, handler(index)); | ||
| }); | ||
| const [proxyState, setProxyState] = store.createStore(getProxies()); | ||
| solidJs.createRenderEffect(() => setProxyState(getProxies())); | ||
| return proxyState; | ||
| return state; | ||
| } | ||
@@ -526,14 +431,12 @@ exports.QueryClient = class QueryClient extends queryCore.QueryClient { | ||
| ); | ||
| solidJs.createEffect(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| setResult((prev) => { | ||
| const nextResult = queryCore.replaceEqualDeep( | ||
| result(), | ||
| prev, | ||
| getResult(mutationCache(), options()) | ||
| ); | ||
| if (result() !== nextResult) { | ||
| setResult(nextResult); | ||
| } | ||
| return prev === nextResult ? prev : nextResult; | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| return result; | ||
@@ -540,0 +443,0 @@ } |
+1
-1
@@ -63,3 +63,3 @@ export { DefinedUseBaseQueryResult } from './_tsup-dts-rollup.cjs'; | ||
| export { useIsRestoring } from './_tsup-dts-rollup.cjs'; | ||
| export { IsRestoringProvider } from './_tsup-dts-rollup.cjs'; | ||
| export { IsRestoringContext } from './_tsup-dts-rollup.cjs'; | ||
| export { focusManager } from './_tsup-dts-rollup.cjs'; | ||
@@ -66,0 +66,0 @@ export { environmentManager } from './_tsup-dts-rollup.cjs'; |
+1
-1
@@ -63,3 +63,3 @@ export { DefinedUseBaseQueryResult } from './_tsup-dts-rollup.js'; | ||
| export { useIsRestoring } from './_tsup-dts-rollup.js'; | ||
| export { IsRestoringProvider } from './_tsup-dts-rollup.js'; | ||
| export { IsRestoringContext } from './_tsup-dts-rollup.js'; | ||
| export { focusManager } from './_tsup-dts-rollup.js'; | ||
@@ -66,0 +66,0 @@ export { environmentManager } from './_tsup-dts-rollup.js'; |
+155
-252
@@ -1,9 +0,8 @@ | ||
| import { MutationObserver, shouldThrowError, QueriesObserver, noop, QueryClient as QueryClient$1, replaceEqualDeep, hydrate, notifyManager, QueryObserver, InfiniteQueryObserver } from '@tanstack/query-core'; | ||
| import { MutationObserver, shouldThrowError, QueriesObserver, QueryClient as QueryClient$1, replaceEqualDeep, noop, notifyManager, QueryObserver, InfiniteQueryObserver } from '@tanstack/query-core'; | ||
| export * from '@tanstack/query-core'; | ||
| import { createContext, useContext, createRenderEffect, onCleanup, createMemo, createComputed, on, mergeProps, createResource, batch, onMount, createSignal, createEffect } from 'solid-js'; | ||
| import { createComponent, isServer } from 'solid-js/web'; | ||
| import { createStore, unwrap, reconcile } from 'solid-js/store'; | ||
| import { createContext, useContext, onCleanup, createMemo, createStore, createRenderEffect, merge, createEffect, reconcile, createSignal, isPending, refresh, snapshot } from 'solid-js'; | ||
| import { createComponent } from '@solidjs/web'; | ||
| // src/useQuery.ts | ||
| var QueryClientContext = createContext(void 0); | ||
| var QueryClientContext = createContext(null); | ||
| var useQueryClient = (queryClient) => { | ||
@@ -20,9 +19,5 @@ if (queryClient) { | ||
| var QueryClientProvider = (props) => { | ||
| createRenderEffect((unmount) => { | ||
| unmount?.(); | ||
| props.client.mount(); | ||
| return props.client.unmount.bind(props.client); | ||
| }); | ||
| props.client.mount(); | ||
| onCleanup(() => props.client.unmount()); | ||
| return createComponent(QueryClientContext.Provider, { | ||
| return createComponent(QueryClientContext, { | ||
| value: () => props.client, | ||
@@ -36,11 +31,24 @@ get children() { | ||
| var useIsRestoring = () => useContext(IsRestoringContext); | ||
| var IsRestoringProvider = IsRestoringContext.Provider; | ||
| // src/useBaseQuery.ts | ||
| var isServer = typeof window === "undefined"; | ||
| function _stripFnsForSSR(obj) { | ||
| if (!isServer) return obj; | ||
| const out = {}; | ||
| for (const k of Object.keys(obj)) { | ||
| if (k === "refetch" || k === "fetchNextPage" || k === "fetchPreviousPage") { | ||
| out[k] = void 0; | ||
| } else { | ||
| out[k] = obj[k]; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function reconcileFn(store, result, reconcileOption, queryHash) { | ||
| if (reconcileOption === false) return result; | ||
| if (typeof reconcileOption === "function") { | ||
| const newData2 = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData2 }; | ||
| const newData = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData }; | ||
| } | ||
| if (reconcileOption === false) return result; | ||
| const key = reconcileOption; | ||
| let data = result.data; | ||
@@ -62,4 +70,7 @@ if (store.data === void 0) { | ||
| } | ||
| const newData = reconcile(data, { key: reconcileOption })(store.data); | ||
| return { ...result, data: newData }; | ||
| if (store.data !== void 0 && data !== void 0) { | ||
| reconcile(data, key)(store.data); | ||
| return { ...result, data: store.data }; | ||
| } | ||
| return { ...result, data }; | ||
| } | ||
@@ -69,3 +80,3 @@ var hydratableObserverResult = (query, result) => { | ||
| const obj = { | ||
| ...unwrap(result), | ||
| ...snapshot(result), | ||
| // During SSR, functions cannot be serialized, so we need to remove them | ||
@@ -102,12 +113,18 @@ // This is safe because we will add these functions back when the query is hydrated | ||
| }); | ||
| const initialOptions = defaultedOptions(); | ||
| const [observer, setObserver] = createSignal( | ||
| new Observer(client(), defaultedOptions()) | ||
| const observer = new Observer(client(), defaultedOptions()); | ||
| const trackedDefaultedOptions = createMemo(() => defaultedOptions()); | ||
| createRenderEffect( | ||
| () => trackedDefaultedOptions(), | ||
| (opts) => { | ||
| observer.setOptions(opts); | ||
| } | ||
| ); | ||
| let observerResult = observer().getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = createStore(observerResult); | ||
| let observerResult = observer.getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = createStore( | ||
| _stripFnsForSSR(observerResult) | ||
| ); | ||
| const createServerSubscriber = (resolve, reject) => { | ||
| return observer().subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| notifyManager.batchCalls(() => { | ||
| const query = observer().getCurrentQuery(); | ||
| const query = observer.getCurrentQuery(); | ||
| const unwrappedResult = hydratableObserverResult(query, result); | ||
@@ -131,8 +148,12 @@ if (result.data !== void 0 && unwrappedResult.isError) { | ||
| const createClientSubscriber = () => { | ||
| const obs = observer(); | ||
| return obs.subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| const previousResult = observerResult; | ||
| observerResult = result; | ||
| setStateWithReconciliation(result); | ||
| queueMicrotask(() => { | ||
| if (unsubscribe) { | ||
| refetch(); | ||
| if (unsubscribe && !disposed && (previousResult.isLoading !== result.isLoading || previousResult.isError !== result.isError)) { | ||
| try { | ||
| refresh(queryResource); | ||
| } catch { | ||
| } | ||
| } | ||
@@ -143,8 +164,9 @@ }); | ||
| function setStateWithReconciliation(res) { | ||
| const opts = observer().options; | ||
| const opts = observer.options; | ||
| const reconcileOptions = opts.reconcile; | ||
| const sanitized = _stripFnsForSSR(res); | ||
| setState((store) => { | ||
| return reconcileFn( | ||
| store, | ||
| res, | ||
| sanitized, | ||
| reconcileOptions === void 0 ? false : reconcileOptions, | ||
@@ -155,107 +177,43 @@ opts.queryHash | ||
| } | ||
| function createDeepSignal() { | ||
| return [ | ||
| () => state, | ||
| (v) => { | ||
| const unwrapped = unwrap(state); | ||
| if (typeof v === "function") { | ||
| v = v(unwrapped); | ||
| } | ||
| if (v?.hydrationData) { | ||
| const { hydrationData, ...rest } = v; | ||
| v = rest; | ||
| } | ||
| setStateWithReconciliation(v); | ||
| } | ||
| ]; | ||
| } | ||
| let unsubscribe = null; | ||
| let disposed = false; | ||
| let resolver = null; | ||
| const [queryResource, { refetch }] = createResource( | ||
| const queryResource = createMemo( | ||
| () => { | ||
| const obs = observer(); | ||
| const opts = trackedDefaultedOptions(); | ||
| const restoring = isRestoring(); | ||
| return new Promise((resolve, reject) => { | ||
| resolver = resolve; | ||
| if (isServer) { | ||
| unsubscribe = createServerSubscriber(resolve, reject); | ||
| } else if (!unsubscribe && !isRestoring()) { | ||
| unsubscribe = createServerSubscriber((data) => { | ||
| resolve(data); | ||
| }, reject); | ||
| } else if (!unsubscribe && !restoring) { | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| obs.updateResult(); | ||
| if (observerResult.isError && !observerResult.isFetching && !isRestoring() && shouldThrowError(obs.options.throwOnError, [ | ||
| observerResult.error, | ||
| obs.getCurrentQuery() | ||
| const currentResult = observer.getOptimisticResult(opts); | ||
| observerResult = currentResult; | ||
| if (currentResult.isError && !currentResult.isFetching && !restoring && shouldThrowError(opts.throwOnError, [ | ||
| currentResult.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| setStateWithReconciliation(observerResult); | ||
| return reject(observerResult.error); | ||
| setStateWithReconciliation(currentResult); | ||
| return reject(currentResult.error); | ||
| } | ||
| if (!observerResult.isLoading) { | ||
| if (!currentResult.isLoading) { | ||
| resolver = null; | ||
| setStateWithReconciliation(currentResult); | ||
| return resolve( | ||
| hydratableObserverResult(obs.getCurrentQuery(), observerResult) | ||
| hydratableObserverResult(observer.getCurrentQuery(), currentResult) | ||
| ); | ||
| } | ||
| setStateWithReconciliation(observerResult); | ||
| queueMicrotask(() => setStateWithReconciliation(currentResult)); | ||
| }); | ||
| }, | ||
| { | ||
| storage: createDeepSignal, | ||
| get deferStream() { | ||
| return options().deferStream; | ||
| }, | ||
| /** | ||
| * If this resource was populated on the server (either sync render, or streamed in over time), onHydrated | ||
| * will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber. | ||
| * | ||
| * Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support. | ||
| * | ||
| * Note that this is only invoked on the client, for queries that were originally run on the server. | ||
| */ | ||
| onHydrated(_k, info) { | ||
| if (info.value && "hydrationData" in info.value) { | ||
| hydrate(client(), { | ||
| // @ts-expect-error - hydrationData is not correctly typed internally | ||
| queries: [{ ...info.value.hydrationData }] | ||
| }); | ||
| } | ||
| if (unsubscribe) return; | ||
| const newOptions = { ...initialOptions }; | ||
| if ((initialOptions.staleTime || !initialOptions.initialData) && info.value) { | ||
| newOptions.refetchOnMount = false; | ||
| } | ||
| observer().setOptions(newOptions); | ||
| setStateWithReconciliation(observer().getOptimisticResult(newOptions)); | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| } | ||
| observerResult, | ||
| { ssrSource: "client" } | ||
| ); | ||
| createComputed( | ||
| on( | ||
| client, | ||
| (c) => { | ||
| if (unsubscribe) { | ||
| unsubscribe(); | ||
| } | ||
| const newObserver = new Observer(c, defaultedOptions()); | ||
| unsubscribe = createClientSubscriber(); | ||
| setObserver(newObserver); | ||
| }, | ||
| { | ||
| defer: true | ||
| } | ||
| ) | ||
| ); | ||
| createComputed( | ||
| on( | ||
| isRestoring, | ||
| (restoring) => { | ||
| if (!restoring && !isServer) { | ||
| refetch(); | ||
| } | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| onCleanup(() => { | ||
| if (isServer && queryResource.loading) { | ||
| disposed = true; | ||
| if (isServer && isPending(queryResource)) { | ||
| unsubscribeQueued = true; | ||
@@ -273,25 +231,27 @@ return; | ||
| }); | ||
| createComputed( | ||
| on( | ||
| [observer, defaultedOptions], | ||
| ([obs, opts]) => { | ||
| obs.setOptions(opts); | ||
| setStateWithReconciliation(obs.getOptimisticResult(opts)); | ||
| refetch(); | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| const handler = { | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| if (state.data !== void 0) { | ||
| return queryResource.latest?.data; | ||
| } | ||
| return queryResource()?.data; | ||
| const errorPassthroughProps = /* @__PURE__ */ new Set([ | ||
| "error", | ||
| "isError", | ||
| "failureCount", | ||
| "failureReason", | ||
| "errorUpdateCount", | ||
| "errorUpdatedAt" | ||
| ]); | ||
| return new Proxy(state, { | ||
| get(target, prop, receiver) { | ||
| if (typeof prop === "symbol") { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| if (errorPassthroughProps.has(prop)) { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| if (state.isError && !state.isFetching && shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| throw state.error; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }; | ||
| return new Proxy(state, handler); | ||
| }); | ||
| } | ||
@@ -317,2 +277,5 @@ | ||
| const observer = new MutationObserver(client(), options()); | ||
| createMemo(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| const mutate = (variables, mutateOptions) => { | ||
@@ -326,23 +289,21 @@ observer.mutate(variables, mutateOptions).catch(noop); | ||
| }); | ||
| createComputed(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| createComputed( | ||
| on( | ||
| () => state.status, | ||
| () => { | ||
| if (state.isError && shouldThrowError(observer.options.throwOnError, [state.error])) { | ||
| throw state.error; | ||
| } | ||
| } | ||
| ) | ||
| ); | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| setState({ | ||
| setState(() => ({ | ||
| ...result, | ||
| mutate, | ||
| mutateAsync: result.mutate | ||
| }); | ||
| })); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| createRenderEffect( | ||
| () => { | ||
| const isError = state.isError; | ||
| const error = state.error; | ||
| if (isError && shouldThrowError(observer.options.throwOnError, [error])) { | ||
| throw error; | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| return state; | ||
@@ -355,10 +316,7 @@ } | ||
| () => queriesOptions().queries.map( | ||
| (options) => mergeProps( | ||
| client().defaultQueryOptions(options), | ||
| { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| (options) => merge(client().defaultQueryOptions(options), { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| ) | ||
| }) | ||
| ) | ||
@@ -373,69 +331,36 @@ ); | ||
| ); | ||
| const [, getCombinedResult] = observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| ); | ||
| const initialResult = getCombinedResult(); | ||
| const [state, setState] = createStore( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| Array.isArray(initialResult) ? initialResult : [initialResult] | ||
| ); | ||
| createRenderEffect( | ||
| on( | ||
| () => queriesOptions().queries.length, | ||
| () => setState( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| ) | ||
| ) | ||
| ); | ||
| const dataResources = createMemo( | ||
| on( | ||
| () => state.length, | ||
| () => state.map((queryRes) => { | ||
| const dataPromise = () => new Promise((resolve) => { | ||
| if (queryRes.isFetching && queryRes.isLoading) return; | ||
| resolve(unwrap(queryRes.data)); | ||
| let unsubscribe = noop; | ||
| createEffect( | ||
| () => { | ||
| if (!isRestoring()) { | ||
| unsubscribe = observer.subscribe((result) => { | ||
| setState( | ||
| reconcile( | ||
| [...result], | ||
| // Use a key function that returns undefined so reconcile | ||
| // uses positional matching and recursively updates nested properties | ||
| () => void 0 | ||
| ) | ||
| ); | ||
| }); | ||
| return createResource(dataPromise); | ||
| }) | ||
| ) | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| dataResource[1].mutate(() => unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| onCleanup(() => { | ||
| unsubscribe(); | ||
| }); | ||
| let taskQueue = []; | ||
| const subscribeToObserver = () => observer.subscribe((result) => { | ||
| taskQueue.push(() => { | ||
| batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| const unwrappedResult = { ...unwrap(result[index]) }; | ||
| setState(index, unwrap(unwrappedResult)); | ||
| dataResource[1].mutate(() => unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| }); | ||
| }); | ||
| queueMicrotask(() => { | ||
| const taskToRun = taskQueue.pop(); | ||
| if (taskToRun) taskToRun(); | ||
| taskQueue = []; | ||
| }); | ||
| }); | ||
| let unsubscribe = noop; | ||
| createComputed((cleanup) => { | ||
| cleanup?.(); | ||
| unsubscribe = isRestoring() ? noop : subscribeToObserver(); | ||
| return () => queueMicrotask(unsubscribe); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| onMount(() => { | ||
| createMemo(() => { | ||
| const queries = defaultedQueries(); | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queries, | ||
| queriesOptions().combine ? { | ||
@@ -445,25 +370,5 @@ combine: queriesOptions().combine | ||
| ); | ||
| return queries; | ||
| }); | ||
| createComputed(() => { | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queriesOptions().combine ? { | ||
| combine: queriesOptions().combine | ||
| } : void 0 | ||
| ); | ||
| }); | ||
| const handler = (index) => ({ | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| return dataResources()[index][0](); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| } | ||
| }); | ||
| const getProxies = () => state.map((s, index) => { | ||
| return new Proxy(s, handler(index)); | ||
| }); | ||
| const [proxyState, setProxyState] = createStore(getProxies()); | ||
| createRenderEffect(() => setProxyState(getProxies())); | ||
| return proxyState; | ||
| return state; | ||
| } | ||
@@ -523,14 +428,12 @@ var QueryClient = class extends QueryClient$1 { | ||
| ); | ||
| createEffect(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| setResult((prev) => { | ||
| const nextResult = replaceEqualDeep( | ||
| result(), | ||
| prev, | ||
| getResult(mutationCache(), options()) | ||
| ); | ||
| if (result() !== nextResult) { | ||
| setResult(nextResult); | ||
| } | ||
| return prev === nextResult ? prev : nextResult; | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| return result; | ||
@@ -545,2 +448,2 @@ } | ||
| export { IsRestoringProvider, QueryClient, QueryClientContext, QueryClientProvider, createInfiniteQuery, useIsFetching as createIsFetching, useIsMutating as createIsMutating, createMutation, useMutationState as createMutationState, createQueries, createQuery, infiniteQueryOptions, mutationOptions, queryOptions, useInfiniteQuery, useIsFetching, useIsMutating, useIsRestoring, useMutation, useMutationState, useQueries, useQuery, useQueryClient }; | ||
| export { IsRestoringContext, QueryClient, QueryClientContext, QueryClientProvider, createInfiniteQuery, useIsFetching as createIsFetching, useIsMutating as createIsMutating, createMutation, useMutationState as createMutationState, createQueries, createQuery, infiniteQueryOptions, mutationOptions, queryOptions, useInfiniteQuery, useIsFetching, useIsMutating, useIsRestoring, useMutation, useMutationState, useQueries, useQuery, useQueryClient }; |
+161
-258
@@ -5,7 +5,6 @@ 'use strict'; | ||
| var solidJs = require('solid-js'); | ||
| var web = require('solid-js/web'); | ||
| var store = require('solid-js/store'); | ||
| var web = require('@solidjs/web'); | ||
| // src/useQuery.ts | ||
| exports.QueryClientContext = solidJs.createContext(void 0); | ||
| exports.QueryClientContext = solidJs.createContext(null); | ||
| exports.useQueryClient = (queryClient) => { | ||
@@ -22,9 +21,5 @@ if (queryClient) { | ||
| exports.QueryClientProvider = (props) => { | ||
| solidJs.createRenderEffect((unmount) => { | ||
| unmount?.(); | ||
| props.client.mount(); | ||
| return props.client.unmount.bind(props.client); | ||
| }); | ||
| props.client.mount(); | ||
| solidJs.onCleanup(() => props.client.unmount()); | ||
| return web.createComponent(exports.QueryClientContext.Provider, { | ||
| return web.createComponent(exports.QueryClientContext, { | ||
| value: () => props.client, | ||
@@ -36,15 +31,28 @@ get children() { | ||
| }; | ||
| var IsRestoringContext = solidJs.createContext(() => false); | ||
| exports.useIsRestoring = () => solidJs.useContext(IsRestoringContext); | ||
| exports.IsRestoringProvider = IsRestoringContext.Provider; | ||
| exports.IsRestoringContext = solidJs.createContext(() => false); | ||
| exports.useIsRestoring = () => solidJs.useContext(exports.IsRestoringContext); | ||
| // src/useBaseQuery.ts | ||
| function reconcileFn(store$1, result, reconcileOption, queryHash) { | ||
| if (reconcileOption === false) return result; | ||
| var isServer = typeof window === "undefined"; | ||
| function _stripFnsForSSR(obj) { | ||
| if (!isServer) return obj; | ||
| const out = {}; | ||
| for (const k of Object.keys(obj)) { | ||
| if (k === "refetch" || k === "fetchNextPage" || k === "fetchPreviousPage") { | ||
| out[k] = void 0; | ||
| } else { | ||
| out[k] = obj[k]; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function reconcileFn(store, result, reconcileOption, queryHash) { | ||
| if (typeof reconcileOption === "function") { | ||
| const newData2 = reconcileOption(store$1.data, result.data); | ||
| return { ...result, data: newData2 }; | ||
| const newData = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData }; | ||
| } | ||
| if (reconcileOption === false) return result; | ||
| const key = reconcileOption; | ||
| let data = result.data; | ||
| if (store$1.data === void 0) { | ||
| if (store.data === void 0) { | ||
| try { | ||
@@ -55,9 +63,12 @@ data = structuredClone(data); | ||
| } | ||
| const newData = store.reconcile(data, { key: reconcileOption })(store$1.data); | ||
| return { ...result, data: newData }; | ||
| if (store.data !== void 0 && data !== void 0) { | ||
| solidJs.reconcile(data, key)(store.data); | ||
| return { ...result, data: store.data }; | ||
| } | ||
| return { ...result, data }; | ||
| } | ||
| var hydratableObserverResult = (query, result) => { | ||
| if (!web.isServer) return result; | ||
| if (!isServer) return result; | ||
| const obj = { | ||
| ...store.unwrap(result), | ||
| ...solidJs.snapshot(result), | ||
| // During SSR, functions cannot be serialized, so we need to remove them | ||
@@ -87,3 +98,3 @@ // This is safe because we will add these functions back when the query is hydrated | ||
| defaultOptions.structuralSharing = false; | ||
| if (web.isServer) { | ||
| if (isServer) { | ||
| defaultOptions.retry = false; | ||
@@ -95,12 +106,18 @@ defaultOptions.throwOnError = true; | ||
| }); | ||
| const initialOptions = defaultedOptions(); | ||
| const [observer, setObserver] = solidJs.createSignal( | ||
| new Observer(client(), defaultedOptions()) | ||
| const observer = new Observer(client(), defaultedOptions()); | ||
| const trackedDefaultedOptions = solidJs.createMemo(() => defaultedOptions()); | ||
| solidJs.createRenderEffect( | ||
| () => trackedDefaultedOptions(), | ||
| (opts) => { | ||
| observer.setOptions(opts); | ||
| } | ||
| ); | ||
| let observerResult = observer().getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = store.createStore(observerResult); | ||
| let observerResult = observer.getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = solidJs.createStore( | ||
| _stripFnsForSSR(observerResult) | ||
| ); | ||
| const createServerSubscriber = (resolve, reject) => { | ||
| return observer().subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| queryCore.notifyManager.batchCalls(() => { | ||
| const query = observer().getCurrentQuery(); | ||
| const query = observer.getCurrentQuery(); | ||
| const unwrappedResult = hydratableObserverResult(query, result); | ||
@@ -124,8 +141,12 @@ if (result.data !== void 0 && unwrappedResult.isError) { | ||
| const createClientSubscriber = () => { | ||
| const obs = observer(); | ||
| return obs.subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| const previousResult = observerResult; | ||
| observerResult = result; | ||
| setStateWithReconciliation(result); | ||
| queueMicrotask(() => { | ||
| if (unsubscribe) { | ||
| refetch(); | ||
| if (unsubscribe && !disposed && (previousResult.isLoading !== result.isLoading || previousResult.isError !== result.isError)) { | ||
| try { | ||
| solidJs.refresh(queryResource); | ||
| } catch { | ||
| } | ||
| } | ||
@@ -136,8 +157,9 @@ }); | ||
| function setStateWithReconciliation(res) { | ||
| const opts = observer().options; | ||
| const opts = observer.options; | ||
| const reconcileOptions = opts.reconcile; | ||
| const sanitized = _stripFnsForSSR(res); | ||
| setState((store) => { | ||
| return reconcileFn( | ||
| store, | ||
| res, | ||
| sanitized, | ||
| reconcileOptions === void 0 ? false : reconcileOptions, | ||
@@ -148,107 +170,43 @@ opts.queryHash | ||
| } | ||
| function createDeepSignal() { | ||
| return [ | ||
| () => state, | ||
| (v) => { | ||
| const unwrapped = store.unwrap(state); | ||
| if (typeof v === "function") { | ||
| v = v(unwrapped); | ||
| } | ||
| if (v?.hydrationData) { | ||
| const { hydrationData, ...rest } = v; | ||
| v = rest; | ||
| } | ||
| setStateWithReconciliation(v); | ||
| } | ||
| ]; | ||
| } | ||
| let unsubscribe = null; | ||
| let disposed = false; | ||
| let resolver = null; | ||
| const [queryResource, { refetch }] = solidJs.createResource( | ||
| const queryResource = solidJs.createMemo( | ||
| () => { | ||
| const obs = observer(); | ||
| const opts = trackedDefaultedOptions(); | ||
| const restoring = isRestoring(); | ||
| return new Promise((resolve, reject) => { | ||
| resolver = resolve; | ||
| if (web.isServer) { | ||
| unsubscribe = createServerSubscriber(resolve, reject); | ||
| } else if (!unsubscribe && !isRestoring()) { | ||
| if (isServer) { | ||
| unsubscribe = createServerSubscriber((data) => { | ||
| resolve(data); | ||
| }, reject); | ||
| } else if (!unsubscribe && !restoring) { | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| obs.updateResult(); | ||
| if (observerResult.isError && !observerResult.isFetching && !isRestoring() && queryCore.shouldThrowError(obs.options.throwOnError, [ | ||
| observerResult.error, | ||
| obs.getCurrentQuery() | ||
| const currentResult = observer.getOptimisticResult(opts); | ||
| observerResult = currentResult; | ||
| if (currentResult.isError && !currentResult.isFetching && !restoring && queryCore.shouldThrowError(opts.throwOnError, [ | ||
| currentResult.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| setStateWithReconciliation(observerResult); | ||
| return reject(observerResult.error); | ||
| setStateWithReconciliation(currentResult); | ||
| return reject(currentResult.error); | ||
| } | ||
| if (!observerResult.isLoading) { | ||
| if (!currentResult.isLoading) { | ||
| resolver = null; | ||
| setStateWithReconciliation(currentResult); | ||
| return resolve( | ||
| hydratableObserverResult(obs.getCurrentQuery(), observerResult) | ||
| hydratableObserverResult(observer.getCurrentQuery(), currentResult) | ||
| ); | ||
| } | ||
| setStateWithReconciliation(observerResult); | ||
| queueMicrotask(() => setStateWithReconciliation(currentResult)); | ||
| }); | ||
| }, | ||
| { | ||
| storage: createDeepSignal, | ||
| get deferStream() { | ||
| return options().deferStream; | ||
| }, | ||
| /** | ||
| * If this resource was populated on the server (either sync render, or streamed in over time), onHydrated | ||
| * will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber. | ||
| * | ||
| * Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support. | ||
| * | ||
| * Note that this is only invoked on the client, for queries that were originally run on the server. | ||
| */ | ||
| onHydrated(_k, info) { | ||
| if (info.value && "hydrationData" in info.value) { | ||
| queryCore.hydrate(client(), { | ||
| // @ts-expect-error - hydrationData is not correctly typed internally | ||
| queries: [{ ...info.value.hydrationData }] | ||
| }); | ||
| } | ||
| if (unsubscribe) return; | ||
| const newOptions = { ...initialOptions }; | ||
| if ((initialOptions.staleTime || !initialOptions.initialData) && info.value) { | ||
| newOptions.refetchOnMount = false; | ||
| } | ||
| observer().setOptions(newOptions); | ||
| setStateWithReconciliation(observer().getOptimisticResult(newOptions)); | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| } | ||
| observerResult, | ||
| { ssrSource: "client" } | ||
| ); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| client, | ||
| (c) => { | ||
| if (unsubscribe) { | ||
| unsubscribe(); | ||
| } | ||
| const newObserver = new Observer(c, defaultedOptions()); | ||
| unsubscribe = createClientSubscriber(); | ||
| setObserver(newObserver); | ||
| }, | ||
| { | ||
| defer: true | ||
| } | ||
| ) | ||
| ); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| isRestoring, | ||
| (restoring) => { | ||
| if (!restoring && !web.isServer) { | ||
| refetch(); | ||
| } | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| solidJs.onCleanup(() => { | ||
| if (web.isServer && queryResource.loading) { | ||
| disposed = true; | ||
| if (isServer && solidJs.isPending(queryResource)) { | ||
| unsubscribeQueued = true; | ||
@@ -261,3 +219,3 @@ return; | ||
| } | ||
| if (resolver && !web.isServer) { | ||
| if (resolver && !isServer) { | ||
| resolver(observerResult); | ||
@@ -267,25 +225,27 @@ resolver = null; | ||
| }); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| [observer, defaultedOptions], | ||
| ([obs, opts]) => { | ||
| obs.setOptions(opts); | ||
| setStateWithReconciliation(obs.getOptimisticResult(opts)); | ||
| refetch(); | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| const handler = { | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| if (state.data !== void 0) { | ||
| return queryResource.latest?.data; | ||
| } | ||
| return queryResource()?.data; | ||
| const errorPassthroughProps = /* @__PURE__ */ new Set([ | ||
| "error", | ||
| "isError", | ||
| "failureCount", | ||
| "failureReason", | ||
| "errorUpdateCount", | ||
| "errorUpdatedAt" | ||
| ]); | ||
| return new Proxy(state, { | ||
| get(target, prop, receiver) { | ||
| if (typeof prop === "symbol") { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| if (errorPassthroughProps.has(prop)) { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| if (state.isError && !state.isFetching && queryCore.shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| throw state.error; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }; | ||
| return new Proxy(state, handler); | ||
| }); | ||
| } | ||
@@ -311,6 +271,9 @@ | ||
| const observer = new queryCore.MutationObserver(client(), options()); | ||
| solidJs.createMemo(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| const mutate = (variables, mutateOptions) => { | ||
| observer.mutate(variables, mutateOptions).catch(queryCore.noop); | ||
| }; | ||
| const [state, setState] = store.createStore({ | ||
| const [state, setState] = solidJs.createStore({ | ||
| ...observer.getCurrentResult(), | ||
@@ -320,23 +283,21 @@ mutate, | ||
| }); | ||
| solidJs.createComputed(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| solidJs.createComputed( | ||
| solidJs.on( | ||
| () => state.status, | ||
| () => { | ||
| if (state.isError && queryCore.shouldThrowError(observer.options.throwOnError, [state.error])) { | ||
| throw state.error; | ||
| } | ||
| } | ||
| ) | ||
| ); | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| setState({ | ||
| setState(() => ({ | ||
| ...result, | ||
| mutate, | ||
| mutateAsync: result.mutate | ||
| }); | ||
| })); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| solidJs.createRenderEffect( | ||
| () => { | ||
| const isError = state.isError; | ||
| const error = state.error; | ||
| if (isError && queryCore.shouldThrowError(observer.options.throwOnError, [error])) { | ||
| throw error; | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| return state; | ||
@@ -349,10 +310,7 @@ } | ||
| () => queriesOptions().queries.map( | ||
| (options) => solidJs.mergeProps( | ||
| client().defaultQueryOptions(options), | ||
| { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| (options) => solidJs.merge(client().defaultQueryOptions(options), { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| ) | ||
| }) | ||
| ) | ||
@@ -367,69 +325,36 @@ ); | ||
| ); | ||
| const [state, setState] = store.createStore( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| const [, getCombinedResult] = observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| ); | ||
| solidJs.createRenderEffect( | ||
| solidJs.on( | ||
| () => queriesOptions().queries.length, | ||
| () => setState( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| ) | ||
| ) | ||
| const initialResult = getCombinedResult(); | ||
| const [state, setState] = solidJs.createStore( | ||
| Array.isArray(initialResult) ? initialResult : [initialResult] | ||
| ); | ||
| const dataResources = solidJs.createMemo( | ||
| solidJs.on( | ||
| () => state.length, | ||
| () => state.map((queryRes) => { | ||
| const dataPromise = () => new Promise((resolve) => { | ||
| if (queryRes.isFetching && queryRes.isLoading) return; | ||
| resolve(store.unwrap(queryRes.data)); | ||
| let unsubscribe = queryCore.noop; | ||
| solidJs.createEffect( | ||
| () => { | ||
| if (!isRestoring()) { | ||
| unsubscribe = observer.subscribe((result) => { | ||
| setState( | ||
| solidJs.reconcile( | ||
| [...result], | ||
| // Use a key function that returns undefined so reconcile | ||
| // uses positional matching and recursively updates nested properties | ||
| () => void 0 | ||
| ) | ||
| ); | ||
| }); | ||
| return solidJs.createResource(dataPromise); | ||
| }) | ||
| ) | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| solidJs.batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| dataResource[1].mutate(() => store.unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| solidJs.onCleanup(() => { | ||
| unsubscribe(); | ||
| }); | ||
| let taskQueue = []; | ||
| const subscribeToObserver = () => observer.subscribe((result) => { | ||
| taskQueue.push(() => { | ||
| solidJs.batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| const unwrappedResult = { ...store.unwrap(result[index]) }; | ||
| setState(index, store.unwrap(unwrappedResult)); | ||
| dataResource[1].mutate(() => store.unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| }); | ||
| }); | ||
| queueMicrotask(() => { | ||
| const taskToRun = taskQueue.pop(); | ||
| if (taskToRun) taskToRun(); | ||
| taskQueue = []; | ||
| }); | ||
| }); | ||
| let unsubscribe = queryCore.noop; | ||
| solidJs.createComputed((cleanup) => { | ||
| cleanup?.(); | ||
| unsubscribe = isRestoring() ? queryCore.noop : subscribeToObserver(); | ||
| return () => queueMicrotask(unsubscribe); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| solidJs.onMount(() => { | ||
| solidJs.createMemo(() => { | ||
| const queries = defaultedQueries(); | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queries, | ||
| queriesOptions().combine ? { | ||
@@ -439,25 +364,5 @@ combine: queriesOptions().combine | ||
| ); | ||
| return queries; | ||
| }); | ||
| solidJs.createComputed(() => { | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queriesOptions().combine ? { | ||
| combine: queriesOptions().combine | ||
| } : void 0 | ||
| ); | ||
| }); | ||
| const handler = (index) => ({ | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| return dataResources()[index][0](); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| } | ||
| }); | ||
| const getProxies = () => state.map((s, index) => { | ||
| return new Proxy(s, handler(index)); | ||
| }); | ||
| const [proxyState, setProxyState] = store.createStore(getProxies()); | ||
| solidJs.createRenderEffect(() => setProxyState(getProxies())); | ||
| return proxyState; | ||
| return state; | ||
| } | ||
@@ -517,14 +422,12 @@ exports.QueryClient = class QueryClient extends queryCore.QueryClient { | ||
| ); | ||
| solidJs.createEffect(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| setResult((prev) => { | ||
| const nextResult = queryCore.replaceEqualDeep( | ||
| result(), | ||
| prev, | ||
| getResult(mutationCache(), options()) | ||
| ); | ||
| if (result() !== nextResult) { | ||
| setResult(nextResult); | ||
| } | ||
| return prev === nextResult ? prev : nextResult; | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| }); | ||
| solidJs.onCleanup(unsubscribe); | ||
| return result; | ||
@@ -531,0 +434,0 @@ } |
@@ -63,3 +63,3 @@ export { DefinedUseBaseQueryResult } from './_tsup-dts-rollup.cjs'; | ||
| export { useIsRestoring } from './_tsup-dts-rollup.cjs'; | ||
| export { IsRestoringProvider } from './_tsup-dts-rollup.cjs'; | ||
| export { IsRestoringContext } from './_tsup-dts-rollup.cjs'; | ||
| export { focusManager } from './_tsup-dts-rollup.cjs'; | ||
@@ -66,0 +66,0 @@ export { environmentManager } from './_tsup-dts-rollup.cjs'; |
+1
-1
@@ -63,3 +63,3 @@ export { DefinedUseBaseQueryResult } from './_tsup-dts-rollup.js'; | ||
| export { useIsRestoring } from './_tsup-dts-rollup.js'; | ||
| export { IsRestoringProvider } from './_tsup-dts-rollup.js'; | ||
| export { IsRestoringContext } from './_tsup-dts-rollup.js'; | ||
| export { focusManager } from './_tsup-dts-rollup.js'; | ||
@@ -66,0 +66,0 @@ export { environmentManager } from './_tsup-dts-rollup.js'; |
+155
-252
@@ -1,9 +0,8 @@ | ||
| import { MutationObserver, shouldThrowError, QueriesObserver, noop, QueryClient as QueryClient$1, replaceEqualDeep, hydrate, notifyManager, QueryObserver, InfiniteQueryObserver } from '@tanstack/query-core'; | ||
| import { MutationObserver, shouldThrowError, QueriesObserver, QueryClient as QueryClient$1, replaceEqualDeep, noop, notifyManager, QueryObserver, InfiniteQueryObserver } from '@tanstack/query-core'; | ||
| export * from '@tanstack/query-core'; | ||
| import { createContext, useContext, createRenderEffect, onCleanup, createMemo, createComputed, on, mergeProps, createResource, batch, onMount, createSignal, createEffect } from 'solid-js'; | ||
| import { createComponent, isServer } from 'solid-js/web'; | ||
| import { createStore, unwrap, reconcile } from 'solid-js/store'; | ||
| import { createContext, useContext, onCleanup, createMemo, createStore, createRenderEffect, merge, createEffect, reconcile, createSignal, isPending, refresh, snapshot } from 'solid-js'; | ||
| import { createComponent } from '@solidjs/web'; | ||
| // src/useQuery.ts | ||
| var QueryClientContext = createContext(void 0); | ||
| var QueryClientContext = createContext(null); | ||
| var useQueryClient = (queryClient) => { | ||
@@ -20,9 +19,5 @@ if (queryClient) { | ||
| var QueryClientProvider = (props) => { | ||
| createRenderEffect((unmount) => { | ||
| unmount?.(); | ||
| props.client.mount(); | ||
| return props.client.unmount.bind(props.client); | ||
| }); | ||
| props.client.mount(); | ||
| onCleanup(() => props.client.unmount()); | ||
| return createComponent(QueryClientContext.Provider, { | ||
| return createComponent(QueryClientContext, { | ||
| value: () => props.client, | ||
@@ -36,11 +31,24 @@ get children() { | ||
| var useIsRestoring = () => useContext(IsRestoringContext); | ||
| var IsRestoringProvider = IsRestoringContext.Provider; | ||
| // src/useBaseQuery.ts | ||
| var isServer = typeof window === "undefined"; | ||
| function _stripFnsForSSR(obj) { | ||
| if (!isServer) return obj; | ||
| const out = {}; | ||
| for (const k of Object.keys(obj)) { | ||
| if (k === "refetch" || k === "fetchNextPage" || k === "fetchPreviousPage") { | ||
| out[k] = void 0; | ||
| } else { | ||
| out[k] = obj[k]; | ||
| } | ||
| } | ||
| return out; | ||
| } | ||
| function reconcileFn(store, result, reconcileOption, queryHash) { | ||
| if (reconcileOption === false) return result; | ||
| if (typeof reconcileOption === "function") { | ||
| const newData2 = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData2 }; | ||
| const newData = reconcileOption(store.data, result.data); | ||
| return { ...result, data: newData }; | ||
| } | ||
| if (reconcileOption === false) return result; | ||
| const key = reconcileOption; | ||
| let data = result.data; | ||
@@ -53,4 +61,7 @@ if (store.data === void 0) { | ||
| } | ||
| const newData = reconcile(data, { key: reconcileOption })(store.data); | ||
| return { ...result, data: newData }; | ||
| if (store.data !== void 0 && data !== void 0) { | ||
| reconcile(data, key)(store.data); | ||
| return { ...result, data: store.data }; | ||
| } | ||
| return { ...result, data }; | ||
| } | ||
@@ -60,3 +71,3 @@ var hydratableObserverResult = (query, result) => { | ||
| const obj = { | ||
| ...unwrap(result), | ||
| ...snapshot(result), | ||
| // During SSR, functions cannot be serialized, so we need to remove them | ||
@@ -93,12 +104,18 @@ // This is safe because we will add these functions back when the query is hydrated | ||
| }); | ||
| const initialOptions = defaultedOptions(); | ||
| const [observer, setObserver] = createSignal( | ||
| new Observer(client(), defaultedOptions()) | ||
| const observer = new Observer(client(), defaultedOptions()); | ||
| const trackedDefaultedOptions = createMemo(() => defaultedOptions()); | ||
| createRenderEffect( | ||
| () => trackedDefaultedOptions(), | ||
| (opts) => { | ||
| observer.setOptions(opts); | ||
| } | ||
| ); | ||
| let observerResult = observer().getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = createStore(observerResult); | ||
| let observerResult = observer.getOptimisticResult(defaultedOptions()); | ||
| const [state, setState] = createStore( | ||
| _stripFnsForSSR(observerResult) | ||
| ); | ||
| const createServerSubscriber = (resolve, reject) => { | ||
| return observer().subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| notifyManager.batchCalls(() => { | ||
| const query = observer().getCurrentQuery(); | ||
| const query = observer.getCurrentQuery(); | ||
| const unwrappedResult = hydratableObserverResult(query, result); | ||
@@ -122,8 +139,12 @@ if (result.data !== void 0 && unwrappedResult.isError) { | ||
| const createClientSubscriber = () => { | ||
| const obs = observer(); | ||
| return obs.subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| const previousResult = observerResult; | ||
| observerResult = result; | ||
| setStateWithReconciliation(result); | ||
| queueMicrotask(() => { | ||
| if (unsubscribe) { | ||
| refetch(); | ||
| if (unsubscribe && !disposed && (previousResult.isLoading !== result.isLoading || previousResult.isError !== result.isError)) { | ||
| try { | ||
| refresh(queryResource); | ||
| } catch { | ||
| } | ||
| } | ||
@@ -134,8 +155,9 @@ }); | ||
| function setStateWithReconciliation(res) { | ||
| const opts = observer().options; | ||
| const opts = observer.options; | ||
| const reconcileOptions = opts.reconcile; | ||
| const sanitized = _stripFnsForSSR(res); | ||
| setState((store) => { | ||
| return reconcileFn( | ||
| store, | ||
| res, | ||
| sanitized, | ||
| reconcileOptions === void 0 ? false : reconcileOptions, | ||
@@ -146,107 +168,43 @@ opts.queryHash | ||
| } | ||
| function createDeepSignal() { | ||
| return [ | ||
| () => state, | ||
| (v) => { | ||
| const unwrapped = unwrap(state); | ||
| if (typeof v === "function") { | ||
| v = v(unwrapped); | ||
| } | ||
| if (v?.hydrationData) { | ||
| const { hydrationData, ...rest } = v; | ||
| v = rest; | ||
| } | ||
| setStateWithReconciliation(v); | ||
| } | ||
| ]; | ||
| } | ||
| let unsubscribe = null; | ||
| let disposed = false; | ||
| let resolver = null; | ||
| const [queryResource, { refetch }] = createResource( | ||
| const queryResource = createMemo( | ||
| () => { | ||
| const obs = observer(); | ||
| const opts = trackedDefaultedOptions(); | ||
| const restoring = isRestoring(); | ||
| return new Promise((resolve, reject) => { | ||
| resolver = resolve; | ||
| if (isServer) { | ||
| unsubscribe = createServerSubscriber(resolve, reject); | ||
| } else if (!unsubscribe && !isRestoring()) { | ||
| unsubscribe = createServerSubscriber((data) => { | ||
| resolve(data); | ||
| }, reject); | ||
| } else if (!unsubscribe && !restoring) { | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| obs.updateResult(); | ||
| if (observerResult.isError && !observerResult.isFetching && !isRestoring() && shouldThrowError(obs.options.throwOnError, [ | ||
| observerResult.error, | ||
| obs.getCurrentQuery() | ||
| const currentResult = observer.getOptimisticResult(opts); | ||
| observerResult = currentResult; | ||
| if (currentResult.isError && !currentResult.isFetching && !restoring && shouldThrowError(opts.throwOnError, [ | ||
| currentResult.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| setStateWithReconciliation(observerResult); | ||
| return reject(observerResult.error); | ||
| setStateWithReconciliation(currentResult); | ||
| return reject(currentResult.error); | ||
| } | ||
| if (!observerResult.isLoading) { | ||
| if (!currentResult.isLoading) { | ||
| resolver = null; | ||
| setStateWithReconciliation(currentResult); | ||
| return resolve( | ||
| hydratableObserverResult(obs.getCurrentQuery(), observerResult) | ||
| hydratableObserverResult(observer.getCurrentQuery(), currentResult) | ||
| ); | ||
| } | ||
| setStateWithReconciliation(observerResult); | ||
| queueMicrotask(() => setStateWithReconciliation(currentResult)); | ||
| }); | ||
| }, | ||
| { | ||
| storage: createDeepSignal, | ||
| get deferStream() { | ||
| return options().deferStream; | ||
| }, | ||
| /** | ||
| * If this resource was populated on the server (either sync render, or streamed in over time), onHydrated | ||
| * will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber. | ||
| * | ||
| * Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support. | ||
| * | ||
| * Note that this is only invoked on the client, for queries that were originally run on the server. | ||
| */ | ||
| onHydrated(_k, info) { | ||
| if (info.value && "hydrationData" in info.value) { | ||
| hydrate(client(), { | ||
| // @ts-expect-error - hydrationData is not correctly typed internally | ||
| queries: [{ ...info.value.hydrationData }] | ||
| }); | ||
| } | ||
| if (unsubscribe) return; | ||
| const newOptions = { ...initialOptions }; | ||
| if ((initialOptions.staleTime || !initialOptions.initialData) && info.value) { | ||
| newOptions.refetchOnMount = false; | ||
| } | ||
| observer().setOptions(newOptions); | ||
| setStateWithReconciliation(observer().getOptimisticResult(newOptions)); | ||
| unsubscribe = createClientSubscriber(); | ||
| } | ||
| } | ||
| observerResult, | ||
| { ssrSource: "client" } | ||
| ); | ||
| createComputed( | ||
| on( | ||
| client, | ||
| (c) => { | ||
| if (unsubscribe) { | ||
| unsubscribe(); | ||
| } | ||
| const newObserver = new Observer(c, defaultedOptions()); | ||
| unsubscribe = createClientSubscriber(); | ||
| setObserver(newObserver); | ||
| }, | ||
| { | ||
| defer: true | ||
| } | ||
| ) | ||
| ); | ||
| createComputed( | ||
| on( | ||
| isRestoring, | ||
| (restoring) => { | ||
| if (!restoring && !isServer) { | ||
| refetch(); | ||
| } | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| onCleanup(() => { | ||
| if (isServer && queryResource.loading) { | ||
| disposed = true; | ||
| if (isServer && isPending(queryResource)) { | ||
| unsubscribeQueued = true; | ||
@@ -264,25 +222,27 @@ return; | ||
| }); | ||
| createComputed( | ||
| on( | ||
| [observer, defaultedOptions], | ||
| ([obs, opts]) => { | ||
| obs.setOptions(opts); | ||
| setStateWithReconciliation(obs.getOptimisticResult(opts)); | ||
| refetch(); | ||
| }, | ||
| { defer: true } | ||
| ) | ||
| ); | ||
| const handler = { | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| if (state.data !== void 0) { | ||
| return queryResource.latest?.data; | ||
| } | ||
| return queryResource()?.data; | ||
| const errorPassthroughProps = /* @__PURE__ */ new Set([ | ||
| "error", | ||
| "isError", | ||
| "failureCount", | ||
| "failureReason", | ||
| "errorUpdateCount", | ||
| "errorUpdatedAt" | ||
| ]); | ||
| return new Proxy(state, { | ||
| get(target, prop, receiver) { | ||
| if (typeof prop === "symbol") { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| if (errorPassthroughProps.has(prop)) { | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| if (state.isError && !state.isFetching && shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery() | ||
| ])) { | ||
| throw state.error; | ||
| } | ||
| return Reflect.get(target, prop, receiver); | ||
| } | ||
| }; | ||
| return new Proxy(state, handler); | ||
| }); | ||
| } | ||
@@ -308,2 +268,5 @@ | ||
| const observer = new MutationObserver(client(), options()); | ||
| createMemo(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| const mutate = (variables, mutateOptions) => { | ||
@@ -317,23 +280,21 @@ observer.mutate(variables, mutateOptions).catch(noop); | ||
| }); | ||
| createComputed(() => { | ||
| observer.setOptions(options()); | ||
| }); | ||
| createComputed( | ||
| on( | ||
| () => state.status, | ||
| () => { | ||
| if (state.isError && shouldThrowError(observer.options.throwOnError, [state.error])) { | ||
| throw state.error; | ||
| } | ||
| } | ||
| ) | ||
| ); | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| setState({ | ||
| setState(() => ({ | ||
| ...result, | ||
| mutate, | ||
| mutateAsync: result.mutate | ||
| }); | ||
| })); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| createRenderEffect( | ||
| () => { | ||
| const isError = state.isError; | ||
| const error = state.error; | ||
| if (isError && shouldThrowError(observer.options.throwOnError, [error])) { | ||
| throw error; | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| return state; | ||
@@ -346,10 +307,7 @@ } | ||
| () => queriesOptions().queries.map( | ||
| (options) => mergeProps( | ||
| client().defaultQueryOptions(options), | ||
| { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| (options) => merge(client().defaultQueryOptions(options), { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? "isRestoring" : "optimistic"; | ||
| } | ||
| ) | ||
| }) | ||
| ) | ||
@@ -364,69 +322,36 @@ ); | ||
| ); | ||
| const [, getCombinedResult] = observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| ); | ||
| const initialResult = getCombinedResult(); | ||
| const [state, setState] = createStore( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| Array.isArray(initialResult) ? initialResult : [initialResult] | ||
| ); | ||
| createRenderEffect( | ||
| on( | ||
| () => queriesOptions().queries.length, | ||
| () => setState( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| )[1]() | ||
| ) | ||
| ) | ||
| ); | ||
| const dataResources = createMemo( | ||
| on( | ||
| () => state.length, | ||
| () => state.map((queryRes) => { | ||
| const dataPromise = () => new Promise((resolve) => { | ||
| if (queryRes.isFetching && queryRes.isLoading) return; | ||
| resolve(unwrap(queryRes.data)); | ||
| let unsubscribe = noop; | ||
| createEffect( | ||
| () => { | ||
| if (!isRestoring()) { | ||
| unsubscribe = observer.subscribe((result) => { | ||
| setState( | ||
| reconcile( | ||
| [...result], | ||
| // Use a key function that returns undefined so reconcile | ||
| // uses positional matching and recursively updates nested properties | ||
| () => void 0 | ||
| ) | ||
| ); | ||
| }); | ||
| return createResource(dataPromise); | ||
| }) | ||
| ) | ||
| } | ||
| }, | ||
| () => { | ||
| } | ||
| ); | ||
| batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| dataResource[1].mutate(() => unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| onCleanup(() => { | ||
| unsubscribe(); | ||
| }); | ||
| let taskQueue = []; | ||
| const subscribeToObserver = () => observer.subscribe((result) => { | ||
| taskQueue.push(() => { | ||
| batch(() => { | ||
| const dataResources_ = dataResources(); | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]; | ||
| const unwrappedResult = { ...unwrap(result[index]) }; | ||
| setState(index, unwrap(unwrappedResult)); | ||
| dataResource[1].mutate(() => unwrap(state[index].data)); | ||
| dataResource[1].refetch(); | ||
| } | ||
| }); | ||
| }); | ||
| queueMicrotask(() => { | ||
| const taskToRun = taskQueue.pop(); | ||
| if (taskToRun) taskToRun(); | ||
| taskQueue = []; | ||
| }); | ||
| }); | ||
| let unsubscribe = noop; | ||
| createComputed((cleanup) => { | ||
| cleanup?.(); | ||
| unsubscribe = isRestoring() ? noop : subscribeToObserver(); | ||
| return () => queueMicrotask(unsubscribe); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| onMount(() => { | ||
| createMemo(() => { | ||
| const queries = defaultedQueries(); | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queries, | ||
| queriesOptions().combine ? { | ||
@@ -436,25 +361,5 @@ combine: queriesOptions().combine | ||
| ); | ||
| return queries; | ||
| }); | ||
| createComputed(() => { | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queriesOptions().combine ? { | ||
| combine: queriesOptions().combine | ||
| } : void 0 | ||
| ); | ||
| }); | ||
| const handler = (index) => ({ | ||
| get(target, prop) { | ||
| if (prop === "data") { | ||
| return dataResources()[index][0](); | ||
| } | ||
| return Reflect.get(target, prop); | ||
| } | ||
| }); | ||
| const getProxies = () => state.map((s, index) => { | ||
| return new Proxy(s, handler(index)); | ||
| }); | ||
| const [proxyState, setProxyState] = createStore(getProxies()); | ||
| createRenderEffect(() => setProxyState(getProxies())); | ||
| return proxyState; | ||
| return state; | ||
| } | ||
@@ -514,14 +419,12 @@ var QueryClient = class extends QueryClient$1 { | ||
| ); | ||
| createEffect(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| setResult((prev) => { | ||
| const nextResult = replaceEqualDeep( | ||
| result(), | ||
| prev, | ||
| getResult(mutationCache(), options()) | ||
| ); | ||
| if (result() !== nextResult) { | ||
| setResult(nextResult); | ||
| } | ||
| return prev === nextResult ? prev : nextResult; | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| }); | ||
| onCleanup(unsubscribe); | ||
| return result; | ||
@@ -536,2 +439,2 @@ } | ||
| export { IsRestoringProvider, QueryClient, QueryClientContext, QueryClientProvider, createInfiniteQuery, useIsFetching as createIsFetching, useIsMutating as createIsMutating, createMutation, useMutationState as createMutationState, createQueries, createQuery, infiniteQueryOptions, mutationOptions, queryOptions, useInfiniteQuery, useIsFetching, useIsMutating, useIsRestoring, useMutation, useMutationState, useQueries, useQuery, useQueryClient }; | ||
| export { IsRestoringContext, QueryClient, QueryClientContext, QueryClientProvider, createInfiniteQuery, useIsFetching as createIsFetching, useIsMutating as createIsMutating, createMutation, useMutationState as createMutationState, createQueries, createQuery, infiniteQueryOptions, mutationOptions, queryOptions, useInfiniteQuery, useIsFetching, useIsMutating, useIsRestoring, useMutation, useMutationState, useQueries, useQuery, useQueryClient }; |
+8
-4
| { | ||
| "name": "@tanstack/solid-query", | ||
| "version": "5.91.3", | ||
| "version": "6.0.0-alpha.0", | ||
| "description": "Primitives for managing, caching and syncing asynchronous and remote data in Solid", | ||
@@ -52,11 +52,15 @@ "author": "tannerlinsley", | ||
| "devDependencies": { | ||
| "@babel/core": "^7.28.0", | ||
| "@babel/preset-typescript": "^7.18.6", | ||
| "@solidjs/testing-library": "^0.8.10", | ||
| "@solidjs/web": "2.0.0-beta.2", | ||
| "babel-preset-solid": "2.0.0-beta.2", | ||
| "npm-run-all2": "^5.0.0", | ||
| "solid-js": "^1.9.7", | ||
| "solid-js": "2.0.0-beta.2", | ||
| "tsup-preset-solid": "^2.2.0", | ||
| "vite-plugin-solid": "^2.11.6", | ||
| "vite-plugin-solid": "3.0.0-next.2", | ||
| "@tanstack/query-test-utils": "0.0.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "solid-js": "^1.6.0" | ||
| "solid-js": ">=2.0.0-beta.0 <3.0.0" | ||
| }, | ||
@@ -63,0 +67,0 @@ "scripts": { |
+1
-1
@@ -87,2 +87,2 @@ /* istanbul ignore file */ | ||
| export const createQueries = useQueries | ||
| export { useIsRestoring, IsRestoringProvider } from './isRestoring' | ||
| export { useIsRestoring, IsRestoringContext } from './isRestoring' |
| import { createContext, useContext } from 'solid-js' | ||
| import type { Accessor } from 'solid-js' | ||
| const IsRestoringContext = createContext<Accessor<boolean>>(() => false) | ||
| export const IsRestoringContext = createContext<Accessor<boolean>>(() => false) | ||
| export const useIsRestoring = () => useContext(IsRestoringContext) | ||
| export const IsRestoringProvider = IsRestoringContext.Provider |
@@ -1,13 +0,8 @@ | ||
| import { | ||
| createContext, | ||
| createRenderEffect, | ||
| onCleanup, | ||
| useContext, | ||
| } from 'solid-js' | ||
| import { createContext, onCleanup, useContext } from 'solid-js' | ||
| import type { QueryClient } from './QueryClient' | ||
| import type { JSX } from 'solid-js' | ||
| export const QueryClientContext = createContext< | ||
| (() => QueryClient) | undefined | ||
| >(undefined) | ||
| export const QueryClientContext = createContext<(() => QueryClient) | null>( | ||
| null, | ||
| ) | ||
@@ -35,14 +30,10 @@ export const useQueryClient = (queryClient?: QueryClient) => { | ||
| ): JSX.Element => { | ||
| createRenderEffect<() => void>((unmount) => { | ||
| unmount?.() | ||
| props.client.mount() | ||
| return props.client.unmount.bind(props.client) | ||
| }) | ||
| props.client.mount() | ||
| onCleanup(() => props.client.unmount()) | ||
| return ( | ||
| <QueryClientContext.Provider value={() => props.client}> | ||
| <QueryClientContext value={() => props.client}> | ||
| {props.children} | ||
| </QueryClientContext.Provider> | ||
| </QueryClientContext> | ||
| ) | ||
| } |
+154
-160
| // Had to disable the lint rule because isServer type is defined as false | ||
| // in solid-js/web package. I'll create a GitHub issue with them to see | ||
| // why that happens. | ||
| import { hydrate, notifyManager, shouldThrowError } from '@tanstack/query-core' | ||
| import { isServer } from 'solid-js/web' | ||
| import { notifyManager, shouldThrowError } from '@tanstack/query-core' | ||
| import { | ||
| createComputed, | ||
| createMemo, | ||
| createResource, | ||
| createSignal, | ||
| on, | ||
| createRenderEffect, | ||
| createStore, | ||
| isPending, | ||
| onCleanup, | ||
| reconcile, | ||
| refresh, | ||
| snapshot, | ||
| } from 'solid-js' | ||
| import { createStore, reconcile, unwrap } from 'solid-js/store' | ||
| import { useQueryClient } from './QueryClientProvider' | ||
| import { useIsRestoring } from './isRestoring' | ||
| import type { UseBaseQueryOptions } from './types' | ||
| import type { Accessor, Signal } from 'solid-js' | ||
| import type { Accessor } from 'solid-js' | ||
| import type { QueryClient } from './QueryClient' | ||
@@ -27,2 +27,25 @@ import type { | ||
| const isServer = typeof window === 'undefined' | ||
| /** | ||
| * During SSR, Solid's store is serialized by seroval which cannot handle | ||
| * functions. Strip `refetch`, `fetchNextPage`, and `fetchPreviousPage` | ||
| * from the observer result before it enters the store so serialization | ||
| * succeeds. On the client this is a no-op (returns the object as-is). | ||
| */ | ||
| function _stripFnsForSSR<TData, TError>( | ||
| obj: QueryObserverResult<TData, TError>, | ||
| ): QueryObserverResult<TData, TError> { | ||
| if (!isServer) return obj | ||
| const out: Record<string, unknown> = {} | ||
| for (const k of Object.keys(obj)) { | ||
| if (k === 'refetch' || k === 'fetchNextPage' || k === 'fetchPreviousPage') { | ||
| out[k] = undefined | ||
| } else { | ||
| out[k] = (obj as any)[k] | ||
| } | ||
| } | ||
| return out as unknown as QueryObserverResult<TData, TError> | ||
| } | ||
| function reconcileFn<TData, TError>( | ||
@@ -37,3 +60,2 @@ store: QueryObserverResult<TData, TError>, | ||
| ): QueryObserverResult<TData, TError> { | ||
| if (reconcileOption === false) return result | ||
| if (typeof reconcileOption === 'function') { | ||
@@ -43,2 +65,7 @@ const newData = reconcileOption(store.data, result.data as TData) | ||
| } | ||
| if (reconcileOption === false) return result | ||
| const key = reconcileOption | ||
| let data = result.data | ||
@@ -61,4 +88,12 @@ if (store.data === undefined) { | ||
| } | ||
| const newData = reconcile(data, { key: reconcileOption })(store.data) | ||
| return { ...result, data: newData } as typeof result | ||
| // reconcile() in Solid 2.0 mutates in place and returns void. | ||
| // We apply it to store.data so the store's nested signals update. | ||
| // On first load (store.data is undefined), there's nothing to reconcile against, | ||
| // so we just return the data as-is. | ||
| if (store.data !== undefined && data !== undefined) { | ||
| reconcile(data, key)(store.data) | ||
| // Return result with the existing store.data reference (now reconciled in place) | ||
| return { ...result, data: store.data } as typeof result | ||
| } | ||
| return { ...result, data } as typeof result | ||
| } | ||
@@ -82,3 +117,3 @@ | ||
| const obj: any = { | ||
| ...unwrap(result), | ||
| ...snapshot(result), | ||
| // During SSR, functions cannot be serialized, so we need to remove them | ||
@@ -146,11 +181,22 @@ // This is safe because we will add these functions back when the query is hydrated | ||
| }) | ||
| const initialOptions = defaultedOptions() | ||
| const [observer, setObserver] = createSignal( | ||
| new Observer(client(), defaultedOptions()), | ||
| const observer = new Observer(client(), defaultedOptions()) | ||
| // Track options reactively so the queryResource memo re-runs on change. | ||
| const trackedDefaultedOptions = createMemo(() => defaultedOptions()) | ||
| // Apply options in an effect to avoid store writes inside the memo. | ||
| // setOptions triggers updateResult → notify → subscription → setState, | ||
| // which must run in an effect context in Solid v2. | ||
| createRenderEffect( | ||
| () => trackedDefaultedOptions(), | ||
| (opts) => { | ||
| observer.setOptions(opts) | ||
| }, | ||
| ) | ||
| let observerResult = observer().getOptimisticResult(defaultedOptions()) | ||
| const [state, setState] = | ||
| createStore<QueryObserverResult<TData, TError>>(observerResult) | ||
| let observerResult = observer.getOptimisticResult(defaultedOptions()) | ||
| const [state, setState] = createStore<QueryObserverResult<TData, TError>>( | ||
| _stripFnsForSSR(observerResult), | ||
| ) | ||
@@ -163,5 +209,5 @@ const createServerSubscriber = ( | ||
| ) => { | ||
| return observer().subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| notifyManager.batchCalls(() => { | ||
| const query = observer().getCurrentQuery() | ||
| const query = observer.getCurrentQuery() | ||
| const unwrappedResult = hydratableObserverResult(query, result) | ||
@@ -188,8 +234,20 @@ | ||
| const createClientSubscriber = () => { | ||
| const obs = observer() | ||
| return obs.subscribe((result) => { | ||
| return observer.subscribe((result) => { | ||
| const previousResult = observerResult | ||
| observerResult = result | ||
| setStateWithReconciliation(result) | ||
| queueMicrotask(() => { | ||
| if (unsubscribe) { | ||
| refetch() | ||
| if ( | ||
| unsubscribe && | ||
| !disposed && | ||
| (previousResult.isLoading !== result.isLoading || | ||
| previousResult.isError !== result.isError) | ||
| ) { | ||
| try { | ||
| refresh(queryResource) | ||
| } catch { | ||
| // NotReadyError is expected when refreshing a memo that returns | ||
| // a Promise. The Loading boundary handles this during rendering, | ||
| // but when refresh is called from a microtask there is no boundary. | ||
| } | ||
| } | ||
@@ -201,5 +259,5 @@ }) | ||
| function setStateWithReconciliation(res: typeof observerResult) { | ||
| const opts = observer().options | ||
| // @ts-expect-error - Reconcile option is not correctly typed internally | ||
| const reconcileOptions = opts.reconcile | ||
| const opts = observer.options | ||
| const reconcileOptions = (opts as any).reconcile | ||
| const sanitized = _stripFnsForSSR(res) | ||
@@ -209,3 +267,3 @@ setState((store) => { | ||
| store, | ||
| res, | ||
| sanitized, | ||
| reconcileOptions === undefined ? false : reconcileOptions, | ||
@@ -217,21 +275,2 @@ opts.queryHash, | ||
| function createDeepSignal<T>(): Signal<T> { | ||
| return [ | ||
| () => state, | ||
| (v: any) => { | ||
| const unwrapped = unwrap(state) | ||
| if (typeof v === 'function') { | ||
| v = v(unwrapped) | ||
| } | ||
| // Hydration data exists on first load after SSR, | ||
| // and should be removed from the observer result | ||
| if (v?.hydrationData) { | ||
| const { hydrationData, ...rest } = v | ||
| v = rest | ||
| } | ||
| setStateWithReconciliation(v) | ||
| }, | ||
| ] as Signal<T> | ||
| } | ||
| /** | ||
@@ -241,2 +280,3 @@ * Unsubscribe is set lazily, so that we can subscribe after hydration when needed. | ||
| let unsubscribe: (() => void) | null = null | ||
| let disposed = false | ||
@@ -251,111 +291,54 @@ /* | ||
| let resolver: ((value: ResourceData) => void) | null = null | ||
| const [queryResource, { refetch }] = createResource<ResourceData | undefined>( | ||
| const queryResource = createMemo<ResourceData>( | ||
| () => { | ||
| const obs = observer() | ||
| // Read trackedDefaultedOptions to ensure this memo re-runs when options change | ||
| const opts = trackedDefaultedOptions() | ||
| // Read isRestoring unconditionally so the memo re-runs when it changes | ||
| const restoring = isRestoring() | ||
| return new Promise((resolve, reject) => { | ||
| resolver = resolve | ||
| if (isServer) { | ||
| unsubscribe = createServerSubscriber(resolve, reject) | ||
| } else if (!unsubscribe && !isRestoring()) { | ||
| unsubscribe = createServerSubscriber((data) => { | ||
| resolve(data as ResourceData) | ||
| }, reject) | ||
| } else if (!unsubscribe && !restoring) { | ||
| unsubscribe = createClientSubscriber() | ||
| } | ||
| obs.updateResult() | ||
| // Use getOptimisticResult instead of updateResult to keep the memo | ||
| // free of store writes (updateResult triggers notify → setState). | ||
| const currentResult = observer.getOptimisticResult(opts) | ||
| observerResult = currentResult | ||
| if ( | ||
| observerResult.isError && | ||
| !observerResult.isFetching && | ||
| !isRestoring() && | ||
| shouldThrowError(obs.options.throwOnError, [ | ||
| observerResult.error, | ||
| obs.getCurrentQuery(), | ||
| currentResult.isError && | ||
| !currentResult.isFetching && | ||
| !restoring && | ||
| shouldThrowError(opts.throwOnError, [ | ||
| currentResult.error, | ||
| observer.getCurrentQuery(), | ||
| ]) | ||
| ) { | ||
| setStateWithReconciliation(observerResult) | ||
| return reject(observerResult.error) | ||
| setStateWithReconciliation(currentResult) | ||
| return reject(currentResult.error) | ||
| } | ||
| if (!observerResult.isLoading) { | ||
| if (!currentResult.isLoading) { | ||
| resolver = null | ||
| setStateWithReconciliation(currentResult) | ||
| return resolve( | ||
| hydratableObserverResult(obs.getCurrentQuery(), observerResult), | ||
| hydratableObserverResult(observer.getCurrentQuery(), currentResult), | ||
| ) | ||
| } | ||
| setStateWithReconciliation(observerResult) | ||
| // Defer the loading-state store write so it runs outside the memo. | ||
| queueMicrotask(() => setStateWithReconciliation(currentResult)) | ||
| }) | ||
| }, | ||
| { | ||
| storage: createDeepSignal, | ||
| get deferStream() { | ||
| return options().deferStream | ||
| }, | ||
| /** | ||
| * If this resource was populated on the server (either sync render, or streamed in over time), onHydrated | ||
| * will be called. This is the point at which we can hydrate the query cache state, and setup the query subscriber. | ||
| * | ||
| * Leveraging onHydrated allows us to plug into the async and streaming support that solidjs resources already support. | ||
| * | ||
| * Note that this is only invoked on the client, for queries that were originally run on the server. | ||
| */ | ||
| onHydrated(_k, info) { | ||
| if (info.value && 'hydrationData' in info.value) { | ||
| hydrate(client(), { | ||
| // @ts-expect-error - hydrationData is not correctly typed internally | ||
| queries: [{ ...info.value.hydrationData }], | ||
| }) | ||
| } | ||
| if (unsubscribe) return | ||
| /** | ||
| * Do not refetch query on mount if query was fetched on server, | ||
| * even if `staleTime` is not set. | ||
| */ | ||
| const newOptions = { ...initialOptions } | ||
| if ( | ||
| (initialOptions.staleTime || !initialOptions.initialData) && | ||
| info.value | ||
| ) { | ||
| newOptions.refetchOnMount = false | ||
| } | ||
| // Setting the options as an immutable object to prevent | ||
| // wonky behavior with observer subscriptions | ||
| observer().setOptions(newOptions) | ||
| setStateWithReconciliation(observer().getOptimisticResult(newOptions)) | ||
| unsubscribe = createClientSubscriber() | ||
| }, | ||
| }, | ||
| observerResult, | ||
| { ssrSource: 'client' }, | ||
| ) | ||
| createComputed( | ||
| on( | ||
| client, | ||
| (c) => { | ||
| if (unsubscribe) { | ||
| unsubscribe() | ||
| } | ||
| const newObserver = new Observer(c, defaultedOptions()) | ||
| unsubscribe = createClientSubscriber() | ||
| setObserver(newObserver) | ||
| }, | ||
| { | ||
| defer: true, | ||
| }, | ||
| ), | ||
| ) | ||
| createComputed( | ||
| on( | ||
| isRestoring, | ||
| (restoring) => { | ||
| if (!restoring && !isServer) { | ||
| refetch() | ||
| } | ||
| }, | ||
| { defer: true }, | ||
| ), | ||
| ) | ||
| onCleanup(() => { | ||
| if (isServer && queryResource.loading) { | ||
| disposed = true | ||
| if (isServer && isPending(queryResource)) { | ||
| unsubscribeQueued = true | ||
@@ -374,30 +357,41 @@ return | ||
| createComputed( | ||
| on( | ||
| [observer, defaultedOptions], | ||
| ([obs, opts]) => { | ||
| obs.setOptions(opts) | ||
| setStateWithReconciliation(obs.getOptimisticResult(opts)) | ||
| refetch() | ||
| }, | ||
| { defer: true }, | ||
| ), | ||
| ) | ||
| // Properties that should never throw — these let users access error info | ||
| // even outside an ErrorBoundary. | ||
| const errorPassthroughProps = new Set([ | ||
| 'error', | ||
| 'isError', | ||
| 'failureCount', | ||
| 'failureReason', | ||
| 'errorUpdateCount', | ||
| 'errorUpdatedAt', | ||
| ]) | ||
| const handler = { | ||
| get( | ||
| target: QueryObserverResult<TData, TError>, | ||
| prop: keyof QueryObserverResult<TData, TError>, | ||
| ): any { | ||
| if (prop === 'data') { | ||
| if (state.data !== undefined) { | ||
| return queryResource.latest?.data | ||
| } | ||
| return queryResource()?.data | ||
| // Return a proxy that throws on property access when throwOnError is enabled | ||
| return new Proxy(state, { | ||
| get(target, prop, receiver) { | ||
| // Always pass through symbols (needed for store internals, iteration, etc.) | ||
| if (typeof prop === 'symbol') { | ||
| return Reflect.get(target, prop, receiver) | ||
| } | ||
| return Reflect.get(target, prop) | ||
| // Always pass through error-related props without throwing | ||
| if (errorPassthroughProps.has(prop)) { | ||
| return Reflect.get(target, prop, receiver) | ||
| } | ||
| // Check throwOnError condition before returning the value | ||
| if ( | ||
| state.isError && | ||
| !state.isFetching && | ||
| shouldThrowError(observer.options.throwOnError, [ | ||
| state.error, | ||
| observer.getCurrentQuery(), | ||
| ]) | ||
| ) { | ||
| throw state.error | ||
| } | ||
| return Reflect.get(target, prop, receiver) | ||
| }, | ||
| } | ||
| return new Proxy(state, handler) | ||
| }) | ||
| } |
+32
-22
| import { MutationObserver, noop, shouldThrowError } from '@tanstack/query-core' | ||
| import { createComputed, createMemo, on, onCleanup } from 'solid-js' | ||
| import { createStore } from 'solid-js/store' | ||
| import { | ||
| createMemo, | ||
| createRenderEffect, | ||
| createStore, | ||
| onCleanup, | ||
| } from 'solid-js' | ||
| import { useQueryClient } from './QueryClientProvider' | ||
@@ -33,2 +37,7 @@ import type { DefaultError } from '@tanstack/query-core' | ||
| // Track options changes and update observer | ||
| createMemo(() => { | ||
| observer.setOptions(options()) | ||
| }) | ||
| const mutate: UseMutateFunction< | ||
@@ -51,26 +60,8 @@ TData, | ||
| createComputed(() => { | ||
| observer.setOptions(options()) | ||
| }) | ||
| createComputed( | ||
| on( | ||
| () => state.status, | ||
| () => { | ||
| if ( | ||
| state.isError && | ||
| shouldThrowError(observer.options.throwOnError, [state.error]) | ||
| ) { | ||
| throw state.error | ||
| } | ||
| }, | ||
| ), | ||
| ) | ||
| const unsubscribe = observer.subscribe((result) => { | ||
| setState({ | ||
| setState(() => ({ | ||
| ...result, | ||
| mutate, | ||
| mutateAsync: result.mutate, | ||
| }) | ||
| })) | ||
| }) | ||
@@ -80,3 +71,22 @@ | ||
| // Use createRenderEffect to throw errors when throwOnError is set. | ||
| // The throw must happen in the compute function (first arg), not the effect | ||
| // function, so that the error goes through notifyStatus and gets wrapped as | ||
| // a StatusError with a source — which is required for <Errored> boundaries | ||
| // to capture it via CollectionQueue.notify. | ||
| createRenderEffect( | ||
| () => { | ||
| const isError = state.isError | ||
| const error = state.error | ||
| if ( | ||
| isError && | ||
| shouldThrowError(observer.options.throwOnError, [error as TError]) | ||
| ) { | ||
| throw error | ||
| } | ||
| }, | ||
| () => {}, | ||
| ) | ||
| return state | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js' | ||
| import { createMemo, createSignal, onCleanup } from 'solid-js' | ||
| import { replaceEqualDeep } from '@tanstack/query-core' | ||
@@ -41,17 +41,15 @@ import { useQueryClient } from './QueryClientProvider' | ||
| createEffect(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| const unsubscribe = mutationCache().subscribe(() => { | ||
| setResult((prev) => { | ||
| const nextResult = replaceEqualDeep( | ||
| result(), | ||
| prev, | ||
| getResult(mutationCache(), options()), | ||
| ) | ||
| if (result() !== nextResult) { | ||
| setResult(nextResult) | ||
| } | ||
| return prev === nextResult ? prev : nextResult | ||
| }) | ||
| onCleanup(unsubscribe) | ||
| }) | ||
| onCleanup(unsubscribe) | ||
| return result | ||
| } |
+50
-116
| import { QueriesObserver, noop } from '@tanstack/query-core' | ||
| import { createStore, unwrap } from 'solid-js/store' | ||
| import { | ||
| batch, | ||
| createComputed, | ||
| createEffect, | ||
| createMemo, | ||
| createRenderEffect, | ||
| createResource, | ||
| mergeProps, | ||
| on, | ||
| createStore, | ||
| merge, | ||
| onCleanup, | ||
| onMount, | ||
| reconcile, | ||
| } from 'solid-js' | ||
@@ -125,3 +121,3 @@ import { useQueryClient } from './QueryClientProvider' | ||
| /** | ||
| * QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param | ||
| * QueriesOptions reducer recursively snapshots function arguments to infer/enforce type param | ||
| */ | ||
@@ -205,14 +201,11 @@ type QueriesOptions< | ||
| queriesOptions().queries.map((options) => | ||
| mergeProps( | ||
| client().defaultQueryOptions(options as QueryObserverOptions), | ||
| { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? 'isRestoring' : 'optimistic' | ||
| }, | ||
| merge(client().defaultQueryOptions(options as QueryObserverOptions), { | ||
| get _optimisticResults() { | ||
| return isRestoring() ? 'isRestoring' : 'optimistic' | ||
| }, | ||
| ), | ||
| }), | ||
| ), | ||
| ) | ||
| const observer = new QueriesObserver( | ||
| const observer = new QueriesObserver<TCombinedResult>( | ||
| client(), | ||
@@ -227,83 +220,48 @@ defaultedQueries(), | ||
| const [state, setState] = createStore<TCombinedResult>( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| (queriesOptions() as QueriesObserverOptions<TCombinedResult>).combine, | ||
| )[1](), | ||
| // Get initial optimistic result | ||
| const [, getCombinedResult] = observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| (queriesOptions() as QueriesObserverOptions<TCombinedResult>).combine, | ||
| ) | ||
| createRenderEffect( | ||
| on( | ||
| () => queriesOptions().queries.length, | ||
| () => | ||
| setState( | ||
| observer.getOptimisticResult( | ||
| defaultedQueries(), | ||
| (queriesOptions() as QueriesObserverOptions<TCombinedResult>) | ||
| .combine, | ||
| )[1](), | ||
| ), | ||
| ), | ||
| ) | ||
| const initialResult = getCombinedResult() | ||
| const dataResources = createMemo( | ||
| on( | ||
| () => state.length, | ||
| () => | ||
| state.map((queryRes) => { | ||
| const dataPromise = () => | ||
| new Promise((resolve) => { | ||
| if (queryRes.isFetching && queryRes.isLoading) return | ||
| resolve(unwrap(queryRes.data)) | ||
| }) | ||
| return createResource(dataPromise) | ||
| }), | ||
| ), | ||
| // Store the combined result in a reactive store | ||
| const [state, setState] = createStore<Array<QueryObserverResult>>( | ||
| (Array.isArray(initialResult) | ||
| ? initialResult | ||
| : [initialResult]) as Array<QueryObserverResult>, | ||
| ) | ||
| batch(() => { | ||
| const dataResources_ = dataResources() | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]! | ||
| dataResource[1].mutate(() => unwrap(state[index]!.data)) | ||
| dataResource[1].refetch() | ||
| } | ||
| }) | ||
| let taskQueue: Array<() => void> = [] | ||
| const subscribeToObserver = () => | ||
| observer.subscribe((result) => { | ||
| taskQueue.push(() => { | ||
| batch(() => { | ||
| const dataResources_ = dataResources() | ||
| for (let index = 0; index < dataResources_.length; index++) { | ||
| const dataResource = dataResources_[index]! | ||
| const unwrappedResult = { ...unwrap(result[index]) } | ||
| // @ts-expect-error typescript pedantry regarding the possible range of index | ||
| setState(index, unwrap(unwrappedResult)) | ||
| dataResource[1].mutate(() => unwrap(state[index]!.data)) | ||
| dataResource[1].refetch() | ||
| } | ||
| // Subscribe to the observer for updates reactively. | ||
| // When isRestoring is true (persist client is restoring), we defer | ||
| // subscription until restoring completes. | ||
| let unsubscribe: () => void = noop | ||
| createEffect( | ||
| () => { | ||
| if (!isRestoring()) { | ||
| unsubscribe = observer.subscribe((result) => { | ||
| setState( | ||
| reconcile( | ||
| [...result] as Array<QueryObserverResult>, | ||
| // Use a key function that returns undefined so reconcile | ||
| // uses positional matching and recursively updates nested properties | ||
| () => undefined, | ||
| ), | ||
| ) | ||
| }) | ||
| }) | ||
| } | ||
| }, | ||
| () => {}, | ||
| ) | ||
| queueMicrotask(() => { | ||
| const taskToRun = taskQueue.pop() | ||
| if (taskToRun) taskToRun() | ||
| taskQueue = [] | ||
| }) | ||
| }) | ||
| let unsubscribe: () => void = noop | ||
| createComputed<() => void>((cleanup) => { | ||
| cleanup?.() | ||
| unsubscribe = isRestoring() ? noop : subscribeToObserver() | ||
| // cleanup needs to be scheduled after synchronous effects take place | ||
| return () => queueMicrotask(unsubscribe) | ||
| onCleanup(() => { | ||
| unsubscribe() | ||
| }) | ||
| onCleanup(unsubscribe) | ||
| onMount(() => { | ||
| // Update observer queries when options change reactively | ||
| const trackedDefaultedQueries = createMemo(() => { | ||
| const queries = defaultedQueries() | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queries, | ||
| queriesOptions().combine | ||
@@ -315,33 +273,9 @@ ? ({ | ||
| ) | ||
| return queries | ||
| }) | ||
| createComputed(() => { | ||
| observer.setQueries( | ||
| defaultedQueries(), | ||
| queriesOptions().combine | ||
| ? ({ | ||
| combine: queriesOptions().combine, | ||
| } as QueriesObserverOptions<TCombinedResult>) | ||
| : undefined, | ||
| ) | ||
| }) | ||
| // Force read of trackedDefaultedQueries to ensure it runs | ||
| void trackedDefaultedQueries | ||
| const handler = (index: number) => ({ | ||
| get(target: QueryObserverResult, prop: keyof QueryObserverResult): any { | ||
| if (prop === 'data') { | ||
| return dataResources()[index]![0]() | ||
| } | ||
| return Reflect.get(target, prop) | ||
| }, | ||
| }) | ||
| const getProxies = () => | ||
| state.map((s, index) => { | ||
| return new Proxy(s, handler(index)) | ||
| }) | ||
| const [proxyState, setProxyState] = createStore(getProxies()) | ||
| createRenderEffect(() => setProxyState(getProxies())) | ||
| return proxyState as TCombinedResult | ||
| return state as unknown as TCombinedResult | ||
| } |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
3
-62.5%232105
-5.15%10
66.67%4205
-9.9%1
Infinity%