diff --git a/examples/solid/basic-typescript/package.json b/examples/solid/basic-typescript/package.json
index 0e265cf44c3..f45f1cf1666 100644
--- a/examples/solid/basic-typescript/package.json
+++ b/examples/solid/basic-typescript/package.json
@@ -10,10 +10,10 @@
},
"license": "MIT",
"dependencies": {
- "@tanstack/solid-query": "^4.3.9",
"solid-js": "^1.5.1"
},
"devDependencies": {
+ "@tanstack/solid-query": "^4.3.9",
"typescript": "^4.8.2",
"vite": "^3.0.9",
"vite-plugin-solid": "^2.3.9"
diff --git a/examples/solid/offline/.eslintrc b/examples/solid/offline/.eslintrc
new file mode 100644
index 00000000000..86b22fec59b
--- /dev/null
+++ b/examples/solid/offline/.eslintrc
@@ -0,0 +1,10 @@
+{
+ "parserOptions": {
+ "project": "./tsconfig.json",
+ "sourceType": "module"
+ },
+ "rules": {
+ "react/react-in-jsx-scope": "off",
+ "jsx-a11y/anchor-is-valid": "off"
+ }
+}
diff --git a/examples/solid/offline/.gitignore b/examples/solid/offline/.gitignore
new file mode 100644
index 00000000000..001e3f924bb
--- /dev/null
+++ b/examples/solid/offline/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+.yalc
+yalc.lock
\ No newline at end of file
diff --git a/examples/solid/offline/README.md b/examples/solid/offline/README.md
new file mode 100644
index 00000000000..310f37f62fd
--- /dev/null
+++ b/examples/solid/offline/README.md
@@ -0,0 +1,6 @@
+# Example
+
+To run this example:
+
+- `npm install`
+- `npm run start`
diff --git a/examples/solid/offline/index.html b/examples/solid/offline/index.html
new file mode 100644
index 00000000000..48c59fc1242
--- /dev/null
+++ b/examples/solid/offline/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Solid App
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
+
diff --git a/examples/solid/offline/mockServiceWorker.js b/examples/solid/offline/mockServiceWorker.js
new file mode 100644
index 00000000000..0966a9df4fb
--- /dev/null
+++ b/examples/solid/offline/mockServiceWorker.js
@@ -0,0 +1,338 @@
+/* eslint-disable */
+/* tslint:disable */
+
+/**
+ * Mock Service Worker (0.39.2).
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ * - Please do NOT serve this file on production.
+ */
+
+const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929'
+const bypassHeaderName = 'x-msw-bypass'
+const activeClientIds = new Set()
+
+self.addEventListener('install', function () {
+ return self.skipWaiting()
+})
+
+self.addEventListener('activate', async function (event) {
+ return self.clients.claim()
+})
+
+self.addEventListener('message', async function (event) {
+ const clientId = event.source.id
+
+ if (!clientId || !self.clients) {
+ return
+ }
+
+ const client = await self.clients.get(clientId)
+
+ if (!client) {
+ return
+ }
+
+ const allClients = await self.clients.matchAll()
+
+ switch (event.data) {
+ case 'KEEPALIVE_REQUEST': {
+ sendToClient(client, {
+ type: 'KEEPALIVE_RESPONSE',
+ })
+ break
+ }
+
+ case 'INTEGRITY_CHECK_REQUEST': {
+ sendToClient(client, {
+ type: 'INTEGRITY_CHECK_RESPONSE',
+ payload: INTEGRITY_CHECKSUM,
+ })
+ break
+ }
+
+ case 'MOCK_ACTIVATE': {
+ activeClientIds.add(clientId)
+
+ sendToClient(client, {
+ type: 'MOCKING_ENABLED',
+ payload: true,
+ })
+ break
+ }
+
+ case 'MOCK_DEACTIVATE': {
+ activeClientIds.delete(clientId)
+ break
+ }
+
+ case 'CLIENT_CLOSED': {
+ activeClientIds.delete(clientId)
+
+ const remainingClients = allClients.filter((client) => {
+ return client.id !== clientId
+ })
+
+ // Unregister itself when there are no more clients
+ if (remainingClients.length === 0) {
+ self.registration.unregister()
+ }
+
+ break
+ }
+ }
+})
+
+// Resolve the "main" client for the given event.
+// Client that issues a request doesn't necessarily equal the client
+// that registered the worker. It's with the latter the worker should
+// communicate with during the response resolving phase.
+async function resolveMainClient(event) {
+ const client = await self.clients.get(event.clientId)
+
+ if (client.frameType === 'top-level') {
+ return client
+ }
+
+ const allClients = await self.clients.matchAll()
+
+ return allClients
+ .filter((client) => {
+ // Get only those clients that are currently visible.
+ return client.visibilityState === 'visible'
+ })
+ .find((client) => {
+ // Find the client ID that's recorded in the
+ // set of clients that have registered the worker.
+ return activeClientIds.has(client.id)
+ })
+}
+
+async function handleRequest(event, requestId) {
+ const client = await resolveMainClient(event)
+ const response = await getResponse(event, client, requestId)
+
+ // Send back the response clone for the "response:*" life-cycle events.
+ // Ensure MSW is active and ready to handle the message, otherwise
+ // this message will pend indefinitely.
+ if (client && activeClientIds.has(client.id)) {
+ ;(async function () {
+ const clonedResponse = response.clone()
+ sendToClient(client, {
+ type: 'RESPONSE',
+ payload: {
+ requestId,
+ type: clonedResponse.type,
+ ok: clonedResponse.ok,
+ status: clonedResponse.status,
+ statusText: clonedResponse.statusText,
+ body:
+ clonedResponse.body === null ? null : await clonedResponse.text(),
+ headers: serializeHeaders(clonedResponse.headers),
+ redirected: clonedResponse.redirected,
+ },
+ })
+ })()
+ }
+
+ return response
+}
+
+async function getResponse(event, client, requestId) {
+ const { request } = event
+ const requestClone = request.clone()
+ const getOriginalResponse = () => fetch(requestClone)
+
+ // Bypass mocking when the request client is not active.
+ if (!client) {
+ return getOriginalResponse()
+ }
+
+ // Bypass initial page load requests (i.e. static assets).
+ // The absence of the immediate/parent client in the map of the active clients
+ // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+ // and is not ready to handle requests.
+ if (!activeClientIds.has(client.id)) {
+ return await getOriginalResponse()
+ }
+
+ // Bypass requests with the explicit bypass header
+ if (requestClone.headers.get(bypassHeaderName) === 'true') {
+ const cleanRequestHeaders = serializeHeaders(requestClone.headers)
+
+ // Remove the bypass header to comply with the CORS preflight check.
+ delete cleanRequestHeaders[bypassHeaderName]
+
+ const originalRequest = new Request(requestClone, {
+ headers: new Headers(cleanRequestHeaders),
+ })
+
+ return fetch(originalRequest)
+ }
+
+ // Send the request to the client-side MSW.
+ const reqHeaders = serializeHeaders(request.headers)
+ const body = await request.text()
+
+ const clientMessage = await sendToClient(client, {
+ type: 'REQUEST',
+ payload: {
+ id: requestId,
+ url: request.url,
+ method: request.method,
+ headers: reqHeaders,
+ cache: request.cache,
+ mode: request.mode,
+ credentials: request.credentials,
+ destination: request.destination,
+ integrity: request.integrity,
+ redirect: request.redirect,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ body,
+ bodyUsed: request.bodyUsed,
+ keepalive: request.keepalive,
+ },
+ })
+
+ switch (clientMessage.type) {
+ case 'MOCK_SUCCESS': {
+ return delayPromise(
+ () => respondWithMock(clientMessage),
+ clientMessage.payload.delay,
+ )
+ }
+
+ case 'MOCK_NOT_FOUND': {
+ return getOriginalResponse()
+ }
+
+ case 'NETWORK_ERROR': {
+ const { name, message } = clientMessage.payload
+ const networkError = new Error(message)
+ networkError.name = name
+
+ // Rejecting a request Promise emulates a network error.
+ throw networkError
+ }
+
+ case 'INTERNAL_ERROR': {
+ const parsedBody = JSON.parse(clientMessage.payload.body)
+
+ console.error(
+ `\
+[MSW] Uncaught exception in the request handler for "%s %s":
+
+${parsedBody.location}
+
+This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
+`,
+ request.method,
+ request.url,
+ )
+
+ return respondWithMock(clientMessage)
+ }
+ }
+
+ return getOriginalResponse()
+}
+
+self.addEventListener('fetch', function (event) {
+ const { request } = event
+ const accept = request.headers.get('accept') || ''
+
+ // Bypass server-sent events.
+ if (accept.includes('text/event-stream')) {
+ return
+ }
+
+ // Bypass navigation requests.
+ if (request.mode === 'navigate') {
+ return
+ }
+
+ // Opening the DevTools triggers the "only-if-cached" request
+ // that cannot be handled by the worker. Bypass such requests.
+ if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
+ return
+ }
+
+ // Bypass all requests when there are no active clients.
+ // Prevents the self-unregistered worked from handling requests
+ // after it's been deleted (still remains active until the next reload).
+ if (activeClientIds.size === 0) {
+ return
+ }
+
+ const requestId = uuidv4()
+
+ return event.respondWith(
+ handleRequest(event, requestId).catch((error) => {
+ if (error.name === 'NetworkError') {
+ console.warn(
+ '[MSW] Successfully emulated a network error for the "%s %s" request.',
+ request.method,
+ request.url,
+ )
+ return
+ }
+
+ // At this point, any exception indicates an issue with the original request/response.
+ console.error(
+ `\
+[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
+ request.method,
+ request.url,
+ `${error.name}: ${error.message}`,
+ )
+ }),
+ )
+})
+
+function serializeHeaders(headers) {
+ const reqHeaders = {}
+ headers.forEach((value, name) => {
+ reqHeaders[name] = reqHeaders[name]
+ ? [].concat(reqHeaders[name]).concat(value)
+ : value
+ })
+ return reqHeaders
+}
+
+function sendToClient(client, message) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel()
+
+ channel.port1.onmessage = (event) => {
+ if (event.data && event.data.error) {
+ return reject(event.data.error)
+ }
+
+ resolve(event.data)
+ }
+
+ client.postMessage(JSON.stringify(message), [channel.port2])
+ })
+}
+
+function delayPromise(cb, duration) {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(cb()), duration)
+ })
+}
+
+function respondWithMock(clientMessage) {
+ return new Response(clientMessage.payload.body, {
+ ...clientMessage.payload,
+ headers: clientMessage.payload.headers,
+ })
+}
+
+function uuidv4() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ const r = (Math.random() * 16) | 0
+ const v = c == 'x' ? r : (r & 0x3) | 0x8
+ return v.toString(16)
+ })
+}
diff --git a/examples/solid/offline/package.json b/examples/solid/offline/package.json
new file mode 100644
index 00000000000..a501d06881a
--- /dev/null
+++ b/examples/solid/offline/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@tanstack/query-example-solid-offline",
+ "version": "0.0.0",
+ "description": "",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "solid-js": "1.5.4"
+ },
+ "devDependencies": {
+ "@solidjs/router": "^0.5.0",
+ "@tanstack/query-async-storage-persister": "workspace:*",
+ "@tanstack/solid-query": "workspace:*",
+ "@tanstack/solid-query-persist-client": "workspace:*",
+ "@types/lodash": "^4.14.186",
+ "deepdash": "^5.3.9",
+ "idb-keyval": "^6.2.0",
+ "ky": "^0.30.0",
+ "lodash": "^4.17.21",
+ "msw": "^0.39.2",
+ "solid-toast": "^0.3.5",
+ "typescript": "^4.8.2",
+ "vite": "^3.0.9",
+ "vite-plugin-solid": "^2.3.9"
+ },
+ "msw": {
+ "workerDirectory": ""
+ }
+}
diff --git a/examples/solid/offline/src/App.tsx b/examples/solid/offline/src/App.tsx
new file mode 100644
index 00000000000..2946887a44d
--- /dev/null
+++ b/examples/solid/offline/src/App.tsx
@@ -0,0 +1,319 @@
+/* @refresh reload */
+
+import {
+ createQuery,
+ QueryClient,
+ //QueryClientProvider,
+ MutationCache,
+ onlineManager,
+ useIsRestoring,
+ //useQueryClient,
+} from '@tanstack/solid-query'
+
+import {
+ PersistQueryClientProvider,
+} from '@tanstack/solid-query-persist-client'
+
+import { createIndexedDBPersister } from './persister'
+
+import { Component, createSignal, For, Match, Setter, Switch } from 'solid-js'
+
+// TODO @tanstack/solid-query-devtools
+//import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+
+//import toast, { Toaster } from "react-hot-toast";
+import toast, { Toaster } from 'solid-toast';
+
+/*
+import {
+ Link,
+ Outlet,
+ ReactLocation,
+ Router,
+ useMatch,
+} from "@tanstack/react-location";
+*/
+
+// TODO: A vs Navigate?
+// https://github.com/solidjs/solid-router#the-navigate-component
+// Solid Router provides a Navigate component that works similarly to A,
+// but it will *immediately* navigate to the provided path
+// as soon as the component is rendered
+
+import { Routes, Route, A as Link, useParams } from "@solidjs/router";
+
+import * as api from "./api";
+import { movieKeys, useMovie } from "./movies";
+
+/* TODO?
+
+import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
+const persister = createSyncStoragePersister({
+ storage: window.localStorage,
+});
+
+import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
+const persister = createAsyncStoragePersister()
+
+*/
+
+const persister = createIndexedDBPersister()
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ cacheTime: 1000 * 60 * 60 * 24, // 24 hours
+ staleTime: 2000,
+ retry: 0,
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ //retryOnMount: false,
+ },
+ },
+ // configure global cache callbacks to show toast notifications
+ mutationCache: new MutationCache({
+ onSuccess: (data: any) => {
+ toast.success(data.message);
+ },
+ onError: (error: any) => {
+ toast.error(error.message);
+ },
+ }),
+});
+
+// we need a default mutation function so that paused mutations can resume after a page reload
+queryClient.setMutationDefaults(movieKeys.all(), {
+ mutationFn: async ({ id, comment }) => {
+ // to avoid clashes with our optimistic update when an offline mutation continues
+ await queryClient.cancelQueries(movieKeys.detail(id));
+ return api.updateMovie(id, comment);
+ },
+});
+
+export function App() {
+ return (
+ {
+ // resume mutations after initial restore from localStorage was successful
+ await queryClient.resumePausedMutations();
+ // no. this would refetch queries
+ //await queryClient.invalidateQueries();
+ }}
+ >
+
+ {
+ //
+ }
+
+ );
+}
+
+function Movies() {
+ const isRestoring = useIsRestoring();
+ return (
+
+
+
+
+ //data={fetchMovie}
+ />
+ This site was made with Solid
}
+ />
+
+ {
+ //
+ }
+
+
+ );
+}
+
+function List() {
+ console.log('List: createQuery', movieKeys.list(), api.fetchMovies)
+ const moviesQuery = createQuery(
+ () => movieKeys.list(),
+ api.fetchMovies
+ );
+
+ return (
+
+
Movies
+
+
+ Loading...
+ We're offline and have no data to show :(
+
+ Error: {(moviesQuery.error as Error).message}
+
+
+ <>
+ {/* TODO solid devtools
+
+ Try to mock offline behaviour with the button in the devtools. You can
+ navigate around as long as there is already data in the cache. You'll
+ get a refetch as soon as you go online again.
+
+ */}
+
+
+ {(movie) => (
+
+ {
+ // TODO preload
+ //
+ }
+
+ {movie.title}
+
+
+ )}
+
+
+
+ Updated at: {new Date(moviesQuery.data?.ts || 0).toLocaleTimeString()}
+
+ {moviesQuery.isFetching ? 'fetching...' : ' '}
+ >
+
+ {
+ // query will be in 'idle' fetchStatus while restoring from localStorage
+ }
+ restoring...
+
+
+
+ )
+}
+
+/* TODO
+function MovieError() {
+ const { error } = useMatch();
+
+ return (
+
+
Back
+
Couldn't load movie!
+
{error.message}
+
+ );
+}
+*/
+
+function Detail() {
+
+ const props = useParams();
+
+ /*
+ const fetchMovie = ({ props: { movieId } }: { props: any }) => {
+ console.log(`fetchMovie: arguments`, arguments)
+ return queryClient.getQueryData(movieKeys.detail(movieId)) ??
+ // do not load if we are offline or hydrating because it returns a promise that is pending until we go online again
+ // we just let the Detail component handle it
+ (onlineManager.isOnline() && !isRestoring
+ ? queryClient.fetchQuery(movieKeys.detail(movieId), () =>
+ api.fetchMovie(movieId)
+ )
+ : undefined)
+ }
+ */
+
+ const { comment, setComment, updateMovie, movieQuery } = useMovie(props.movieId);
+
+ function submitForm(event: any) {
+ event.preventDefault();
+
+ updateMovie.mutate({
+ id: props.movieId,
+ comment: comment(),
+ } as any);
+ }
+
+ return (
+
+
+
+ No movieId
+
+
+ Loading...
+
+
+ We're offline and have no data to show :(
+
+
+ Error: {(movieQuery.error as Error).message}
+
+
+
+
+ {
+ // query will be in 'idle' fetchStatus while restoring from localStorage
+ }
+ restoring...
+
+
+ )
+}
diff --git a/examples/solid/offline/src/api.ts b/examples/solid/offline/src/api.ts
new file mode 100644
index 00000000000..4708b542363
--- /dev/null
+++ b/examples/solid/offline/src/api.ts
@@ -0,0 +1,101 @@
+import { setupWorker, rest } from "msw";
+import ky from "ky";
+
+export type Movie = {
+ id: string,
+ title: string,
+ comment: string,
+}
+
+const movies: Movie[] = [
+ {
+ id: "1",
+ title: "Guardians of the Galaxy",
+ comment: "",
+ },
+ {
+ id: "2",
+ title: "Wall-E",
+ comment: "",
+ },
+];
+
+export const fetchMovie = (id: string) => (
+ ky.get(`/movies/${id}`).json() as Promise<{ movie: Movie, ts: number }>
+);
+
+export const fetchMovies = () => (
+ ky.get("/movies").json() as Promise<{ movies: Array, ts: number }>
+);
+
+export const updateMovie = (id: string, comment: string) => (
+ ky.post(`/movies/${id}`, { json: { comment } }).json()
+ // as Promise
+ // should return void (command query separation)
+);
+
+interface MoviesBody {
+ comment: string
+}
+
+/*
+interface MoviesResponse {
+ id: string
+ message: string
+}
+*/
+
+// https://mswjs.io/
+export const worker = setupWorker(
+ ...[
+ rest.get("/movies", (req, res, ctx) => {
+ return res(
+ ctx.delay(1000),
+ ctx.json({
+ ts: Date.now(),
+ movies: movies.map(({ id, title }) => ({ id, title })),
+ })
+ );
+ }),
+ rest.get("/movies/:id", (req, res, ctx) => {
+ const { id } = req.params;
+
+ const movie = movies.find((movie) => movie.id === id);
+ if (!movie) {
+ return res(ctx.status(404, `Movie with id ${id} not found`));
+ }
+
+ return res(
+ ctx.delay(1000),
+ ctx.json({
+ ts: Date.now(),
+ movie,
+ })
+ );
+ }),
+ /*
+ FIXME
+ Type 'MoviesResponse' does not satisfy the constraint 'PathParams'.
+ Index signature for type 'string' is missing in type 'MoviesResponse'. ts(2344)
+ */
+ //rest.post("/movies/:id", (req, res, ctx) => {
+ rest.post("/movies/:id", (req, res, ctx) => {
+ const { id } = req.params;
+ const { comment } = req.body as MoviesBody;
+
+ const movie = movies.find((movie) => movie.id === id);
+ if (!movie) {
+ return res(ctx.status(404, `Movie with id ${id} not found`));
+ }
+
+ movie.comment = `${comment}\n(updated via API /movies/:id)`;
+
+ return res(
+ ctx.delay(1000),
+ ctx.json({
+ message: `Successfully updated movie ${id}`,
+ })
+ );
+ }),
+ ]
+);
diff --git a/examples/solid/offline/src/assets/favicon.ico b/examples/solid/offline/src/assets/favicon.ico
new file mode 100644
index 00000000000..b836b2bccac
Binary files /dev/null and b/examples/solid/offline/src/assets/favicon.ico differ
diff --git a/examples/solid/offline/src/index.tsx b/examples/solid/offline/src/index.tsx
new file mode 100644
index 00000000000..8ec17a3fe9c
--- /dev/null
+++ b/examples/solid/offline/src/index.tsx
@@ -0,0 +1,16 @@
+import { render } from 'solid-js/web'
+
+import { App } from './App'
+import { Router } from "@solidjs/router";
+
+import { worker } from "./api"
+
+const app = () => (
+
+
+
+)
+
+worker.start()
+
+render(app, document.getElementById('root') as HTMLElement)
diff --git a/examples/solid/offline/src/movies.ts b/examples/solid/offline/src/movies.ts
new file mode 100644
index 00000000000..53be15190e8
--- /dev/null
+++ b/examples/solid/offline/src/movies.ts
@@ -0,0 +1,83 @@
+import {
+ createMutation,
+ createQuery,
+ useQueryClient,
+} from "@tanstack/solid-query";
+
+import type { Movie } from "./api"
+
+type QueryData = {
+ movie: Movie,
+}
+
+import * as api from "./api";
+import { createSignal } from "solid-js";
+
+// query key factory
+export const movieKeys = {
+ all: () => ["movies"],
+ list: () => [...movieKeys.all(), "list"],
+ details: () => [...movieKeys.all(), "detail"],
+ detail: (id: string) => [...movieKeys.details(), id],
+};
+
+export const useMovie = (movieId: string) => {
+ const queryClient = useQueryClient();
+
+ const movieQuery = createQuery(
+ () => movieKeys.detail(movieId),
+ () => api.fetchMovie(movieId)
+ );
+
+ // local value in
+ const [commentLocal, setCommentLocal] = createSignal(undefined);
+
+ const updateMovie = createMutation({
+ mutationKey: movieKeys.detail(movieId),
+ onMutate: async () => {
+ await queryClient.cancelQueries(movieKeys.detail(movieId));
+ const previousData: QueryData = queryClient.getQueryData(movieKeys.detail(movieId))!;
+
+ const queryData = {
+ ...previousData,
+ };
+
+ // check if comment was changed
+ // fix the "noop" case: submit form with no change
+ // -> comment should be old value, not "undefined"
+ const commentLocalValue = commentLocal();
+ if (commentLocalValue !== undefined) {
+ // comment was changed
+ queryData.movie = {
+ ...previousData.movie,
+ comment: commentLocalValue,
+ };
+ }
+
+ queryClient.setQueryData(movieKeys.detail(movieId), queryData);
+
+ return { previousData };
+ },
+ onError: (_, __, context) => {
+ queryClient.setQueryData(movieKeys.detail(movieId), context?.previousData);
+ },
+ onSettled: () => {
+ // refetch -> update movieQuery.data.movie.comment
+ queryClient.invalidateQueries(movieKeys.detail(movieId));
+
+ // remove local state so that server state is taken instead
+ // TODO do this after a successful refetch
+ setCommentLocal(undefined);
+ },
+ onSuccess(data: any, _variables, _context) {
+ console.log('updateMovie onSuccess: data.message', data.message)
+ },
+ });
+
+ return {
+ comment: () => commentLocal() ?? movieQuery.data?.movie.comment,
+ setComment: setCommentLocal,
+ updateMovie,
+ movieQuery,
+ };
+};
diff --git a/examples/solid/offline/src/persister.ts b/examples/solid/offline/src/persister.ts
new file mode 100644
index 00000000000..76553f9723f
--- /dev/null
+++ b/examples/solid/offline/src/persister.ts
@@ -0,0 +1,80 @@
+import { unwrap } from "solid-js/store"
+import type { PersistedClient, Persister } from "@tanstack/solid-query-persist-client";
+import { get, set, del } from "idb-keyval";
+
+import _lodashModule from 'lodash';
+import _deepdashModule from 'deepdash';
+const lodash = _deepdashModule(_lodashModule);
+
+/**
+ * Creates an Indexed DB persister
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
+ * @see https://tanstack.com/query/v4/docs/plugins/persistQueryClient#building-a-persister
+ */
+export function createIndexedDBPersister(idbValidKey: IDBValidKey = "tanstack-query"): Persister {
+ let _persistNum = 0
+ return {
+ // FIXME this is called too often
+ // at least 3 times too often
+ persistClient: async (client: PersistedClient) => {
+ _persistNum++
+ const persistNum = _persistNum
+ // remove Proxy objects from solid-js
+ // fix: DOMException: Failed to execute 'put' on 'IDBObjectStore': [object Array] could not be cloned.
+ client = unwrap(client)
+ /*
+ client = lodash.cloneDeep(client)
+ // FIXME client contains a function: client.clientState.queries[0].state.data.movie.comment
+ // workaround: comment -> comment()
+ for (let i = 0; i < client.clientState.queries.length; i++) {
+ const query = client.clientState.queries[i]
+ //query.state.data = unwrap(query.state.data) // no effect
+ if (typeof query.state.data?.movie?.comment == "function") {
+ // FIXME reactivity is lost
+ query.state.data.movie.comment = query.state.data.movie.comment()
+ }
+ }
+ */
+ // FIXME caching mutations: DOMException: Failed to execute 'put' on 'IDBObjectStore': function () { [native code] } could not be cloned.
+ // fix: comment -> comment()
+ // https://github.com/localForage/localForage/issues/610
+ //console.log(`persister persistClient ${persistNum}: client`, client)
+ //console.time('persister persistClient') // debug
+ const t1 = Date.now()
+ try {
+ await set(idbValidKey, client);
+ }
+ catch (error: any) {
+ // debug
+ //console.dir({ message: error.message })
+ //console.dir({ error }) // not helpful. does not have the error location
+ // find error location
+ const target = (
+ error.message.match(/^Failed to execute 'put' on 'IDBObjectStore': (.*) could not be cloned\.$/) ||
+ [null, null]
+ )[1]
+ if (target == 'function () { [native code] }') {
+ console.error(`persister persistClient ${persistNum}: error: client contains a function:`, lodash.findPathDeep(client, (val) => typeof val == 'function'))
+ }
+ else {
+ console.error(`persister persistClient ${persistNum}: error`, error)
+ }
+ //if (error.message == "Failed to execute 'put' on 'IDBObjectStore': function () { [native code] } could not be cloned.")
+ }
+ const t2 = Date.now()
+ console.log(`persister persistClient ${persistNum}: done after ${t2 - t1}ms`)
+ //console.timeEnd('persister persistClient') // debug
+ },
+
+ restoreClient: async () => {
+ console.time('persister restoreClient') // debug
+ const client = await get(idbValidKey);
+ console.timeEnd('persister restoreClient') // debug
+ return client;
+ },
+
+ removeClient: async () => {
+ await del(idbValidKey);
+ },
+ };
+}
diff --git a/examples/solid/offline/tsconfig.json b/examples/solid/offline/tsconfig.json
new file mode 100644
index 00000000000..249b2732a74
--- /dev/null
+++ b/examples/solid/offline/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "types": ["vite/client"],
+ "noEmit": true,
+ "isolatedModules": true
+ }
+}
diff --git a/examples/solid/offline/vite.config.ts b/examples/solid/offline/vite.config.ts
new file mode 100644
index 00000000000..89d951f1ddc
--- /dev/null
+++ b/examples/solid/offline/vite.config.ts
@@ -0,0 +1,26 @@
+import { defineConfig } from 'vite'
+import solidPlugin from 'vite-plugin-solid'
+
+// Progressive Web App
+// https://vite-pwa-org.netlify.app/guide/
+// based on workbox
+// https://web.dev/learn/pwa/workbox/
+//import { VitePWA } from 'vite-plugin-pwa'
+
+export default defineConfig({
+ plugins: [
+ solidPlugin(),
+ //VitePWA({ registerType: 'autoUpdate' }),
+ ],
+ server: {
+ port: 3000,
+ },
+ build: {
+ target: 'esnext',
+ },
+ resolve: {
+ // NOTE(milahu): this would break deps
+ // vite error: Could not resolve "@mswjs/cookies"
+ //preserveSymlinks: true,
+ },
+})
diff --git a/packages/query-core/src/index.ts b/packages/query-core/src/index.ts
index 82f71fd8226..b1cb3152423 100644
--- a/packages/query-core/src/index.ts
+++ b/packages/query-core/src/index.ts
@@ -27,6 +27,7 @@ export { dehydrate, hydrate } from './hydration'
// Types
export * from './types'
export type { Query, QueryState } from './query'
+export type { QueriesObserverListener } from './queriesObserver'
export type { Mutation } from './mutation'
export type { Logger } from './logger'
export type {
diff --git a/packages/query-core/src/queriesObserver.ts b/packages/query-core/src/queriesObserver.ts
index 1e0062a07c0..09b40d518dc 100644
--- a/packages/query-core/src/queriesObserver.ts
+++ b/packages/query-core/src/queriesObserver.ts
@@ -10,7 +10,7 @@ import type { NotifyOptions } from './queryObserver'
import { QueryObserver } from './queryObserver'
import { Subscribable } from './subscribable'
-type QueriesObserverListener = (result: QueryObserverResult[]) => void
+export type QueriesObserverListener = (result: QueryObserverResult[]) => void
export class QueriesObserver extends Subscribable {
private client: QueryClient
diff --git a/packages/query-core/src/queryObserver.ts b/packages/query-core/src/queryObserver.ts
index 3d1259a64ef..b331b2d2cdf 100644
--- a/packages/query-core/src/queryObserver.ts
+++ b/packages/query-core/src/queryObserver.ts
@@ -14,6 +14,7 @@ import type {
QueryObserverBaseResult,
QueryObserverOptions,
QueryObserverResult,
+ QueryObserverListener,
QueryOptions,
RefetchOptions,
} from './types'
@@ -23,10 +24,6 @@ import { focusManager } from './focusManager'
import { Subscribable } from './subscribable'
import { canFetch, isCancelledError } from './retryer'
-type QueryObserverListener = (
- result: QueryObserverResult,
-) => void
-
export interface NotifyOptions {
cache?: boolean
listeners?: boolean
diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts
index 6d3f0ea9ac8..f9f6fa5ee3a 100644
--- a/packages/query-core/src/types.ts
+++ b/packages/query-core/src/types.ts
@@ -455,6 +455,10 @@ export type QueryObserverResult =
| QueryObserverLoadingErrorResult
| QueryObserverLoadingResult
+export type QueryObserverListener = (
+ result: QueryObserverResult,
+) => void
+
export interface InfiniteQueryObserverBaseResult<
TData = unknown,
TError = unknown,
diff --git a/packages/solid-query-persist-client/.eslintrc b/packages/solid-query-persist-client/.eslintrc
new file mode 100644
index 00000000000..cefbf99ca1b
--- /dev/null
+++ b/packages/solid-query-persist-client/.eslintrc
@@ -0,0 +1,10 @@
+{
+ "parserOptions": {
+ "project": "./tsconfig.json",
+ "sourceType": "module"
+ },
+ "rules": {
+ "react/react-in-jsx-scope": "off",
+ "react-hooks/rules-of-hooks": "off"
+ }
+}
diff --git a/packages/solid-query-persist-client/jest-preset.js b/packages/solid-query-persist-client/jest-preset.js
new file mode 100644
index 00000000000..cc0a653b640
--- /dev/null
+++ b/packages/solid-query-persist-client/jest-preset.js
@@ -0,0 +1,7 @@
+const solidPreset = require('solid-jest/preset/browser/jest-preset')
+const tanStackPreset = require('../../jest-preset')
+
+module.exports = {
+ ...tanStackPreset,
+ ...solidPreset,
+}
diff --git a/packages/solid-query-persist-client/jest.config.ts b/packages/solid-query-persist-client/jest.config.ts
new file mode 100644
index 00000000000..46aedae757c
--- /dev/null
+++ b/packages/solid-query-persist-client/jest.config.ts
@@ -0,0 +1,5 @@
+export default {
+ displayName: 'solid-query',
+ preset: './jest-preset.js',
+ transform: { '^.+\\.(ts|tsx)$': './transform.js' },
+}
diff --git a/packages/solid-query-persist-client/package.json b/packages/solid-query-persist-client/package.json
new file mode 100644
index 00000000000..d40c741b2b6
--- /dev/null
+++ b/packages/solid-query-persist-client/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@tanstack/solid-query-persist-client",
+ "version": "4.13.0",
+ "description": "Solid bindings to work with persisters in TanStack Query",
+ "author": "tannerlinsley",
+ "license": "MIT",
+ "repository": "tanstack/query",
+ "homepage": "https://tanstack.com/query",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "types": "build/lib/index.d.ts",
+ "main": "build/lib/index.js",
+ "module": "build/lib/index.esm.js",
+ "exports": {
+ ".": {
+ "types": "./build/lib/index.d.ts",
+ "solid": "./build/solid/index.js",
+ "import": "./build/lib/index.mjs",
+ "browser": "./build/lib/index.mjs",
+ "require": "./build/lib/index.js",
+ "node": "./build/lib/index.js",
+ "default": "./build/lib/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "sideEffects": false,
+ "scripts": {
+ "clean": "rm -rf ./build",
+ "test:eslint": "../../node_modules/.bin/eslint --ext .ts,.tsx ./src",
+ "test:jest": "../../node_modules/.bin/jest --config ./jest.config.ts",
+ "test:dev": "pnpm run test:jest --watch"
+ },
+ "files": [
+ "build/lib/*",
+ "build/umd/*",
+ "build/solid/*",
+ "src"
+ ],
+ "devDependencies": {
+ "@tanstack/solid-query": "workspace:*",
+ "solid-jest": "^0.2.0"
+ },
+ "dependencies": {
+ "@tanstack/query-persist-client-core": "workspace:*"
+ },
+ "peerDependencies": {
+ "@tanstack/solid-query": "workspace:*",
+ "solid-js": "^1.5.7"
+ }
+}
diff --git a/packages/solid-query-persist-client/src/PersistQueryClientProvider.tsx b/packages/solid-query-persist-client/src/PersistQueryClientProvider.tsx
new file mode 100644
index 00000000000..7043e7b9109
--- /dev/null
+++ b/packages/solid-query-persist-client/src/PersistQueryClientProvider.tsx
@@ -0,0 +1,57 @@
+// based on react-query-persist-client/src/PersistQueryClientProvider.tsx
+
+import { createSignal, onMount, onCleanup, mergeProps } from 'solid-js'
+
+import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
+import { persistQueryClient } from '@tanstack/query-persist-client-core'
+import type { QueryClientProviderProps } from '@tanstack/solid-query'
+import { QueryClientProvider, IsRestoringProvider } from '@tanstack/solid-query'
+
+export type PersistQueryClientProviderProps = QueryClientProviderProps & {
+ persistOptions: Omit
+ onSuccess?: () => void
+}
+
+export const PersistQueryClientProvider = (
+ props: PersistQueryClientProviderProps,
+) => {
+ const mergedProps = mergeProps(
+ {
+ contextSharing: false,
+ },
+ props,
+ )
+
+ const [isRestoring, setIsRestoring] = createSignal(true)
+
+ let isStale = false
+
+ const [unsubscribe, restorePromise] = persistQueryClient({
+ ...mergedProps.persistOptions,
+ queryClient: mergedProps.client,
+ })
+
+ restorePromise.then(() => {
+ if (!isStale) {
+ mergedProps.onSuccess?.()
+ setIsRestoring(false)
+ }
+ })
+
+ onMount(() => mergedProps.client.mount())
+
+ onCleanup(() => {
+ mergedProps.client.unmount()
+
+ isStale = true
+ unsubscribe()
+ })
+
+ return (
+
+
+ {mergedProps.children}
+
+
+ )
+}
diff --git a/packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx b/packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx
new file mode 100644
index 00000000000..786207f5ece
--- /dev/null
+++ b/packages/solid-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx
@@ -0,0 +1,544 @@
+// based on react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx
+
+import { createEffect, createSignal } from 'solid-js'
+import { render, screen, waitFor } from 'solid-testing-library'
+
+import type {
+ CreateQueryResult,
+ DefinedCreateQueryResult,
+} from '@tanstack/solid-query'
+import {
+ QueryClient,
+ createQuery,
+ createQueries,
+} from '@tanstack/solid-query'
+import type {
+ PersistedClient,
+ Persister,
+} from '@tanstack/query-persist-client-core'
+import type { QueryKey } from '@tanstack/query-core'
+import { persistQueryClientSave } from '@tanstack/query-persist-client-core'
+
+// copy of solid-query/src/__tests__/utils.tsx
+import { createQueryClient, mockLogger, queryKey, sleep } from './utils'
+
+import { PersistQueryClientProvider } from '../PersistQueryClientProvider'
+
+const createMockPersister = (): Persister => {
+ let storedState: PersistedClient | undefined
+
+ return {
+ async persistClient(persistClient: PersistedClient) {
+ storedState = persistClient
+ },
+ async restoreClient() {
+ await sleep(10)
+ return storedState
+ },
+ removeClient() {
+ storedState = undefined
+ },
+ }
+}
+
+const createMockErrorPersister = (
+ removeClient: Persister['removeClient'],
+): [Error, Persister] => {
+ const error = new Error('restore failed')
+ return [
+ error,
+ {
+ async persistClient() {
+ // noop
+ },
+ async restoreClient() {
+ await sleep(10)
+ throw error
+ },
+ removeClient,
+ },
+ ]
+}
+
+describe('PersistQueryClientProvider', () => {
+ test('restores cache from persister', async () => {
+ const key: () => QueryKey = queryKey()
+ const states: CreateQueryResult[] = []
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ function Page() {
+ const state = createQuery(key, async () => {
+ await sleep(10)
+ return 'fetched'
+ })
+
+ states.push(state)
+
+ return (
+
+
{state.data}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => (
+
+
+
+ ))
+
+ await waitFor(() => screen.getByText('fetchStatus: idle'))
+ await waitFor(() => screen.getByText('hydrated'))
+ await waitFor(() => screen.getByText('fetched'))
+
+ expect(states).toHaveLength(4)
+
+ expect(states[0]).toMatchObject({
+ status: 'loading',
+ fetchStatus: 'idle',
+ data: undefined,
+ })
+
+ expect(states[1]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[2]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[3]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'fetched',
+ })
+ })
+
+ test('should also put createQueries into idle state', async () => {
+ const key = queryKey()
+ const states: CreateQueryResult[] = []
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ function Page() {
+ const [state] = createQueries({
+ queries: [
+ {
+ queryKey: key,
+ queryFn: async (): Promise => {
+ await sleep(10)
+ return 'fetched'
+ },
+ },
+ ],
+ })
+
+ states.push(state)
+
+ return (
+
+
{state.data}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => (
+
+
+
+ ))
+
+ await waitFor(() => screen.getByText('fetchStatus: idle'))
+ await waitFor(() => screen.getByText('hydrated'))
+ await waitFor(() => screen.getByText('fetched'))
+
+ expect(states).toHaveLength(4)
+
+ expect(states[0]).toMatchObject({
+ status: 'loading',
+ fetchStatus: 'idle',
+ data: undefined,
+ })
+
+ expect(states[1]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[2]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[3]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'fetched',
+ })
+ })
+
+ test('should show initialData while restoring', async () => {
+ const key = queryKey()
+ const states: DefinedCreateQueryResult[] = []
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ function Page() {
+ const state = createQuery(
+ key,
+ async () => {
+ await sleep(10)
+ return 'fetched'
+ },
+ {
+ initialData: 'initial',
+ // make sure that initial data is older than the hydration data
+ // otherwise initialData would be newer and takes precedence
+ initialDataUpdatedAt: 1,
+ },
+ )
+
+ states.push(state)
+
+ return (
+
+
{state.data}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => (
+
+
+
+ ))
+
+ await waitFor(() => screen.getByText('initial'))
+ await waitFor(() => screen.getByText('hydrated'))
+ await waitFor(() => screen.getByText('fetched'))
+
+ expect(states).toHaveLength(4)
+
+ expect(states[0]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'initial',
+ })
+
+ expect(states[1]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[2]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[3]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'fetched',
+ })
+ })
+
+ test('should not refetch after restoring when data is fresh', async () => {
+ const key = queryKey()
+ const states: CreateQueryResult[] = []
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ function Page() {
+ const state = createQuery(
+ key,
+ async () => {
+ await sleep(10)
+ return 'fetched'
+ },
+ {
+ staleTime: Infinity,
+ },
+ )
+
+ states.push(state)
+
+ return (
+
+
data: {state.data ?? 'null'}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => (
+
+
+
+ ))
+
+ await waitFor(() => screen.getByText('data: null'))
+ await waitFor(() => screen.getByText('data: hydrated'))
+
+ expect(states).toHaveLength(2)
+
+ expect(states[0]).toMatchObject({
+ status: 'loading',
+ fetchStatus: 'idle',
+ data: undefined,
+ })
+
+ expect(states[1]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'hydrated',
+ })
+ })
+
+ test('should call onSuccess after successful restoring', async () => {
+ const key = queryKey()
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ function Page() {
+ const state = createQuery(key, async () => {
+ await sleep(10)
+ return 'fetched'
+ })
+
+ return (
+
+
{state.data}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ const onSuccess = jest.fn()
+
+ render(() => (
+
+
+
+ ))
+ expect(onSuccess).toHaveBeenCalledTimes(0)
+
+ await waitFor(() => screen.getByText('hydrated'))
+ expect(onSuccess).toHaveBeenCalledTimes(1)
+ await waitFor(() => screen.getByText('fetched'))
+ })
+
+ test('should remove cache after non-successful restoring', async () => {
+ const key = queryKey()
+ jest.spyOn(console, 'warn').mockImplementation(() => undefined)
+ jest.spyOn(console, 'error').mockImplementation(() => undefined)
+
+ const queryClient = createQueryClient()
+ const removeClient = jest.fn()
+
+ const [error, persister] = createMockErrorPersister(removeClient)
+
+ function Page() {
+ const state = createQuery(key, async () => {
+ await sleep(10)
+ return 'fetched'
+ })
+
+ return (
+
+
{state.data}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => (
+
+
+
+ ))
+
+ await waitFor(() => screen.getByText('fetched'))
+ expect(removeClient).toHaveBeenCalledTimes(1)
+ expect(mockLogger.error).toHaveBeenCalledTimes(1)
+ expect(mockLogger.error).toHaveBeenCalledWith(error)
+ })
+
+ test('should be able to persist into multiple clients', async () => {
+ const key = queryKey()
+ const states: CreateQueryResult[] = []
+
+ const queryClient = createQueryClient()
+ await queryClient.prefetchQuery(key(), () => Promise.resolve('hydrated'))
+
+ const persister = createMockPersister()
+
+ await persistQueryClientSave({ queryClient, persister })
+
+ queryClient.clear()
+
+ const onSuccess = jest.fn()
+
+ const queryFn1 = jest.fn().mockImplementation(async () => {
+ await sleep(10)
+ return 'queryFn1'
+ })
+ const queryFn2 = jest.fn().mockImplementation(async () => {
+ await sleep(10)
+ return 'queryFn2'
+ })
+
+ function App() {
+ const [client, setClient] = createSignal(
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ queryFn: queryFn1,
+ },
+ },
+ }),
+ )
+
+ createEffect(() => {
+ setClient(
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ queryFn: queryFn2,
+ },
+ },
+ }),
+ )
+ }, [])
+
+ return (
+
+
+
+ )
+ }
+
+ function Page() {
+ const state = createQuery(key)
+
+ states.push(state)
+
+ return (
+
+
{String(state.data)}
+ fetchStatus: {state.fetchStatus}
+
+ )
+ }
+
+ render(() => )
+
+ await waitFor(() => screen.getByText('hydrated'))
+ await waitFor(() => screen.getByText('queryFn2'))
+
+ expect(queryFn1).toHaveBeenCalledTimes(0)
+ expect(queryFn2).toHaveBeenCalledTimes(1)
+ expect(onSuccess).toHaveBeenCalledTimes(1)
+
+ expect(states).toHaveLength(5)
+
+ expect(states[0]).toMatchObject({
+ status: 'loading',
+ fetchStatus: 'idle',
+ data: undefined,
+ })
+
+ expect(states[1]).toMatchObject({
+ status: 'loading',
+ fetchStatus: 'idle',
+ data: undefined,
+ })
+
+ expect(states[2]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[3]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'fetching',
+ data: 'hydrated',
+ })
+
+ expect(states[4]).toMatchObject({
+ status: 'success',
+ fetchStatus: 'idle',
+ data: 'queryFn2',
+ })
+ })
+})
diff --git a/packages/solid-query-persist-client/src/__tests__/utils.tsx b/packages/solid-query-persist-client/src/__tests__/utils.tsx
new file mode 100644
index 00000000000..41c904f127a
--- /dev/null
+++ b/packages/solid-query-persist-client/src/__tests__/utils.tsx
@@ -0,0 +1,75 @@
+import type { QueryClientConfig } from '@tanstack/query-core'
+import { QueryClient } from '@tanstack/query-core'
+import type { ParentProps } from 'solid-js'
+import { createEffect, createSignal, onCleanup, Show } from 'solid-js'
+
+let queryKeyCount = 0
+export function queryKey(): () => Array {
+ const localQueryKeyCount = queryKeyCount++
+ return () => [`query_${localQueryKeyCount}`]
+}
+
+export const Blink = (
+ props: {
+ duration: number
+ } & ParentProps,
+) => {
+ const [shouldShow, setShouldShow] = createSignal(true)
+
+ createEffect(() => {
+ setShouldShow(true)
+ const timeout = setActTimeout(() => setShouldShow(false), props.duration)
+ onCleanup(() => clearTimeout(timeout))
+ })
+
+ return (
+ off>}>
+ <>{props.children}>
+
+ )
+}
+
+export function createQueryClient(config?: QueryClientConfig): QueryClient {
+ jest.spyOn(console, 'error').mockImplementation(() => undefined)
+ return new QueryClient({ logger: mockLogger, ...config })
+}
+
+export function mockVisibilityState(value: DocumentVisibilityState) {
+ return jest.spyOn(document, 'visibilityState', 'get').mockReturnValue(value)
+}
+
+export function mockNavigatorOnLine(value: boolean) {
+ return jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(value)
+}
+
+export const mockLogger = {
+ log: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+}
+
+export function sleep(timeout: number): Promise {
+ return new Promise((resolve, _reject) => {
+ setTimeout(resolve, timeout)
+ })
+}
+
+export function setActTimeout(fn: () => void, ms?: number) {
+ return setTimeout(() => {
+ fn()
+ }, ms)
+}
+
+/**
+ * Assert the parameter is of a specific type.
+ */
+export function expectType(_: T): void {
+ return undefined
+}
+
+/**
+ * Assert the parameter is not typed as `any`
+ */
+export function expectTypeNotAny(_: 0 extends 1 & T ? never : T): void {
+ return undefined
+}
diff --git a/packages/solid-query-persist-client/src/index.ts b/packages/solid-query-persist-client/src/index.ts
new file mode 100644
index 00000000000..15705049b12
--- /dev/null
+++ b/packages/solid-query-persist-client/src/index.ts
@@ -0,0 +1,7 @@
+/* istanbul ignore file */
+
+// Re-export core
+export * from '@tanstack/query-persist-client-core'
+
+// Solid Query
+export * from './PersistQueryClientProvider'
diff --git a/packages/solid-query-persist-client/transform.js b/packages/solid-query-persist-client/transform.js
new file mode 100644
index 00000000000..d62bbfe50ed
--- /dev/null
+++ b/packages/solid-query-persist-client/transform.js
@@ -0,0 +1,9 @@
+const babelJest = require('babel-jest')
+
+module.exports = babelJest.default.createTransformer({
+ presets: [
+ 'babel-preset-solid',
+ '@babel/preset-env',
+ '@babel/preset-typescript',
+ ],
+})
diff --git a/packages/solid-query-persist-client/tsconfig.json b/packages/solid-query-persist-client/tsconfig.json
new file mode 100644
index 00000000000..d8313ed8344
--- /dev/null
+++ b/packages/solid-query-persist-client/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "rootDir": "./src",
+ "outDir": "./build/solid",
+ "declarationDir": "./build/lib",
+ "tsBuildInfoFile": "./build/.tsbuildinfo",
+ "jsx": "preserve",
+ "jsxImportSource": "solid-js",
+ "emitDeclarationOnly": false
+ },
+ "include": ["src"],
+ "references": [
+ { "path": "../query-persist-client-core" },
+ { "path": "../solid-query" }
+ ]
+}
diff --git a/packages/solid-query/src/createBaseQuery.ts b/packages/solid-query/src/createBaseQuery.ts
index 209c70532c1..06ba2040b55 100644
--- a/packages/solid-query/src/createBaseQuery.ts
+++ b/packages/solid-query/src/createBaseQuery.ts
@@ -1,7 +1,12 @@
import type { QueryObserver } from '@tanstack/query-core'
-import type { QueryKey, QueryObserverResult } from '@tanstack/query-core'
+import type {
+ QueryKey,
+ QueryObserverResult,
+ QueryObserverListener,
+} from '@tanstack/query-core'
import type { CreateBaseQueryOptions } from './types'
import { useQueryClient } from './QueryClientProvider'
+import { useIsRestoring } from './isRestoring'
import {
onMount,
onCleanup,
@@ -31,11 +36,15 @@ export function createBaseQuery<
Observer: typeof QueryObserver,
): QueryObserverResult {
const queryClient = useQueryClient({ context: options.context })
+ const isRestoring = useIsRestoring()
const emptyData = Symbol('empty')
const defaultedOptions = queryClient.defaultQueryOptions(options)
- defaultedOptions._optimisticResults = 'optimistic'
+ defaultedOptions._optimisticResults = isRestoring()
+ ? 'isRestoring'
+ : 'optimistic' // Include callbacks in batch renders
const observer = new Observer(queryClient, defaultedOptions)
+ // get the initial result
const [state, setState] = createStore>(
// @ts-ignore
observer.getOptimisticResult(defaultedOptions),
@@ -61,7 +70,7 @@ export function createBaseQuery<
let taskQueue: Array<() => void> = []
- const unsubscribe = observer.subscribe((result) => {
+ const handleResult: QueryObserverListener = (result) => {
taskQueue.push(() => {
batch(() => {
const unwrappedResult = { ...unwrap(result) }
@@ -85,7 +94,28 @@ export function createBaseQuery<
}
taskQueue = []
})
- })
+ }
+
+ let unsubscribe: () => void = () => undefined
+
+ if (!isRestoring()) {
+ unsubscribe = observer.subscribe(handleResult)
+ } else {
+ createComputed(
+ on(isRestoring, () => {
+ if (isRestoring()) {
+ return
+ }
+ // result was restored from cache
+ const newDefaultedOptions = queryClient.defaultQueryOptions(options)
+ newDefaultedOptions._optimisticResults = 'optimistic'
+ observer.setOptions(newDefaultedOptions)
+
+ handleResult(observer.getCurrentResult())
+ unsubscribe = observer.subscribe(handleResult)
+ }),
+ )
+ }
onCleanup(() => unsubscribe())
diff --git a/packages/solid-query/src/createQueries.ts b/packages/solid-query/src/createQueries.ts
index 1fc449641d0..261f265503a 100644
--- a/packages/solid-query/src/createQueries.ts
+++ b/packages/solid-query/src/createQueries.ts
@@ -1,7 +1,9 @@
-import { createComputed, onCleanup, onMount } from 'solid-js'
+import { createComputed, onCleanup, onMount, on } from 'solid-js'
import type { QueryFunction } from '@tanstack/query-core'
import { QueriesObserver } from '@tanstack/query-core'
+import type { QueriesObserverListener } from '@tanstack/query-core'
import { useQueryClient } from './QueryClientProvider'
+import { useIsRestoring } from './isRestoring'
import type {
CreateQueryOptions,
CreateQueryResult,
@@ -150,13 +152,16 @@ export function createQueries(queriesOptions: {
context?: CreateQueryOptions['context']
}): QueriesResults {
const queryClient = useQueryClient({ context: queriesOptions.context })
+ const isRestoring = useIsRestoring()
const normalizeOptions = (
options: ArrType,
) => {
const normalizedOptions = { ...options, queryKey: options.queryKey?.() }
const defaultedOptions = queryClient.defaultQueryOptions(normalizedOptions)
- defaultedOptions._optimisticResults = 'optimistic'
+ defaultedOptions._optimisticResults = isRestoring()
+ ? 'isRestoring'
+ : 'optimistic'
return defaultedOptions
}
@@ -172,7 +177,7 @@ export function createQueries(queriesOptions: {
const taskQueue: Array<() => void> = []
- const unsubscribe = observer.subscribe((result) => {
+ const handleResult: QueriesObserverListener = (result) => {
taskQueue.push(() => {
setState(unwrap(result))
})
@@ -184,7 +189,30 @@ export function createQueries(queriesOptions: {
taskQueue.splice(0, taskQueue.length)
}
})
- })
+ }
+
+ let unsubscribe: () => void = () => undefined
+
+ if (!isRestoring()) {
+ unsubscribe = observer.subscribe(handleResult)
+ } else {
+ createComputed(
+ on(isRestoring, () => {
+ if (isRestoring()) {
+ return
+ }
+ // result was restored from cache
+ // query.options._optimisticResults = 'optimistic'
+ const updateDefaultedQueries = queriesOptions.queries.map((options) =>
+ normalizeOptions(options),
+ )
+ observer.setQueries(updateDefaultedQueries)
+
+ handleResult(observer.getCurrentResult())
+ unsubscribe = observer.subscribe(handleResult)
+ }),
+ )
+ }
onCleanup(unsubscribe)
diff --git a/packages/solid-query/src/index.ts b/packages/solid-query/src/index.ts
index 7090337d2ff..608ea056abb 100644
--- a/packages/solid-query/src/index.ts
+++ b/packages/solid-query/src/index.ts
@@ -17,3 +17,8 @@ export { useIsMutating } from './useIsMutating'
export { createMutation } from './createMutation'
export { createInfiniteQuery } from './createInfiniteQuery'
export { createQueries } from './createQueries'
+export {
+ IsRestoringContext,
+ IsRestoringProvider,
+ useIsRestoring,
+} from './isRestoring'
diff --git a/packages/solid-query/src/isRestoring.ts b/packages/solid-query/src/isRestoring.ts
new file mode 100644
index 00000000000..8cb5e7b51a1
--- /dev/null
+++ b/packages/solid-query/src/isRestoring.ts
@@ -0,0 +1,11 @@
+// based on react-query/src/isRestoring.tsx
+
+import { createContext, useContext } from 'solid-js'
+
+const isRestoring = () => false
+const IsRestoringContext = createContext(isRestoring)
+
+const useIsRestoring = () => useContext(IsRestoringContext)
+const IsRestoringProvider = IsRestoringContext.Provider
+
+export { IsRestoringContext, IsRestoringProvider, useIsRestoring }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6295e18d24a..e8a1eb29abd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -599,9 +599,9 @@ importers:
vite: ^3.0.9
vite-plugin-solid: ^2.3.9
dependencies:
- '@tanstack/solid-query': link:../../../packages/solid-query
solid-js: 1.5.4
devDependencies:
+ '@tanstack/solid-query': link:../../../packages/solid-query
typescript: 4.8.3
vite: 3.1.3
vite-plugin-solid: 2.3.9_solid-js@1.5.4+vite@3.1.3
@@ -621,6 +621,41 @@ importers:
vite: 3.1.3
vite-plugin-solid: 2.3.9_solid-js@1.5.4+vite@3.1.3
+ examples/solid/offline:
+ specifiers:
+ '@solidjs/router': ^0.5.0
+ '@tanstack/query-async-storage-persister': workspace:*
+ '@tanstack/solid-query': workspace:*
+ '@tanstack/solid-query-persist-client': workspace:*
+ '@types/lodash': ^4.14.186
+ deepdash: ^5.3.9
+ idb-keyval: ^6.2.0
+ ky: ^0.30.0
+ lodash: ^4.17.21
+ msw: ^0.39.2
+ solid-js: 1.5.4
+ solid-toast: ^0.3.5
+ typescript: ^4.8.2
+ vite: ^3.0.9
+ vite-plugin-solid: ^2.3.9
+ dependencies:
+ solid-js: 1.5.4
+ devDependencies:
+ '@solidjs/router': 0.5.0_solid-js@1.5.4
+ '@tanstack/query-async-storage-persister': link:../../../packages/query-async-storage-persister
+ '@tanstack/solid-query': link:../../../packages/solid-query
+ '@tanstack/solid-query-persist-client': link:../../../packages/solid-query-persist-client
+ '@types/lodash': 4.14.186
+ deepdash: 5.3.9
+ idb-keyval: 6.2.0
+ ky: 0.30.0
+ lodash: 4.17.21
+ msw: 0.39.2
+ solid-toast: 0.3.5_solid-js@1.5.4
+ typescript: 4.8.4
+ vite: 3.1.4
+ vite-plugin-solid: 2.3.9_solid-js@1.5.4+vite@3.1.4
+
examples/solid/simple:
specifiers:
'@tanstack/solid-query': ^4.3.9
@@ -768,6 +803,17 @@ importers:
devDependencies:
solid-jest: 0.2.0
+ packages/solid-query-persist-client:
+ specifiers:
+ '@tanstack/query-persist-client-core': workspace:*
+ '@tanstack/solid-query': workspace:*
+ solid-jest: ^0.2.0
+ dependencies:
+ '@tanstack/query-persist-client-core': link:../query-persist-client-core
+ devDependencies:
+ '@tanstack/solid-query': link:../solid-query
+ solid-jest: 0.2.0
+
packages/vue-query:
specifiers:
'@tanstack/match-sorter-utils': ^8.1.1
@@ -4929,7 +4975,6 @@ packages:
dependencies:
'@types/set-cookie-parser': 2.4.2
set-cookie-parser: 2.5.0
- dev: false
/@mswjs/interceptors/0.15.3:
resolution: {integrity: sha512-GJ1qzBq82EQ3bwhsvw5nScbrLzOSI5H/TyB2CGd1K7dDqX58DJDLJHexiN+S5Ucvl6/84FjRdIysz0RxE/L8MA==}
@@ -4943,7 +4988,6 @@ packages:
strict-event-emitter: 0.2.4
transitivePeerDependencies:
- supports-color
- dev: false
/@next/env/12.2.2:
resolution: {integrity: sha512-BqDwE4gDl1F608TpnNxZqrCn6g48MBjvmWFEmeX5wEXDXh3IkAOw6ASKUgjT8H4OUePYFqghDFUss5ZhnbOUjw==}
@@ -5086,7 +5130,6 @@ packages:
/@open-draft/until/1.0.3:
resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==}
- dev: false
/@react-native-community/cli-debugger-ui/5.0.1:
resolution: {integrity: sha512-5gGKaaXYOVE423BUqxIfvfAVSj5Cg1cU/TpGbeg/iqpy2CfqyWqJB3tTuVUbOOiOvR5wbU8tti6pIi1pchJ+oA==}
@@ -5423,6 +5466,14 @@ packages:
'@sinonjs/commons': 1.8.3
dev: true
+ /@solidjs/router/0.5.0_solid-js@1.5.4:
+ resolution: {integrity: sha512-rNR07l21tWWDVmCbaapggB89rEX7jlM2XChpTLqEGEnj46LzVZ8zgvjcF6NNKScByAlLpoQUkVIjB2KHpcMi+w==}
+ peerDependencies:
+ solid-js: ^1.5.3
+ dependencies:
+ solid-js: 1.5.4
+ dev: true
+
/@swc/helpers/0.4.2:
resolution: {integrity: sha512-556Az0VX7WR6UdoTn4htt/l3zPQ7bsQWK+HqdG4swV7beUCxo/BqmvbOpUkTIm/9ih86LIf1qsUnywNL3obGHw==}
dependencies:
@@ -5616,7 +5667,6 @@ packages:
/@types/cookie/0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
- dev: false
/@types/estree/0.0.39:
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
@@ -5653,7 +5703,6 @@ packages:
/@types/js-levenshtein/1.1.1:
resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==}
- dev: false
/@types/jscodeshift/0.11.5:
resolution: {integrity: sha512-7JV0qdblTeWFigevmwFUgROXX395F+MQx6v0YqPn8Bx0B4Sng6alEejz9PENzgLYpG+zL0O4tGdBzc4gKZH8XA==}
@@ -5670,6 +5719,10 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
+ /@types/lodash/4.14.186:
+ resolution: {integrity: sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==}
+ dev: true
+
/@types/luxon/2.3.2:
resolution: {integrity: sha512-WOehptuhKIXukSUUkRgGbj2c997Uv/iUgYgII8U7XLJqq9W2oF0kQ6frEznRQbdurioz+L/cdaIm4GutTQfgmA==}
dev: true
@@ -5754,7 +5807,6 @@ packages:
resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
dependencies:
'@types/node': 17.0.45
- dev: false
/@types/stack-utils/2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
@@ -6351,7 +6403,6 @@ packages:
/@xmldom/xmldom/0.7.5:
resolution: {integrity: sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==}
engines: {node: '>=10.0.0'}
- dev: false
/JSONStream/1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
@@ -7070,7 +7121,6 @@ packages:
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
- dev: false
/bl/4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -7078,7 +7128,6 @@ packages:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.0
- dev: false
/blueimp-md5/2.19.0:
resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
@@ -7328,7 +7377,6 @@ packages:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
- dev: false
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
@@ -7344,7 +7392,6 @@ packages:
/chardet/0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
- dev: false
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
@@ -7359,7 +7406,6 @@ packages:
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
- dev: false
/ci-env/1.17.0:
resolution: {integrity: sha512-NtTjhgSEqv4Aj90TUYHQLxHdnCPXnjdtuGG1X8lTfp/JqeXTdw0FTWl/vUAPuvbWZTF8QVpv6ASe/XacE+7R2A==}
@@ -7398,17 +7444,14 @@ packages:
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
- dev: false
/cli-spinners/2.6.1:
resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==}
engines: {node: '>=6'}
- dev: false
/cli-width/3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
- dev: false
/cliui/6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
@@ -7436,7 +7479,6 @@ packages:
/clone/1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
- dev: false
/clsx/1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
@@ -7641,7 +7683,6 @@ packages:
/cookie/0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
- dev: false
/copy-anything/3.0.2:
resolution: {integrity: sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==}
@@ -7887,6 +7928,13 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true
+ /deepdash/5.3.9:
+ resolution: {integrity: sha512-GRzJ0q9PDj2T+J2fX+b+TlUa2NlZ11l6vJ8LHNKVGeZ8CfxCuJaCychTq07iDRTvlfO8435jlvVS1QXBrW9kMg==}
+ dependencies:
+ lodash: 4.17.21
+ lodash-es: 4.17.21
+ dev: true
+
/deepmerge/3.3.0:
resolution: {integrity: sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==}
engines: {node: '>=0.10.0'}
@@ -7901,7 +7949,6 @@ packages:
resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==}
dependencies:
clone: 1.0.4
- dev: false
/define-lazy-prop/2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
@@ -9006,7 +9053,6 @@ packages:
/events/3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
- dev: false
/exec-sh/0.3.6:
resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==}
@@ -9222,7 +9268,6 @@ packages:
chardet: 0.7.0
iconv-lite: 0.4.24
tmp: 0.0.33
- dev: false
/extglob/2.0.4:
resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
@@ -9330,7 +9375,6 @@ packages:
engines: {node: '>=8'}
dependencies:
escape-string-regexp: 1.0.5
- dev: false
/file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
@@ -9716,7 +9760,6 @@ packages:
/graphql/16.6.0:
resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
- dev: false
/gzip-size/5.1.1:
resolution: {integrity: sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==}
@@ -9801,7 +9844,6 @@ packages:
/headers-polyfill/3.0.9:
resolution: {integrity: sha512-FFIXpxbA9HZJXofXqS4IBRa7Z8F1Y+/DwxHSEOOTswZxym8Kz+f6DNhrtnCRcjWcTN7LjjbE5stz0UnoUPNprQ==}
- dev: false
/hermes-engine/0.7.2:
resolution: {integrity: sha512-E2DkRaO97gwL98LPhgfkMqhHiNsrAjIfEk3wWYn2Y31xdkdWn0572H7RnVcGujMJVqZNJvtknxlpsUb8Wzc3KA==}
@@ -9917,6 +9959,12 @@ packages:
safer-buffer: 2.1.2
dev: false
+ /idb-keyval/6.2.0:
+ resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==}
+ dependencies:
+ safari-14-idb-fix: 3.0.0
+ dev: true
+
/ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -10008,7 +10056,6 @@ packages:
strip-ansi: 6.0.1
through: 2.3.8
wrap-ansi: 7.0.0
- dev: false
/internal-slot/1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
@@ -10063,7 +10110,6 @@ packages:
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
- dev: false
/is-boolean-object/1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
@@ -10198,7 +10244,6 @@ packages:
/is-interactive/1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
- dev: false
/is-module/1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
@@ -10211,7 +10256,6 @@ packages:
/is-node-process/1.0.1:
resolution: {integrity: sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==}
- dev: false
/is-number-object/1.0.7:
resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
@@ -10307,7 +10351,6 @@ packages:
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
- dev: false
/is-weakref/1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
@@ -10998,7 +11041,6 @@ packages:
/js-levenshtein/1.1.6:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'}
- dev: false
/js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -11323,7 +11365,6 @@ packages:
/ky/0.30.0:
resolution: {integrity: sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==}
engines: {node: '>=12'}
- dev: false
/language-subtag-registry/0.3.22:
resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
@@ -11410,6 +11451,10 @@ packages:
dependencies:
p-locate: 5.0.0
+ /lodash-es/4.17.21:
+ resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+ dev: true
+
/lodash._reinterpolate/3.0.0:
resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==}
dev: false
@@ -11482,7 +11527,6 @@ packages:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
- dev: false
/logkitty/0.7.1:
resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==}
@@ -12179,11 +12223,9 @@ packages:
transitivePeerDependencies:
- encoding
- supports-color
- dev: false
/mute-stream/0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
- dev: false
/mz/2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -12596,16 +12638,13 @@ packages:
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
- dev: false
/os-tmpdir/1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
- dev: false
/outvariant/1.3.0:
resolution: {integrity: sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ==}
- dev: false
/p-finally/1.0.0:
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
@@ -12783,7 +12822,6 @@ packages:
/path-to-regexp/6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
- dev: false
/path-type/4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
@@ -13421,7 +13459,6 @@ packages:
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
- dev: false
/recast/0.20.5:
resolution: {integrity: sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==}
@@ -13600,7 +13637,6 @@ packages:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
- dev: false
/ret/0.1.15:
resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
@@ -13717,7 +13753,6 @@ packages:
/run-async/2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
- dev: false
/run-parallel/1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -13729,6 +13764,10 @@ packages:
dependencies:
tslib: 2.4.0
+ /safari-14-idb-fix/3.0.0:
+ resolution: {integrity: sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==}
+ dev: true
+
/safe-buffer/5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
@@ -13857,7 +13896,6 @@ packages:
/set-cookie-parser/2.5.0:
resolution: {integrity: sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w==}
- dev: false
/set-value/2.0.1:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
@@ -14077,6 +14115,14 @@ packages:
solid-js: 1.5.7
dev: true
+ /solid-toast/0.3.5_solid-js@1.5.4:
+ resolution: {integrity: sha512-m4PX2ESex9nSwt9d9XTr3r+Kp3vjSoViDWj3gj6Xxgz21IKYODMKhACXurlZCztz56NQBnfX2m4es4I8JgXGQQ==}
+ peerDependencies:
+ solid-js: ^1.5.4
+ dependencies:
+ solid-js: 1.5.4
+ dev: true
+
/sort-by/1.2.0:
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
dependencies:
@@ -14219,7 +14265,6 @@ packages:
/statuses/2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
- dev: false
/stream-buffers/2.2.0:
resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==}
@@ -14242,7 +14287,6 @@ packages:
resolution: {integrity: sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==}
dependencies:
events: 3.3.0
- dev: false
/strict-uri-encode/2.0.0:
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
@@ -14574,7 +14618,6 @@ packages:
engines: {node: '>=0.6.0'}
dependencies:
os-tmpdir: 1.0.2
- dev: false
/tmpl/1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -14816,7 +14859,6 @@ packages:
/type-fest/1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
- dev: false
/typedarray-to-buffer/3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
@@ -15074,6 +15116,23 @@ packages:
- supports-color
dev: true
+ /vite-plugin-solid/2.3.9_solid-js@1.5.4+vite@3.1.4:
+ resolution: {integrity: sha512-+lprsYgt9DVNp0kbDj2d2HWAPI13L8ff5xslk9SjiPBcsY/YUZ/1Wj0J/Oj5aiVAhwfPm8IcM3bzyHJUPlmc8w==}
+ peerDependencies:
+ solid-js: ^1.3.17
+ vite: ^3.0.0
+ dependencies:
+ '@babel/core': 7.19.1
+ '@babel/preset-typescript': 7.18.6_@babel+core@7.19.1
+ babel-preset-solid: 1.5.4_@babel+core@7.19.1
+ merge-anything: 5.0.4
+ solid-js: 1.5.4
+ solid-refresh: 0.4.1_solid-js@1.5.4
+ vite: 3.1.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/vite/3.1.3:
resolution: {integrity: sha512-/3XWiktaopByM5bd8dqvHxRt5EEgRikevnnrpND0gRfNkrMrPaGGexhtLCzv15RcCMtV2CLw+BPas8YFeSG0KA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -15209,7 +15268,6 @@ packages:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.3
- dev: false
/web-streams-polyfill/3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
diff --git a/rollup.config.ts b/rollup.config.ts
index 87176c882f1..a6bfd1915a1 100644
--- a/rollup.config.ts
+++ b/rollup.config.ts
@@ -172,6 +172,20 @@ export default function rollup(options: RollupOptions): RollupOptions[] {
},
bundleUMDGlobals: ['@tanstack/query-core'],
}),
+ ...buildConfigs({
+ name: 'solid-query-persist-client',
+ packageDir: 'packages/solid-query-persist-client',
+ jsName: 'SolidQueryPersistClient',
+ outputFile: 'index',
+ entryFile: 'src/index.ts',
+ globals: {
+ 'solid-js/store': 'SolidStore',
+ 'solid-js': 'Solid',
+ '@tanstack/query-persist-client-core': 'QueryPersistClientCore',
+ '@tanstack/solid-query': 'SolidQuery',
+ },
+ bundleUMDGlobals: ['@tanstack/query-persist-client-core'],
+ }),
...buildConfigs({
name: 'vue-query',
packageDir: 'packages/vue-query',
diff --git a/tsconfig.json b/tsconfig.json
index 9e3853018ec..daa727e29d4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,6 +10,7 @@
{ "path": "packages/react-query-devtools" },
{ "path": "packages/react-query-persist-client" },
{ "path": "packages/solid-query" },
+ { "path": "packages/solid-query-persist-client" },
{ "path": "packages/vue-query" },
]
}