diff --git a/docs/config.json b/docs/config.json index d905826fb49..98044339aac 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1585,6 +1585,31 @@ "to": "framework/vue/plugins/createPersister" } ] + }, + { + "label": "preact", + "children": [ + { + "label": "persistQueryClient", + "to": "framework/preact/plugins/persistQueryClient" + }, + { + "label": "createSyncStoragePersister", + "to": "framework/preact/plugins/createSyncStoragePersister" + }, + { + "label": "createAsyncStoragePersister", + "to": "framework/preact/plugins/createAsyncStoragePersister" + }, + { + "label": "broadcastQueryClient (Experimental)", + "to": "framework/preact/plugins/broadcastQueryClient" + }, + { + "label": "createPersister (Experimental)", + "to": "framework/preact/plugins/createPersister" + } + ] } ] } diff --git a/docs/framework/preact/plugins/createAsyncStoragePersister.md b/docs/framework/preact/plugins/createAsyncStoragePersister.md new file mode 100644 index 00000000000..eee01369127 --- /dev/null +++ b/docs/framework/preact/plugins/createAsyncStoragePersister.md @@ -0,0 +1,6 @@ +--- +id: createAsyncStoragePersister +title: createAsyncStoragePersister +ref: docs/framework/react/plugins/createAsyncStoragePersister.md +replace: { 'react-query': 'preact-query' } +--- diff --git a/docs/framework/preact/plugins/createPersister.md b/docs/framework/preact/plugins/createPersister.md new file mode 100644 index 00000000000..890f3cc5d25 --- /dev/null +++ b/docs/framework/preact/plugins/createPersister.md @@ -0,0 +1,6 @@ +--- +id: createPersister +title: experimental_createPersister +ref: docs/framework/react/plugins/createPersister.md +replace: { 'react-query': 'preact-query' } +--- diff --git a/docs/framework/preact/plugins/createSyncStoragePersister.md b/docs/framework/preact/plugins/createSyncStoragePersister.md new file mode 100644 index 00000000000..087210dcd87 --- /dev/null +++ b/docs/framework/preact/plugins/createSyncStoragePersister.md @@ -0,0 +1,6 @@ +--- +id: createSyncStoragePersister +title: createSyncStoragePersister +ref: docs/framework/react/plugins/createSyncStoragePersister.md +replace: { 'react-query': 'preact-query' } +--- diff --git a/packages/preact-query-persist-client/eslint.config.js b/packages/preact-query-persist-client/eslint.config.js new file mode 100644 index 00000000000..4e7c7b4c7bb --- /dev/null +++ b/packages/preact-query-persist-client/eslint.config.js @@ -0,0 +1,38 @@ +// @ts-check +// @ts-ignore: no types for eslint-config-preact +import preact from 'eslint-config-preact' +// eslint-config-preact uses typescript-eslint under the hood +import tseslint from 'typescript-eslint' + +import rootConfig from './root.eslint.config.js' + +export default [ + ...rootConfig, + ...preact, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: true, + }, + }, + plugins: { + 'typescript-eslint': tseslint.plugin, + }, + rules: { + // Disable base rule to prevent overload false positives + 'no-redeclare': 'off', + 'no-duplicate-imports': 'off', + 'no-unused-vars': 'off', + 'import/order': 'off', + 'sort-imports': 'off', + 'no-import-assign': 'off', + // TS-aware version handles overloads correctly + '@typescript-eslint/no-redeclare': 'error', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/no-unnecessary-condition': 'off', + }, + }, +] diff --git a/packages/preact-query-persist-client/package.json b/packages/preact-query-persist-client/package.json new file mode 100644 index 00000000000..6d52cd850c1 --- /dev/null +++ b/packages/preact-query-persist-client/package.json @@ -0,0 +1,77 @@ +{ + "name": "@tanstack/preact-query-persist-client", + "version": "5.91.0", + "description": "Preact bindings to work with persisters in TanStack/preact-query", + "author": "tannerlinsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/query.git", + "directory": "packages/preact-query-persist-client" + }, + "homepage": "https://tanstack.com/query", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "scripts": { + "clean": "premove ./build ./coverage ./dist-ts", + "compile": "tsc --build", + "test:eslint": "eslint --concurrency=auto ./src", + "test:types": "npm-run-all --serial test:types:*", + "test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build tsconfig.legacy.json", + "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build tsconfig.legacy.json", + "test:types:tscurrent": "tsc --build", + "test:lib": "vitest --retry=3", + "test:lib:dev": "pnpm run test:lib --watch", + "test:build": "publint --strict && attw --pack", + "build": "tsup --tsconfig tsconfig.prod.json" + }, + "type": "module", + "types": "build/legacy/index.d.ts", + "main": "build/legacy/index.cjs", + "module": "build/legacy/index.js", + "exports": { + ".": { + "@tanstack/custom-condition": "./src/index.ts", + "import": { + "types": "./build/modern/index.d.ts", + "default": "./build/modern/index.js" + }, + "require": { + "types": "./build/modern/index.d.cts", + "default": "./build/modern/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "files": [ + "build", + "src", + "!src/__tests__" + ], + "dependencies": { + "@tanstack/query-persist-client-core": "workspace:*" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.2", + "@tanstack/preact-query": "workspace:*", + "@tanstack/query-test-utils": "workspace:*", + "@testing-library/preact": "^3.2.4", + "eslint-config-preact": "^2.0.0", + "npm-run-all2": "^5.0.0", + "preact": "^10.28.0", + "typescript-eslint": "^8.54.0" + }, + "peerDependencies": { + "@tanstack/preact-query": "workspace:^", + "preact": "^10.0.0" + } +} diff --git a/packages/preact-query-persist-client/root.eslint.config.js b/packages/preact-query-persist-client/root.eslint.config.js new file mode 120000 index 00000000000..35dedbe5a4a --- /dev/null +++ b/packages/preact-query-persist-client/root.eslint.config.js @@ -0,0 +1 @@ +../../eslint.config.js \ No newline at end of file diff --git a/packages/preact-query-persist-client/root.tsup.config.js b/packages/preact-query-persist-client/root.tsup.config.js new file mode 120000 index 00000000000..fb8e9add9a3 --- /dev/null +++ b/packages/preact-query-persist-client/root.tsup.config.js @@ -0,0 +1 @@ +../../scripts/getTsupConfig.js \ No newline at end of file diff --git a/packages/preact-query-persist-client/src/PersistQueryClientProvider.tsx b/packages/preact-query-persist-client/src/PersistQueryClientProvider.tsx new file mode 100644 index 00000000000..416214aebb2 --- /dev/null +++ b/packages/preact-query-persist-client/src/PersistQueryClientProvider.tsx @@ -0,0 +1,55 @@ +import { useEffect, useRef, useState } from 'preact/hooks' +import type { VNode } from 'preact' + +import { + persistQueryClientRestore, + persistQueryClientSubscribe, +} from '@tanstack/query-persist-client-core' +import { IsRestoringProvider, QueryClientProvider } from '@tanstack/preact-query' +import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core' +import type { OmitKeyof, QueryClientProviderProps } from '@tanstack/preact-query' + +export type PersistQueryClientProviderProps = QueryClientProviderProps & { + persistOptions: OmitKeyof + onSuccess?: () => Promise | unknown + onError?: () => Promise | unknown +} + +export const PersistQueryClientProvider = ({ + children, + persistOptions, + onSuccess, + onError, + ...props +}: PersistQueryClientProviderProps): VNode => { + const [isRestoring, setIsRestoring] = useState(true) + const refs = useRef({ persistOptions, onSuccess, onError }) + const didRestore = useRef(false) + + useEffect(() => { + refs.current = { persistOptions, onSuccess, onError } + }) + + useEffect(() => { + const options = { + ...refs.current.persistOptions, + queryClient: props.client, + } + if (!didRestore.current) { + didRestore.current = true + persistQueryClientRestore(options) + .then(() => refs.current.onSuccess?.()) + .catch(() => refs.current.onError?.()) + .finally(() => { + setIsRestoring(false) + }) + } + return isRestoring ? undefined : persistQueryClientSubscribe(options) + }, [props.client, isRestoring]) + + return ( + + {children} + + ) +} diff --git a/packages/preact-query-persist-client/src/index.ts b/packages/preact-query-persist-client/src/index.ts new file mode 100644 index 00000000000..cd94f0dee94 --- /dev/null +++ b/packages/preact-query-persist-client/src/index.ts @@ -0,0 +1,4 @@ +// Re-export core +export * from '@tanstack/query-persist-client-core' + +export * from './PersistQueryClientProvider' diff --git a/packages/preact-query-persist-client/test-setup.ts b/packages/preact-query-persist-client/test-setup.ts new file mode 100644 index 00000000000..62f11f2a29d --- /dev/null +++ b/packages/preact-query-persist-client/test-setup.ts @@ -0,0 +1,14 @@ +import '@testing-library/jest-dom/vitest' +import { act, cleanup as cleanupRTL } from '@testing-library/preact' +import { afterEach } from 'vitest' +import { notifyManager } from '@tanstack/preact-query' + +// https://testing-library.com/docs/preact-testing-library/api#cleanup +afterEach(() => { + cleanupRTL() +}) + +// Wrap notifications with act to make sure Preact knows about Query updates +notifyManager.setNotifyFunction((fn) => { + act(fn) +}) diff --git a/packages/preact-query-persist-client/tsconfig.json b/packages/preact-query-persist-client/tsconfig.json new file mode 100644 index 00000000000..7551d3b9cf4 --- /dev/null +++ b/packages/preact-query-persist-client/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist-ts", + "rootDir": ".", + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "include": ["src", "test-setup.ts", "*.config.*", "package.json"], + "references": [ + { "path": "../query-persist-client-core" }, + { "path": "../preact-query" } + ] +} diff --git a/packages/preact-query-persist-client/tsconfig.legacy.json b/packages/preact-query-persist-client/tsconfig.legacy.json new file mode 100644 index 00000000000..c31e0fb485f --- /dev/null +++ b/packages/preact-query-persist-client/tsconfig.legacy.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact", + "outDir": "./dist-ts/legacy" + }, + "include": ["src"], + "exclude": ["src/__tests__"], + "references": [ + { "path": "../query-persist-client-core" }, + { "path": "../preact-query" } + ] +} diff --git a/packages/preact-query-persist-client/tsconfig.prod.json b/packages/preact-query-persist-client/tsconfig.prod.json new file mode 100644 index 00000000000..0f4c92da065 --- /dev/null +++ b/packages/preact-query-persist-client/tsconfig.prod.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": false, + "composite": false, + "rootDir": "../../" + } +} diff --git a/packages/preact-query-persist-client/tsup.config.ts b/packages/preact-query-persist-client/tsup.config.ts new file mode 100644 index 00000000000..ef4a978d123 --- /dev/null +++ b/packages/preact-query-persist-client/tsup.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'tsup' +import { legacyConfig, modernConfig } from './root.tsup.config.js' + +export default defineConfig([ + modernConfig({ entry: ['src/*.ts', 'src/*.tsx'] }), + legacyConfig({ entry: ['src/*.ts', 'src/*.tsx'] }), +]) diff --git a/packages/preact-query-persist-client/vite.config.ts b/packages/preact-query-persist-client/vite.config.ts new file mode 100644 index 00000000000..f18f9c82684 --- /dev/null +++ b/packages/preact-query-persist-client/vite.config.ts @@ -0,0 +1,35 @@ +import preact from '@preact/preset-vite' +import { defineConfig } from 'vitest/config' +import type { UserConfig as ViteUserConfig } from 'vite' + +import packageJson from './package.json' + +export default defineConfig({ + plugins: [preact() as ViteUserConfig['plugins']], + // fix from https://github.com/vitest-dev/vitest/issues/6992#issuecomment-2509408660 + resolve: { + conditions: ['@tanstack/custom-condition'], + }, + environments: { + ssr: { + resolve: { + conditions: ['@tanstack/custom-condition'], + }, + }, + }, + test: { + name: packageJson.name, + dir: './src', + watch: false, + environment: 'jsdom', + setupFiles: ['test-setup.ts'], + coverage: { + enabled: true, + provider: 'istanbul', + include: ['src/**/*'], + exclude: ['src/__tests__/**'], + }, + typecheck: { enabled: true }, + restoreMocks: true, + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07dd7a5b163..8cebc34e04a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2426,6 +2426,37 @@ importers: specifier: ^8.54.0 version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + packages/preact-query-persist-client: + dependencies: + '@tanstack/query-persist-client-core': + specifier: workspace:* + version: link:../query-persist-client-core + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.2 + version: 2.10.3(@babel/core@7.29.0)(preact@10.28.3)(rollup@4.57.1)(vite@6.4.1(@types/node@22.19.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.46.0)(yaml@2.8.2)) + '@tanstack/preact-query': + specifier: workspace:* + version: link:../preact-query + '@tanstack/query-test-utils': + specifier: workspace:* + version: link:../query-test-utils + '@testing-library/preact': + specifier: ^3.2.4 + version: 3.2.4(preact@10.28.3) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@9.39.2(jiti@2.6.1)) + npm-run-all2: + specifier: ^5.0.0 + version: 5.0.2 + preact: + specifier: ^10.28.0 + version: 10.28.3 + typescript-eslint: + specifier: ^8.54.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + packages/query-async-storage-persister: dependencies: '@tanstack/query-core':