diff --git a/.changeset/lemon-memes-divide.md b/.changeset/lemon-memes-divide.md new file mode 100644 index 00000000000..c1f3e131b70 --- /dev/null +++ b/.changeset/lemon-memes-divide.md @@ -0,0 +1,5 @@ +--- +'@tanstack/lit-query': minor +--- + +Add initial @tanstack/lit-query package diff --git a/.gitignore b/.gitignore index 6e71fdf278c..638a5ac304f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ yarn.lock build coverage dist +dist-cjs dist-ts # misc diff --git a/examples/lit/basic/README.md b/examples/lit/basic/README.md new file mode 100644 index 00000000000..6d79ad06c1a --- /dev/null +++ b/examples/lit/basic/README.md @@ -0,0 +1,5 @@ +# Example + +To run this example from the repo root: + +- `pnpm --dir examples/lit/basic run dev` diff --git a/examples/lit/basic/basic-query.html b/examples/lit/basic/basic-query.html new file mode 100644 index 00000000000..19de73348a1 --- /dev/null +++ b/examples/lit/basic/basic-query.html @@ -0,0 +1,12 @@ + + + + + + Lit Query Basic Example + + + + + + diff --git a/examples/lit/basic/config/port.d.ts b/examples/lit/basic/config/port.d.ts new file mode 100644 index 00000000000..12e9b85cc4d --- /dev/null +++ b/examples/lit/basic/config/port.d.ts @@ -0,0 +1 @@ +export const DEMO_PORT: number diff --git a/examples/lit/basic/config/port.js b/examples/lit/basic/config/port.js new file mode 100644 index 00000000000..bff39bff2ee --- /dev/null +++ b/examples/lit/basic/config/port.js @@ -0,0 +1,22 @@ +const DEFAULT_DEMO_PORT = 4173 +const envPort = process.env.DEMO_PORT + +function resolvePort() { + if (!envPort) { + return DEFAULT_DEMO_PORT + } + + const parsedPort = Number.parseInt(envPort, 10) + const isValidPort = + Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 + + if (!isValidPort) { + throw new Error( + `Invalid DEMO_PORT "${envPort}". Expected an integer between 1 and 65535.`, + ) + } + + return parsedPort +} + +export const DEMO_PORT = resolvePort() diff --git a/examples/lit/basic/index.html b/examples/lit/basic/index.html new file mode 100644 index 00000000000..a2962e70475 --- /dev/null +++ b/examples/lit/basic/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Lit Query E2E Demo + + + + + + diff --git a/examples/lit/basic/lifecycle-contract.html b/examples/lit/basic/lifecycle-contract.html new file mode 100644 index 00000000000..5d0f5e5e5a1 --- /dev/null +++ b/examples/lit/basic/lifecycle-contract.html @@ -0,0 +1,12 @@ + + + + + + TanStack Lit Query Lifecycle Contract Fixture + + + + + + diff --git a/examples/lit/basic/mutation.html b/examples/lit/basic/mutation.html new file mode 100644 index 00000000000..1d5de500efb --- /dev/null +++ b/examples/lit/basic/mutation.html @@ -0,0 +1,12 @@ + + + + + + Lit Query Mutation Example + + + + + + diff --git a/examples/lit/basic/package.json b/examples/lit/basic/package.json new file mode 100644 index 00000000000..969932dc834 --- /dev/null +++ b/examples/lit/basic/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/query-example-lit-basic", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/lit-query": "^0.1.0", + "@tanstack/query-core": "^5.99.0", + "lit": "^3.3.1" + }, + "devDependencies": { + "typescript": "5.8.3", + "vite": "^6.4.1" + } +} diff --git a/examples/lit/basic/src/basic-query.ts b/examples/lit/basic/src/basic-query.ts new file mode 100644 index 00000000000..352f938cf81 --- /dev/null +++ b/examples/lit/basic/src/basic-query.ts @@ -0,0 +1,90 @@ +import { html, LitElement } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { QueryClientProvider, createQueryController } from '@tanstack/lit-query' +import { + fetchTodosFromServer, + resetTodoApi, + type TodosResponse, +} from './todoApi' + +resetTodoApi() + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, +}) + +class BasicQueryProvider extends QueryClientProvider { + constructor() { + super() + this.client = queryClient + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +customElements.define('basic-query-provider', BasicQueryProvider) + +class BasicQueryExample extends LitElement { + private readonly todos = createQueryController(this, { + queryKey: ['todos'], + queryFn: fetchTodosFromServer, + }) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + const query = this.todos() + return html` +
+

Basic Query Example

+

+ Status: ${query.status} +

+ + + ${query.isPending + ? html`

Loading...

` + : null} + ${query.isError + ? html`

Error: ${String(query.error)}

` + : null} + + +
+ ` + } +} + +customElements.define('basic-query-example', BasicQueryExample) + +class BasicQueryRoot extends LitElement { + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + return html` + + + + ` + } +} + +customElements.define('basic-query-root', BasicQueryRoot) diff --git a/examples/lit/basic/src/lifecycle-contract.ts b/examples/lit/basic/src/lifecycle-contract.ts new file mode 100644 index 00000000000..88d66e154cb --- /dev/null +++ b/examples/lit/basic/src/lifecycle-contract.ts @@ -0,0 +1,220 @@ +import { html, LitElement } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { QueryClientProvider, createQueryController } from '@tanstack/lit-query' + +type ContractProbeData = { + provider: 'provider-a' | 'provider-b' + payload: string +} + +type ContractTarget = 'orphan' | 'provider-a' | 'provider-b' + +const contractQueryKey = ['lifecycle-contract', 'provider-binding'] as const +let contractConsumerInstanceCount = 0 + +function createContractClient(data: ContractProbeData): QueryClient { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Number.POSITIVE_INFINITY, + }, + }, + }) + + client.setQueryData(contractQueryKey, data) + return client +} + +const contractClientA = createContractClient({ + provider: 'provider-a', + payload: 'provider-a cache', +}) + +const contractClientB = createContractClient({ + provider: 'provider-b', + payload: 'provider-b cache', +}) + +class ContractProviderA extends QueryClientProvider { + constructor() { + super() + this.client = contractClientA + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +if (!customElements.get('contract-provider-a')) { + customElements.define('contract-provider-a', ContractProviderA) +} + +class ContractProviderB extends QueryClientProvider { + constructor() { + super() + this.client = contractClientB + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +if (!customElements.get('contract-provider-b')) { + customElements.define('contract-provider-b', ContractProviderB) +} + +class LifecycleContractConsumer extends LitElement { + private readonly instanceId = ++contractConsumerInstanceCount + + private readonly query = createQueryController( + this, + { + queryKey: contractQueryKey, + queryFn: async () => { + throw new Error( + 'Lifecycle contract fixture unexpectedly fetched from queryFn.', + ) + }, + retry: false, + staleTime: Number.POSITIVE_INFINITY, + }, + ) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + private renderQueryState() { + try { + const query = this.query() + + return html` +
query: ${query.status}
+
+ provider: ${query.data?.provider ?? 'none'} +
+
+ payload: ${query.data?.payload ?? 'none'} +
+
+ error: ${query.error ? String(query.error) : 'none'} +
+ ` + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + + return html` +
query: missing-client
+
provider: none
+
payload: none
+
error: ${message}
+ ` + } + } + + render() { + return html` +
instance: ${this.instanceId}
+ ${this.renderQueryState()} + ` + } +} + +if (!customElements.get('lifecycle-contract-consumer')) { + customElements.define( + 'lifecycle-contract-consumer', + LifecycleContractConsumer, + ) +} + +class LifecycleContractRoot extends LitElement { + static properties = { + currentTarget: { state: true }, + } + + private currentTarget: ContractTarget = 'orphan' + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + override firstUpdated(): void { + this.moveConsumerTo('orphan') + } + + moveConsumerTo(target: ContractTarget): void { + const consumer = this.ensureConsumer() + const destination = this.getContainer(target) + destination.appendChild(consumer) + this.currentTarget = target + this.requestUpdate() + } + + private ensureConsumer(): LifecycleContractConsumer { + const existing = this.querySelector( + 'lifecycle-contract-consumer', + ) as LifecycleContractConsumer | null + if (existing) { + return existing + } + + return document.createElement( + 'lifecycle-contract-consumer', + ) as LifecycleContractConsumer + } + + private getContainer(target: ContractTarget): HTMLElement { + const selector = + target === 'orphan' + ? '[data-contract-slot="orphan"]' + : target === 'provider-a' + ? 'contract-provider-a' + : 'contract-provider-b' + + const container = this.querySelector(selector) + if (!(container instanceof HTMLElement)) { + throw new Error( + `Lifecycle contract container not found for target "${target}".`, + ) + } + + return container + } + + render() { + return html` +
+

Lifecycle Contract Fixture

+

+ Exercises the same consumer across missing-provider and provider + reparent flows. +

+
+ location: ${this.currentTarget} +
+ +
+

Orphan Zone

+
+
+ +
+

Provider A

+ +
+ +
+

Provider B

+ +
+
+ ` + } +} + +if (!customElements.get('lifecycle-contract-root')) { + customElements.define('lifecycle-contract-root', LifecycleContractRoot) +} diff --git a/examples/lit/basic/src/main.ts b/examples/lit/basic/src/main.ts new file mode 100644 index 00000000000..9376eda6764 --- /dev/null +++ b/examples/lit/basic/src/main.ts @@ -0,0 +1,275 @@ +import { html, LitElement } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { + QueryClientProvider, + createMutationController, + createQueryController, + useIsFetching, + useIsMutating, +} from '@tanstack/lit-query' +import { + addTodoOnServer, + failNextFetchRequest, + failNextMutationRequest, + fetchTodosFromServer, + resetTodoApi, + type Todo, + type TodosResponse, +} from './todoApi' + +resetTodoApi() + +const demoQueryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, +}) + +class DemoQueryProvider extends QueryClientProvider { + constructor() { + super() + this.client = demoQueryClient + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +customElements.define('demo-query-provider', DemoQueryProvider) + +class TanstackLitQueryDemo extends LitElement { + static properties = { + nextTodoTitle: { state: true }, + cacheSeedCount: { state: true }, + } + + private nextTodoTitle = 'Add mutation assertion' + private cacheSeedCount = 0 + + private readonly todosQuery = createQueryController( + this, + { + queryKey: ['todos'], + queryFn: fetchTodosFromServer, + }, + ) + + private readonly createTodoMutation = createMutationController< + Todo, + Error, + string + >(this, { + mutationKey: ['create-todo'], + mutationFn: addTodoOnServer, + onSuccess: (createdTodo) => { + demoQueryClient.setQueryData(['todos'], (existing) => { + if (!existing) { + return { + items: [createdTodo], + requestCount: 0, + source: 'cache', + } + } + + return { + items: [...existing.items, createdTodo], + requestCount: existing.requestCount, + source: 'cache', + } + }) + }, + }) + + private readonly isFetching = useIsFetching(this, { + queryKey: ['todos'], + }) + + private readonly isMutating = useIsMutating(this, { + mutationKey: ['create-todo'], + }) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + private onTitleInput(event: Event): void { + const target = event.target as HTMLInputElement + this.nextTodoTitle = target.value + } + + private addTodo(): void { + const title = this.nextTodoTitle.trim() + if (!title) { + return + } + + this.createTodoMutation.mutate(title) + this.nextTodoTitle = '' + } + + private async invalidateTodos(): Promise { + await demoQueryClient.invalidateQueries({ queryKey: ['todos'] }) + } + + private seedCacheOnlyTodo(): void { + this.cacheSeedCount += 1 + + const seedTodo: Todo = { + id: 10_000 + this.cacheSeedCount, + title: `Seeded cache todo ${this.cacheSeedCount}`, + } + + demoQueryClient.setQueryData(['todos'], (existing) => { + if (!existing) { + return { + items: [seedTodo], + requestCount: 0, + source: 'cache', + } + } + + return { + items: [...existing.items, seedTodo], + requestCount: existing.requestCount, + source: 'cache', + } + }) + } + + private forceNextFetchFailure(): void { + failNextFetchRequest() + } + + private forceNextMutationFailure(): void { + failNextMutationRequest() + } + + private async resetDemoState(): Promise { + resetTodoApi() + this.cacheSeedCount = 0 + this.nextTodoTitle = 'Add mutation assertion' + + await demoQueryClient.resetQueries({ queryKey: ['todos'] }) + this.createTodoMutation.reset() + } + + render() { + const query = this.todosQuery() + const mutation = this.createTodoMutation() + const todos = query.data?.items ?? [] + + return html` +
+

TanStack Lit Query E2E Demo

+

Verifies integration between Lit, query-core, and this adapter.

+ +
+
query: ${query.status}
+
mutation: ${mutation.status}
+
fetches: ${this.isFetching()}
+
+ mutations: ${this.isMutating()} +
+
+ server-requests: ${query.data?.requestCount ?? 0} +
+
+ source: ${query.data?.source ?? 'none'} +
+
+ +
+ + + + + + +
+ +
+ + + +
+ + ${query.isError + ? html`
${String(query.error)}
` + : null} + ${mutation.isError + ? html`
+ ${String(mutation.error)} +
` + : null} + +
    + ${todos.map( + (todo) => + html`
  • ${todo.id}: ${todo.title}
  • `, + )} +
+
+ ` + } +} + +customElements.define('tanstack-lit-query-demo', TanstackLitQueryDemo) + +class DemoRoot extends LitElement { + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + return html` + + + + ` + } +} + +customElements.define('demo-root', DemoRoot) diff --git a/examples/lit/basic/src/mutation.ts b/examples/lit/basic/src/mutation.ts new file mode 100644 index 00000000000..01b0b39dd49 --- /dev/null +++ b/examples/lit/basic/src/mutation.ts @@ -0,0 +1,144 @@ +import { html, LitElement } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { + QueryClientProvider, + createMutationController, + createQueryController, +} from '@tanstack/lit-query' +import { + addTodoOnServer, + fetchTodosFromServer, + resetTodoApi, + type Todo, + type TodosResponse, +} from './todoApi' + +resetTodoApi() + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, +}) + +class MutationExampleProvider extends QueryClientProvider { + constructor() { + super() + this.client = queryClient + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +customElements.define('mutation-example-provider', MutationExampleProvider) + +class MutationExample extends LitElement { + static properties = { + nextTitle: { state: true }, + } + + private nextTitle = 'Created from mutation example' + + private readonly todos = createQueryController(this, { + queryKey: ['todos'], + queryFn: fetchTodosFromServer, + }) + + private readonly addTodo = createMutationController( + this, + { + mutationKey: ['create-todo'], + mutationFn: addTodoOnServer, + onSuccess: (created) => { + queryClient.setQueryData(['todos'], (existing) => { + if (!existing) { + return { + items: [created], + requestCount: 0, + source: 'cache', + } + } + + return { + items: [...existing.items, created], + requestCount: existing.requestCount, + source: 'cache', + } + }) + }, + }, + ) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + private onInput(event: Event): void { + const target = event.target as HTMLInputElement + this.nextTitle = target.value + } + + private submit(): void { + const title = this.nextTitle.trim() + if (!title) return + this.addTodo.mutate(title) + this.nextTitle = '' + } + + render() { + const query = this.todos() + const mutation = this.addTodo() + const items = query.data?.items ?? [] + + return html` +
+

Mutation Example

+

+ Query: ${query.status} +

+

+ Mutation: ${mutation.status} +

+ + + + + +
    + ${items.map( + (todo) => + html`
  • ${todo.title}
  • `, + )} +
+
+ ` + } +} + +customElements.define('mutation-example', MutationExample) + +class MutationExampleRoot extends LitElement { + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + return html` + + + + ` + } +} + +customElements.define('mutation-example-root', MutationExampleRoot) diff --git a/examples/lit/basic/src/todoApi.ts b/examples/lit/basic/src/todoApi.ts new file mode 100644 index 00000000000..6ea4aa4dac1 --- /dev/null +++ b/examples/lit/basic/src/todoApi.ts @@ -0,0 +1,77 @@ +export type Todo = { + id: number + title: string +} + +export type TodosResponse = { + items: Todo[] + requestCount: number + source: 'server' | 'cache' +} + +let todos: Todo[] = [ + { id: 1, title: 'Ship lit-query alpha' }, + { id: 2, title: 'Write integration checks' }, +] + +let requestCount = 0 +let nextTodoId = 3 +let failNextFetch = false +let failNextMutation = false + +const delay = (ms: number): Promise => + new Promise((resolve) => { + setTimeout(resolve, ms) + }) + +export async function fetchTodosFromServer(): Promise { + await delay(90) + if (failNextFetch) { + failNextFetch = false + throw new Error('Forced fetch failure (test)') + } + requestCount += 1 + + return { + items: todos.map((todo) => ({ ...todo })), + requestCount, + source: 'server', + } +} + +export async function addTodoOnServer(title: string): Promise { + await delay(70) + if (failNextMutation) { + failNextMutation = false + throw new Error('Forced mutation failure (test)') + } + + const nextTodo: Todo = { + id: nextTodoId, + title, + } + + nextTodoId += 1 + todos = [...todos, nextTodo] + + return { ...nextTodo } +} + +export function resetTodoApi(): void { + todos = [ + { id: 1, title: 'Ship lit-query alpha' }, + { id: 2, title: 'Write integration checks' }, + ] + requestCount = 0 + nextTodoId = 3 + failNextFetch = false + failNextMutation = false +} + +export function failNextFetchRequest(): void { + failNextFetch = true +} + +export function failNextMutationRequest(): void { + failNextMutation = true +} diff --git a/examples/lit/basic/tsconfig.json b/examples/lit/basic/tsconfig.json new file mode 100644 index 00000000000..d6e16f3ca50 --- /dev/null +++ b/examples/lit/basic/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "useDefineForClassFields": false + }, + "include": ["src/**/*.ts", "vite.config.ts"] +} diff --git a/examples/lit/basic/vite.config.ts b/examples/lit/basic/vite.config.ts new file mode 100644 index 00000000000..1d3028a8fb5 --- /dev/null +++ b/examples/lit/basic/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import { DEMO_PORT } from './config/port.js' + +export default defineConfig({ + server: { + host: '127.0.0.1', + port: DEMO_PORT, + strictPort: true, + }, + preview: { + host: '127.0.0.1', + port: DEMO_PORT, + strictPort: true, + }, +}) diff --git a/examples/lit/pagination/README.md b/examples/lit/pagination/README.md new file mode 100644 index 00000000000..695072f25d0 --- /dev/null +++ b/examples/lit/pagination/README.md @@ -0,0 +1,5 @@ +# Example + +To run this example from the repo root: + +- `pnpm --dir examples/lit/pagination run dev` diff --git a/examples/lit/pagination/config/ports.d.ts b/examples/lit/pagination/config/ports.d.ts new file mode 100644 index 00000000000..740d57d9863 --- /dev/null +++ b/examples/lit/pagination/config/ports.d.ts @@ -0,0 +1,2 @@ +export const DEMO_PORT: number +export const API_PORT: number diff --git a/examples/lit/pagination/config/ports.js b/examples/lit/pagination/config/ports.js new file mode 100644 index 00000000000..c4afc201ce2 --- /dev/null +++ b/examples/lit/pagination/config/ports.js @@ -0,0 +1,26 @@ +const DEFAULT_DEMO_PORT = 4183 +const DEFAULT_API_PORT = 4184 + +function readPortFromEnv(name, fallback) { + const rawValue = process.env[name] + if (!rawValue) { + return fallback + } + + const parsed = Number.parseInt(rawValue, 10) + const valid = Number.isInteger(parsed) && parsed >= 1 && parsed <= 65_535 + + if (!valid) { + throw new Error( + `Invalid ${name}="${rawValue}". Expected integer in [1, 65535].`, + ) + } + + return parsed +} + +export const DEMO_PORT = readPortFromEnv( + 'PAGINATION_DEMO_PORT', + DEFAULT_DEMO_PORT, +) +export const API_PORT = readPortFromEnv('PAGINATION_API_PORT', DEFAULT_API_PORT) diff --git a/examples/lit/pagination/index.html b/examples/lit/pagination/index.html new file mode 100644 index 00000000000..5d3fe8e8121 --- /dev/null +++ b/examples/lit/pagination/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Lit Query Pagination Demo + + + + + + diff --git a/examples/lit/pagination/package.json b/examples/lit/pagination/package.json new file mode 100644 index 00000000000..7e4bf0c01d8 --- /dev/null +++ b/examples/lit/pagination/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/query-example-lit-pagination", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "node ./scripts/dev.mjs", + "build": "tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/lit-query": "^0.1.0", + "@tanstack/query-core": "^5.99.0", + "lit": "^3.3.1" + }, + "devDependencies": { + "typescript": "5.8.3", + "vite": "^6.4.1" + } +} diff --git a/examples/lit/pagination/scripts/dev.mjs b/examples/lit/pagination/scripts/dev.mjs new file mode 100644 index 00000000000..2a58a8da0fd --- /dev/null +++ b/examples/lit/pagination/scripts/dev.mjs @@ -0,0 +1,80 @@ +import { spawn } from 'node:child_process' +import { once } from 'node:events' +import { API_PORT } from '../config/ports.js' + +const viteCommand = process.platform === 'win32' ? 'vite.cmd' : 'vite' +const cwd = new URL('..', import.meta.url) + +function forwardOutput(prefix, stream, output) { + stream.on('data', (chunk) => { + output.write(`${prefix}${chunk}`) + }) +} + +function start(name, command, args, extraEnv = {}) { + const child = spawn(command, args, { + cwd, + stdio: ['ignore', 'pipe', 'pipe'], + env: { + ...process.env, + ...extraEnv, + }, + }) + + forwardOutput(`[${name}] `, child.stdout, process.stdout) + forwardOutput(`[${name}] `, child.stderr, process.stderr) + + return child +} + +async function stop(child) { + if (!child || child.exitCode !== null) { + return + } + + child.kill('SIGTERM') + await Promise.race([ + once(child, 'exit'), + new Promise((resolve) => setTimeout(resolve, 2000)), + ]) + + if (child.exitCode === null) { + child.kill('SIGKILL') + await Promise.race([ + once(child, 'exit'), + new Promise((resolve) => setTimeout(resolve, 2000)), + ]) + } +} + +async function run() { + const api = start('api', process.execPath, ['./server/index.mjs']) + const web = start('web', viteCommand, [], { + VITE_PAGINATION_API_PORT: String(API_PORT), + }) + + const shutdown = async () => { + await Promise.all([stop(web), stop(api)]) + } + + process.on('SIGINT', shutdown) + process.on('SIGTERM', shutdown) + + const [winner] = await Promise.race([ + once(api, 'exit').then(([code]) => ({ name: 'api', code })), + once(web, 'exit').then(([code]) => ({ name: 'web', code })), + ]) + + await shutdown() + + if (winner.code !== 0 && winner.code !== null) { + process.exitCode = winner.code + } else { + process.exitCode = 1 + } +} + +run().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/examples/lit/pagination/server/index.mjs b/examples/lit/pagination/server/index.mjs new file mode 100644 index 00000000000..b9722992326 --- /dev/null +++ b/examples/lit/pagination/server/index.mjs @@ -0,0 +1,340 @@ +import { createServer } from 'node:http' +import { API_PORT } from '../config/ports.js' + +const PAGE_SIZE = 10 +const TOTAL_PROJECTS = 50 +const JSON_CONTENT_TYPE = 'application/json' + +function createSeedProjects() { + return Array.from({ length: TOTAL_PROJECTS }, (_, index) => ({ + id: index + 1, + name: `Project ${index + 1}`, + owner: `Team ${(index % 5) + 1}`, + isFavorite: false, + })) +} + +let projects = createSeedProjects() +let nextProjectId = projects.length + 1 +let totalRequestCount = 0 +let totalMutationCount = 0 +let failNextMutation = false +const perPageRequestCount = new Map() + +function writeJson(res, status, body) { + const payload = JSON.stringify(body) + res.writeHead(status, { + 'content-type': 'application/json; charset=utf-8', + 'cache-control': 'no-store', + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET,POST,PATCH,OPTIONS', + 'access-control-allow-headers': 'content-type', + 'content-length': Buffer.byteLength(payload), + }) + res.end(payload) +} + +function parsePositiveInt(rawValue, fallback) { + if (rawValue == null || rawValue === '') { + return fallback + } + + const parsed = Number.parseInt(rawValue, 10) + if (!Number.isInteger(parsed) || parsed < 1) { + return undefined + } + + return parsed +} + +function parseNonNegativeInt(rawValue, fallback) { + if (rawValue == null || rawValue === '') { + return fallback + } + + const parsed = Number.parseInt(rawValue, 10) + if (!Number.isInteger(parsed) || parsed < 0) { + return undefined + } + + return parsed +} + +function resetState() { + projects = createSeedProjects() + nextProjectId = projects.length + 1 + totalRequestCount = 0 + totalMutationCount = 0 + failNextMutation = false + perPageRequestCount.clear() +} + +async function sleep(ms) { + if (!ms || ms <= 0) { + return + } + + await new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + +async function readJsonBody(req) { + const contentType = req.headers['content-type'] + if (!contentType || !contentType.startsWith(JSON_CONTENT_TYPE)) { + return { + ok: false, + status: 415, + error: 'Expected application/json request body', + } + } + + const chunks = [] + for await (const chunk of req) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) + } + + const raw = Buffer.concat(chunks).toString('utf8') + if (!raw) { + return { + ok: false, + status: 400, + error: 'Request body is required', + } + } + + try { + return { + ok: true, + value: JSON.parse(raw), + } + } catch { + return { + ok: false, + status: 400, + error: 'Request body must be valid JSON', + } + } +} + +function buildProjectsPage(page) { + const totalPages = Math.max(1, Math.ceil(projects.length / PAGE_SIZE)) + const boundedPage = Math.min(page, totalPages) + const startIndex = (boundedPage - 1) * PAGE_SIZE + const endIndex = startIndex + PAGE_SIZE + const items = projects.slice(startIndex, endIndex) + + totalRequestCount += 1 + const pageRequests = (perPageRequestCount.get(boundedPage) ?? 0) + 1 + perPageRequestCount.set(boundedPage, pageRequests) + + return { + page: boundedPage, + pageSize: PAGE_SIZE, + totalPages, + totalProjects: projects.length, + hasMore: boundedPage < totalPages, + projects: items, + requestMeta: { + totalRequestCount, + pageRequestCount: pageRequests, + totalMutationCount, + }, + } +} + +function maybeFailMutation(res) { + if (!failNextMutation) { + return false + } + + failNextMutation = false + writeJson(res, 500, { error: 'Forced mutation failure (test)' }) + return true +} + +function validateProjectName(name) { + if (typeof name !== 'string') { + return 'Project name must be a string' + } + + const trimmed = name.trim() + if (trimmed.length < 3) { + return 'Project name must be at least 3 characters' + } + + if (trimmed.length > 60) { + return 'Project name must be 60 characters or fewer' + } + + return null +} + +function validateOwner(owner) { + if (typeof owner !== 'string') { + return 'Owner must be a string' + } + + const trimmed = owner.trim() + if (!trimmed) { + return 'Owner is required' + } + + if (trimmed.length > 40) { + return 'Owner must be 40 characters or fewer' + } + + return null +} + +const server = createServer(async (req, res) => { + if (req.method === 'OPTIONS') { + res.writeHead(204, { + 'access-control-allow-origin': '*', + 'access-control-allow-methods': 'GET,POST,PATCH,OPTIONS', + 'access-control-allow-headers': 'content-type', + 'cache-control': 'no-store', + }) + res.end() + return + } + + if (!req.url) { + writeJson(res, 400, { error: 'Missing URL' }) + return + } + + const requestUrl = new URL(req.url, `http://127.0.0.1:${API_PORT}`) + + if (requestUrl.pathname === '/api/projects' && req.method === 'GET') { + const page = parsePositiveInt(requestUrl.searchParams.get('page'), 1) + if (!page) { + writeJson(res, 400, { error: 'Invalid page parameter' }) + return + } + + const delayMs = parseNonNegativeInt(requestUrl.searchParams.get('delay'), 0) + if (delayMs === undefined) { + writeJson(res, 400, { error: 'Invalid delay parameter' }) + return + } + + await sleep(delayMs) + + if (requestUrl.searchParams.get('error') === 'true') { + writeJson(res, 500, { + error: 'Forced server error (test)', + page, + }) + return + } + + writeJson(res, 200, buildProjectsPage(page)) + return + } + + if (requestUrl.pathname === '/api/projects' && req.method === 'POST') { + if (maybeFailMutation(res)) { + return + } + + const payload = await readJsonBody(req) + if (!payload.ok) { + writeJson(res, payload.status, { error: payload.error }) + return + } + + const nameError = validateProjectName(payload.value?.name) + if (nameError) { + writeJson(res, 422, { error: nameError }) + return + } + + const ownerError = validateOwner(payload.value?.owner) + if (ownerError) { + writeJson(res, 422, { error: ownerError }) + return + } + + const project = { + id: nextProjectId, + name: payload.value.name.trim(), + owner: payload.value.owner.trim(), + isFavorite: false, + } + + nextProjectId += 1 + totalMutationCount += 1 + projects = [project, ...projects] + + writeJson(res, 201, { + project, + mutationCount: totalMutationCount, + }) + return + } + + const patchMatch = requestUrl.pathname.match(/^\/api\/projects\/(\d+)$/) + if (patchMatch && req.method === 'PATCH') { + if (maybeFailMutation(res)) { + return + } + + const projectId = Number.parseInt(patchMatch[1], 10) + const projectIndex = projects.findIndex( + (project) => project.id === projectId, + ) + if (projectIndex === -1) { + writeJson(res, 404, { error: `Project ${projectId} was not found` }) + return + } + + const payload = await readJsonBody(req) + if (!payload.ok) { + writeJson(res, payload.status, { error: payload.error }) + return + } + + if (typeof payload.value?.isFavorite !== 'boolean') { + writeJson(res, 422, { error: 'isFavorite must be a boolean' }) + return + } + + const nextProject = { + ...projects[projectIndex], + isFavorite: payload.value.isFavorite, + } + + totalMutationCount += 1 + projects = projects.map((project, index) => + index === projectIndex ? nextProject : project, + ) + + writeJson(res, 200, { + project: nextProject, + mutationCount: totalMutationCount, + }) + return + } + + if ( + requestUrl.pathname === '/api/testing/fail-next-mutation' && + req.method === 'POST' + ) { + failNextMutation = true + writeJson(res, 200, { ok: true }) + return + } + + if (requestUrl.pathname === '/api/reset' && req.method === 'POST') { + resetState() + writeJson(res, 200, { ok: true }) + return + } + + writeJson(res, 404, { error: 'Not found' }) +}) + +server.listen(API_PORT, '127.0.0.1', () => { + console.log(`[api] listening on http://127.0.0.1:${API_PORT}`) +}) diff --git a/examples/lit/pagination/src/api.ts b/examples/lit/pagination/src/api.ts new file mode 100644 index 00000000000..8f4e1cd2091 --- /dev/null +++ b/examples/lit/pagination/src/api.ts @@ -0,0 +1,166 @@ +export type Project = { + id: number + name: string + owner: string + isFavorite: boolean +} + +export type ProjectsPageResponse = { + page: number + pageSize: number + totalPages: number + totalProjects: number + hasMore: boolean + projects: Project[] + requestMeta: { + totalRequestCount: number + pageRequestCount: number + totalMutationCount: number + } +} + +export type CreateProjectInput = { + name: string + owner: string +} + +export type ToggleProjectFavoriteInput = { + id: number + isFavorite: boolean +} + +export type ProjectMutationResponse = { + project: Project + mutationCount: number +} + +export type ProjectsQueryKey = readonly ['projects', number, number, boolean] + +const DEFAULT_API_PORT = 4184 +const configuredApiPort = Number.parseInt( + import.meta.env.VITE_PAGINATION_API_PORT ?? String(DEFAULT_API_PORT), + 10, +) +const API_PORT = Number.isInteger(configuredApiPort) + ? configuredApiPort + : DEFAULT_API_PORT +const API_BASE_URL = `http://127.0.0.1:${API_PORT}` + +function buildProjectsUrl( + page: number, + delayMs: number, + forceError: boolean, +): URL { + const url = new URL('/api/projects', API_BASE_URL) + url.searchParams.set('page', String(page)) + + if (delayMs > 0) { + url.searchParams.set('delay', String(delayMs)) + } + + if (forceError) { + url.searchParams.set('error', 'true') + } + + return url +} + +async function readJsonOrThrow( + response: Response, + fallbackMessage: string, +): Promise { + if (response.ok) { + return (await response.json()) as T + } + + const payload = (await response.json().catch(() => null)) as { + error?: string + } | null + + throw new Error( + payload && typeof payload === 'object' && 'error' in payload + ? String(payload.error ?? fallbackMessage) + : fallbackMessage, + ) +} + +async function requestJson( + url: URL, + init: RequestInit, + fallbackMessage: string, +): Promise { + const response = await fetch(url, init) + return readJsonOrThrow(response, fallbackMessage) +} + +export function projectsQueryKey( + page: number, + delayMs: number, + forceError: boolean, +): ProjectsQueryKey { + return ['projects', page, delayMs, forceError] as const +} + +export async function fetchProjectsPage( + page: number, + delayMs: number, + forceError: boolean, +): Promise { + const response = await fetch(buildProjectsUrl(page, delayMs, forceError)) + return readJsonOrThrow( + response, + `Failed to fetch projects page ${page}`, + ) +} + +export async function createProjectOnServer( + input: CreateProjectInput, +): Promise { + return requestJson( + new URL('/api/projects', API_BASE_URL), + { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(input), + }, + 'Failed to create project', + ) +} + +export async function toggleProjectFavoriteOnServer( + input: ToggleProjectFavoriteInput, +): Promise { + return requestJson( + new URL(`/api/projects/${input.id}`, API_BASE_URL), + { + method: 'PATCH', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ isFavorite: input.isFavorite }), + }, + `Failed to update project ${input.id}`, + ) +} + +export async function armNextProjectMutationFailureOnServer(): Promise { + await requestJson<{ ok: true }>( + new URL('/api/testing/fail-next-mutation', API_BASE_URL), + { + method: 'POST', + }, + 'Failed to arm next mutation failure', + ) +} + +export async function resetProjectsApiState(): Promise { + await requestJson<{ ok: true }>( + new URL('/api/reset', API_BASE_URL), + { + method: 'POST', + }, + 'Failed to reset API state', + ) +} diff --git a/examples/lit/pagination/src/main.ts b/examples/lit/pagination/src/main.ts new file mode 100644 index 00000000000..3e7f7f4bcbf --- /dev/null +++ b/examples/lit/pagination/src/main.ts @@ -0,0 +1,583 @@ +import { html, LitElement } from 'lit' +import { + keepPreviousData, + QueryClient, + type QueryKey, +} from '@tanstack/query-core' +import { + type CreateQueryOptions, + type MutationResultAccessor, + QueryClientProvider, + createMutationController, + createQueryController, + type QueryResultAccessor, +} from '@tanstack/lit-query' +import { + armNextProjectMutationFailureOnServer, + createProjectOnServer, + fetchProjectsPage, + projectsQueryKey, + resetProjectsApiState, + toggleProjectFavoriteOnServer, + type CreateProjectInput, + type Project, + type ProjectsPageResponse, + type ToggleProjectFavoriteInput, +} from './api' + +type ProjectsCacheSnapshot = Array<[QueryKey, ProjectsPageResponse | undefined]> +type FavoriteMutationContext = { + snapshots: ProjectsCacheSnapshot +} + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: 5_000, + }, + mutations: { + retry: false, + }, + }, +}) + +class PaginationQueryProvider extends QueryClientProvider { + constructor() { + super() + this.client = queryClient + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +customElements.define('pagination-query-provider', PaginationQueryProvider) + +class PaginationDemo extends LitElement { + static properties = { + page: { state: true }, + delayMs: { state: true }, + forceErrorMode: { state: true }, + prefetchStatus: { state: true }, + resetError: { state: true }, + draftName: { state: true }, + draftOwner: { state: true }, + mutationControlStatus: { state: true }, + mutationControlError: { state: true }, + } + + private page = 1 + private delayMs = 250 + private forceErrorMode = false + private prefetchStatus = 'idle' + private resetError: string | undefined + private draftName = 'Platform Rollout' + private draftOwner = 'Team 6' + private mutationControlStatus = 'idle' + private mutationControlError: string | undefined + private lastAutoPrefetchPage = 0 + private readonly projectsQueryOptions: CreateQueryOptions< + ProjectsPageResponse, + Error + > + private readonly projectsQuery: QueryResultAccessor< + ProjectsPageResponse, + Error + > + private readonly createProjectMutation: MutationResultAccessor< + Project, + Error, + CreateProjectInput, + unknown + > + private readonly favoriteMutation: MutationResultAccessor< + Project, + Error, + ToggleProjectFavoriteInput, + FavoriteMutationContext + > + + constructor() { + super() + + this.projectsQueryOptions = { + queryKey: projectsQueryKey(this.page, this.delayMs, this.forceErrorMode), + queryFn: () => + fetchProjectsPage(this.page, this.delayMs, this.forceErrorMode), + placeholderData: keepPreviousData, + } + + this.projectsQuery = createQueryController( + this, + this.projectsQueryOptions, + ) + + this.createProjectMutation = createMutationController< + Project, + Error, + CreateProjectInput + >( + this, + { + mutationKey: ['create-project'], + mutationFn: async (input) => { + const response = await createProjectOnServer(input) + return response.project + }, + onMutate: () => { + this.mutationControlStatus = 'idle' + this.mutationControlError = undefined + }, + onSuccess: async () => { + this.page = 1 + this.lastAutoPrefetchPage = 0 + this.prefetchStatus = 'idle' + this.draftName = '' + this.draftOwner = 'Team 6' + this.syncProjectsQueryOptions() + await queryClient.invalidateQueries({ + queryKey: ['projects'], + refetchType: 'none', + }) + await this.projectsQuery.refetch() + }, + }, + queryClient, + ) + + this.favoriteMutation = createMutationController< + Project, + Error, + ToggleProjectFavoriteInput, + FavoriteMutationContext + >( + this, + { + mutationKey: ['toggle-project-favorite'], + mutationFn: async (input) => { + const response = await toggleProjectFavoriteOnServer(input) + return response.project + }, + onMutate: async (variables) => { + this.mutationControlStatus = 'idle' + this.mutationControlError = undefined + await queryClient.cancelQueries({ queryKey: ['projects'] }) + + const snapshots = queryClient.getQueriesData({ + queryKey: ['projects'], + }) + + for (const [key, existing] of snapshots) { + if (!existing) { + continue + } + + queryClient.setQueryData(key, { + ...existing, + projects: existing.projects.map((project) => + project.id === variables.id + ? { ...project, isFavorite: variables.isFavorite } + : project, + ), + }) + } + + return { snapshots } + }, + onError: (_error, _variables, context) => { + for (const [key, snapshot] of context?.snapshots ?? []) { + queryClient.setQueryData(key, snapshot) + } + }, + onSettled: async () => { + await queryClient.invalidateQueries({ queryKey: ['projects'] }) + }, + }, + queryClient, + ) + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + override updated(): void { + this.maybePrefetchNextPage() + } + + private syncProjectsQueryOptions(): void { + this.projectsQueryOptions.queryKey = projectsQueryKey( + this.page, + this.delayMs, + this.forceErrorMode, + ) + this.projectsQueryOptions.queryFn = () => + fetchProjectsPage(this.page, this.delayMs, this.forceErrorMode) + } + + private refetchForCurrentState(): void { + this.syncProjectsQueryOptions() + void this.projectsQuery.refetch() + } + + private async maybePrefetchNextPage(): Promise { + const query = this.projectsQuery() + const currentData = query.data + + if (!currentData || query.isPlaceholderData || !currentData.hasMore) { + return + } + + if (this.lastAutoPrefetchPage === currentData.page) { + return + } + + this.lastAutoPrefetchPage = currentData.page + await this.prefetchPage(currentData.page + 1) + } + + private onDelayInput(event: Event): void { + const target = event.target as HTMLInputElement + const nextValue = Number.parseInt(target.value, 10) + + if (!Number.isInteger(nextValue) || nextValue < 0) { + return + } + + this.delayMs = nextValue + this.refetchForCurrentState() + } + + private onErrorModeToggle(event: Event): void { + const target = event.target as HTMLInputElement + this.forceErrorMode = target.checked + this.refetchForCurrentState() + } + + private onDraftNameInput(event: Event): void { + const target = event.target as HTMLInputElement + this.draftName = target.value + } + + private onDraftOwnerInput(event: Event): void { + const target = event.target as HTMLInputElement + this.draftOwner = target.value + } + + private async prefetchPage(targetPage: number): Promise { + this.prefetchStatus = `pending:${targetPage}` + + try { + await queryClient.prefetchQuery({ + queryKey: projectsQueryKey( + targetPage, + this.delayMs, + this.forceErrorMode, + ), + queryFn: () => + fetchProjectsPage(targetPage, this.delayMs, this.forceErrorMode), + }) + this.prefetchStatus = `ready:${targetPage}` + } catch (error) { + this.prefetchStatus = `error:${String(error)}` + } + } + + private async prefetchNext(): Promise { + const query = this.projectsQuery() + const currentData = query.data + + if (!currentData?.hasMore) { + this.prefetchStatus = 'skipped:no-next-page' + return + } + + await this.prefetchPage(currentData.page + 1) + } + + private goToPreviousPage(): void { + if (this.page > 1) { + this.page -= 1 + this.refetchForCurrentState() + } + } + + private goToNextPage(): void { + const currentData = this.projectsQuery().data + if (!currentData?.hasMore) { + return + } + + this.page += 1 + this.refetchForCurrentState() + } + + private async resetDemoState(): Promise { + this.resetError = undefined + + try { + await resetProjectsApiState() + this.page = 1 + this.delayMs = 250 + this.forceErrorMode = false + this.prefetchStatus = 'idle' + this.resetError = undefined + this.draftName = 'Platform Rollout' + this.draftOwner = 'Team 6' + this.mutationControlStatus = 'idle' + this.mutationControlError = undefined + this.lastAutoPrefetchPage = 0 + this.syncProjectsQueryOptions() + this.createProjectMutation.reset() + this.favoriteMutation.reset() + await queryClient.resetQueries({ queryKey: ['projects'] }) + await this.projectsQuery.refetch() + } catch (error) { + this.resetError = String(error) + } + } + + private submitCreateProject(): void { + const name = this.draftName.trim() + const owner = this.draftOwner.trim() + + if (!name || !owner) { + return + } + + this.createProjectMutation.mutate({ name, owner }) + } + + private toggleFavorite(project: Project): void { + this.favoriteMutation.mutate({ + id: project.id, + isFavorite: !project.isFavorite, + }) + } + + private async armNextMutationFailure(): Promise { + this.mutationControlError = undefined + + try { + await armNextProjectMutationFailureOnServer() + this.mutationControlStatus = 'armed' + } catch (error) { + this.mutationControlStatus = 'error' + this.mutationControlError = String(error) + } + } + + render() { + const query = this.projectsQuery() + const projects = query.data?.projects ?? [] + const hasMore = query.data?.hasMore ?? false + const createProject = this.createProjectMutation() + const favoriteProject = this.favoriteMutation() + + return html` +
+

TanStack Lit Query Pagination Demo

+

+ Pagination + mutation demo with optimistic favorite toggles, + invalidation, and deterministic server failures. +

+ +
+
query: ${query.status}
+
+ isFetching: ${query.isFetching ? 'yes' : 'no'} +
+
+ isPlaceholderData: ${query.isPlaceholderData ? 'yes' : 'no'} +
+
page: ${this.page}
+
+ response-page: ${query.data?.page ?? '-'} +
+
has-more: ${hasMore ? 'yes' : 'no'}
+
+ total-projects: ${query.data?.totalProjects ?? 0} +
+
+ total-requests: ${query.data?.requestMeta.totalRequestCount ?? 0} +
+
+ page-requests: ${query.data?.requestMeta.pageRequestCount ?? 0} +
+
+ total-mutations: ${query.data?.requestMeta.totalMutationCount ?? 0} +
+
+ prefetch: ${this.prefetchStatus} +
+
+ + ${query.isError + ? html`

${String(query.error)}

` + : null} + ${this.resetError + ? html`

${this.resetError}

` + : null} + +
+ + + + + + + + + + +
+ mutation-control: ${this.mutationControlStatus} +
+ ${this.mutationControlError + ? html`
+ ${this.mutationControlError} +
` + : null} +
+ +
+
+ create-mutation: ${createProject.status} +
+ ${createProject.isError + ? html`
+ ${String(createProject.error)} +
` + : null} + + + + + + + + +
+ +
+
+ favorite-mutation: ${favoriteProject.status} +
+ ${favoriteProject.isError + ? html`
+ ${String(favoriteProject.error)} +
` + : null} +
+ +
+ + +
+ +
    + ${projects.map( + (project) => html` +
  • + ${project.id}: ${project.name} (${project.owner}) + ${project.isFavorite ? 'favorite' : 'standard'} + +
  • + `, + )} +
+
+ ` + } +} + +customElements.define('pagination-demo', PaginationDemo) + +class PaginationDemoRoot extends LitElement { + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + return html` + + + + ` + } +} + +customElements.define('pagination-demo-root', PaginationDemoRoot) diff --git a/examples/lit/pagination/src/vite-env.d.ts b/examples/lit/pagination/src/vite-env.d.ts new file mode 100644 index 00000000000..3d865d7345e --- /dev/null +++ b/examples/lit/pagination/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_PAGINATION_API_PORT?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/examples/lit/pagination/tsconfig.json b/examples/lit/pagination/tsconfig.json new file mode 100644 index 00000000000..d6e16f3ca50 --- /dev/null +++ b/examples/lit/pagination/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "useDefineForClassFields": false + }, + "include": ["src/**/*.ts", "vite.config.ts"] +} diff --git a/examples/lit/pagination/vite.config.ts b/examples/lit/pagination/vite.config.ts new file mode 100644 index 00000000000..371006b81cf --- /dev/null +++ b/examples/lit/pagination/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import { DEMO_PORT } from './config/ports.js' + +export default defineConfig({ + server: { + host: '127.0.0.1', + port: DEMO_PORT, + strictPort: true, + }, + preview: { + host: '127.0.0.1', + port: DEMO_PORT, + strictPort: true, + }, +}) diff --git a/examples/lit/ssr/README.md b/examples/lit/ssr/README.md new file mode 100644 index 00000000000..3c538d78f13 --- /dev/null +++ b/examples/lit/ssr/README.md @@ -0,0 +1,5 @@ +# Example + +To run this example from the repo root: + +- `pnpm --dir examples/lit/ssr run dev` diff --git a/examples/lit/ssr/config/ports.d.ts b/examples/lit/ssr/config/ports.d.ts new file mode 100644 index 00000000000..23d3eea7080 --- /dev/null +++ b/examples/lit/ssr/config/ports.d.ts @@ -0,0 +1,5 @@ +export const SSR_HOST: string +export const SSR_PORT: number +export const SSR_CONNECT_HOST: string +export const SSR_BASE_URL: string +export const SSR_PUBLIC_ORIGIN: string diff --git a/examples/lit/ssr/config/ports.js b/examples/lit/ssr/config/ports.js new file mode 100644 index 00000000000..a2872fdd9e4 --- /dev/null +++ b/examples/lit/ssr/config/ports.js @@ -0,0 +1,63 @@ +const DEFAULT_SSR_HOST = '127.0.0.1' +const DEFAULT_SSR_PORT = 4174 + +function normalizeUrlHost(host) { + if (host.includes(':') && !host.startsWith('[') && !host.endsWith(']')) { + return `[${host}]` + } + + return host +} + +function resolvePort(name, fallback) { + const value = process.env[name] + if (!value) { + return fallback + } + + const parsedPort = Number.parseInt(value, 10) + const isValidPort = + Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65_535 + + if (!isValidPort) { + throw new Error( + `Invalid ${name} "${value}". Expected an integer between 1 and 65535.`, + ) + } + + return parsedPort +} + +export const SSR_PORT = resolvePort('SSR_PORT', DEFAULT_SSR_PORT) +export const SSR_HOST = process.env.SSR_HOST ?? DEFAULT_SSR_HOST + +function resolveConnectHost(host) { + if (host === '0.0.0.0') { + return '127.0.0.1' + } + + if (host === '::') { + return '[::1]' + } + + return normalizeUrlHost(host) +} + +function resolvePublicOrigin(host, port) { + const explicitOrigin = process.env.SSR_PUBLIC_ORIGIN + if (explicitOrigin) { + const url = new URL(explicitOrigin) + return url.origin + } + + const explicitPublicHost = process.env.SSR_PUBLIC_HOST + const publicHost = explicitPublicHost + ? normalizeUrlHost(explicitPublicHost) + : resolveConnectHost(host) + + return `http://${publicHost}:${port}` +} + +export const SSR_CONNECT_HOST = resolveConnectHost(SSR_HOST) +export const SSR_BASE_URL = `http://${SSR_CONNECT_HOST}:${SSR_PORT}` +export const SSR_PUBLIC_ORIGIN = resolvePublicOrigin(SSR_HOST, SSR_PORT) diff --git a/examples/lit/ssr/index.html b/examples/lit/ssr/index.html new file mode 100644 index 00000000000..7632b49fba0 --- /dev/null +++ b/examples/lit/ssr/index.html @@ -0,0 +1,15 @@ + + + + + + Lit Query SSR Example + + + __SSR_APP_HTML__ + + + + diff --git a/examples/lit/ssr/package.json b/examples/lit/ssr/package.json new file mode 100644 index 00000000000..6b2928b5850 --- /dev/null +++ b/examples/lit/ssr/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/query-example-lit-ssr", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "node ./scripts/dev.mjs", + "build": "tsc --noEmit && vite build" + }, + "dependencies": { + "@lit-labs/ssr": "^3.3.0", + "@tanstack/lit-query": "^0.1.0", + "@tanstack/query-core": "^5.99.0", + "lit": "^3.3.1" + }, + "devDependencies": { + "@lit-labs/ssr-client": "^1.1.7", + "tsx": "^4.19.0", + "typescript": "5.8.3", + "vite": "^6.4.1" + } +} diff --git a/examples/lit/ssr/scripts/dev.mjs b/examples/lit/ssr/scripts/dev.mjs new file mode 100644 index 00000000000..0280720b806 --- /dev/null +++ b/examples/lit/ssr/scripts/dev.mjs @@ -0,0 +1,51 @@ +import { spawn, spawnSync } from 'node:child_process' +import { once } from 'node:events' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm' +const tsxCommand = process.platform === 'win32' ? 'tsx.cmd' : 'tsx' +const cwd = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +function runBuild() { + const result = spawnSync(npmCommand, ['run', 'build'], { + cwd, + stdio: 'inherit', + }) + + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +async function run() { + runBuild() + + const server = spawn(tsxCommand, ['./server/index.mjs'], { + cwd, + stdio: 'inherit', + }) + + const stopServer = (signal) => { + if (server.exitCode === null) { + server.kill(signal) + } + } + + process.on('SIGINT', () => stopServer('SIGINT')) + process.on('SIGTERM', () => stopServer('SIGTERM')) + + const outcome = await Promise.race([ + once(server, 'error').then(([error]) => { + throw error + }), + once(server, 'exit').then(([code]) => ({ code })), + ]) + + process.exitCode = outcome.code ?? 0 +} + +run().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/examples/lit/ssr/server/index.mjs b/examples/lit/ssr/server/index.mjs new file mode 100644 index 00000000000..3c80d93829e --- /dev/null +++ b/examples/lit/ssr/server/index.mjs @@ -0,0 +1,308 @@ +import { createServer } from 'node:http' +import { readFile } from 'node:fs/promises' +import { dirname, extname, resolve, sep } from 'node:path' +import { setTimeout as sleep } from 'node:timers/promises' +import { fileURLToPath } from 'node:url' +import { render } from '@lit-labs/ssr' +import { collectResult } from '@lit-labs/ssr/lib/render-result.js' +import { html } from 'lit' +import { QueryClient, dehydrate } from '@tanstack/lit-query' +import { + SSR_BASE_URL, + SSR_HOST, + SSR_PORT, + SSR_PUBLIC_ORIGIN, +} from '../config/ports.js' +import { + DATA_QUERY_KEY, + DEFAULT_MESSAGE, + QUERY_STALE_TIME, + createDataQueryOptions, +} from '../src/api.js' +import { + getSsrQueryControllerCreationCount, + resetSsrQueryControllerCreationCount, +} from '../src/app.ts' + +const serverDir = dirname(fileURLToPath(import.meta.url)) +const distDir = resolve(serverDir, '../dist') +const templatePath = resolve(distDir, 'index.html') + +const contentTypes = { + '.css': 'text/css; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.svg': 'image/svg+xml; charset=utf-8', +} + +let requestCount = 0 +let failNextDataRequest = false +let nextDataDelayMs = 0 + +const apiCorsHeaders = { + 'access-control-allow-headers': 'content-type', + 'access-control-allow-methods': 'GET,POST,OPTIONS', + 'access-control-allow-origin': '*', +} + +function createBrowserQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: QUERY_STALE_TIME, + }, + }, + }) +} + +function nextDataResponse() { + requestCount += 1 + return { + message: DEFAULT_MESSAGE, + requestCount, + servedAt: new Date().toISOString(), + } +} + +function resetApiState() { + requestCount = 0 + failNextDataRequest = false + nextDataDelayMs = 0 +} + +function parseNonNegativeDelayMs(value) { + const parsedDelayMs = Number.parseInt(value ?? '', 10) + if (!Number.isInteger(parsedDelayMs) || parsedDelayMs < 0) { + return undefined + } + + return parsedDelayMs +} + +async function consumeNextDataDelay() { + const delayMs = nextDataDelayMs + nextDataDelayMs = 0 + + if (delayMs > 0) { + await sleep(delayMs) + } +} + +function consumeFailNextDataRequest() { + if (!failNextDataRequest) { + return false + } + + failNextDataRequest = false + return true +} + +function writeBuffer(res, statusCode, contentType, payload, extraHeaders = {}) { + res.writeHead(statusCode, { + 'cache-control': 'no-store', + 'content-length': payload.byteLength, + 'content-type': contentType, + ...extraHeaders, + }) + res.end(payload) +} + +function writeHtml(res, statusCode, body, extraHeaders = {}) { + const payload = Buffer.from(body) + writeBuffer( + res, + statusCode, + 'text/html; charset=utf-8', + payload, + extraHeaders, + ) +} + +function writeJson(res, statusCode, body) { + const payload = Buffer.from(JSON.stringify(body)) + writeBuffer( + res, + statusCode, + 'application/json; charset=utf-8', + payload, + apiCorsHeaders, + ) +} + +function serializeJsonForHtml(value) { + return JSON.stringify(value).replace(/[<>&\u2028\u2029]/g, (character) => { + switch (character) { + case '<': + return '\\u003c' + case '>': + return '\\u003e' + case '&': + return '\\u0026' + case '\u2028': + return '\\u2028' + case '\u2029': + return '\\u2029' + default: + return character + } + }) +} + +async function readTemplate() { + try { + return await readFile(templatePath, 'utf8') + } catch (error) { + if (error && typeof error === 'object' && 'code' in error) { + throw new Error( + 'Missing built client assets. Run "pnpm --dir examples/lit/ssr run build" from the repo root first.', + ) + } + + throw error + } +} + +async function serveAsset(pathname, res) { + if (!pathname.startsWith('/assets/')) { + return false + } + + const assetPath = resolve(distDir, `.${pathname}`) + const distRootPrefix = `${distDir}${sep}` + if (!assetPath.startsWith(distRootPrefix)) { + return false + } + + try { + const file = await readFile(assetPath) + const contentType = + contentTypes[extname(assetPath)] ?? 'application/octet-stream' + writeBuffer(res, 200, contentType, file) + return true + } catch { + return false + } +} + +async function renderPage(res) { + const queryClient = createBrowserQueryClient() + resetSsrQueryControllerCreationCount(queryClient) + await queryClient.prefetchQuery(createDataQueryOptions(SSR_PUBLIC_ORIGIN)) + + const prefetchedQueryState = queryClient.getQueryState(DATA_QUERY_KEY) + if (prefetchedQueryState?.status !== 'success') { + throw new Error( + 'SSR prefetch did not complete successfully. Refusing to render loading HTML.', + ) + } + + const appHtml = await collectResult( + render( + html``, + ), + ) + + const dehydratedState = dehydrate(queryClient) + const controllerCreationCount = + getSsrQueryControllerCreationCount(queryClient) + if (controllerCreationCount < 1) { + throw new Error('SSR render did not exercise createQueryController.') + } + + const template = await readTemplate() + const htmlDocument = template + .replace('__SSR_APP_HTML__', appHtml) + .replace('__QUERY_STATE_JSON__', serializeJsonForHtml(dehydratedState)) + + writeHtml(res, 200, htmlDocument, { + 'x-ssr-query-controller-created': String(controllerCreationCount), + }) +} + +const server = createServer(async (req, res) => { + const requestUrl = new URL(req.url ?? '/', SSR_BASE_URL) + const method = req.method ?? 'GET' + + if (method === 'OPTIONS' && requestUrl.pathname.startsWith('/api/')) { + res.writeHead(204, { + 'cache-control': 'no-store', + ...apiCorsHeaders, + }) + res.end() + return + } + + if (method === 'GET' && requestUrl.pathname === '/api/data') { + await consumeNextDataDelay() + + if (consumeFailNextDataRequest()) { + writeJson(res, 500, { error: 'Forced data failure (test)' }) + return + } + + writeJson(res, 200, nextDataResponse()) + return + } + + if (method === 'GET' && requestUrl.pathname === '/api/request-count') { + writeJson(res, 200, { count: requestCount }) + return + } + + if (method === 'POST' && requestUrl.pathname === '/api/reset') { + resetApiState() + writeJson(res, 200, { ok: true }) + return + } + + if (method === 'POST' && requestUrl.pathname === '/api/test/fail-next-data') { + failNextDataRequest = true + writeJson(res, 200, { ok: true }) + return + } + + if ( + method === 'POST' && + requestUrl.pathname === '/api/test/delay-next-data' + ) { + const delayMs = parseNonNegativeDelayMs(requestUrl.searchParams.get('ms')) + if (delayMs === undefined) { + writeJson(res, 400, { error: 'Invalid ms query parameter.' }) + return + } + + nextDataDelayMs = delayMs + writeJson(res, 200, { ok: true, delayMs }) + return + } + + if (method === 'GET' && requestUrl.pathname === '/') { + try { + await renderPage(res) + } catch (error) { + console.error('[ssr] render failed:', error) + writeHtml( + res, + 500, + 'SSR render failed.', + ) + } + return + } + + if (method === 'GET' && (await serveAsset(requestUrl.pathname, res))) { + return + } + + writeJson(res, 404, { error: 'Not found' }) +}) + +server.listen(SSR_PORT, SSR_HOST, () => { + console.log( + `[ssr] listening on ${SSR_BASE_URL} (public origin ${SSR_PUBLIC_ORIGIN})`, + ) +}) diff --git a/examples/lit/ssr/src/api.ts b/examples/lit/ssr/src/api.ts new file mode 100644 index 00000000000..eb9656de655 --- /dev/null +++ b/examples/lit/ssr/src/api.ts @@ -0,0 +1,69 @@ +import type { CreateQueryOptions } from '@tanstack/lit-query' + +export const DATA_QUERY_KEY = ['ssr-example-data'] as const +export const DEFAULT_MESSAGE = 'Hello from SSR!' +export const QUERY_STALE_TIME = 30_000 + +export type DataResponse = { + message: string + requestCount: number + servedAt: string +} + +function resolveApiUrl(pathname: string, apiBaseUrl: string): string { + if (!apiBaseUrl) { + return pathname + } + + return new URL(pathname, apiBaseUrl).toString() +} + +async function readJson(response: Response): Promise { + let payload: TResponse | { error?: string } | null = null + + try { + payload = (await response.json()) as TResponse | { error?: string } + } catch { + if (response.ok) { + throw new Error( + `Failed to parse JSON response with status ${response.status}.`, + ) + } + } + + if (!response.ok) { + const errorDetail = + payload && + typeof payload === 'object' && + 'error' in payload && + typeof payload.error === 'string' + ? `: ${payload.error}` + : '' + + throw new Error( + `Request failed with status ${response.status}${errorDetail}`, + ) + } + + return payload as TResponse +} + +export function createDataQueryOptions(apiBaseUrl = '') { + return { + queryKey: DATA_QUERY_KEY, + queryFn: async ({ signal }) => { + const response = await fetch(resolveApiUrl('/api/data', apiBaseUrl), { + signal, + }) + return readJson(response) + }, + retry: false, + staleTime: QUERY_STALE_TIME, + } satisfies CreateQueryOptions< + DataResponse, + Error, + DataResponse, + DataResponse, + typeof DATA_QUERY_KEY + > +} diff --git a/examples/lit/ssr/src/app.ts b/examples/lit/ssr/src/app.ts new file mode 100644 index 00000000000..903bbcb2c9c --- /dev/null +++ b/examples/lit/ssr/src/app.ts @@ -0,0 +1,167 @@ +import { LitElement, css, html } from 'lit' +import { + createQueryController, + type QueryClient, + type QueryResultAccessor, +} from '@tanstack/lit-query' +import { createDataQueryOptions, type DataResponse } from './api.js' + +const ssrQueryControllerCreationCounts = new WeakMap() + +function incrementSsrQueryControllerCreationCount( + queryClient: QueryClient, +): void { + ssrQueryControllerCreationCounts.set( + queryClient, + (ssrQueryControllerCreationCounts.get(queryClient) ?? 0) + 1, + ) +} + +export function resetSsrQueryControllerCreationCount( + queryClient: QueryClient, +): void { + ssrQueryControllerCreationCounts.set(queryClient, 0) +} + +export function getSsrQueryControllerCreationCount( + queryClient: QueryClient, +): number { + return ssrQueryControllerCreationCounts.get(queryClient) ?? 0 +} + +export class SsrApp extends LitElement { + static properties = { + apiBaseUrl: { attribute: 'api-base-url' }, + queryClient: { attribute: false }, + } + + static styles = css` + :host { + color: #1f2937; + display: block; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + max-width: 32rem; + padding: 1.5rem; + } + + article { + background: #ffffff; + border: 1px solid #d1d5db; + border-radius: 1rem; + box-shadow: 0 12px 40px rgba(15, 23, 42, 0.08); + padding: 1.25rem; + } + + h1 { + font-size: 1.25rem; + margin: 0 0 1rem; + } + + p { + margin: 0.5rem 0; + } + + button { + background: #111827; + border: none; + border-radius: 999px; + color: #ffffff; + cursor: pointer; + font: inherit; + margin-top: 1rem; + padding: 0.65rem 1rem; + } + + button[disabled] { + cursor: wait; + opacity: 0.65; + } + ` + + apiBaseUrl = '' + queryClient?: QueryClient + + private dataQuery?: QueryResultAccessor + + protected override willUpdate(): void { + if (!this.dataQuery && this.queryClient) { + incrementSsrQueryControllerCreationCount(this.queryClient) + this.dataQuery = createQueryController( + this, + createDataQueryOptions(this.apiBaseUrl), + this.queryClient, + ) + } + } + + private readonly handleRefetch = (): void => { + void this.dataQuery?.refetch() + } + + protected override render() { + if (!this.dataQuery) { + return html` +
+

Lit Query SSR

+

Loading...

+
+ ` + } + + const query = this.dataQuery() + + if (query.isPending) { + return html` +
+

Lit Query SSR

+

Loading...

+
+ ` + } + + if (query.isError) { + return html` +
+

Lit Query SSR

+

Error

+

${query.error?.message}

+
+ ` + } + + return html` +
+

Lit Query SSR

+

${query.isFetching ? 'Refreshing' : 'Ready'}

+

${query.data.message}

+

+ Request count: ${query.data.requestCount} +

+

Served at: ${query.data.servedAt}

+ +
+ ` + } +} + +if (!customElements.get('ssr-app')) { + customElements.define('ssr-app', SsrApp) +} + +declare global { + interface HTMLElementTagNameMap { + 'ssr-app': SsrApp + } +} diff --git a/examples/lit/ssr/src/main.ts b/examples/lit/ssr/src/main.ts new file mode 100644 index 00000000000..6c8b52e9e71 --- /dev/null +++ b/examples/lit/ssr/src/main.ts @@ -0,0 +1,63 @@ +import '@lit-labs/ssr-client/lit-element-hydrate-support.js' + +import { QueryClient, hydrate, type DehydratedState } from '@tanstack/lit-query' +import { QUERY_STALE_TIME } from './api.js' + +type HydratableSsrApp = HTMLElement & { + queryClient?: QueryClient +} + +function createQueryClient(): QueryClient { + return new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: QUERY_STALE_TIME, + }, + }, + }) +} + +function readDehydratedState(): DehydratedState { + const stateElement = document.getElementById('__QUERY_STATE__') + if (!stateElement) { + throw new Error('Missing dehydrated state script.') + } + + const stateText = stateElement.textContent?.trim() ?? 'null' + return JSON.parse(stateText) as DehydratedState +} + +async function bootstrap() { + if (document.readyState === 'loading') { + await new Promise((resolve) => { + document.addEventListener('DOMContentLoaded', () => resolve(), { + once: true, + }) + }) + } + + const appElement = document.querySelector( + 'ssr-app', + ) as HydratableSsrApp | null + if (!appElement) { + throw new Error('Expected the SSR app element to exist before hydration.') + } + + const queryClient = createQueryClient() + queryClient.mount() + hydrate(queryClient, readDehydratedState()) + appElement.queryClient = queryClient + + window.addEventListener( + 'pagehide', + () => { + queryClient.unmount() + }, + { once: true }, + ) + + await import('./app.js') +} + +void bootstrap() diff --git a/examples/lit/ssr/tsconfig.json b/examples/lit/ssr/tsconfig.json new file mode 100644 index 00000000000..84271844ba9 --- /dev/null +++ b/examples/lit/ssr/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "useDefineForClassFields": false + }, + "include": ["config/**/*.d.ts", "src/**/*.ts", "vite.config.ts"] +} diff --git a/examples/lit/ssr/vite.config.ts b/examples/lit/ssr/vite.config.ts new file mode 100644 index 00000000000..582d23b9700 --- /dev/null +++ b/examples/lit/ssr/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + target: 'es2022', + }, +}) diff --git a/integrations/lit-vite/index.html b/integrations/lit-vite/index.html new file mode 100644 index 00000000000..b3974a125e0 --- /dev/null +++ b/integrations/lit-vite/index.html @@ -0,0 +1,13 @@ + + + + + Vite + Lit + + + + + + + + diff --git a/integrations/lit-vite/package.json b/integrations/lit-vite/package.json new file mode 100644 index 00000000000..f5e040bc08a --- /dev/null +++ b/integrations/lit-vite/package.json @@ -0,0 +1,17 @@ +{ + "name": "lit-vite", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit && vite build" + }, + "dependencies": { + "@tanstack/lit-query": "workspace:*", + "@tanstack/query-core": "workspace:*", + "lit": "^3.3.1", + "vite": "^6.4.1" + }, + "devDependencies": { + "typescript": "5.8.3" + } +} diff --git a/integrations/lit-vite/src/main.ts b/integrations/lit-vite/src/main.ts new file mode 100644 index 00000000000..5da031300e8 --- /dev/null +++ b/integrations/lit-vite/src/main.ts @@ -0,0 +1,56 @@ +import { LitElement, html } from 'lit' +import { + QueryClient, + QueryClientProvider, + createQueryController, +} from '@tanstack/lit-query' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}) + +class LitQueryProvider extends QueryClientProvider { + constructor() { + super() + this.client = queryClient + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} + +class LitQueryApp extends LitElement { + private readonly query = createQueryController(this, { + queryKey: ['test'], + queryFn: async () => { + await new Promise((resolve) => setTimeout(resolve, 100)) + return 'Success' + }, + }) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + const query = this.query() + + if (query.isPending) { + return html`
Loading...
` + } + + if (query.isError) { + return html`
An error has occurred!
` + } + + return html`
${query.data}
` + } +} + +customElements.define('lit-query-provider', LitQueryProvider) +customElements.define('lit-query-app', LitQueryApp) diff --git a/integrations/lit-vite/tsconfig.json b/integrations/lit-vite/tsconfig.json new file mode 100644 index 00000000000..f223644e2f2 --- /dev/null +++ b/integrations/lit-vite/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "useDefineForClassFields": false + }, + "include": ["src/**/*.ts"] +} diff --git a/integrations/lit-vite/vite.config.ts b/integrations/lit-vite/vite.config.ts new file mode 100644 index 00000000000..4f1b25a0c3d --- /dev/null +++ b/integrations/lit-vite/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite' + +export default defineConfig({}) diff --git a/knip.json b/knip.json index 2d444d217cc..cde2b9b3b5d 100644 --- a/knip.json +++ b/knip.json @@ -19,6 +19,9 @@ "entry": ["src/v4/**/*.cjs", "src/v5/**/*.cjs"], "ignore": ["**/__testfixtures__/**"] }, + "packages/lit-query": { + "ignore": ["src/tests/**"] + }, "packages/vue-query": { "ignoreDependencies": ["vue2", "vue2.7"] } diff --git a/packages/lit-query/.editorconfig b/packages/lit-query/.editorconfig new file mode 100644 index 00000000000..9d08a1a828a --- /dev/null +++ b/packages/lit-query/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/packages/lit-query/.npmignore b/packages/lit-query/.npmignore new file mode 100644 index 00000000000..f497387664b --- /dev/null +++ b/packages/lit-query/.npmignore @@ -0,0 +1,2 @@ +docs/ +src/tests/ diff --git a/packages/lit-query/.prettierignore b/packages/lit-query/.prettierignore new file mode 100644 index 00000000000..f800743a0af --- /dev/null +++ b/packages/lit-query/.prettierignore @@ -0,0 +1,10 @@ +dist +dist-cjs +node_modules +coverage +.claude +.references +*.tgz +package-lock.json +examples/**/output +examples/**/dist diff --git a/packages/lit-query/README.md b/packages/lit-query/README.md new file mode 100644 index 00000000000..2f650351056 --- /dev/null +++ b/packages/lit-query/README.md @@ -0,0 +1,131 @@ +# @tanstack/lit-query + +Lit adapter for `@tanstack/query-core` using Lit reactive controllers. + +## Install + +```bash +npm install @tanstack/lit-query @tanstack/query-core lit @lit/context +``` + +For local development in this repository: + +```bash +npm install +npm run build +``` + +## Quick Start + +```ts +import { LitElement, html } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { QueryClientProvider, createQueryController } from '@tanstack/lit-query' + +const client = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) + +class AppProvider extends QueryClientProvider { + constructor() { + super() + this.client = client + } + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } +} +customElements.define('app-provider', AppProvider) + +class UsersView extends LitElement { + private readonly users = createQueryController(this, { + queryKey: ['users'], + queryFn: async () => { + const response = await fetch('/api/users') + return response.json() as Promise> + }, + }) + + protected override createRenderRoot(): HTMLElement | DocumentFragment { + return this + } + + render() { + const query = this.users() + if (query.isPending) return html`Loading...` + if (query.isError) return html`Error` + return html`
    + ${query.data?.map((u) => html`
  • ${u.name}
  • `)} +
` + } +} +customElements.define('users-view', UsersView) +``` + +## API Surface (v1) + +- `QueryClientProvider`, `useQueryClient`, `resolveQueryClient` +- `createQueryController` +- `createMutationController` +- `createInfiniteQueryController` +- `createQueriesController` +- `useIsFetching`, `useIsMutating`, `useMutationState` +- `queryOptions`, `infiniteQueryOptions`, `mutationOptions` + +## Runnable Examples + +This repo includes runnable Lit examples under the top-level `examples/lit` +directory so they can be surfaced in the docs: + +- `examples/lit/basic`: Vite Lit app covering query and mutation primitives. +- `examples/lit/pagination`: pagination, prefetching, optimistic updates, and error recovery. +- `examples/lit/ssr`: Lit SSR render, dehydrate, and hydrate flow. + +Run an example from the repo root: + +```bash +pnpm --dir examples/lit/basic run dev +pnpm --dir examples/lit/pagination run dev +pnpm --dir examples/lit/ssr run dev +``` + +Open: + +- `http://127.0.0.1:4173/` (basic example) +- `http://127.0.0.1:4183/` (pagination example app) +- `http://127.0.0.1:4174/` (SSR example app) + +Use a different port (optional): + +```bash +DEMO_PORT=4180 pnpm --dir examples/lit/basic run dev +PAGINATION_DEMO_PORT=4181 PAGINATION_API_PORT=4182 pnpm --dir examples/lit/pagination run dev +SSR_PORT=4180 pnpm --dir examples/lit/ssr run dev +SSR_HOST=0.0.0.0 pnpm --dir examples/lit/ssr run dev +``` + +## Integration Smoke + +For the framework build smoke used in CI: + +```bash +pnpm --dir integrations/lit-vite run build +``` + +## Quality Gates + +- Core matrix: `docs/TEST_MATRIX.md` +- Integration matrix: `docs/TEST_MATRIX_INTEGRATION.md` +- Perf matrix: `docs/TEST_MATRIX_PERF.md` +- RFC and phase log: `docs/RFC-v4.1.md` + +Current local quality gate: + +```bash +npm run test:types && npm run test:lib && npm run build && npm run test:build +``` + +## License + +MIT diff --git a/packages/lit-query/eslint.config.js b/packages/lit-query/eslint.config.js new file mode 100644 index 00000000000..c3642e60191 --- /dev/null +++ b/packages/lit-query/eslint.config.js @@ -0,0 +1,52 @@ +// @ts-check + +import js from '@eslint/js' +import vitest from '@vitest/eslint-plugin' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { + ignores: [ + 'dist/**', + 'coverage/**', + 'node_modules/**', + '.claude/**', + '.references/**', + '**/dist/**', + '**/dist-cjs/**', + 'examples/**/output/**', + '**/*.d.ts', + ], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{ts,js,mjs,cjs}'], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + 'no-console': 'off', + }, + }, + { + files: ['src/tests/**/*.{ts,js,mjs}', 'examples/**/e2e/**/*.{js,mjs}'], + plugins: { + vitest, + }, + languageOptions: { + globals: { + ...vitest.environments.env.globals, + }, + }, + rules: { + ...vitest.configs.recommended.rules, + 'vitest/expect-expect': 'off', + }, + }, +) diff --git a/packages/lit-query/package.json b/packages/lit-query/package.json new file mode 100644 index 00000000000..f597c61166a --- /dev/null +++ b/packages/lit-query/package.json @@ -0,0 +1,63 @@ +{ + "name": "@tanstack/lit-query", + "version": "0.1.0", + "description": "Lit adapter for TanStack Query Core", + "license": "MIT", + "type": "module", + "main": "dist-cjs/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist", + "dist-cjs", + "src/**/*.ts", + "!src/tests/**/*" + ], + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist-cjs/index.d.cts", + "default": "./dist-cjs/index.js" + }, + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "compile": "node ../../node_modules/typescript/lib/tsc.js --build", + "build:deps": "pnpm --dir ../query-core run build", + "build": "pnpm run build:deps && pnpm run build:esm && pnpm run build:cjs", + "build:esm": "node ../../node_modules/typescript/lib/tsc.js -p tsconfig.build.json", + "build:cjs": "node -e \"require('node:fs').rmSync('dist-cjs', { recursive: true, force: true })\" && node ../../node_modules/typescript/lib/tsc.js -p tsconfig.build.cjs.json && node scripts/write-cjs-package.mjs", + "test:types": "node ../../node_modules/typescript/lib/tsc.js --noEmit", + "test:eslint": "eslint .", + "lint:fix": "eslint . --fix", + "test:lib": "vitest run", + "test:lib:dev": "vitest", + "test:watch": "pnpm run test:lib:dev", + "test:build": "publint --strict && attw --pack && node scripts/check-cjs-types-smoke.mjs", + "measure:bundle": "pnpm run build && node scripts/measure-bundle.mjs", + "measure:bundle:raw": "node scripts/measure-bundle.mjs", + "perf:l3": "pnpm run build && node scripts/l3-stress.mjs", + "perf:l3:raw": "node scripts/l3-stress.mjs" + }, + "dependencies": { + "@lit/context": "^1.1.6", + "@tanstack/query-core": "workspace:*", + "lit": "^3.3.1" + }, + "peerDependencies": { + "@tanstack/query-core": "^5.0.0", + "lit": ">=2.8.0 <4" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "globals": "^17.4.0", + "typescript-eslint": "^8.54.0" + } +} diff --git a/packages/lit-query/scripts/check-cjs-types-smoke.mjs b/packages/lit-query/scripts/check-cjs-types-smoke.mjs new file mode 100644 index 00000000000..a8c508d11d7 --- /dev/null +++ b/packages/lit-query/scripts/check-cjs-types-smoke.mjs @@ -0,0 +1,137 @@ +import { execFile as execFileCallback } from 'node:child_process' +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { promisify } from 'node:util' + +const execFile = promisify(execFileCallback) +const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm' +const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm' +const projectDir = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const workspaceRoot = resolve(projectDir, '..', '..') +const typeRootsDir = resolve(workspaceRoot, 'node_modules', '@types') +const tscEntrypoint = resolve( + workspaceRoot, + 'node_modules', + 'typescript', + 'lib', + 'tsc.js', +) + +const tempRoot = await mkdtemp(join(tmpdir(), 'tanstack-lit-query-cjs-smoke-')) +const packDir = resolve(tempRoot, 'pack') +const consumerDir = resolve(tempRoot, 'consumer') + +try { + const tarballPath = await packProject(packDir) + + await writeConsumerFixture(consumerDir) + await installConsumer(consumerDir, tarballPath) + await typecheckConsumer(consumerDir) + + console.log('CommonJS TypeScript smoke test passed.') +} finally { + await rm(tempRoot, { recursive: true, force: true }) +} + +async function packProject(destination) { + await mkdir(destination, { recursive: true }) + + const { stdout } = await execFile( + pnpmCommand, + ['pack', '--json', '--pack-destination', destination], + { + cwd: projectDir, + }, + ) + const packResult = JSON.parse(stdout) + const filename = Array.isArray(packResult) + ? packResult[0]?.filename + : packResult?.filename + + if (typeof filename !== 'string') { + throw new Error(`Unexpected pack output: ${stdout}`) + } + + return resolve(destination, filename) +} + +async function writeConsumerFixture(consumerDirectory) { + await rm(consumerDirectory, { recursive: true, force: true }) + await mkdir(consumerDirectory, { recursive: true }) + + await writeFile( + resolve(consumerDirectory, 'package.json'), + `${JSON.stringify( + { + private: true, + type: 'commonjs', + }, + null, + 2, + )}\n`, + 'utf8', + ) + + await writeFile( + resolve(consumerDirectory, 'index.cts'), + [ + "const pkg = require('@tanstack/lit-query')", + '', + "type CreateQueryOptions = import('@tanstack/lit-query').CreateQueryOptions", + '', + 'const options: CreateQueryOptions = {', + " queryKey: ['cjs-smoke'],", + " queryFn: async () => 'ok',", + '}', + '', + "if (typeof pkg.createQueryController !== 'function') {", + " throw new Error('createQueryController export is missing in CommonJS consumer.')", + '}', + '', + 'void pkg.queryOptions(options)', + '', + ].join('\n'), + 'utf8', + ) + + await writeFile( + resolve(consumerDirectory, 'tsconfig.json'), + `${JSON.stringify( + { + compilerOptions: { + module: 'Node16', + moduleResolution: 'Node16', + target: 'ES2022', + strict: true, + noEmit: true, + types: ['node'], + typeRoots: [typeRootsDir], + }, + include: ['index.cts'], + }, + null, + 2, + )}\n`, + 'utf8', + ) + + await writeFile( + resolve(consumerDirectory, '.npmrc'), + 'package-lock=false\n', + 'utf8', + ) +} + +async function installConsumer(consumerDirectory, tarballPath) { + await execFile(npmCommand, ['install', '--silent', tarballPath], { + cwd: consumerDirectory, + }) +} + +async function typecheckConsumer(consumerDirectory) { + await execFile(process.execPath, [tscEntrypoint, '-p', 'tsconfig.json'], { + cwd: consumerDirectory, + }) +} diff --git a/packages/lit-query/scripts/l3-stress.mjs b/packages/lit-query/scripts/l3-stress.mjs new file mode 100644 index 00000000000..55f52a9a73f --- /dev/null +++ b/packages/lit-query/scripts/l3-stress.mjs @@ -0,0 +1,124 @@ +import { QueryClient } from '@tanstack/query-core' +import { createQueryController } from '../dist/index.js' + +class TestControllerHost { + controllers = new Set() + updatesRequested = 0 + updateComplete = Promise.resolve(true) + + addController(controller) { + this.controllers.add(controller) + } + + removeController(controller) { + this.controllers.delete(controller) + } + + requestUpdate() { + this.updatesRequested += 1 + } + + connect() { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnect() { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } + + update() { + for (const controller of this.controllers) { + controller.hostUpdate?.() + } + + for (const controller of this.controllers) { + controller.hostUpdated?.() + } + } +} + +async function waitFor(assertion, timeoutMs = 3000) { + const startedAt = Date.now() + while (!assertion()) { + if (Date.now() - startedAt > timeoutMs) { + throw new Error(`Timed out waiting for assertion after ${timeoutMs}ms`) + } + await new Promise((resolve) => setTimeout(resolve, 5)) + } +} + +async function run(cycles = 1000) { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + }, + }) + + const queryKey = ['l3-stress'] + const startedAt = Date.now() + const initialHeapMb = process.memoryUsage().heapUsed / (1024 * 1024) + + for (let cycle = 0; cycle < cycles; cycle += 1) { + const host = new TestControllerHost() + const query = createQueryController( + host, + { + queryKey, + gcTime: 0, + queryFn: async () => cycle, + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess, 4000) + + const cacheQuery = client.getQueryCache().find({ queryKey }) + const connectedCount = cacheQuery?.getObserversCount() ?? 0 + if (connectedCount !== 1) { + throw new Error( + `observer_count_connected_invalid:${connectedCount}:cycle:${cycle}`, + ) + } + + host.disconnect() + query.destroy() + + const disconnectedCount = cacheQuery?.getObserversCount() ?? 0 + if (disconnectedCount !== 0) { + throw new Error( + `observer_count_disconnected_invalid:${disconnectedCount}:cycle:${cycle}`, + ) + } + } + + const elapsedMs = Date.now() - startedAt + const finalHeapMb = process.memoryUsage().heapUsed / (1024 * 1024) + const memoryGrowthMb = Number((finalHeapMb - initialHeapMb).toFixed(3)) + + const summary = { + measuredAt: new Date().toISOString(), + cycles, + elapsedMs, + initialHeapMb: Number(initialHeapMb.toFixed(3)), + finalHeapMb: Number(finalHeapMb.toFixed(3)), + memoryGrowthMb, + retainedObserversAfterRun: + client.getQueryCache().find({ queryKey })?.getObserversCount() ?? 0, + } + + console.log(JSON.stringify(summary, null, 2)) +} + +run().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/lit-query/scripts/measure-bundle.mjs b/packages/lit-query/scripts/measure-bundle.mjs new file mode 100644 index 00000000000..b8adec12b17 --- /dev/null +++ b/packages/lit-query/scripts/measure-bundle.mjs @@ -0,0 +1,43 @@ +import { readFile, readdir, stat } from 'node:fs/promises' +import path from 'node:path' +import { gzipSync } from 'node:zlib' +import { fileURLToPath } from 'node:url' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const repoRoot = path.resolve(scriptDir, '..') +const distDir = path.join(repoRoot, 'dist') +const entryFile = path.join(distDir, 'index.js') + +async function getDirSizeBytes(dirPath) { + let total = 0 + const entries = await readdir(dirPath, { withFileTypes: true }) + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name) + if (entry.isDirectory()) { + total += await getDirSizeBytes(fullPath) + } else if (entry.isFile()) { + total += (await stat(fullPath)).size + } + } + return total +} + +async function run() { + const entry = await readFile(entryFile) + const entryGzip = gzipSync(entry) + const distBytes = await getDirSizeBytes(distDir) + + const output = { + measuredAt: new Date().toISOString(), + entryJsBytes: entry.length, + entryJsGzipBytes: entryGzip.length, + distTotalBytes: distBytes, + } + + console.log(JSON.stringify(output, null, 2)) +} + +run().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/lit-query/scripts/write-cjs-package.mjs b/packages/lit-query/scripts/write-cjs-package.mjs new file mode 100644 index 00000000000..dcf076d7832 --- /dev/null +++ b/packages/lit-query/scripts/write-cjs-package.mjs @@ -0,0 +1,73 @@ +import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises' +import { dirname, relative, resolve } from 'node:path' + +const projectDir = process.cwd() +const esmDir = resolve(projectDir, 'dist') +const outDir = resolve(process.cwd(), 'dist-cjs') +const esmOnlyPackages = new Set(['lit']) +const esmImportTypeRegex = /import type \{([^}]*)\} from (['"])([^'"]+)\2;/g +const esmValueImportRegex = /import \{([^}]*)\} from (['"])([^'"]+)\2;/g +const importTypeExpressionRegex = /import\((['"])([^'"]+)\1\)/g + +await mkdir(outDir, { recursive: true }) +await writeFile( + resolve(outDir, 'package.json'), + `${JSON.stringify({ type: 'commonjs' }, null, 2)}\n`, + 'utf8', +) + +for (const declarationFile of await findDeclarationFiles(esmDir)) { + const source = await readFile(declarationFile, 'utf8') + const relativePath = relative(esmDir, declarationFile) + const outputPath = resolve(outDir, relativePath.replace(/\.d\.ts$/, '.d.cts')) + + await mkdir(dirname(outputPath), { recursive: true }) + await writeFile(outputPath, rewriteDeclaration(source), 'utf8') +} + +async function findDeclarationFiles(rootDir) { + const entries = await readdir(rootDir, { withFileTypes: true }) + const files = [] + + for (const entry of entries) { + const entryPath = resolve(rootDir, entry.name) + + if (entry.isDirectory()) { + files.push(...(await findDeclarationFiles(entryPath))) + continue + } + + if (entry.isFile() && entry.name.endsWith('.d.ts')) { + files.push(entryPath) + } + } + + return files +} + +function rewriteDeclaration(source) { + return source + .replace(/^\/\/# sourceMappingURL=.*$\n?/gm, '') + .replace(/(['"])(\.\.?\/[^'"]+)\.js\1/g, '$1$2.cjs$1') + .replace(esmImportTypeRegex, (match, specifiers, quote, packageName) => { + if (!esmOnlyPackages.has(packageName)) { + return match + } + + return `import type {${specifiers}} from ${quote}${packageName}${quote} with { "resolution-mode": "import" };` + }) + .replace(esmValueImportRegex, (match, specifiers, quote, packageName) => { + if (!esmOnlyPackages.has(packageName)) { + return match + } + + return `import type {${specifiers}} from ${quote}${packageName}${quote} with { "resolution-mode": "import" };` + }) + .replace(importTypeExpressionRegex, (match, quote, packageName) => { + if (packageName !== 'lit-html') { + return match + } + + return `import(${quote}${packageName}${quote}, { with: { "resolution-mode": "import" } })` + }) +} diff --git a/packages/lit-query/src/QueryClientProvider.ts b/packages/lit-query/src/QueryClientProvider.ts new file mode 100644 index 00000000000..57977f6574a --- /dev/null +++ b/packages/lit-query/src/QueryClientProvider.ts @@ -0,0 +1,111 @@ +import { ContextProvider } from '@lit/context' +import type { QueryClient } from '@tanstack/query-core' +import type { TemplateResult } from 'lit' +import { LitElement, html } from 'lit' +import { + createMissingQueryClientError, + queryClientContext, + registerDefaultQueryClient, + unregisterDefaultQueryClient, +} from './context.js' + +export class QueryClientProvider extends LitElement { + static properties = { + client: { attribute: false }, + } + + declare client: QueryClient + + private readonly contextProvider: ContextProvider + + private mountedClient: QueryClient | undefined + + constructor() { + super() + this.contextProvider = new ContextProvider(this, { + context: queryClientContext, + }) + } + + connectedCallback(): void { + super.connectedCallback() + const client = this.requireClient() + this.contextProvider.setValue(client) + this.mountClient(client) + } + + disconnectedCallback(): void { + this.unmountClient(this.mountedClient) + super.disconnectedCallback() + } + + protected willUpdate(changedProperties: Map): void { + if (!changedProperties.has('client')) { + return + } + + const nextClient = this.client + if (!nextClient) { + if (this.isConnected) { + this.unmountClient(this.mountedClient) + // Sentinel: notify active consumers that the provider is now unbound. + this.contextProvider.setValue(undefined as unknown as QueryClient) + throw createMissingQueryClientError() + } + + return + } + + const previousClient = changedProperties.get('client') as + | QueryClient + | undefined + if (previousClient && previousClient !== nextClient && this.isConnected) { + this.unmountClient(previousClient) + } + + this.contextProvider.setValue(nextClient) + + if (this.isConnected) { + this.mountClient(nextClient) + } + } + + render(): TemplateResult { + return html`` + } + + private mountClient(client: QueryClient): void { + if (this.mountedClient === client) { + return + } + + if (this.mountedClient) { + this.unmountClient(this.mountedClient) + } + + client.mount() + registerDefaultQueryClient(client) + this.mountedClient = client + } + + private unmountClient(client?: QueryClient): void { + if (!client) { + return + } + + client.unmount() + unregisterDefaultQueryClient(client) + + if (this.mountedClient === client) { + this.mountedClient = undefined + } + } + + private requireClient(): QueryClient { + if (!this.client) { + throw createMissingQueryClientError() + } + + return this.client + } +} diff --git a/packages/lit-query/src/accessor.ts b/packages/lit-query/src/accessor.ts new file mode 100644 index 00000000000..e110c8e891c --- /dev/null +++ b/packages/lit-query/src/accessor.ts @@ -0,0 +1,18 @@ +export type Accessor = T | (() => T) + +export function readAccessor(value: Accessor): T { + return typeof value === 'function' ? (value as () => T)() : value +} + +export type ValueAccessor = (() => T) & { + readonly current: T +} + +export function createValueAccessor(getter: () => T): ValueAccessor { + const accessor = (() => getter()) as ValueAccessor + Object.defineProperty(accessor, 'current', { + get: getter, + enumerable: true, + }) + return accessor +} diff --git a/packages/lit-query/src/context.ts b/packages/lit-query/src/context.ts new file mode 100644 index 00000000000..bdb3888b839 --- /dev/null +++ b/packages/lit-query/src/context.ts @@ -0,0 +1,72 @@ +import { createContext } from '@lit/context' +import type { QueryClient } from '@tanstack/query-core' + +export const queryClientContext = createContext( + Symbol.for('tanstack-query-client'), +) + +const missingQueryClientMessage = + 'No QueryClient available. Pass one explicitly or render within QueryClientProvider.' +const ambiguousQueryClientMessage = + 'Multiple QueryClients are mounted. Pass one explicitly instead of relying on global QueryClient helpers.' + +const registeredClients = new Map() +let defaultClient: QueryClient | undefined + +export function registerDefaultQueryClient(client: QueryClient): void { + registeredClients.set(client, (registeredClients.get(client) ?? 0) + 1) + defaultClient = client +} + +export function unregisterDefaultQueryClient(client: QueryClient): void { + const count = registeredClients.get(client) + if (count === undefined) { + return + } + + if (count > 1) { + registeredClients.set(client, count - 1) + return + } + + registeredClients.delete(client) + if (defaultClient !== client) { + return + } + + const remaining = [...registeredClients.keys()] + defaultClient = remaining.at(-1) +} + +export function getDefaultQueryClient(): QueryClient | undefined { + if (registeredClients.size > 1) { + return undefined + } + + return defaultClient +} + +export function createMissingQueryClientError(): Error { + return new Error(missingQueryClientMessage) +} + +function createAmbiguousQueryClientError(): Error { + return new Error(ambiguousQueryClientMessage) +} + +export function useQueryClient(): QueryClient { + const client = getDefaultQueryClient() + if (client) { + return client + } + + if (registeredClients.size > 1) { + throw createAmbiguousQueryClientError() + } + + throw createMissingQueryClientError() +} + +export function resolveQueryClient(explicit?: QueryClient): QueryClient { + return explicit ?? useQueryClient() +} diff --git a/packages/lit-query/src/controllers/BaseController.ts b/packages/lit-query/src/controllers/BaseController.ts new file mode 100644 index 00000000000..ba303edd417 --- /dev/null +++ b/packages/lit-query/src/controllers/BaseController.ts @@ -0,0 +1,284 @@ +import { ContextEvent } from '@lit/context' +import type { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { + createMissingQueryClientError, + queryClientContext, +} from '../context.js' + +type QueryClientResolutionState = + | 'pre-connect' + | 'awaiting-context' + | 'bound' + | 'missing' + +export abstract class BaseController implements ReactiveController { + protected result: TResult + + private readonly explicitClient?: QueryClient + private contextClient: QueryClient | undefined + private contextUnsubscribe: (() => void) | undefined + + private connected = false + private destroyed = false + private updateQueued = false + private clientChangeQueued = false + private connectionAttempt = 0 + private queryClientResolutionState: QueryClientResolutionState + + protected constructor( + protected readonly host: ReactiveControllerHost, + initialResult: TResult, + queryClient?: QueryClient, + ) { + this.explicitClient = queryClient + this.result = initialResult + this.queryClientResolutionState = queryClient ? 'bound' : 'pre-connect' + + host.addController(this) + } + + hostConnected(): void { + if (this.connected || this.destroyed) { + return + } + + this.connected = true + let contextResolutionAttempt: number | undefined + + if (this.explicitClient) { + this.queryClientResolutionState = 'bound' + } else { + contextResolutionAttempt = ++this.connectionAttempt + this.beginContextResolution() + } + + // Defer onConnected to ensure subclass constructors complete before + // lifecycle callbacks access subclass state. This handles the case where + // addController is called on an already-connected host (e.g., during + // willUpdate), which synchronously triggers hostConnected before + // subclass field initialization. + queueMicrotask(() => { + if (this.connected && !this.destroyed) { + this.onConnected() + } + }) + + if (contextResolutionAttempt !== undefined) { + // Provider-backed controllers on already-connected hosts should finish + // their deferred onConnected pass before a context client binds. + this.queueContextResolution(contextResolutionAttempt) + } + } + + hostDisconnected(): void { + if (!this.connected) { + return + } + + this.connected = false + + if (!this.explicitClient) { + this.connectionAttempt += 1 + this.clearContextClient() + this.updateQueryClientResolutionState('pre-connect') + } + + this.onDisconnected() + } + + hostUpdate(): void { + if (this.destroyed) { + return + } + + this.onHostUpdate() + } + + destroy(): void { + if (this.destroyed) { + return + } + + this.destroyed = true + this.connected = false + this.connectionAttempt += 1 + this.clearContextClient() + this.queryClientResolutionState = this.explicitClient + ? 'bound' + : 'pre-connect' + this.onDisconnected() + + if ('removeController' in this.host) { + this.host.removeController(this) + } + } + + protected tryGetQueryClient(): QueryClient | undefined { + return this.explicitClient ?? this.contextClient + } + + protected getQueryClient(): QueryClient { + const client = this.tryGetQueryClient() + if (!client) { + throw createMissingQueryClientError() + } + + return client + } + + protected setResult(next: TResult): void { + if (Object.is(this.result, next)) { + return + } + + this.result = next + this.queueUpdate() + } + + get current(): TResult { + if (this.queryClientResolutionState === 'missing') { + throw createMissingQueryClientError() + } + + return this.result + } + + protected get connectedState(): boolean { + return this.connected + } + + protected queueUpdate(): void { + if (this.updateQueued) { + return + } + + this.updateQueued = true + queueMicrotask(() => { + this.updateQueued = false + if (!this.destroyed) { + this.host.requestUpdate() + } + }) + } + + private queueQueryClientChanged(): void { + if (this.clientChangeQueued) { + return + } + + this.clientChangeQueued = true + queueMicrotask(() => { + this.clientChangeQueued = false + if (!this.destroyed) { + this.onQueryClientChanged() + } + }) + } + + private beginContextResolution(): void { + this.clearContextClient() + this.updateQueryClientResolutionState('awaiting-context') + } + + private queueContextResolution(attempt: number): void { + queueMicrotask(() => { + if ( + this.destroyed || + !this.connected || + attempt !== this.connectionAttempt || + this.queryClientResolutionState !== 'awaiting-context' + ) { + return + } + + this.dispatchContextRequest(attempt) + this.queueInitialContextResolutionCompletion(attempt) + }) + } + + private dispatchContextRequest(attempt: number): void { + if (!('dispatchEvent' in this.host)) { + return + } + + const contextTarget = this.host as ReactiveControllerHost & EventTarget + contextTarget.dispatchEvent( + new ContextEvent( + queryClientContext, + contextTarget as unknown as Element, + (value, unsubscribe) => { + if ( + this.destroyed || + !this.connected || + attempt !== this.connectionAttempt + ) { + unsubscribe?.() + return + } + + if ( + this.contextUnsubscribe && + this.contextUnsubscribe !== unsubscribe + ) { + this.contextUnsubscribe() + } + + const resolutionChanged = this.updateQueryClientResolutionState( + value === undefined ? 'missing' : 'bound', + ) + const clientChanged = this.contextClient !== value + + this.contextClient = value + this.contextUnsubscribe = unsubscribe + + if (resolutionChanged || clientChanged) { + this.queueUpdate() + this.queueQueryClientChanged() + } + }, + true, + ), + ) + } + + private queueInitialContextResolutionCompletion(attempt: number): void { + queueMicrotask(() => { + if ( + this.destroyed || + !this.connected || + attempt !== this.connectionAttempt || + this.queryClientResolutionState !== 'awaiting-context' + ) { + return + } + + if (this.updateQueryClientResolutionState('missing')) { + this.queueUpdate() + this.queueQueryClientChanged() + } + }) + } + + private clearContextClient(): void { + this.contextUnsubscribe?.() + this.contextUnsubscribe = undefined + this.contextClient = undefined + } + + private updateQueryClientResolutionState( + nextState: QueryClientResolutionState, + ): boolean { + if (this.queryClientResolutionState === nextState) { + return false + } + + this.queryClientResolutionState = nextState + return true + } + + protected abstract onConnected(): void + protected abstract onDisconnected(): void + protected abstract onHostUpdate(): void + protected abstract onQueryClientChanged(): void +} diff --git a/packages/lit-query/src/createInfiniteQueryController.ts b/packages/lit-query/src/createInfiniteQueryController.ts new file mode 100644 index 00000000000..9604f11ee8c --- /dev/null +++ b/packages/lit-query/src/createInfiniteQueryController.ts @@ -0,0 +1,331 @@ +import { + InfiniteQueryObserver, + type DefaultError, + type DefaultedInfiniteQueryObserverOptions, + type InfiniteData, + type InfiniteQueryObserverOptions, + type InfiniteQueryObserverResult, + type QueryKey, +} from '@tanstack/query-core' +import type { QueryClient } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { createMissingQueryClientError } from './context.js' +import { BaseController } from './controllers/BaseController.js' + +export type CreateInfiniteQueryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = InfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> + +export type InfiniteQueryResultAccessor = ValueAccessor< + InfiniteQueryObserverResult +> & { + refetch: InfiniteQueryObserverResult['refetch'] + fetchNextPage: InfiniteQueryObserverResult['fetchNextPage'] + fetchPreviousPage: InfiniteQueryObserverResult< + TData, + TError + >['fetchPreviousPage'] + destroy: () => void +} + +function createPendingInfiniteQueryResult< + TData, + TError, +>(): InfiniteQueryObserverResult { + return { + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPaused: false, + isPending: true, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isEnabled: true, + isSuccess: false, + fetchStatus: 'idle', + status: 'pending', + refetch: (() => + Promise.reject( + createMissingQueryClientError(), + )) as InfiniteQueryObserverResult['refetch'], + fetchNextPage: (() => + Promise.reject( + createMissingQueryClientError(), + )) as InfiniteQueryObserverResult['fetchNextPage'], + fetchPreviousPage: (() => + Promise.reject( + createMissingQueryClientError(), + )) as InfiniteQueryObserverResult['fetchPreviousPage'], + hasNextPage: false, + hasPreviousPage: false, + isFetchNextPageError: false, + isFetchingNextPage: false, + isFetchPreviousPageError: false, + isFetchingPreviousPage: false, + promise: Promise.resolve(undefined as never), + } as unknown as InfiniteQueryObserverResult +} + +class InfiniteQueryController< + TQueryFnData, + TError, + TData, + TQueryKey extends QueryKey, + TPageParam, +> extends BaseController> { + private readonly options: Accessor< + CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + > + private observer: + | InfiniteQueryObserver + | undefined + private unsubscribe: (() => void) | undefined + private queryClient: QueryClient | undefined + + constructor( + host: ReactiveControllerHost, + options: Accessor< + CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + >, + queryClient?: QueryClient, + ) { + super(host, createPendingInfiniteQueryResult(), queryClient) + this.options = options + + if (!queryClient) { + return + } + + if (typeof options === 'function') { + return + } + + const defaulted = this.defaultOptions(queryClient) + const observer = new InfiniteQueryObserver(queryClient, defaulted) + this.queryClient = queryClient + this.observer = observer + this.result = observer.getOptimisticResult(defaulted) + } + + protected onConnected(): void { + if (!this.syncClient()) { + return + } + + this.refreshOptions() + this.subscribe() + this.observer?.updateResult() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + protected onDisconnected(): void { + this.unsubscribeObserver() + this.syncClient() + } + + protected onHostUpdate(): void { + if (typeof this.options !== 'function') { + return + } + + this.refreshOptions() + } + + protected onQueryClientChanged(): void { + if (!this.syncClient() || !this.connectedState) { + return + } + + this.refreshOptions() + this.subscribe() + this.observer?.updateResult() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + refetch: InfiniteQueryObserverResult['refetch'] = ( + ...args + ) => { + if (!this.refreshOptions()) { + return Promise.reject(createMissingQueryClientError()) + } + + return this.result.refetch(...args) + } + + fetchNextPage: InfiniteQueryObserverResult['fetchNextPage'] = ( + ...args + ) => { + if (!this.refreshOptions()) { + return Promise.reject(createMissingQueryClientError()) + } + + return this.result.fetchNextPage(...args) + } + + fetchPreviousPage: InfiniteQueryObserverResult< + TData, + TError + >['fetchPreviousPage'] = (...args) => { + if (!this.refreshOptions()) { + return Promise.reject(createMissingQueryClientError()) + } + + return this.result.fetchPreviousPage(...args) + } + + private subscribe(): void { + if (!this.observer) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.observer.subscribe((next) => { + this.setResult(next) + }) + } + + private unsubscribeObserver(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribeObserver() + this.queryClient = undefined + this.observer = undefined + this.setResult(createPendingInfiniteQueryResult()) + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribeObserver() + this.queryClient = nextClient + const options = this.defaultOptions(this.queryClient) + this.observer = new InfiniteQueryObserver(this.queryClient, options) + this.setResult(this.observer.getOptimisticResult(options)) + return true + } + + private refreshOptions(): boolean { + if (!this.syncClient() || !this.observer || !this.queryClient) { + return false + } + + const options = this.defaultOptions(this.queryClient) + this.observer.setOptions(options) + this.setResult(this.observer.getOptimisticResult(options)) + return true + } + + private defaultOptions( + client = this.queryClient, + ): DefaultedInfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > { + if (!client) { + throw createMissingQueryClientError() + } + + const defaulted = client.defaultQueryOptions( + readAccessor(this.options), + ) as DefaultedInfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + ;(defaulted as { _optimisticResults?: 'optimistic' })._optimisticResults = + 'optimistic' + return defaulted + } +} + +export function createInfiniteQueryController< + TQueryFnData = unknown, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + host: ReactiveControllerHost, + options: Accessor< + CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + >, + queryClient?: QueryClient, +): InfiniteQueryResultAccessor { + const controller = new InfiniteQueryController(host, options, queryClient) + + return Object.assign( + createValueAccessor(() => controller.current), + { + refetch: controller.refetch, + fetchNextPage: controller.fetchNextPage, + fetchPreviousPage: controller.fetchPreviousPage, + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/createMutationController.ts b/packages/lit-query/src/createMutationController.ts new file mode 100644 index 00000000000..7389c6582c8 --- /dev/null +++ b/packages/lit-query/src/createMutationController.ts @@ -0,0 +1,295 @@ +import { + MutationObserver, + type DefaultError, + type MutateOptions, + type MutationObserverOptions, + type MutationObserverResult, +} from '@tanstack/query-core' +import type { QueryClient } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { createMissingQueryClientError } from './context.js' +import { BaseController } from './controllers/BaseController.js' + +export type CreateMutationOptions< + TData = unknown, + TError = DefaultError, + TVariables = void, + TOnMutateResult = unknown, +> = MutationObserverOptions + +export type MutationResultAccessor = + ValueAccessor< + MutationObserverResult + > & { + mutate: ( + variables: TVariables, + options?: MutateOptions, + ) => void + mutateAsync: MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['mutate'] + reset: MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['reset'] + destroy: () => void + } + +function createIdleMutationResult< + TData, + TError, + TVariables, + TOnMutateResult, +>(): MutationObserverResult { + return { + context: undefined, + data: undefined, + error: null, + failureCount: 0, + failureReason: null, + isError: false, + isIdle: true, + isPending: false, + isPaused: false, + isSuccess: false, + status: 'idle', + submittedAt: 0, + variables: undefined, + mutate: (() => + Promise.reject( + createMissingQueryClientError(), + )) as MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['mutate'], + reset: (() => undefined) as MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['reset'], + } as MutationObserverResult +} + +class MutationController< + TData, + TError, + TVariables, + TOnMutateResult, +> extends BaseController< + MutationObserverResult +> { + private readonly options: Accessor< + CreateMutationOptions + > + private observer: + | MutationObserver + | undefined + private unsubscribe: (() => void) | undefined + private queryClient: QueryClient | undefined + + constructor( + host: ReactiveControllerHost, + options: Accessor< + CreateMutationOptions + >, + queryClient?: QueryClient, + ) { + super(host, createIdleMutationResult(), queryClient) + this.options = options + + if (!queryClient) { + return + } + + if (typeof options === 'function') { + return + } + + const observer = new MutationObserver( + queryClient, + this.defaultOptions(queryClient), + ) + this.queryClient = queryClient + this.observer = observer + this.result = observer.getCurrentResult() + } + + protected onConnected(): void { + if (!this.syncClient()) { + return + } + + this.refreshOptions() + this.subscribe() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + protected onDisconnected(): void { + this.unsubscribeObserver() + this.syncClient() + } + + protected onHostUpdate(): void { + if (typeof this.options !== 'function') { + return + } + + this.refreshOptions() + } + + protected onQueryClientChanged(): void { + if (!this.syncClient() || !this.connectedState) { + return + } + + this.refreshOptions() + this.subscribe() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + mutate = ( + variables: TVariables, + mutateOptions?: MutateOptions, + ): void => { + if (!this.syncClient() || !this.observer) { + throw createMissingQueryClientError() + } + + void this.observer.mutate(variables, mutateOptions).catch(() => { + // Intentionally swallow in sync mutate path. + }) + } + + mutateAsync: MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['mutate'] = (...args) => { + if (!this.syncClient() || !this.observer) { + return Promise.reject(createMissingQueryClientError()) + } + + return this.observer.mutate(...args) + } + + reset: MutationObserverResult< + TData, + TError, + TVariables, + TOnMutateResult + >['reset'] = () => { + if (!this.syncClient() || !this.observer) { + return + } + + this.observer.reset() + this.setResult(this.observer.getCurrentResult()) + } + + private subscribe(): void { + if (!this.observer) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.observer.subscribe((next) => { + this.setResult(next) + }) + } + + private unsubscribeObserver(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribeObserver() + this.queryClient = undefined + this.observer = undefined + this.setResult(createIdleMutationResult()) + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribeObserver() + this.queryClient = nextClient + this.observer = new MutationObserver( + this.queryClient, + this.defaultOptions(this.queryClient), + ) + this.setResult(this.observer.getCurrentResult()) + return true + } + + private refreshOptions(): boolean { + if (!this.syncClient() || !this.observer || !this.queryClient) { + return false + } + + this.observer.setOptions(this.defaultOptions()) + this.setResult(this.observer.getCurrentResult()) + return true + } + + private defaultOptions( + client = this.queryClient, + ): MutationObserverOptions { + if (!client) { + throw createMissingQueryClientError() + } + + return client.defaultMutationOptions(readAccessor(this.options)) + } +} + +export function createMutationController< + TData = unknown, + TError = DefaultError, + TVariables = void, + TOnMutateResult = unknown, +>( + host: ReactiveControllerHost, + options: Accessor< + CreateMutationOptions + >, + queryClient?: QueryClient, +): MutationResultAccessor { + const controller = new MutationController(host, options, queryClient) + + return Object.assign( + createValueAccessor(() => controller.current), + { + mutate: controller.mutate, + mutateAsync: controller.mutateAsync, + reset: controller.reset, + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/createQueriesController.ts b/packages/lit-query/src/createQueriesController.ts new file mode 100644 index 00000000000..b938f4c93f6 --- /dev/null +++ b/packages/lit-query/src/createQueriesController.ts @@ -0,0 +1,563 @@ +import { + QueriesObserver, + type DefaultError, + type DefinedQueryObserverResult, + type OmitKeyof, + type QueriesObserverOptions, + type QueryFunction, + type QueryKey, + type QueryObserverOptions, + type QueryObserverResult, + type ThrowOnError, +} from '@tanstack/query-core' +import type { QueryClient } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { createMissingQueryClientError } from './context.js' +import { BaseController } from './controllers/BaseController.js' + +export type CreateQueriesInput< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = QueryObserverOptions + +type CreateQueriesInputForController< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = OmitKeyof, never> + +type MAXIMUM_DEPTH = 20 + +type SkipTokenForCreateQueries = symbol + +type GetCreateQueriesInput = T extends { + queryFnData: infer TQueryFnData + error?: infer TError + data: infer TData +} + ? CreateQueriesInputForController + : T extends { queryFnData: infer TQueryFnData; error?: infer TError } + ? CreateQueriesInputForController + : T extends { data: infer TData; error?: infer TError } + ? CreateQueriesInputForController + : T extends [infer TQueryFnData, infer TError, infer TData] + ? CreateQueriesInputForController + : T extends [infer TQueryFnData, infer TError] + ? CreateQueriesInputForController + : T extends [infer TQueryFnData] + ? CreateQueriesInputForController + : T extends { + queryFn?: + | QueryFunction + | SkipTokenForCreateQueries + select?: (data: any) => infer TData + throwOnError?: ThrowOnError + } + ? CreateQueriesInputForController< + TQueryFnData, + unknown extends TError ? DefaultError : TError, + unknown extends TData ? TQueryFnData : TData, + TQueryKey + > + : CreateQueriesInputForController + +type GetDefinedOrUndefinedCreateQueriesResult< + T, + TData, + TError = unknown, +> = T extends { + initialData?: infer TInitialData +} + ? unknown extends TInitialData + ? QueryObserverResult + : TInitialData extends TData + ? DefinedQueryObserverResult + : TInitialData extends () => infer TInitialDataResult + ? unknown extends TInitialDataResult + ? QueryObserverResult + : TInitialDataResult extends TData + ? DefinedQueryObserverResult + : QueryObserverResult + : QueryObserverResult + : QueryObserverResult + +type GetCreateQueriesResult = T extends { + queryFnData: any + error?: infer TError + data: infer TData +} + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends { queryFnData: infer TQueryFnData; error?: infer TError } + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends { data: infer TData; error?: infer TError } + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends [any, infer TError, infer TData] + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends [infer TQueryFnData, infer TError] + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends [infer TQueryFnData] + ? GetDefinedOrUndefinedCreateQueriesResult + : T extends { + queryFn?: + | QueryFunction + | SkipTokenForCreateQueries + select?: (data: any) => infer TData + throwOnError?: ThrowOnError + } + ? GetDefinedOrUndefinedCreateQueriesResult< + T, + unknown extends TData ? TQueryFnData : TData, + unknown extends TError ? DefaultError : TError + > + : QueryObserverResult + +export type CreateQueriesOptions< + T extends Array, + TResults extends Array = [], + TDepth extends ReadonlyArray = [], +> = TDepth['length'] extends MAXIMUM_DEPTH + ? Array + : T extends [] + ? [] + : T extends [infer Head] + ? [...TResults, GetCreateQueriesInput] + : T extends [infer Head, ...infer Tails] + ? CreateQueriesOptions< + [...Tails], + [...TResults, GetCreateQueriesInput], + [...TDepth, 1] + > + : ReadonlyArray extends T + ? T + : T extends Array< + CreateQueriesInputForController< + infer TQueryFnData, + infer TError, + infer TData, + infer TQueryKey + > + > + ? Array< + CreateQueriesInputForController< + TQueryFnData, + TError, + TData, + TQueryKey + > + > + : Array + +export type CreateQueriesResults< + T extends Array, + TResults extends Array = [], + TDepth extends ReadonlyArray = [], +> = TDepth['length'] extends MAXIMUM_DEPTH + ? Array + : T extends [] + ? [] + : T extends [infer Head] + ? [...TResults, GetCreateQueriesResult] + : T extends [infer Head, ...infer Tails] + ? CreateQueriesResults< + [...Tails], + [...TResults, GetCreateQueriesResult], + [...TDepth, 1] + > + : { [K in keyof T]: GetCreateQueriesResult } + +export type CreateQueriesControllerOptions< + TQueryOptions extends Array = Array, + TCombinedResult = CreateQueriesResults, +> = { + queries: Accessor< + | readonly [...CreateQueriesOptions] + | readonly [ + ...{ + [K in keyof TQueryOptions]: GetCreateQueriesInput + }, + ] + > + combine?: (result: CreateQueriesResults) => TCombinedResult +} + +export type QueriesResultAccessor = + ValueAccessor & { + destroy: () => void + } + +function createPendingQueryObserverResult(): QueryObserverResult { + return { + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPaused: false, + isPending: true, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isEnabled: true, + isSuccess: false, + fetchStatus: 'idle', + status: 'pending', + refetch: (() => + Promise.reject( + createMissingQueryClientError(), + )) as QueryObserverResult['refetch'], + promise: Promise.resolve(undefined as never), + } as unknown as QueryObserverResult +} + +function createPlaceholderQueryObserverResult( + query: QueryObserverOptions, +): QueryObserverResult { + const initialData = + typeof query.initialData === 'function' + ? query.initialData() + : query.initialData + + if (initialData === undefined) { + return createPendingQueryObserverResult() + } + + const data = query.select ? query.select(initialData) : initialData + const initialDataUpdatedAt = + typeof query.initialDataUpdatedAt === 'function' + ? query.initialDataUpdatedAt() + : query.initialDataUpdatedAt + + return { + ...createPendingQueryObserverResult(), + data, + dataUpdatedAt: initialDataUpdatedAt ?? Date.now(), + isPending: false, + isInitialLoading: false, + isLoading: false, + isSuccess: true, + status: 'success', + promise: Promise.resolve(data as never), + } as QueryObserverResult +} + +function resolveQueriesOptions( + optionsAccessor: Accessor< + CreateQueriesControllerOptions + >, + client: QueryClient, +): { + queries: Array + combine: QueriesObserverOptions['combine'] +} { + const resolvedOptions = readAccessor(optionsAccessor) + const resolvedQueries = readAccessor(resolvedOptions.queries) + const combine = + resolvedOptions.combine as QueriesObserverOptions['combine'] + + return { + queries: resolvedQueries.map((query) => { + const defaulted = client.defaultQueryOptions( + query as QueryObserverOptions, + ) + ;(defaulted as { _optimisticResults?: 'optimistic' })._optimisticResults = + 'optimistic' + return defaulted + }), + combine, + } +} + +class QueriesController< + TQueryOptions extends Array, + TCombinedResult, +> extends BaseController { + private readonly options: Accessor< + CreateQueriesControllerOptions + > + private observer: QueriesObserver | undefined + private unsubscribe: (() => void) | undefined + private queryClient: QueryClient | undefined + private explicitInitializationError: unknown | undefined + private placeholderInitialized = false + private placeholderRetryableFailure = true + + constructor( + host: ReactiveControllerHost, + options: Accessor< + CreateQueriesControllerOptions + >, + queryClient?: QueryClient, + ) { + super(host, [] as unknown as TCombinedResult, queryClient) + this.options = options + + queueMicrotask(() => { + this.placeholderRetryableFailure = false + }) + + if (!queryClient) { + return + } + + if (this.shouldRefreshOnHostUpdate()) { + return + } + + this.tryInitializeExplicitClient(queryClient) + } + + protected onConnected(): void { + if (!this.syncClient()) { + return + } + + this.refreshOptions() + this.subscribe() + } + + protected onDisconnected(): void { + this.unsubscribeObserver() + this.syncClient() + } + + protected onHostUpdate(): void { + if (!this.shouldRefreshOnHostUpdate()) { + return + } + + if (!this.refreshOptions()) { + this.setPlaceholderResult() + } + } + + protected onQueryClientChanged(): void { + if (!this.syncClient() || !this.connectedState) { + return + } + + this.refreshOptions() + this.subscribe() + } + + private subscribe(): void { + if (!this.observer) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.observer.subscribe((next) => { + const { combine } = this.readResolvedOptions() + this.setResult(this.computeResult(next, combine)) + }) + } + + private tryInitializeExplicitClient(queryClient: QueryClient): boolean { + try { + const { queries, combine } = resolveQueriesOptions( + this.options, + queryClient, + ) + const observer = new QueriesObserver(queryClient, queries, { + combine, + } as QueriesObserverOptions) + this.queryClient = queryClient + this.observer = observer + this.result = this.computeResult(observer.getCurrentResult(), combine) + this.explicitInitializationError = undefined + this.placeholderInitialized = true + return true + } catch (error) { + // Retry after construction completes so late host fields used by + // static queries/combine callbacks can finish initializing first. + this.explicitInitializationError = error + this.queryClient = undefined + this.observer = undefined + return false + } + } + + private retryExplicitInitializationIfNeeded(): boolean { + if (!this.explicitInitializationError || this.shouldRefreshOnHostUpdate()) { + return false + } + + const explicitClient = this.tryGetQueryClient() + if (!explicitClient) { + return false + } + + return this.tryInitializeExplicitClient(explicitClient) + } + + private unsubscribeObserver(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribeObserver() + this.queryClient = undefined + this.observer = undefined + this.setPlaceholderResult() + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribeObserver() + this.queryClient = nextClient + const { queries, combine } = this.readResolvedOptions() + this.observer = new QueriesObserver(this.queryClient, queries, { + combine, + } as QueriesObserverOptions) + this.setResult( + this.computeResult(this.observer.getCurrentResult(), combine), + ) + this.placeholderInitialized = true + return true + } + + private refreshOptions(): boolean { + if (!this.syncClient() || !this.observer) { + return false + } + + const { queries, combine } = this.readResolvedOptions() + + this.observer.setQueries(queries, { + combine, + } as QueriesObserverOptions) + + const [rawResult, getCombinedResult] = this.observer.getOptimisticResult( + queries, + combine, + ) + + this.setResult(getCombinedResult(rawResult)) + return true + } + + private readResolvedOptions(client = this.queryClient): { + queries: Array + combine: QueriesObserverOptions['combine'] + } { + if (!client) { + throw createMissingQueryClientError() + } + + return resolveQueriesOptions( + this.options as Accessor< + CreateQueriesControllerOptions + >, + client, + ) + } + + private shouldRefreshOnHostUpdate(): boolean { + if (typeof this.options === 'function') { + return true + } + + return typeof this.options.queries === 'function' + } + + private computeResult( + rawResult: Array, + combine: QueriesObserverOptions['combine'], + ): TCombinedResult { + return (combine ? combine(rawResult) : rawResult) as TCombinedResult + } + + private static createPlaceholderResult( + optionsAccessor: Accessor< + CreateQueriesControllerOptions + >, + ): TCombinedResult { + const resolvedOptions = readAccessor(optionsAccessor) + const queries = readAccessor(resolvedOptions.queries) + const placeholders = queries.map((query) => + createPlaceholderQueryObserverResult(query as QueryObserverOptions), + ) + return ( + resolvedOptions.combine + ? resolvedOptions.combine(placeholders as never) + : placeholders + ) as TCombinedResult + } + + readCurrent(): TCombinedResult { + if (this.retryExplicitInitializationIfNeeded()) { + return this.current + } + + if (this.explicitInitializationError && !this.placeholderRetryableFailure) { + throw this.explicitInitializationError + } + + if (!this.queryClient && !this.observer && !this.placeholderInitialized) { + try { + // Early reads can happen during class-field initialization, before + // accessors referenced by queries/combine are ready. Retry normally + // after construction finishes and only surface errors after that point. + this.setPlaceholderResult() + } catch (error) { + if (!this.placeholderRetryableFailure) { + throw error + } + } + } + + return this.current + } + + private setPlaceholderResult(): void { + this.result = QueriesController.createPlaceholderResult(this.options) + this.placeholderInitialized = true + } +} + +export function createQueriesController< + TQueryOptions extends Array, + TCombinedResult = CreateQueriesResults, +>( + host: ReactiveControllerHost, + options: Accessor< + CreateQueriesControllerOptions + >, + queryClient?: QueryClient, +): QueriesResultAccessor { + const controller = new QueriesController(host, options, queryClient) + + return Object.assign( + createValueAccessor(() => controller.readCurrent()), + { + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/createQueryController.ts b/packages/lit-query/src/createQueryController.ts new file mode 100644 index 00000000000..e444b648b7c --- /dev/null +++ b/packages/lit-query/src/createQueryController.ts @@ -0,0 +1,287 @@ +import { + QueryObserver, + type DefaultError, + type DefaultedQueryObserverOptions, + type QueryKey, + type QueryObserverOptions, + type QueryObserverResult, +} from '@tanstack/query-core' +import type { QueryClient } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { createMissingQueryClientError } from './context.js' +import { BaseController } from './controllers/BaseController.js' + +export type CreateQueryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = QueryObserverOptions + +export type QueryResultAccessor = ValueAccessor< + QueryObserverResult +> & { + refetch: QueryObserverResult['refetch'] + suspense: () => Promise> + destroy: () => void +} + +function createPendingQueryResult(): QueryObserverResult< + TData, + TError +> { + return { + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPaused: false, + isPending: true, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: true, + isEnabled: true, + isSuccess: false, + fetchStatus: 'idle', + status: 'pending', + refetch: (() => + Promise.reject(createMissingQueryClientError())) as QueryObserverResult< + TData, + TError + >['refetch'], + promise: Promise.resolve(undefined as never), + } as unknown as QueryObserverResult +} + +class QueryController< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey extends QueryKey, +> extends BaseController> { + private readonly options: Accessor< + CreateQueryOptions + > + private observer: + | QueryObserver + | undefined + private unsubscribe: (() => void) | undefined + private queryClient: QueryClient | undefined + + constructor( + host: ReactiveControllerHost, + options: Accessor< + CreateQueryOptions + >, + queryClient?: QueryClient, + ) { + const initialClient = queryClient + super(host, createPendingQueryResult(), queryClient) + this.options = options + + if (!initialClient) { + return + } + + if (typeof options === 'function') { + return + } + + const defaulted = this.defaultOptions(initialClient) + const observer = new QueryObserver(initialClient, defaulted) + this.queryClient = initialClient + this.observer = observer + this.result = observer.getOptimisticResult(defaulted) + } + + protected onConnected(): void { + if (!this.syncClient()) { + return + } + + this.refreshOptions() + this.subscribe() + this.observer?.updateResult() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + protected onDisconnected(): void { + this.unsubscribeObserver() + this.syncClient() + } + + protected onHostUpdate(): void { + if (typeof this.options !== 'function') { + return + } + + this.refreshOptions() + } + + protected onQueryClientChanged(): void { + if (!this.syncClient()) { + return + } + + if (!this.connectedState) { + return + } + + this.refreshOptions() + this.subscribe() + this.observer?.updateResult() + if (this.observer) { + this.setResult(this.observer.getCurrentResult()) + } + } + + refetch: QueryObserverResult['refetch'] = (...args) => { + if (!this.refreshOptions()) { + return Promise.reject(createMissingQueryClientError()) + } + + return this.result.refetch(...args) + } + + suspense = async (): Promise> => { + if (!this.syncClient() || !this.observer || !this.queryClient) { + throw createMissingQueryClientError() + } + + const options = this.defaultOptions(this.queryClient) + this.observer.setOptions(options) + const optimistic = this.observer.getOptimisticResult(options) + if (options.enabled !== false && optimistic.isStale) { + return this.observer.fetchOptimistic(options) + } + + return optimistic + } + + private subscribe(): void { + if (!this.observer) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.observer.subscribe((next) => { + this.setResult(next) + }) + } + + private unsubscribeObserver(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribeObserver() + this.queryClient = undefined + this.observer = undefined + this.setResult(createPendingQueryResult()) + return false + } + + if (nextClient === this.queryClient && this.observer) { + return true + } + + this.unsubscribeObserver() + this.queryClient = nextClient + const options = this.defaultOptions() + this.observer = new QueryObserver(this.queryClient, options) + this.setResult(this.observer.getOptimisticResult(options)) + return true + } + + private refreshOptions(): boolean { + if (!this.syncClient() || !this.observer) { + return false + } + + const options = this.defaultOptions(this.queryClient) + this.observer.setOptions(options) + this.setResult(this.observer.getOptimisticResult(options)) + return true + } + + private defaultOptions( + client = this.queryClient, + ): DefaultedQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey + > { + const resolvedClient = client ?? this.tryGetQueryClient() + if (!resolvedClient) { + throw createMissingQueryClientError() + } + + this.queryClient = resolvedClient + const defaulted = resolvedClient.defaultQueryOptions( + readAccessor(this.options), + ) as DefaultedQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey + > + ;(defaulted as { _optimisticResults?: 'optimistic' })._optimisticResults = + 'optimistic' + return defaulted + } +} + +export function createQueryController< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + host: ReactiveControllerHost, + options: Accessor< + CreateQueryOptions + >, + queryClient?: QueryClient, +): QueryResultAccessor { + const controller = new QueryController(host, options, queryClient) + + return Object.assign( + createValueAccessor(() => controller.current), + { + refetch: controller.refetch, + suspense: controller.suspense, + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/index.ts b/packages/lit-query/src/index.ts new file mode 100644 index 00000000000..bf87f1dc45b --- /dev/null +++ b/packages/lit-query/src/index.ts @@ -0,0 +1,72 @@ +/* istanbul ignore file */ + +export * from '@tanstack/query-core' + +export type { Accessor, ValueAccessor } from './accessor.js' + +export { + getDefaultQueryClient, + queryClientContext, + registerDefaultQueryClient, + resolveQueryClient, + unregisterDefaultQueryClient, + useQueryClient, +} from './context.js' + +export { QueryClientProvider } from './QueryClientProvider.js' + +export type { + CreateInfiniteQueryOptions, + InfiniteQueryResultAccessor, +} from './createInfiniteQueryController.js' +export { createInfiniteQueryController } from './createInfiniteQueryController.js' + +export type { + CreateMutationOptions, + MutationResultAccessor, +} from './createMutationController.js' +export { createMutationController } from './createMutationController.js' + +export type { + CreateQueriesControllerOptions, + CreateQueriesInput, + QueriesResultAccessor, +} from './createQueriesController.js' +export { createQueriesController } from './createQueriesController.js' + +export type { + CreateQueryOptions, + QueryResultAccessor, +} from './createQueryController.js' +export { createQueryController } from './createQueryController.js' + +export type { IsFetchingAccessor } from './useIsFetching.js' +export { useIsFetching } from './useIsFetching.js' + +export type { IsMutatingAccessor } from './useIsMutating.js' +export { useIsMutating } from './useIsMutating.js' + +export type { + MutationStateAccessor, + MutationStateOptions, +} from './useMutationState.js' +export { useMutationState } from './useMutationState.js' + +export type { + DefinedInitialDataOptions, + UndefinedInitialDataOptions, + UnusedSkipTokenOptions, +} from './queryOptions.js' +export { queryOptions } from './queryOptions.js' + +export { infiniteQueryOptions } from './infiniteQueryOptions.js' +export { mutationOptions } from './mutationOptions.js' + +export type { + InfiniteQueryControllerOptions, + MutationControllerOptions, + MutationControllerResult, + QueriesControllerOptions, + QueryControllerOptions, + QueryControllerResult, +} from './types.js' diff --git a/packages/lit-query/src/infiniteQueryOptions.ts b/packages/lit-query/src/infiniteQueryOptions.ts new file mode 100644 index 00000000000..441de987d76 --- /dev/null +++ b/packages/lit-query/src/infiniteQueryOptions.ts @@ -0,0 +1,30 @@ +import type { + DefaultError, + InfiniteData, + InfiniteQueryObserverOptions, + QueryKey, +} from '@tanstack/query-core' + +export function infiniteQueryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +>( + options: InfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, +): InfiniteQueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam +> { + return options +} diff --git a/packages/lit-query/src/mutationOptions.ts b/packages/lit-query/src/mutationOptions.ts new file mode 100644 index 00000000000..eededd78cfd --- /dev/null +++ b/packages/lit-query/src/mutationOptions.ts @@ -0,0 +1,15 @@ +import type { + DefaultError, + MutationObserverOptions, +} from '@tanstack/query-core' + +export function mutationOptions< + TData = unknown, + TError = DefaultError, + TVariables = void, + TOnMutateResult = unknown, +>( + options: MutationObserverOptions, +): MutationObserverOptions { + return options +} diff --git a/packages/lit-query/src/queryOptions.ts b/packages/lit-query/src/queryOptions.ts new file mode 100644 index 00000000000..54bb2edd93c --- /dev/null +++ b/packages/lit-query/src/queryOptions.ts @@ -0,0 +1,102 @@ +import type { + DataTag, + DefaultError, + InitialDataFunction, + NonUndefinedGuard, + OmitKeyof, + QueryFunction, + QueryKey, + QueryObserverOptions, + SkipToken, +} from '@tanstack/query-core' + +export type DefinedInitialDataOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = Omit< + QueryObserverOptions, + 'queryFn' +> & { + initialData: + | NonUndefinedGuard + | (() => NonUndefinedGuard) + queryFn?: QueryFunction +} + +export type UnusedSkipTokenOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = OmitKeyof< + QueryObserverOptions, + 'queryFn' +> & { + queryFn?: Exclude< + QueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey + >['queryFn'], + SkipToken | undefined + > +} + +export type UndefinedInitialDataOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = QueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey +> & { + initialData?: + | undefined + | InitialDataFunction> + | NonUndefinedGuard +} + +export function queryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: DefinedInitialDataOptions, +): DefinedInitialDataOptions & { + queryKey: DataTag +} + +export function queryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: UnusedSkipTokenOptions, +): UnusedSkipTokenOptions & { + queryKey: DataTag +} + +export function queryOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: UndefinedInitialDataOptions, +): UndefinedInitialDataOptions & { + queryKey: DataTag +} + +export function queryOptions(options: unknown) { + return options +} diff --git a/packages/lit-query/src/tests/base-controller.test.ts b/packages/lit-query/src/tests/base-controller.test.ts new file mode 100644 index 00000000000..8587632fb8d --- /dev/null +++ b/packages/lit-query/src/tests/base-controller.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClient } from '@tanstack/query-core' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { BaseController } from '../controllers/BaseController.js' + +const providerTagName = 'test-query-client-provider-base-controller' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +class RecordingController extends BaseController { + readonly lifecycle: string[] = [] + + constructor(host: ReactiveControllerHost) { + super(host, 'pending') + } + + protected onConnected(): void { + this.lifecycle.push( + `connected:${this.tryGetQueryClient() ? 'client' : 'missing'}`, + ) + } + + protected onDisconnected(): void {} + + protected onHostUpdate(): void {} + + protected onQueryClientChanged(): void { + this.lifecycle.push( + `changed:${this.tryGetQueryClient() ? 'client' : 'missing'}`, + ) + } +} + +class AlreadyConnectedContextHost + extends HTMLElement + implements ReactiveControllerHost +{ + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + + connectedCallback(): void { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnectedCallback(): void { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } + + attachController(): RecordingController { + return new RecordingController(this) + } +} + +const hostTagName = 'test-base-controller-context-host' +if (!customElements.get(hostTagName)) { + customElements.define(hostTagName, AlreadyConnectedContextHost) +} + +describe('BaseController', () => { + it('defers provider resolution on already-connected hosts until after onConnected', async () => { + const client = new QueryClient() + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + + const host = document.createElement( + hostTagName, + ) as AlreadyConnectedContextHost + provider.append(host) + + document.body.append(provider) + await provider.updateComplete + + const controller = host.attachController() + await Promise.resolve() + await Promise.resolve() + + expect(controller.lifecycle).toEqual([ + 'connected:missing', + 'changed:client', + ]) + + controller.destroy() + provider.remove() + await Promise.resolve() + }) +}) diff --git a/packages/lit-query/src/tests/client-switch-controllers.test.ts b/packages/lit-query/src/tests/client-switch-controllers.test.ts new file mode 100644 index 00000000000..3e60bfd88b9 --- /dev/null +++ b/packages/lit-query/src/tests/client-switch-controllers.test.ts @@ -0,0 +1,482 @@ +import { describe, expect, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createInfiniteQueryController } from '../createInfiniteQueryController.js' +import { createMutationController } from '../createMutationController.js' +import { createQueriesController } from '../createQueriesController.js' +import { waitFor } from './testHost.js' + +const providerTagName = 'test-query-client-provider-switch' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +class BaseControllerHostElement + extends HTMLElement + implements ReactiveControllerHost +{ + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + + connectedCallback(): void { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnectedCallback(): void { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } +} + +class MutationSwitchHostElement extends BaseControllerHostElement { + mutationCalls = 0 + readonly mutationKey = ['switch-mutation'] as const + + readonly mutation = createMutationController(this, () => ({ + mutationKey: this.mutationKey, + mutationFn: async (value: number) => { + this.mutationCalls += 1 + return value + 1 + }, + })) +} + +const mutationHostTagName = 'test-mutation-switch-host' +if (!customElements.get(mutationHostTagName)) { + customElements.define(mutationHostTagName, MutationSwitchHostElement) +} + +class QueriesSwitchHostElement extends BaseControllerHostElement { + queryCalls = 0 + readonly queryKey = ['switch-queries'] as const + + readonly queries = createQueriesController(this, () => ({ + queries: [ + { + queryKey: this.queryKey, + queryFn: async () => { + this.queryCalls += 1 + return `q-${this.queryCalls}` + }, + retry: false, + }, + ] as const, + combine: (results) => results.map((result) => result.data), + })) +} + +const queriesHostTagName = 'test-queries-switch-host' +if (!customElements.get(queriesHostTagName)) { + customElements.define(queriesHostTagName, QueriesSwitchHostElement) +} + +class InfiniteSwitchHostElement extends BaseControllerHostElement { + pageCalls = 0 + readonly queryKey = ['switch-infinite'] as const + + readonly infinite = createInfiniteQueryController(this, () => ({ + queryKey: this.queryKey, + initialPageParam: 0, + queryFn: async ({ pageParam }) => { + this.pageCalls += 1 + return Number(pageParam) + }, + getNextPageParam: (lastPage: number) => + lastPage < 1 ? lastPage + 1 : undefined, + retry: false, + })) +} + +const infiniteHostTagName = 'test-infinite-switch-host' +if (!customElements.get(infiniteHostTagName)) { + customElements.define(infiniteHostTagName, InfiniteSwitchHostElement) +} + +describe('LQ-003 client-switch coverage across controllers', () => { + it('switches mutation controller to new provider client while connected', async () => { + const clientA = new QueryClient() + const clientB = new QueryClient() + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = clientA + document.body.append(provider) + await provider.updateComplete + + const consumer = document.createElement( + mutationHostTagName, + ) as MutationSwitchHostElement + provider.append(consumer) + + await Promise.resolve() + await Promise.resolve() + await expect(consumer.mutation.mutateAsync(1)).resolves.toBe(2) + + const countAAfterFirst = clientA + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length + expect(countAAfterFirst).toBeGreaterThan(0) + + provider.client = clientB + await provider.updateComplete + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(2)).resolves.toBe(3) + + const countAAfterSecond = clientA + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length + const countBAfterSecond = clientB + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length + + expect(countAAfterSecond).toBe(countAAfterFirst) + expect(countBAfterSecond).toBeGreaterThan(0) + + consumer.mutation.destroy() + provider.remove() + await Promise.resolve() + }) + + it('switches queries controller to new provider client while connected', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = clientA + document.body.append(provider) + await provider.updateComplete + + const consumer = document.createElement( + queriesHostTagName, + ) as QueriesSwitchHostElement + provider.append(consumer) + + await Promise.resolve() + await waitFor(() => typeof consumer.queries()[0] === 'string') + + const cacheAEntryBeforeSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + expect(cacheAEntryBeforeSwitch?.getObserversCount()).toBe(1) + + provider.client = clientB + await provider.updateComplete + + await waitFor(() => { + const cacheBEntry = clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + return Boolean(cacheBEntry && cacheBEntry.getObserversCount() === 1) + }) + + const cacheAEntryAfterSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + expect(cacheAEntryAfterSwitch?.getObserversCount() ?? 0).toBe(0) + + void clientB.invalidateQueries({ queryKey: consumer.queryKey }) + await waitFor(() => consumer.queryCalls >= 2) + + consumer.queries.destroy() + provider.remove() + await Promise.resolve() + }) + + it('switches infinite query controller to new provider client while connected', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = clientA + document.body.append(provider) + await provider.updateComplete + + const consumer = document.createElement( + infiniteHostTagName, + ) as InfiniteSwitchHostElement + provider.append(consumer) + + await Promise.resolve() + await waitFor(() => consumer.infinite().isSuccess) + expect(consumer.infinite().data?.pages).toEqual([0]) + + const cacheAEntryBeforeSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + expect(cacheAEntryBeforeSwitch?.getObserversCount()).toBe(1) + + provider.client = clientB + await provider.updateComplete + + await waitFor(() => { + const cacheBEntry = clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + return Boolean(cacheBEntry && cacheBEntry.getObserversCount() === 1) + }) + await waitFor( + () => + consumer.infinite().isSuccess && + (consumer.infinite().data?.pages.length ?? 0) >= 1, + 4000, + ) + await waitFor(() => consumer.infinite().hasNextPage === true, 4000) + + const cacheAEntryAfterSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + expect(cacheAEntryAfterSwitch?.getObserversCount() ?? 0).toBe(0) + + await consumer.infinite.fetchNextPage() + await waitFor(() => consumer.infinite().data?.pages.length === 2, 4000) + expect(consumer.infinite().data?.pages).toEqual([0, 1]) + + consumer.infinite.destroy() + provider.remove() + await Promise.resolve() + }) + + it('reparents mutation controller under a different provider and binds the new nearest client', async () => { + const clientA = new QueryClient() + const clientB = new QueryClient() + + const providerA = document.createElement( + providerTagName, + ) as QueryClientProvider + providerA.client = clientA + const providerB = document.createElement( + providerTagName, + ) as QueryClientProvider + providerB.client = clientB + + const consumer = document.createElement( + mutationHostTagName, + ) as MutationSwitchHostElement + providerA.append(consumer) + + document.body.append(providerA) + await providerA.updateComplete + await Promise.resolve() + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(1)).resolves.toBe(2) + + consumer.remove() + await new Promise((resolve) => setTimeout(resolve, 0)) + providerA.remove() + + providerB.append(consumer) + document.body.append(providerB) + await providerB.updateComplete + await Promise.resolve() + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(2)).resolves.toBe(3) + expect( + clientA.getMutationCache().findAll({ mutationKey: consumer.mutationKey }) + .length, + ).toBeGreaterThan(0) + expect( + clientB.getMutationCache().findAll({ mutationKey: consumer.mutationKey }) + .length, + ).toBeGreaterThan(0) + + consumer.mutation.destroy() + providerB.remove() + await Promise.resolve() + }) + + it('reparents queries controller under a different provider without cross-tree leakage', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const providerA = document.createElement( + providerTagName, + ) as QueryClientProvider + providerA.client = clientA + const providerB = document.createElement( + providerTagName, + ) as QueryClientProvider + providerB.client = clientB + + const consumer = document.createElement( + queriesHostTagName, + ) as QueriesSwitchHostElement + providerA.append(consumer) + + document.body.append(providerA) + await providerA.updateComplete + + await waitFor(() => typeof consumer.queries()[0] === 'string') + + consumer.remove() + await waitFor( + () => + (clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0) === 0, + ) + providerA.remove() + + providerB.append(consumer) + document.body.append(providerB) + await providerB.updateComplete + await waitFor( + () => + typeof consumer.queries()[0] === 'string' && consumer.queryCalls >= 2, + ) + + expect( + clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0, + ).toBe(0) + expect( + clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount(), + ).toBe(1) + + consumer.queries.destroy() + providerA.remove() + providerB.remove() + await Promise.resolve() + }) + + it('reparents infinite query controller under a different provider and binds the new nearest client', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const providerA = document.createElement( + providerTagName, + ) as QueryClientProvider + providerA.client = clientA + const providerB = document.createElement( + providerTagName, + ) as QueryClientProvider + providerB.client = clientB + + const consumer = document.createElement( + infiniteHostTagName, + ) as InfiniteSwitchHostElement + providerA.append(consumer) + + document.body.append(providerA) + await providerA.updateComplete + + await waitFor(() => consumer.infinite().isSuccess) + + consumer.remove() + await waitFor( + () => + (clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0) === 0, + ) + providerA.remove() + + providerB.append(consumer) + document.body.append(providerB) + await providerB.updateComplete + await waitFor( + () => + consumer.infinite().isSuccess && + (consumer.infinite().data?.pages.length ?? 0) >= 1 && + consumer.pageCalls >= 2, + ) + + expect( + clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0, + ).toBe(0) + expect( + clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount(), + ).toBe(1) + + consumer.infinite.destroy() + providerA.remove() + providerB.remove() + await Promise.resolve() + }) +}) diff --git a/packages/lit-query/src/tests/context-provider.test.ts b/packages/lit-query/src/tests/context-provider.test.ts new file mode 100644 index 00000000000..8428e0b75f1 --- /dev/null +++ b/packages/lit-query/src/tests/context-provider.test.ts @@ -0,0 +1,213 @@ +import { describe, expect, it, vi } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import { createQueryController } from '../createQueryController.js' +import { + getDefaultQueryClient, + resolveQueryClient, + useQueryClient, +} from '../index.js' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { + TestElementHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const tagName = 'test-query-client-provider' +if (!customElements.get(tagName)) { + customElements.define(tagName, QueryClientProvider) +} + +class ProviderContextConsumerElement extends TestElementHost { + readonly query = createQueryController(this, { + queryKey: ['provider-context-consumer'] as const, + queryFn: async () => 'ok', + retry: false, + }) +} + +const consumerTagName = 'test-query-client-provider-consumer' +if (!customElements.get(consumerTagName)) { + customElements.define(consumerTagName, ProviderContextConsumerElement) +} + +describe('QueryClientProvider/context', () => { + it('registers and unregisters the default query client for public helpers', async () => { + const client = new QueryClient() + const provider = document.createElement(tagName) as QueryClientProvider + provider.client = client + + document.body.append(provider) + await provider.updateComplete + + expect(useQueryClient()).toBe(client) + expect(resolveQueryClient()).toBe(client) + + provider.remove() + await Promise.resolve() + + expect(() => useQueryClient()).toThrowError(/No QueryClient available/) + }) + + it('prefers an explicit client in resolveQueryClient', () => { + const explicit = new QueryClient() + expect(resolveQueryClient(explicit)).toBe(explicit) + }) + + it('keeps the default client registered until the last provider using it disconnects', async () => { + const client = new QueryClient() + const providerA = document.createElement(tagName) as QueryClientProvider + const providerB = document.createElement(tagName) as QueryClientProvider + providerA.client = client + providerB.client = client + + document.body.append(providerA) + document.body.append(providerB) + await providerA.updateComplete + await providerB.updateComplete + + expect(useQueryClient()).toBe(client) + + providerB.remove() + await Promise.resolve() + + expect(useQueryClient()).toBe(client) + + providerA.remove() + await Promise.resolve() + + expect(() => useQueryClient()).toThrowError(/No QueryClient available/) + }) + + it('throws when multiple different providers make global lookup ambiguous', async () => { + const clientA = new QueryClient() + const clientB = new QueryClient() + const providerA = document.createElement(tagName) as QueryClientProvider + const providerB = document.createElement(tagName) as QueryClientProvider + providerA.client = clientA + providerB.client = clientB + + document.body.append(providerA) + document.body.append(providerB) + await providerA.updateComplete + await providerB.updateComplete + + expect(getDefaultQueryClient()).toBeUndefined() + expect(() => useQueryClient()).toThrowError( + /Multiple QueryClients are mounted/, + ) + expect(() => resolveQueryClient()).toThrowError( + /Multiple QueryClients are mounted/, + ) + + providerB.remove() + await Promise.resolve() + + expect(getDefaultQueryClient()).toBe(clientA) + expect(useQueryClient()).toBe(clientA) + + providerA.remove() + await Promise.resolve() + }) + + it('requires an explicit client before connect', () => { + const provider = document.createElement(tagName) as QueryClientProvider + expect(() => provider.connectedCallback()).toThrowError( + /No QueryClient available/, + ) + }) + + it('S8: provider swap while disconnected preserves mount/unmount contract', async () => { + const clientA = new QueryClient() + const clientB = new QueryClient() + + const mountA = vi.spyOn(clientA, 'mount') + const unmountA = vi.spyOn(clientA, 'unmount') + const mountB = vi.spyOn(clientB, 'mount') + const unmountB = vi.spyOn(clientB, 'unmount') + + const provider = document.createElement(tagName) as QueryClientProvider + provider.client = clientA + + document.body.append(provider) + await provider.updateComplete + + expect(mountA).toHaveBeenCalledTimes(1) + expect(unmountA).toHaveBeenCalledTimes(0) + expect(mountB).toHaveBeenCalledTimes(0) + expect(unmountB).toHaveBeenCalledTimes(0) + + provider.remove() + await Promise.resolve() + + expect(unmountA).toHaveBeenCalledTimes(1) + expect(mountB).toHaveBeenCalledTimes(0) + + provider.client = clientB + await provider.updateComplete + + expect(unmountA).toHaveBeenCalledTimes(1) + expect(mountB).toHaveBeenCalledTimes(0) + + document.body.append(provider) + await provider.updateComplete + + expect(mountA).toHaveBeenCalledTimes(1) + expect(unmountA).toHaveBeenCalledTimes(1) + expect(mountB).toHaveBeenCalledTimes(1) + expect(unmountB).toHaveBeenCalledTimes(0) + + provider.remove() + await Promise.resolve() + + expect(unmountB).toHaveBeenCalledTimes(1) + + mountA.mockRestore() + unmountA.mockRestore() + mountB.mockRestore() + unmountB.mockRestore() + }) + + it('LC-PROVIDER-01: invalid connected client updates tear down the mounted client before surfacing the error', async () => { + const client = new QueryClient() + const mount = vi.spyOn(client, 'mount') + const unmount = vi.spyOn(client, 'unmount') + + const provider = document.createElement(tagName) as QueryClientProvider + const consumer = document.createElement( + consumerTagName, + ) as ProviderContextConsumerElement + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + await waitFor(() => consumer.query().isSuccess) + + expect(mount).toHaveBeenCalledTimes(1) + expect(unmount).toHaveBeenCalledTimes(0) + expect(consumer.query().data).toBe('ok') + + provider.client = undefined as unknown as QueryClient + await expect(provider.updateComplete).rejects.toThrow( + /No QueryClient available/, + ) + expect(unmount).toHaveBeenCalledTimes(1) + expect(getDefaultQueryClient()).toBeUndefined() + expect(() => useQueryClient()).toThrowError(/No QueryClient available/) + await waitForMissingQueryClient(() => consumer.query()) + await expect(consumer.query.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.query.destroy() + provider.remove() + await Promise.resolve() + + expect(unmount).toHaveBeenCalledTimes(1) + + mount.mockRestore() + unmount.mockRestore() + }) +}) diff --git a/packages/lit-query/src/tests/counters-and-state.test.ts b/packages/lit-query/src/tests/counters-and-state.test.ts new file mode 100644 index 00000000000..3a28b5a5161 --- /dev/null +++ b/packages/lit-query/src/tests/counters-and-state.test.ts @@ -0,0 +1,648 @@ +import { describe, expect, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createMutationController } from '../createMutationController.js' +import { createQueryController } from '../createQueryController.js' +import { useIsFetching } from '../useIsFetching.js' +import { useIsMutating } from '../useIsMutating.js' +import { useMutationState } from '../useMutationState.js' +import { + TestControllerHost, + TestElementHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const providerTagName = 'test-query-client-provider-counters' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +let explicitCountersClient: QueryClient | undefined + +class ContextCountersHostElement extends TestElementHost { + readonly queryKey = ['context-counters', 'query'] as const + readonly mutationKey = ['context-counters', 'mutation'] as const + + private resolveQuery: (() => void) | undefined + private resolveMutation: (() => void) | undefined + + readonly query = createQueryController( + this, + { + queryKey: this.queryKey, + queryFn: () => + new Promise((resolve) => { + this.resolveQuery = () => resolve('query-ok') + }), + retry: false, + }, + explicitCountersClient, + ) + + readonly mutation = createMutationController( + this, + { + mutationKey: this.mutationKey, + mutationFn: () => + new Promise((resolve) => { + this.resolveMutation = () => resolve('mutation-ok') + }), + }, + explicitCountersClient, + ) + + readonly isFetching = useIsFetching( + this, + { queryKey: this.queryKey }, + explicitCountersClient, + ) + + readonly isMutating = useIsMutating( + this, + { mutationKey: this.mutationKey }, + explicitCountersClient, + ) + + readonly mutationStatuses = useMutationState( + this, + { + filters: { mutationKey: this.mutationKey }, + select: (mutation) => mutation.state.status, + }, + explicitCountersClient, + ) + + resolvePendingQuery(): void { + this.resolveQuery?.() + } + + resolvePendingMutation(): void { + this.resolveMutation?.() + } +} + +const contextCountersTagName = 'test-context-counters-host' +if (!customElements.get(contextCountersTagName)) { + customElements.define(contextCountersTagName, ContextCountersHostElement) +} + +describe('useIsFetching/useIsMutating/useMutationState', () => { + it('LC-COUNTERS-01: pre-connect placeholders stay zero/empty until a provider binds', async () => { + const consumer = document.createElement( + contextCountersTagName, + ) as ContextCountersHostElement + + expect(consumer.query().status).toBe('pending') + expect(consumer.isFetching()).toBe(0) + expect(consumer.isMutating()).toBe(0) + expect(consumer.mutationStatuses()).toEqual([]) + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.isFetching() === 1) + consumer.resolvePendingQuery() + await waitFor(() => consumer.query().isSuccess) + await waitFor(() => consumer.isFetching() === 0) + + consumer.mutation.mutate() + await waitFor(() => consumer.isMutating() === 1) + consumer.resolvePendingMutation() + await waitFor(() => consumer.isMutating() === 0) + await waitFor(() => consumer.mutationStatuses().includes('success')) + + consumer.query.destroy() + consumer.mutation.destroy() + consumer.isFetching.destroy() + consumer.isMutating.destroy() + consumer.mutationStatuses.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-COUNTERS-02: explicit client takes precedence over provider context', async () => { + const explicitClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }) + const providerClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }) + explicitCountersClient = explicitClient + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = providerClient + + const consumer = document.createElement( + contextCountersTagName, + ) as ContextCountersHostElement + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.isFetching() === 1) + consumer.resolvePendingQuery() + await waitFor(() => consumer.query().isSuccess) + + consumer.mutation.mutate() + await waitFor(() => consumer.isMutating() === 1) + consumer.resolvePendingMutation() + await waitFor(() => consumer.isMutating() === 0) + + expect( + explicitClient.getQueryCache().find({ queryKey: consumer.queryKey }), + ).toBeDefined() + expect( + providerClient.getQueryCache().find({ queryKey: consumer.queryKey }), + ).toBeUndefined() + expect( + explicitClient + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length, + ).toBeGreaterThan(0) + expect( + providerClient + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length, + ).toBe(0) + + consumer.query.destroy() + consumer.mutation.destroy() + consumer.isFetching.destroy() + consumer.isMutating.destroy() + consumer.mutationStatuses.destroy() + provider.remove() + explicitCountersClient = undefined + await Promise.resolve() + }) + + it('tracks fetch/mutate counters and mutation state', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + + const query = createQueryController( + host, + { + queryKey: ['counter-test'], + queryFn: async () => { + await new Promise((resolve) => setTimeout(resolve, 40)) + return 'done' + }, + }, + client, + ) + + const mutation = createMutationController( + host, + { + mutationFn: async (value: number) => { + await new Promise((resolve) => setTimeout(resolve, 40)) + return value + 10 + }, + }, + client, + ) + + const isFetching = useIsFetching(host, {}, client) + const isMutating = useIsMutating(host, {}, client) + const mutationStatuses = useMutationState( + host, + { + select: (item) => item.state.status, + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => isFetching() === 1) + await waitFor(() => query().isSuccess) + await waitFor(() => isFetching() === 0) + + mutation.mutate(1) + await waitFor(() => isMutating() === 1) + await waitFor(() => isMutating() === 0) + await waitFor(() => mutationStatuses().includes('success')) + }) + + it('S1: useIsFetching tracks filters and filter reactivity', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let resolveA: (() => void) | undefined + let resolveB: (() => void) | undefined + let activeFilter: { queryKey?: readonly string[] } = { + queryKey: ['fetch-a'], + } + + createQueryController( + host, + { + queryKey: ['fetch-a'], + queryFn: () => + new Promise((resolve) => { + resolveA = () => resolve('a') + }), + }, + client, + ) + + createQueryController( + host, + { + queryKey: ['fetch-b'], + queryFn: () => + new Promise((resolve) => { + resolveB = () => resolve('b') + }), + }, + client, + ) + + const isFetchingAll = useIsFetching(host, {}, client) + const isFetchingFiltered = useIsFetching(host, () => activeFilter, client) + + host.connect() + host.update() + + await waitFor(() => isFetchingAll() === 2) + await waitFor(() => isFetchingFiltered() === 1) + + activeFilter = { queryKey: ['fetch-b'] } + host.update() + await waitFor(() => isFetchingFiltered() === 1) + + resolveA?.() + await waitFor(() => isFetchingAll() === 1) + await waitFor(() => isFetchingFiltered() === 1) + + resolveB?.() + await waitFor(() => isFetchingAll() === 0) + await waitFor(() => isFetchingFiltered() === 0) + }) + + it('S2: useIsMutating tracks mutation filters and reactivity', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + let resolveA: (() => void) | undefined + let resolveB: (() => void) | undefined + let activeFilter: { mutationKey?: readonly string[] } = { + mutationKey: ['mut-a'], + } + + const mutationA = createMutationController( + host, + { + mutationKey: ['mut-a'], + mutationFn: () => + new Promise((resolve) => { + resolveA = () => resolve(1) + }), + }, + client, + ) + + const mutationB = createMutationController( + host, + { + mutationKey: ['mut-b'], + mutationFn: () => + new Promise((resolve) => { + resolveB = () => resolve(2) + }), + }, + client, + ) + + const isMutatingAll = useIsMutating(host, {}, client) + const isMutatingFiltered = useIsMutating(host, () => activeFilter, client) + + host.connect() + host.update() + + mutationA.mutate() + mutationB.mutate() + + await waitFor(() => isMutatingAll() === 2) + await waitFor(() => isMutatingFiltered() === 1) + + activeFilter = { mutationKey: ['mut-b'] } + host.update() + await waitFor(() => isMutatingFiltered() === 1) + + resolveA?.() + await waitFor(() => isMutatingAll() === 1) + await waitFor(() => isMutatingFiltered() === 1) + + resolveB?.() + await waitFor(() => isMutatingAll() === 0) + await waitFor(() => isMutatingFiltered() === 0) + }) + + it('S3: useMutationState selects and filters by mutation key/status', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + let activeFilter: { mutationKey?: readonly string[] } = { + mutationKey: ['state-a'], + } + + const mutationA = createMutationController( + host, + { + mutationKey: ['state-a'], + mutationFn: async () => 'ok', + }, + client, + ) + + const mutationB = createMutationController( + host, + { + mutationKey: ['state-b'], + mutationFn: async () => { + throw new Error('state-b-failure') + }, + }, + client, + ) + + const mutationStatuses = useMutationState( + host, + { + filters: () => activeFilter, + select: (item) => item.state.status, + }, + client, + ) + + host.connect() + host.update() + + await expect(mutationA.mutateAsync(undefined)).resolves.toBe('ok') + await expect(mutationB.mutateAsync(undefined)).rejects.toThrow( + 'state-b-failure', + ) + + await waitFor( + () => + mutationStatuses().length === 1 && mutationStatuses()[0] === 'success', + ) + + activeFilter = { mutationKey: ['state-b'] } + host.update() + + await waitFor( + () => + mutationStatuses().length === 1 && mutationStatuses()[0] === 'error', + ) + }) + + it('S4: useMutationState refreshes when the select closure changes on host update', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + let label = 'before' + + const mutation = createMutationController( + host, + { + mutationKey: ['state-select-reactivity'], + mutationFn: async () => 'ok', + }, + client, + ) + + const mutationLabels = useMutationState( + host, + { + filters: { + mutationKey: ['state-select-reactivity'], + }, + select: () => label, + }, + client, + ) + + host.connect() + host.update() + + await expect(mutation.mutateAsync(undefined)).resolves.toBe('ok') + await waitFor( + () => mutationLabels().length === 1 && mutationLabels()[0] === 'before', + ) + + label = 'after' + host.update() + + await waitFor( + () => mutationLabels().length === 1 && mutationLabels()[0] === 'after', + ) + + mutation.destroy() + mutationLabels.destroy() + }) + + it('LC-COUNTERS-03: read-only helpers fail after handshake and recover under a provider', async () => { + const consumer = document.createElement( + contextCountersTagName, + ) as ContextCountersHostElement + + expect(consumer.query().status).toBe('pending') + expect(consumer.isFetching()).toBe(0) + expect(consumer.isMutating()).toBe(0) + expect(consumer.mutationStatuses()).toEqual([]) + + document.body.append(consumer) + await waitForMissingQueryClient(() => consumer.query()) + + expect(() => consumer.isFetching()).toThrow(/No QueryClient available/) + expect(() => consumer.isMutating()).toThrow(/No QueryClient available/) + expect(() => consumer.mutationStatuses()).toThrow( + /No QueryClient available/, + ) + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.isFetching() === 1) + consumer.resolvePendingQuery() + await waitFor(() => consumer.query().isSuccess) + await waitFor(() => consumer.isFetching() === 0) + + consumer.query.destroy() + consumer.mutation.destroy() + consumer.isFetching.destroy() + consumer.isMutating.destroy() + consumer.mutationStatuses.destroy() + provider.remove() + await Promise.resolve() + }) + + it('ALREADYCONN-COUNTERS-01: read-only helpers on already-connected host with explicit client do not throw', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }) + + const producerHost = new TestControllerHost() + let resolveQuery: (() => void) | undefined + let resolveMutation: (() => void) | undefined + + createQueryController( + producerHost, + { + queryKey: ['already-connected-counters-query'], + queryFn: () => + new Promise((resolve) => { + resolveQuery = () => resolve('query-ok') + }), + retry: false, + }, + client, + ) + + const producerMutation = createMutationController( + producerHost, + { + mutationKey: ['already-connected-counters-mutation'], + mutationFn: () => + new Promise((resolve) => { + resolveMutation = () => resolve('mutation-ok') + }), + }, + client, + ) + + producerHost.connect() + producerHost.update() + await waitFor(() => client.isFetching() === 1) + + producerMutation.mutate() + await waitFor(() => client.isMutating() === 1) + + class AlreadyConnectedHost implements ReactiveControllerHost { + private readonly controllers = new Set() + private isConnected = true + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + const host = new AlreadyConnectedHost() + const isFetching = useIsFetching(host, {}, client) + const isMutating = useIsMutating(host, {}, client) + const mutationStatuses = useMutationState( + host, + { + filters: { mutationKey: ['already-connected-counters-mutation'] }, + select: (mutation) => mutation.state.status, + }, + client, + ) + + await Promise.resolve() + await Promise.resolve() + + await waitFor(() => isFetching() === 1) + await waitFor(() => isMutating() === 1) + await waitFor(() => mutationStatuses().includes('pending')) + + resolveQuery?.() + resolveMutation?.() + + await waitFor(() => isFetching() === 0) + await waitFor(() => isMutating() === 0) + await waitFor(() => mutationStatuses().includes('success')) + + isFetching.destroy() + isMutating.destroy() + mutationStatuses.destroy() + producerMutation.destroy() + }) +}) diff --git a/packages/lit-query/src/tests/infinite-and-options.test.ts b/packages/lit-query/src/tests/infinite-and-options.test.ts new file mode 100644 index 00000000000..cd3418f69fd --- /dev/null +++ b/packages/lit-query/src/tests/infinite-and-options.test.ts @@ -0,0 +1,423 @@ +import { describe, expect, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createInfiniteQueryController } from '../createInfiniteQueryController.js' +import { createMutationController } from '../createMutationController.js' +import { createQueryController } from '../createQueryController.js' +import { infiniteQueryOptions } from '../infiniteQueryOptions.js' +import { mutationOptions } from '../mutationOptions.js' +import { queryOptions } from '../queryOptions.js' +import { + TestControllerHost, + TestElementHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const providerTagName = 'test-query-client-provider-infinite' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +let explicitInfiniteClient: QueryClient | undefined + +class ContextInfiniteHostElement extends TestElementHost { + readonly queryKey = ['context-infinite'] as const + + readonly infinite = createInfiniteQueryController( + this, + { + queryKey: this.queryKey, + initialPageParam: 0, + queryFn: async ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => (lastPage < 1 ? lastPage + 1 : undefined), + getPreviousPageParam: (firstPage) => + firstPage > -1 ? firstPage - 1 : undefined, + retry: false, + }, + explicitInfiniteClient, + ) +} + +const contextInfiniteTagName = 'test-context-infinite-host' +if (!customElements.get(contextInfiniteTagName)) { + customElements.define(contextInfiniteTagName, ContextInfiniteHostElement) +} + +describe('createInfiniteQueryController', () => { + it('LC-INF-01: first provider connection resolves from the pre-connect placeholder state', async () => { + const consumer = document.createElement( + contextInfiniteTagName, + ) as ContextInfiniteHostElement + + expect(consumer.infinite().status).toBe('pending') + await expect(consumer.infinite.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(consumer.infinite.fetchNextPage()).rejects.toThrow( + /No QueryClient available/, + ) + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.infinite().isSuccess) + expect(consumer.infinite().data?.pages).toEqual([0]) + + consumer.infinite.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-INF-02: explicit client takes precedence over provider context', async () => { + const explicitClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const providerClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + explicitInfiniteClient = explicitClient + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = providerClient + + const consumer = document.createElement( + contextInfiniteTagName, + ) as ContextInfiniteHostElement + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.infinite().isSuccess) + expect(consumer.infinite().data?.pages).toEqual([0]) + expect( + explicitClient.getQueryCache().find({ queryKey: consumer.queryKey }), + ).toBeDefined() + expect( + providerClient.getQueryCache().find({ queryKey: consumer.queryKey }), + ).toBeUndefined() + + consumer.infinite.destroy() + provider.remove() + explicitInfiniteClient = undefined + await Promise.resolve() + }) + + it('M14: supports initial page, fetchNextPage, and fetchPreviousPage', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const infinite = createInfiniteQueryController( + host, + { + queryKey: ['m14', 'infinite'], + initialPageParam: 0, + queryFn: async ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => + lastPage < 1 ? lastPage + 1 : undefined, + getPreviousPageParam: (firstPage) => + firstPage > -1 ? firstPage - 1 : undefined, + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => infinite().isSuccess) + expect(infinite().data?.pages).toEqual([0]) + + await infinite.fetchNextPage() + await waitFor(() => (infinite().data?.pages.length ?? 0) === 2) + expect(infinite().data?.pages).toEqual([0, 1]) + + await infinite.fetchPreviousPage() + await waitFor(() => (infinite().data?.pages.length ?? 0) === 3) + expect(infinite().data?.pages).toEqual([-1, 0, 1]) + }) + + it('INFEDGE-01: next-page failure preserves prior pages consistently', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const infinite = createInfiniteQueryController( + host, + { + queryKey: ['infedge-01'], + initialPageParam: 0, + queryFn: async ({ pageParam }) => { + const page = Number(pageParam) + if (page === 1) { + throw new Error('next-page-failed') + } + return page + }, + getNextPageParam: (lastPage) => + lastPage < 1 ? lastPage + 1 : undefined, + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => infinite().isSuccess) + expect(infinite().data?.pages).toEqual([0]) + + const nextPageResult = await infinite.fetchNextPage() + expect(nextPageResult.isFetchNextPageError).toBe(true) + expect(nextPageResult.error).toBeInstanceOf(Error) + await waitFor(() => infinite().isFetchNextPageError) + expect(infinite().data?.pages).toEqual([0]) + }) + + it('LC-INF-03: missing provider fails deterministically and imperative methods align', async () => { + const consumer = document.createElement( + contextInfiniteTagName, + ) as ContextInfiniteHostElement + const placeholderResult = consumer.infinite() + + expect(placeholderResult.status).toBe('pending') + + document.body.append(consumer) + + expect(() => consumer.infinite()).not.toThrow() + await waitForMissingQueryClient(() => consumer.infinite()) + + await expect(consumer.infinite.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(consumer.infinite.fetchNextPage()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(placeholderResult.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(placeholderResult.fetchNextPage()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(placeholderResult.fetchPreviousPage()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.infinite.destroy() + consumer.remove() + await Promise.resolve() + }) + + it('ALREADYCONN-INF-01: infinite query controller on already-connected host with explicit client does not throw', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + client.setQueryData(['already-connected-infinite'], { + pages: [0], + pageParams: [0], + }) + + class AlreadyConnectedHost implements ReactiveControllerHost { + private readonly controllers = new Set() + private isConnected = true + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + const host = new AlreadyConnectedHost() + const infinite = createInfiniteQueryController( + host, + { + queryKey: ['already-connected-infinite'], + initialPageParam: 0, + queryFn: async ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => + lastPage < 1 ? lastPage + 1 : undefined, + staleTime: 30_000, + }, + client, + ) + + await Promise.resolve() + await Promise.resolve() + + expect(infinite().isSuccess).toBe(true) + expect(infinite().data?.pages).toEqual([0]) + + infinite.destroy() + }) + + it('LC-INF-04: explicit-client infinite accessors defer until host fields are initialized', () => { + const client = new QueryClient() + + class DeferredExplicitInfiniteHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly infinite = createInfiniteQueryController( + this, + () => ({ + queryKey: ['deferred-explicit-infinite', this.id] as const, + initialPageParam: 0, + queryFn: async ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => + lastPage < 1 ? lastPage + 1 : undefined, + retry: false, + }), + client, + ) + + readonly firstRead = this.infinite() + readonly id = 'alpha' + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new DeferredExplicitInfiniteHost()).not.toThrow() + + const host = new DeferredExplicitInfiniteHost() + expect(host.infinite().status).toBe('pending') + + host.infinite.destroy() + }) +}) + +describe('options helpers integration', () => { + it('OPT-01: queryOptions integrates with createQueryController', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const host = new TestControllerHost() + + const query = createQueryController( + host, + queryOptions({ + queryKey: ['opt-01', 'query'] as const, + queryFn: async () => 'query-ok', + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe('query-ok') + }) + + it('OPT-01: mutationOptions integrates with createMutationController', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const mutation = createMutationController( + host, + mutationOptions({ + mutationFn: async (value: number) => value + 10, + }), + client, + ) + + host.connect() + host.update() + await expect(mutation.mutateAsync(5)).resolves.toBe(15) + expect(mutation().isSuccess).toBe(true) + }) + + it('OPT-01: infiniteQueryOptions integrates with createInfiniteQueryController', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const host = new TestControllerHost() + + const infinite = createInfiniteQueryController( + host, + infiniteQueryOptions({ + queryKey: ['opt-01', 'infinite'], + initialPageParam: 0, + queryFn: async ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => + lastPage < 1 ? lastPage + 1 : undefined, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => infinite().isSuccess) + expect(infinite().data?.pages).toEqual([0]) + }) +}) diff --git a/packages/lit-query/src/tests/mutation-controller.test.ts b/packages/lit-query/src/tests/mutation-controller.test.ts new file mode 100644 index 00000000000..6353cc5eed5 --- /dev/null +++ b/packages/lit-query/src/tests/mutation-controller.test.ts @@ -0,0 +1,472 @@ +import { describe, expect, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createMutationController } from '../createMutationController.js' +import { + TestControllerHost, + TestElementHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const providerTagName = 'test-query-client-provider-mutation' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +let explicitMutationClient: QueryClient | undefined + +class ContextMutationHostElement extends TestElementHost { + readonly mutationKey = ['context-mutation'] as const + + readonly mutation = createMutationController( + this, + { + mutationKey: this.mutationKey, + mutationFn: async (value: number) => value + 1, + }, + explicitMutationClient, + ) +} + +const contextMutationTagName = 'test-context-mutation-host' +if (!customElements.get(contextMutationTagName)) { + customElements.define(contextMutationTagName, ContextMutationHostElement) +} + +describe('createMutationController', () => { + it('LC-MUT-01: first provider connection resolves from the pre-connect placeholder state', async () => { + const consumer = document.createElement( + contextMutationTagName, + ) as ContextMutationHostElement + + expect(consumer.mutation().isIdle).toBe(true) + expect(() => consumer.mutation.mutate(1)).toThrowError( + /No QueryClient available/, + ) + await expect(consumer.mutation.mutateAsync(1)).rejects.toThrow( + /No QueryClient available/, + ) + + const client = new QueryClient() + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(1)).resolves.toBe(2) + expect(consumer.mutation().isSuccess).toBe(true) + + consumer.mutation.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-MUT-02: explicit client takes precedence over provider context', async () => { + const explicitClient = new QueryClient() + const providerClient = new QueryClient() + explicitMutationClient = explicitClient + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = providerClient + + const consumer = document.createElement( + contextMutationTagName, + ) as ContextMutationHostElement + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(2)).resolves.toBe(3) + + expect( + explicitClient + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length, + ).toBeGreaterThan(0) + expect( + providerClient + .getMutationCache() + .findAll({ mutationKey: consumer.mutationKey }).length, + ).toBe(0) + + consumer.mutation.destroy() + provider.remove() + explicitMutationClient = undefined + await Promise.resolve() + }) + + it('supports mutate and mutateAsync paths', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const mutation = createMutationController( + host, + { + mutationFn: async (value: number) => value + 1, + }, + client, + ) + + host.connect() + host.update() + + const result = await mutation.mutateAsync(1) + expect(result).toBe(2) + expect(mutation().isSuccess).toBe(true) + expect(mutation().data).toBe(2) + + mutation.mutate(2) + await waitFor(() => mutation().data === 3) + expect(mutation().isSuccess).toBe(true) + }) + + it('M9: mutation state transitions cover idle/pending/success/error', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const mutation = createMutationController( + host, + { + mutationFn: async (value: number) => { + await new Promise((resolve) => setTimeout(resolve, 10)) + if (value < 0) { + throw new Error('negative-not-allowed') + } + return value + 1 + }, + }, + client, + ) + + host.connect() + host.update() + + expect(mutation().isIdle).toBe(true) + + const successPromise = mutation.mutateAsync(10) + await waitFor(() => mutation().isPending) + await expect(successPromise).resolves.toBe(11) + await waitFor(() => mutation().isSuccess) + expect(mutation().data).toBe(11) + + const errorPromise = mutation.mutateAsync(-1) + await waitFor(() => mutation().isPending) + await expect(errorPromise).rejects.toThrow('negative-not-allowed') + await waitFor(() => mutation().isError) + expect(mutation().error).toBeInstanceOf(Error) + }) + + it('M10: reset clears mutation state back to idle baseline', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const mutation = createMutationController( + host, + { + mutationFn: async () => { + throw new Error('reset-target') + }, + }, + client, + ) + + host.connect() + host.update() + + await expect(mutation.mutateAsync(undefined)).rejects.toThrow( + 'reset-target', + ) + await waitFor(() => mutation().isError) + expect(mutation().error).toBeInstanceOf(Error) + + mutation.reset() + expect(mutation().isIdle).toBe(true) + expect(mutation().isPaused).toBe(false) + expect(mutation().isError).toBe(false) + expect(mutation().error).toBeNull() + expect(mutation().data).toBeUndefined() + }) + + it('M11: mutate is non-throwing while mutateAsync rejects on error', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const mutation = createMutationController( + host, + { + mutationFn: async (value: number) => { + if (value < 0) { + throw new Error('negative-not-allowed') + } + + return value + 1 + }, + }, + client, + ) + + host.connect() + host.update() + + expect(() => mutation.mutate(-1)).not.toThrow() + await waitFor(() => mutation().isError) + expect(mutation().error).toBeInstanceOf(Error) + + await expect(mutation.mutateAsync(-1)).rejects.toThrow( + 'negative-not-allowed', + ) + }) + + it('M12: mutation callback order and call counts are deterministic', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + const callbackEvents: string[] = [] + + const mutation = createMutationController( + host, + { + mutationFn: async (value: number) => { + await new Promise((resolve) => setTimeout(resolve, 5)) + if (value < 0) { + throw new Error('callback-order-failure') + } + return value + 1 + }, + onSuccess: (_data, value) => { + callbackEvents.push(`success:${value}`) + }, + onError: (_error, value) => { + callbackEvents.push(`error:${value}`) + }, + onSettled: (_data, _error, value) => { + callbackEvents.push(`settled:${value}`) + }, + }, + client, + ) + + host.connect() + host.update() + + await expect(mutation.mutateAsync(1)).resolves.toBe(2) + await expect(mutation.mutateAsync(-1)).rejects.toThrow( + 'callback-order-failure', + ) + + expect(callbackEvents).toEqual([ + 'success:1', + 'settled:1', + 'error:-1', + 'settled:-1', + ]) + }) + + it('AREACT-02: refreshed mutation callbacks use latest closures', async () => { + const client = new QueryClient() + const host = new TestControllerHost() + const callbackEvents: string[] = [] + let version = 'v1' + + const mutation = createMutationController( + host, + () => ({ + mutationFn: async (value: number) => { + if (value < 0) { + throw new Error('freshness-failure') + } + return value + 1 + }, + onSuccess: () => { + callbackEvents.push(`success:${version}`) + }, + onError: () => { + callbackEvents.push(`error:${version}`) + }, + onSettled: () => { + callbackEvents.push(`settled:${version}`) + }, + }), + client, + ) + + host.connect() + host.update() + + await expect(mutation.mutateAsync(1)).resolves.toBe(2) + expect(callbackEvents.slice(0, 2)).toEqual(['success:v1', 'settled:v1']) + + version = 'v2' + host.update() + + await expect(mutation.mutateAsync(-1)).rejects.toThrow('freshness-failure') + expect(callbackEvents.slice(2)).toEqual(['error:v2', 'settled:v2']) + }) + + it('LC-MUT-03: missing provider becomes a deterministic missing-client state', async () => { + const consumer = document.createElement( + contextMutationTagName, + ) as ContextMutationHostElement + const placeholderResult = consumer.mutation() + + expect(placeholderResult.isIdle).toBe(true) + expect(placeholderResult.isPaused).toBe(false) + + document.body.append(consumer) + + expect(() => consumer.mutation()).not.toThrow() + await waitForMissingQueryClient(() => consumer.mutation()) + + expect(() => consumer.mutation.mutate(1)).toThrow( + /No QueryClient available/, + ) + await expect(consumer.mutation.mutateAsync(1)).rejects.toThrow( + /No QueryClient available/, + ) + await expect(placeholderResult.mutate(1)).rejects.toThrow( + /No QueryClient available/, + ) + + expect(() => consumer.mutation.reset()).not.toThrow() + expect(() => placeholderResult.reset()).not.toThrow() + + consumer.mutation.destroy() + consumer.remove() + await Promise.resolve() + }) + + it('LC-MUT-04: later valid provider adoption recovers without reconstruction', async () => { + const consumer = document.createElement( + contextMutationTagName, + ) as ContextMutationHostElement + + document.body.append(consumer) + await waitForMissingQueryClient(() => consumer.mutation()) + + const client = new QueryClient() + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await Promise.resolve() + + await expect(consumer.mutation.mutateAsync(1)).resolves.toBe(2) + expect(consumer.mutation().isSuccess).toBe(true) + + consumer.mutation.destroy() + provider.remove() + await Promise.resolve() + }) + + it('ALREADYCONN-MUT-01: mutation controller on already-connected host with explicit client does not throw', async () => { + // Regression test for SSR hydration scenario where controller is created + // during willUpdate on an already-connected host. + const client = new QueryClient() + + // Create a host that simulates Lit's behavior: addController calls + // hostConnected immediately if the host is already connected + class AlreadyConnectedHost { + private readonly controllers = new Set<{ + hostConnected?: () => void + }>() + private isConnected = true + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: { hostConnected?: () => void }): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: { hostConnected?: () => void }): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + const host = new AlreadyConnectedHost() + + // This should NOT throw even though hostConnected runs during construction + const mutation = createMutationController( + host, + { + mutationKey: ['already-connected-mutation-test'], + mutationFn: async (value: number) => value * 2, + }, + client, + ) + + // Wait for the deferred onConnected to complete + await Promise.resolve() + await Promise.resolve() + + // Mutation controller should work correctly + expect(mutation().isIdle).toBe(true) + await expect(mutation.mutateAsync(5)).resolves.toBe(10) + expect(mutation().isSuccess).toBe(true) + + mutation.destroy() + }) + + it('LC-MUT-05: explicit-client mutation accessors defer until host fields are initialized', () => { + const client = new QueryClient() + + class DeferredExplicitMutationHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly mutation = createMutationController( + this, + () => ({ + mutationKey: ['deferred-explicit-mutation', this.id] as const, + mutationFn: async (value: number) => value + this.offset, + }), + client, + ) + + readonly firstRead = this.mutation() + readonly id = 'alpha' + readonly offset = 1 + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new DeferredExplicitMutationHost()).not.toThrow() + + const host = new DeferredExplicitMutationHost() + expect(host.mutation().isIdle).toBe(true) + + host.mutation.destroy() + }) +}) diff --git a/packages/lit-query/src/tests/queries-controller.test.ts b/packages/lit-query/src/tests/queries-controller.test.ts new file mode 100644 index 00000000000..1fd9c536007 --- /dev/null +++ b/packages/lit-query/src/tests/queries-controller.test.ts @@ -0,0 +1,705 @@ +import { describe, expect, it } from 'vitest' +import { QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createQueriesController } from '../createQueriesController.js' +import { queryOptions } from '../queryOptions.js' +import { + TestControllerHost, + TestElementHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const providerTagName = 'test-query-client-provider-queries' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +let explicitQueriesClient: QueryClient | undefined + +class ContextQueriesHostElement extends TestElementHost { + readonly queryKeys = [ + ['context-queries', 'alpha'] as const, + ['context-queries', 'beta'] as const, + ] + + readonly queries = createQueriesController( + this, + { + queries: this.queryKeys.map((queryKey) => ({ + queryKey, + queryFn: async () => queryKey[1], + retry: false, + })), + combine: (results) => + results.map((result) => ({ + status: result.status, + data: result.data, + })), + }, + explicitQueriesClient, + ) +} + +const contextQueriesTagName = 'test-context-queries-host' +if (!customElements.get(contextQueriesTagName)) { + customElements.define(contextQueriesTagName, ContextQueriesHostElement) +} + +class RawContextQueriesHostElement extends TestElementHost { + readonly queryKeys = [ + ['raw-context-queries', 'alpha'] as const, + ['raw-context-queries', 'beta'] as const, + ] + + readonly queries = createQueriesController(this, { + queries: this.queryKeys.map((queryKey) => ({ + queryKey, + queryFn: async () => queryKey[1], + retry: false, + })), + }) +} + +const rawContextQueriesTagName = 'test-raw-context-queries-host' +if (!customElements.get(rawContextQueriesTagName)) { + customElements.define(rawContextQueriesTagName, RawContextQueriesHostElement) +} + +class DeferredFieldsQueriesHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly queries = createQueriesController(this, () => ({ + queries: this.ids.map((id) => ({ + queryKey: ['deferred-fields-queries', id] as const, + queryFn: async () => id, + retry: false, + })), + combine: (results) => results.map((result) => result.status), + })) + + readonly firstRead = this.queries() + readonly ids = ['alpha', 'beta'] as const + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } +} + +describe('createQueriesController', () => { + it('LC-QUERIES-01: first provider connection resolves from the pre-connect placeholder state', async () => { + const consumer = document.createElement( + contextQueriesTagName, + ) as ContextQueriesHostElement + + expect(consumer.queries()).toEqual([ + { status: 'pending', data: undefined }, + { status: 'pending', data: undefined }, + ]) + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor( + () => + consumer.queries()[0]?.status === 'success' && + consumer.queries()[1]?.status === 'success', + ) + expect(consumer.queries().map((item) => item.data)).toEqual([ + 'alpha', + 'beta', + ]) + + consumer.queries.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-QUERIES-02: explicit client takes precedence over provider context', async () => { + const explicitClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const providerClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + explicitQueriesClient = explicitClient + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = providerClient + + const consumer = document.createElement( + contextQueriesTagName, + ) as ContextQueriesHostElement + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor( + () => + consumer.queries()[0]?.status === 'success' && + consumer.queries()[1]?.status === 'success', + ) + + expect( + explicitClient.getQueryCache().find({ queryKey: consumer.queryKeys[0]! }), + ).toBeDefined() + expect( + providerClient.getQueryCache().find({ queryKey: consumer.queryKeys[0]! }), + ).toBeUndefined() + + consumer.queries.destroy() + provider.remove() + explicitQueriesClient = undefined + await Promise.resolve() + }) + + it('combines multiple query results', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + + const queries = createQueriesController( + host, + { + queries: [ + { + queryKey: ['q1'], + queryFn: async () => 'alpha', + }, + { + queryKey: ['q2'], + queryFn: async () => 'beta', + }, + ] as const, + combine: (results) => results.map((result) => result.data), + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => queries().every((value) => typeof value === 'string')) + expect(queries()).toEqual(['alpha', 'beta']) + }) + + it('M13: supports dynamic add/remove and keeps partial failure stability', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let includeThird = false + let includeFailing = true + + const queries = createQueriesController( + host, + () => ({ + queries: [ + { + queryKey: ['m13', 'alpha'] as const, + queryFn: async () => 'alpha', + }, + ...(includeFailing + ? [ + { + queryKey: ['m13', 'failing'] as const, + queryFn: async () => { + throw new Error('m13-fail') + }, + }, + ] + : []), + ...(includeThird + ? [ + { + queryKey: ['m13', 'gamma'] as const, + queryFn: async () => 'gamma', + }, + ] + : []), + ] as const, + combine: (results) => + results.map((result) => ({ + status: result.status, + data: result.data, + error: result.error instanceof Error ? result.error.message : null, + })), + }), + client, + ) + + host.connect() + host.update() + + await waitFor(() => queries().length === 2) + await waitFor( + () => + queries()[0]?.status === 'success' && queries()[1]?.status === 'error', + ) + expect(queries()[0]).toMatchObject({ status: 'success', data: 'alpha' }) + expect(queries()[1]).toMatchObject({ status: 'error', error: 'm13-fail' }) + + includeThird = true + host.update() + await waitFor(() => queries().length === 3) + await waitFor( + () => + queries()[0]?.status === 'success' && + queries()[1]?.status === 'error' && + queries()[2]?.status === 'success', + ) + expect(queries()[2]).toMatchObject({ status: 'success', data: 'gamma' }) + + includeFailing = false + host.update() + await waitFor(() => queries().length === 2) + await waitFor( + () => + queries()[0]?.status === 'success' && + queries()[1]?.status === 'success', + ) + expect(queries().map((item) => item.data)).toEqual(['alpha', 'gamma']) + }) + + it('CQS-ADV-01: reordering queries preserves documented result order mapping', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let order: Array<'first' | 'second'> = ['first', 'second'] + + const queries = createQueriesController( + host, + () => ({ + queries: order.map((id) => ({ + queryKey: ['cqs-adv-01', id] as const, + queryFn: async () => id, + })), + combine: (results) => results.map((result) => result.data), + }), + client, + ) + + host.connect() + host.update() + + await waitFor(() => queries().every((value) => typeof value === 'string')) + expect(queries()).toEqual(['first', 'second']) + + order = ['second', 'first'] + host.update() + await waitFor(() => queries()[0] === 'second' && queries()[1] === 'first') + expect(queries()).toEqual(['second', 'first']) + }) + + it('CQS-ADV-02: duplicate query keys return stable per-index results by contract', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let callCount = 0 + + const queries = createQueriesController( + host, + { + queries: [ + { + queryKey: ['dup-key'] as const, + queryFn: async () => { + callCount += 1 + return 'shared-value' + }, + }, + { + queryKey: ['dup-key'] as const, + queryFn: async () => { + callCount += 1 + return 'shared-value' + }, + }, + ] as const, + combine: (results) => + results.map((result) => ({ + status: result.status, + data: result.data, + })), + }, + client, + ) + + host.connect() + host.update() + + await waitFor( + () => + queries().length === 2 && + queries()[0]?.status === 'success' && + queries()[1]?.status === 'success', + ) + expect(queries()[0]?.data).toBe('shared-value') + expect(queries()[1]?.data).toBe('shared-value') + expect(callCount).toBeGreaterThan(0) + }) + + it('LC-QUERIES-03: missing provider fails after handshake and later provider adoption recovers', async () => { + const consumer = document.createElement( + contextQueriesTagName, + ) as ContextQueriesHostElement + + expect(consumer.queries()).toEqual([ + { status: 'pending', data: undefined }, + { status: 'pending', data: undefined }, + ]) + + document.body.append(consumer) + + expect(() => consumer.queries()).not.toThrow() + await waitForMissingQueryClient(() => consumer.queries()) + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor( + () => + consumer.queries()[0]?.status === 'success' && + consumer.queries()[1]?.status === 'success', + ) + expect(consumer.queries().map((item) => item.data)).toEqual([ + 'alpha', + 'beta', + ]) + + consumer.queries.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-QUERIES-04: raw query results reject placeholder refetch after missing-client handshake', async () => { + const consumer = document.createElement( + rawContextQueriesTagName, + ) as RawContextQueriesHostElement + + const firstQuery = consumer.queries()[0] + expect(firstQuery?.status).toBe('pending') + + document.body.append(consumer) + + await waitForMissingQueryClient(() => consumer.queries()) + await expect(firstQuery?.refetch()).rejects.toThrow( + 'No QueryClient available. Pass one explicitly or render within QueryClientProvider.', + ) + + consumer.queries.destroy() + consumer.remove() + await Promise.resolve() + }) + + it('LC-QUERIES-05: constructor defers placeholder accessors until host fields are initialized', () => { + expect(() => new DeferredFieldsQueriesHost()).not.toThrow() + + const host = new DeferredFieldsQueriesHost() + expect(host.queries()).toEqual(['pending', 'pending']) + }) + + it('LC-QUERIES-06: placeholder combine materializes defined initialData before a client is available', () => { + const host = new TestControllerHost() + const queries = createQueriesController(host, { + queries: [ + queryOptions({ + queryKey: ['placeholder-initial-data'] as const, + queryFn: async () => ({ id: 4, name: 'Marie' }), + initialData: { id: 0, name: 'Seed' }, + }), + ] as const, + combine: (result) => result[0].data.name, + }) + + expect(queries()).toBe('Seed') + + queries.destroy() + }) + + it('LC-QUERIES-07: explicit-client constructor defers dynamic accessors until host fields are initialized', () => { + const client = new QueryClient() + + class DeferredExplicitQueriesHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly queries = createQueriesController( + this, + () => ({ + queries: this.ids.map((id) => ({ + queryKey: ['deferred-explicit-queries', id] as const, + queryFn: async () => id, + retry: false, + })), + combine: (results) => results.map((result) => result.status), + }), + client, + ) + + readonly firstRead = this.queries() + readonly ids = ['alpha', 'beta'] as const + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new DeferredExplicitQueriesHost()).not.toThrow() + + const host = new DeferredExplicitQueriesHost() + expect(host.queries()).toEqual(['pending', 'pending']) + + host.queries.destroy() + }) + + it('LC-QUERIES-08: explicit-client constructor defers static combine callbacks until host fields are initialized', () => { + const client = new QueryClient() + + class DeferredExplicitCombineQueriesHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly queries = createQueriesController( + this, + { + queries: [ + { + queryKey: ['deferred-explicit-combine-queries', 'alpha'] as const, + queryFn: async () => 'alpha', + retry: false, + }, + ] as const, + combine: (results) => + this.ids.map((id, index) => `${id}:${results[index]?.status}`), + }, + client, + ) + + readonly firstRead = this.queries() + readonly ids = ['alpha'] as const + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new DeferredExplicitCombineQueriesHost()).not.toThrow() + + const host = new DeferredExplicitCombineQueriesHost() + expect(host.queries()).toEqual(['alpha:pending']) + + host.queries.destroy() + }) + + it('LC-QUERIES-09: explicit-client constructor re-surfaces permanent static combine errors after initialization', async () => { + const client = new QueryClient() + + class InvalidExplicitCombineQueriesHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly queries = createQueriesController( + this, + { + queries: [ + { + queryKey: ['invalid-explicit-combine-queries', 'alpha'] as const, + queryFn: async () => 'alpha', + retry: false, + }, + ] as const, + combine: () => { + throw new Error('invalid combine') + }, + }, + client, + ) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new InvalidExplicitCombineQueriesHost()).not.toThrow() + + const host = new InvalidExplicitCombineQueriesHost() + await Promise.resolve() + + expect(() => host.queries()).toThrow('invalid combine') + }) + + it('ALREADYCONN-QUERIES-01: queries controller on already-connected host with explicit client does not throw', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + client.setQueryData(['already-connected-queries', 'alpha'], 'alpha') + client.setQueryData(['already-connected-queries', 'beta'], 'beta') + + class AlreadyConnectedHost implements ReactiveControllerHost { + private readonly controllers = new Set() + private isConnected = true + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + const host = new AlreadyConnectedHost() + const queries = createQueriesController( + host, + { + queries: [ + { + queryKey: ['already-connected-queries', 'alpha'] as const, + queryFn: async () => 'fetched-alpha', + staleTime: 30_000, + }, + { + queryKey: ['already-connected-queries', 'beta'] as const, + queryFn: async () => 'fetched-beta', + staleTime: 30_000, + }, + ] as const, + combine: (results) => + results.map((result) => ({ + status: result.status, + data: result.data, + })), + }, + client, + ) + + await Promise.resolve() + await Promise.resolve() + + expect(queries()).toEqual([ + { status: 'success', data: 'alpha' }, + { status: 'success', data: 'beta' }, + ]) + + queries.destroy() + }) +}) diff --git a/packages/lit-query/src/tests/query-controller.test.ts b/packages/lit-query/src/tests/query-controller.test.ts new file mode 100644 index 00000000000..bc618ff830e --- /dev/null +++ b/packages/lit-query/src/tests/query-controller.test.ts @@ -0,0 +1,1326 @@ +import { describe, expect, it } from 'vitest' +import { keepPreviousData, QueryClient } from '@tanstack/query-core' +import type { ReactiveController, ReactiveControllerHost } from 'lit' +import { QueryClientProvider } from '../QueryClientProvider.js' +import { createQueryController } from '../createQueryController.js' +import { + TestControllerHost, + waitFor, + waitForMissingQueryClient, +} from './testHost.js' + +const providerTagName = 'test-query-client-provider-query' +if (!customElements.get(providerTagName)) { + customElements.define(providerTagName, QueryClientProvider) +} + +class QueryConsumerHostElement + extends HTMLElement + implements ReactiveControllerHost +{ + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + readonly queryKey = ['query-controller', 'provider-switch'] as const + queryCalls = 0 + + readonly query = createQueryController(this, () => ({ + queryKey: this.queryKey, + queryFn: async () => { + this.queryCalls += 1 + return `value-${this.queryCalls}` + }, + retry: false, + })) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + + connectedCallback(): void { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnectedCallback(): void { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } +} + +const consumerTagName = 'test-query-consumer-host' +if (!customElements.get(consumerTagName)) { + customElements.define(consumerTagName, QueryConsumerHostElement) +} + +describe('createQueryController', () => { + it('M1: does not request update after destroy when microtask flushes', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const query = createQueryController( + host, + { + queryKey: ['query-controller', 'm1'], + queryFn: async () => 'done', + }, + client, + ) + + host.connect() + query.destroy() + + await Promise.resolve() + await Promise.resolve() + + expect(host.updatesRequested).toBe(0) + }) + + it('M2: returns observer count to baseline after 100 lifecycle cycles', async () => { + const queryKey = ['query-controller', 'm2'] as const + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + for (let cycle = 0; cycle < 100; cycle += 1) { + const host = new TestControllerHost() + const query = createQueryController( + host, + { + queryKey, + queryFn: async () => cycle, + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + + const cacheQuery = client.getQueryCache().find({ queryKey }) + expect(cacheQuery?.getObserversCount()).toBe(1) + + host.disconnect() + query.destroy() + expect(cacheQuery?.getObserversCount() ?? 0).toBe(0) + } + }) + + it('fetches and updates query state', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let callCount = 0 + + const query = createQueryController( + host, + { + queryKey: ['user', 'fetches-and-updates'], + queryFn: async () => { + callCount += 1 + return { id: 1, name: 'Ada' } + }, + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => query().isSuccess) + + expect(query().data).toEqual({ id: 1, name: 'Ada' }) + expect(callCount).toBe(1) + expect(host.updatesRequested).toBeGreaterThan(0) + }) + + it('M4: transitions from pending to success with expected contract', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const query = createQueryController( + host, + { + queryKey: ['query-controller', 'm4'], + queryFn: async () => { + await new Promise((resolve) => setTimeout(resolve, 10)) + return 'ok' + }, + }, + client, + ) + + expect(query().status).toBe('pending') + expect(query().isSuccess).toBe(false) + + host.connect() + host.update() + + await waitFor(() => query().isSuccess) + expect(query().status).toBe('success') + expect(query().data).toBe('ok') + }) + + it('M6: does not fetch when enabled=false and fetches after enabling', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let callCount = 0 + let enabled = false + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 'm6'], + enabled, + queryFn: async () => { + callCount += 1 + return 'enabled-result' + }, + }), + client, + ) + + host.connect() + host.update() + + await new Promise((resolve) => setTimeout(resolve, 25)) + expect(callCount).toBe(0) + expect(query().isSuccess).toBe(false) + + enabled = true + host.update() + await waitFor(() => query().isSuccess) + + expect(callCount).toBe(1) + expect(query().data).toBe('enabled-result') + }) + + it('M7: remount with gcTime=0 has no observer leak and refetches', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const queryKey = ['query-controller', 'm7'] as const + let callCount = 0 + + const firstHost = new TestControllerHost() + const firstQuery = createQueryController( + firstHost, + { + queryKey, + gcTime: 0, + queryFn: async () => { + callCount += 1 + return `value-${callCount}` + }, + }, + client, + ) + + firstHost.connect() + firstHost.update() + await waitFor(() => firstQuery().isSuccess) + + const firstCacheEntry = client.getQueryCache().find({ queryKey }) + expect(firstCacheEntry?.getObserversCount()).toBe(1) + expect(callCount).toBe(1) + + firstHost.disconnect() + firstQuery.destroy() + + await waitFor(() => { + // With gcTime:0, cache entry may be immediately removed after last observer unmounts. + const entry = client.getQueryCache().find({ queryKey }) + return !entry || entry.getObserversCount() === 0 + }) + + const secondHost = new TestControllerHost() + const secondQuery = createQueryController( + secondHost, + { + queryKey, + gcTime: 0, + queryFn: async () => { + callCount += 1 + return `value-${callCount}` + }, + }, + client, + ) + + secondHost.connect() + secondHost.update() + await waitFor(() => secondQuery().isSuccess) + await waitFor(() => secondQuery().data === 'value-2') + + const secondCacheEntry = client.getQueryCache().find({ queryKey }) + expect(secondCacheEntry?.getObserversCount()).toBe(1) + expect(callCount).toBe(2) + expect(secondQuery().data).toBe('value-2') + }) + + it('M8: applies latest accessor key/options on updates and refetch', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let keyId = 1 + const seenKeys: number[] = [] + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', keyId] as const, + queryFn: async ({ queryKey }) => { + const id = queryKey[1] as number + seenKeys.push(id) + return `user-${id}` + }, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe('user-1') + + keyId = 2 + host.update() + await waitFor(() => query().isSuccess && query().data === 'user-2') + + await query.refetch() + expect(query().data).toBe('user-2') + expect(seenKeys.includes(1)).toBe(true) + expect(seenKeys.includes(2)).toBe(true) + }) + + it('QSEM-01: refetchOnMount follows stale-vs-fresh policy', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + let staleCalls = 0 + const staleKey = ['query-controller', 'qsem-01', 'stale'] as const + + const staleHostA = new TestControllerHost() + const staleQueryA = createQueryController( + staleHostA, + { + queryKey: staleKey, + staleTime: 0, + refetchOnMount: true, + queryFn: async () => { + staleCalls += 1 + return `stale-${staleCalls}` + }, + }, + client, + ) + + staleHostA.connect() + staleHostA.update() + await waitFor(() => staleQueryA().isSuccess) + expect(staleCalls).toBe(1) + staleHostA.disconnect() + staleQueryA.destroy() + + const staleHostB = new TestControllerHost() + const staleQueryB = createQueryController( + staleHostB, + { + queryKey: staleKey, + staleTime: 0, + refetchOnMount: true, + queryFn: async () => { + staleCalls += 1 + return `stale-${staleCalls}` + }, + }, + client, + ) + + staleHostB.connect() + staleHostB.update() + await waitFor(() => staleCalls >= 2) + expect(staleQueryB().isSuccess).toBe(true) + staleHostB.disconnect() + staleQueryB.destroy() + + let freshCalls = 0 + const freshKey = ['query-controller', 'qsem-01', 'fresh'] as const + + const freshHostA = new TestControllerHost() + const freshQueryA = createQueryController( + freshHostA, + { + queryKey: freshKey, + staleTime: Number.POSITIVE_INFINITY, + refetchOnMount: true, + queryFn: async () => { + freshCalls += 1 + return `fresh-${freshCalls}` + }, + }, + client, + ) + + freshHostA.connect() + freshHostA.update() + await waitFor(() => freshQueryA().isSuccess) + expect(freshCalls).toBe(1) + freshHostA.disconnect() + freshQueryA.destroy() + + const freshHostB = new TestControllerHost() + const freshQueryB = createQueryController( + freshHostB, + { + queryKey: freshKey, + staleTime: Number.POSITIVE_INFINITY, + refetchOnMount: true, + queryFn: async () => { + freshCalls += 1 + return `fresh-${freshCalls}` + }, + }, + client, + ) + + freshHostB.connect() + freshHostB.update() + await waitFor(() => freshQueryB().isSuccess) + await new Promise((resolve) => setTimeout(resolve, 25)) + expect(freshCalls).toBe(1) + expect(freshQueryB().data).toBe('fresh-1') + }) + + it('QSEM-02: select transforms data and select-throw surfaces as error', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let shouldThrow = false + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 'qsem-02'], + queryFn: async () => ({ value: 2 }), + select: (payload: { value: number }) => { + if (shouldThrow) { + throw new Error('select-failed') + } + + return payload.value * 10 + }, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe(20) + + shouldThrow = true + await query.refetch() + await waitFor(() => query().isError) + expect(query().error).toBeInstanceOf(Error) + expect((query().error as Error).message).toContain('select-failed') + }) + + it('S5: keepPreviousData preserves prior data during key transitions', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let keyId = 1 + let resolveSecond: ((value: string) => void) | undefined + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 's5', keyId] as const, + queryFn: async ({ queryKey }) => { + const id = queryKey[2] as number + if (id === 1) { + return 'value-1' + } + + return new Promise((resolve) => { + resolveSecond = resolve + }) + }, + placeholderData: keepPreviousData, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess && query().data === 'value-1') + + keyId = 2 + host.update() + + await waitFor(() => query().isFetching) + await waitFor(() => query().isPlaceholderData) + expect(query().data).toBe('value-1') + + resolveSecond?.('value-2') + await waitFor(() => query().isSuccess && query().data === 'value-2') + expect(query().isPlaceholderData).toBe(false) + }) + + it('QSEM-03: invalidation triggers refetch and updates result state', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const queryKey = ['query-controller', 'qsem-03'] as const + let callCount = 0 + + const query = createQueryController( + host, + { + queryKey, + queryFn: async () => { + callCount += 1 + return `v${callCount}` + }, + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe('v1') + expect(callCount).toBe(1) + + void client.invalidateQueries({ queryKey }) + await waitFor(() => callCount >= 2) + await waitFor(() => query().data === 'v2') + expect(query().isSuccess).toBe(true) + }) + + it('CANCEL-02: stale older response does not overwrite newer key result', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let keyId = 'old' + let resolveOld: ((value: string) => void) | undefined + let resolveNew: ((value: string) => void) | undefined + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 'cancel-02', keyId] as const, + queryFn: async ({ queryKey }) => { + const id = queryKey[2] as string + return new Promise((resolve) => { + if (id === 'old') { + resolveOld = resolve + } else { + resolveNew = resolve + } + }) + }, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => typeof resolveOld === 'function') + + keyId = 'new' + host.update() + await waitFor(() => typeof resolveNew === 'function') + + resolveNew?.('new-value') + await waitFor(() => query().data === 'new-value') + + resolveOld?.('old-value') + await new Promise((resolve) => setTimeout(resolve, 20)) + + expect(query().data).toBe('new-value') + expect(query().isSuccess).toBe(true) + }) + + it('CANCEL-01: queryFn receives AbortSignal and prior request is aborted on key switch', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let keyId: 'old' | 'new' = 'old' + let oldSignal: AbortSignal | undefined + let resolveOld: ((value: string) => void) | undefined + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 'cancel-01', keyId] as const, + queryFn: async ({ signal, queryKey }) => { + const id = queryKey[2] as 'old' | 'new' + if (id === 'old') { + oldSignal = signal + return new Promise((resolve) => { + resolveOld = resolve + signal.addEventListener('abort', () => resolve('old-aborted'), { + once: true, + }) + }) + } + + return 'new-success' + }, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => typeof oldSignal !== 'undefined') + + expect(oldSignal).toBeInstanceOf(AbortSignal) + expect(oldSignal?.aborted).toBe(false) + + keyId = 'new' + host.update() + await waitFor(() => query().data === 'new-success') + + expect(oldSignal?.aborted).toBe(true) + resolveOld?.('old-late') + await new Promise((resolve) => setTimeout(resolve, 20)) + expect(query().data).toBe('new-success') + }) + + it('S6: rapid key churn maintains stable final state without duplicate observers', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let keyId = 0 + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 's6', keyId] as const, + queryFn: async ({ queryKey }) => { + const id = queryKey[2] as number + await new Promise((resolve) => + setTimeout(resolve, Math.max(1, 20 - id)), + ) + return `result-${id}` + }, + }), + client, + ) + + host.connect() + host.update() + + for (let i = 1; i <= 20; i += 1) { + keyId = i + host.update() + } + + await waitFor(() => query().isSuccess && query().data === 'result-20') + + const latestCacheEntry = client + .getQueryCache() + .find({ queryKey: ['query-controller', 's6', 20] }) + expect(latestCacheEntry?.getObserversCount()).toBe(1) + }) + + it('LIFE-01: disconnect while in-flight does not process detached updates', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let resolveFetch: ((value: string) => void) | undefined + + const query = createQueryController( + host, + { + queryKey: ['query-controller', 'life-01'], + queryFn: () => + new Promise((resolve) => { + resolveFetch = resolve + }), + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isFetching) + + host.disconnect() + await Promise.resolve() + const updatesAfterDisconnect = host.updatesRequested + + resolveFetch?.('late-value') + await new Promise((resolve) => setTimeout(resolve, 20)) + + expect(host.updatesRequested).toBe(updatesAfterDisconnect) + }) + + it('LIFE-02: reconnect after in-flight settle yields correct snapshot', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let resolveFetch: ((value: string) => void) | undefined + + const query = createQueryController( + host, + { + queryKey: ['query-controller', 'life-02'], + queryFn: () => + new Promise((resolve) => { + resolveFetch = resolve + }), + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isFetching) + + host.disconnect() + resolveFetch?.('reconnected-value') + await new Promise((resolve) => setTimeout(resolve, 20)) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe('reconnected-value') + }) + + it('AREACT-01: latest select closure is used after host updates', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let multiplier = 1 + + const query = createQueryController( + host, + () => ({ + queryKey: ['query-controller', 'areact-01'], + queryFn: async () => 2, + select: (value: number) => value * multiplier, + }), + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + expect(query().data).toBe(2) + + multiplier = 3 + host.update() + await waitFor(() => query().data === 6) + + multiplier = 4 + host.update() + await query.refetch() + expect(query().data).toBe(8) + }) + + it('M3: switches provider client while connected with a single active observer', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = clientA + + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + provider.append(consumer) + document.body.append(provider) + + await provider.updateComplete + await consumer.updateComplete + await waitFor(() => consumer.query().isSuccess && consumer.queryCalls >= 1) + + const oldCacheQueryBeforeSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + expect(oldCacheQueryBeforeSwitch?.getObserversCount()).toBe(1) + + provider.client = clientB + await provider.updateComplete + + await waitFor(() => { + const newCacheQuery = clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + return Boolean(newCacheQuery && newCacheQuery.getObserversCount() === 1) + }) + + const oldCacheQueryAfterSwitch = clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + const newCacheQueryAfterSwitch = clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + + expect(oldCacheQueryAfterSwitch?.getObserversCount() ?? 0).toBe(0) + expect(newCacheQueryAfterSwitch?.getObserversCount()).toBe(1) + + void clientB.invalidateQueries({ queryKey: consumer.queryKey }) + await waitFor(() => consumer.queryCalls >= 2) + + consumer.query.destroy() + provider.remove() + await Promise.resolve() + }) + + it('M5: tracks retry failure metadata before eventual success', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + let attempts = 0 + + const query = createQueryController( + host, + { + queryKey: ['query-controller', 'm5'], + retry: 2, + retryDelay: 30, + queryFn: async () => { + attempts += 1 + await new Promise((resolve) => setTimeout(resolve, 5)) + if (attempts < 3) { + throw new Error(`attempt-${attempts}`) + } + return 'success' + }, + }, + client, + ) + + host.connect() + host.update() + + await waitFor(() => query().failureCount >= 1) + expect(query().failureReason).toBeInstanceOf(Error) + expect(query().isPending || query().isError).toBe(true) + + await waitFor(() => query().isSuccess) + expect(query().data).toBe('success') + expect(attempts).toBe(3) + }) + + it('is reconnect-idempotent without duplicate subscriptions', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const host = new TestControllerHost() + const queryKey = ['todos', 'reconnect-idempotent'] as const + + const query = createQueryController( + host, + { + queryKey, + queryFn: async () => ['a', 'b'], + }, + client, + ) + + host.connect() + host.update() + await waitFor(() => query().isSuccess) + + const cacheQuery = client.getQueryCache().find({ queryKey }) + expect(cacheQuery).toBeDefined() + expect(cacheQuery?.getObserversCount()).toBe(1) + + host.disconnect() + expect(cacheQuery?.getObserversCount()).toBe(0) + + host.connect() + host.update() + await waitFor(() => cacheQuery?.getObserversCount() === 1) + + host.connect() + host.update() + + expect(cacheQuery?.getObserversCount()).toBe(1) + }) + + it('M17: no-explicit-client constructor path is safe before provider resolution', async () => { + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + + expect(consumer.query().status).toBe('pending') + + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + provider.append(consumer) + + document.body.append(provider) + await provider.updateComplete + await consumer.updateComplete + + await waitFor(() => consumer.query().isSuccess) + expect(consumer.query().data).toBeDefined() + expect(consumer.queryCalls).toBeGreaterThan(0) + + consumer.query.destroy() + provider.remove() + await Promise.resolve() + }) + + it('LC-QUERY-01: provider-backed first connection does not spuriously throw during handshake', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + provider.append(consumer) + + expect(() => consumer.query()).not.toThrow() + + document.body.append(provider) + + expect(() => consumer.query()).not.toThrow() + + await provider.updateComplete + await consumer.updateComplete + await waitFor(() => consumer.query().isSuccess) + + expect(consumer.query().data).toBeDefined() + + consumer.query.destroy() + provider.remove() + await Promise.resolve() + }) + + it('throws after the initial placeholder phase when no provider is available', async () => { + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + + expect(consumer.query().status).toBe('pending') + + document.body.append(consumer) + + expect(() => consumer.query()).not.toThrow() + await waitForMissingQueryClient(() => consumer.query()) + + await expect(consumer.query.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(consumer.query.suspense()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.query.destroy() + consumer.remove() + await Promise.resolve() + }) + + it('LC-QUERY-03: wrapper and result-object imperative methods share the missing-client contract', async () => { + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + const placeholderResult = consumer.query() + + document.body.append(consumer) + await waitForMissingQueryClient(() => consumer.query()) + + await expect(consumer.query.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + await expect(placeholderResult.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.query.destroy() + consumer.remove() + await Promise.resolve() + }) + + it('LC-QUERY-04: reconnect outside any provider clears stale provider-derived client state', async () => { + const client = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const provider = document.createElement( + providerTagName, + ) as QueryClientProvider + provider.client = client + + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + provider.append(consumer) + document.body.append(provider) + + await provider.updateComplete + await consumer.updateComplete + await waitFor(() => consumer.query().isSuccess) + + expect( + client + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount(), + ).toBe(1) + + provider.removeChild(consumer) + await waitFor( + () => + (client + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0) === 0, + ) + await new Promise((resolve) => setTimeout(resolve, 0)) + consumer.connectedCallback() + + await expect(consumer.query.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.query.destroy() + consumer.remove() + provider.remove() + await Promise.resolve() + }) + + it('LC-QUERY-05: reconnect under a different provider rebinds cleanly with later recovery', async () => { + const clientA = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const clientB = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }) + const providerA = document.createElement( + providerTagName, + ) as QueryClientProvider + providerA.client = clientA + const providerB = document.createElement( + providerTagName, + ) as QueryClientProvider + providerB.client = clientB + + const consumer = document.createElement( + consumerTagName, + ) as QueryConsumerHostElement + providerA.append(consumer) + + document.body.append(providerA) + + await providerA.updateComplete + await consumer.updateComplete + await waitFor(() => consumer.query().isSuccess) + + providerA.removeChild(consumer) + await waitFor( + () => + (clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0) === 0, + ) + consumer.connectedCallback() + await expect(consumer.query.refetch()).rejects.toThrow( + /No QueryClient available/, + ) + + consumer.disconnectedCallback() + providerB.append(consumer) + document.body.append(providerB) + await providerB.updateComplete + await waitFor(() => consumer.query().isSuccess && consumer.queryCalls >= 2) + + expect( + clientA + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount() ?? 0, + ).toBe(0) + expect( + clientB + .getQueryCache() + .find({ queryKey: consumer.queryKey }) + ?.getObserversCount(), + ).toBe(1) + expect(consumer.query().data).toBe(`value-${consumer.queryCalls}`) + + consumer.query.destroy() + providerA.remove() + providerB.remove() + await Promise.resolve() + }) + + it('reuses hydrated data on an already-connected host without an eager refetch', async () => { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: 30_000 } }, + }) + let queryFnCalls = 0 + + client.setQueryData(['already-connected-test'], 'hydrated-value') + + // Simulate Lit's synchronous hostConnected call on already-connected hosts. + class AlreadyConnectedHost implements ReactiveControllerHost { + private readonly controllers = new Set() + private isConnected = true + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + if (this.isConnected) { + controller.hostConnected?.() + } + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + const host = new AlreadyConnectedHost() + + const query = createQueryController( + host, + { + queryKey: ['already-connected-test'], + queryFn: async () => { + queryFnCalls += 1 + return 'fetched-value' + }, + staleTime: 30_000, + }, + client, + ) + + await Promise.resolve() + await Promise.resolve() + + expect(query().data).toBe('hydrated-value') + expect(query().isSuccess).toBe(true) + await new Promise((resolve) => setTimeout(resolve, 50)) + expect(queryFnCalls).toBe(0) + + await query.refetch() + await waitFor(() => query().data === 'fetched-value') + expect(queryFnCalls).toBe(1) + + query.destroy() + }) + + it('defers explicit-client query accessors until host fields are initialized', () => { + const client = new QueryClient() + + class DeferredExplicitQueryHost implements ReactiveControllerHost { + private readonly controllers = new Set() + + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + readonly query = createQueryController( + this, + () => ({ + queryKey: ['deferred-explicit-query', this.id] as const, + queryFn: async () => this.id, + retry: false, + }), + client, + ) + + readonly firstRead = this.query() + readonly id = 'alpha' + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + } + + expect(() => new DeferredExplicitQueryHost()).not.toThrow() + + const host = new DeferredExplicitQueryHost() + expect(host.query().status).toBe('pending') + + host.query.destroy() + }) +}) diff --git a/packages/lit-query/src/tests/testHost.ts b/packages/lit-query/src/tests/testHost.ts new file mode 100644 index 00000000000..186433fe467 --- /dev/null +++ b/packages/lit-query/src/tests/testHost.ts @@ -0,0 +1,117 @@ +import type { ReactiveController, ReactiveControllerHost } from 'lit' + +export class TestControllerHost implements ReactiveControllerHost { + private readonly controllers = new Set() + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + + connect(): void { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnect(): void { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } + + update(): void { + for (const controller of this.controllers) { + controller.hostUpdate?.() + } + + for (const controller of this.controllers) { + controller.hostUpdated?.() + } + } +} + +export class TestElementHost + extends HTMLElement + implements ReactiveControllerHost +{ + protected readonly controllers = new Set() + updatesRequested = 0 + readonly updateComplete: Promise = Promise.resolve(true) + + addController(controller: ReactiveController): void { + this.controllers.add(controller) + } + + removeController(controller: ReactiveController): void { + this.controllers.delete(controller) + } + + requestUpdate(): void { + this.updatesRequested += 1 + } + + connectedCallback(): void { + for (const controller of this.controllers) { + controller.hostConnected?.() + } + } + + disconnectedCallback(): void { + for (const controller of this.controllers) { + controller.hostDisconnected?.() + } + } + + flushHostUpdate(): void { + for (const controller of this.controllers) { + controller.hostUpdate?.() + } + + for (const controller of this.controllers) { + controller.hostUpdated?.() + } + } +} + +export async function waitFor( + assertion: () => boolean, + timeoutMs = 2000, +): Promise { + const startedAt = Date.now() + while (!assertion()) { + if (Date.now() - startedAt > timeoutMs) { + throw new Error(`Timed out waiting for assertion after ${timeoutMs}ms`) + } + await new Promise((resolve) => setTimeout(resolve, 10)) + } +} + +function isMissingQueryClientError(error: unknown): boolean { + return ( + error instanceof Error && /No QueryClient available/.test(error.message) + ) +} + +export async function waitForMissingQueryClient( + read: () => unknown, + timeoutMs = 2000, +): Promise { + await waitFor(() => { + try { + read() + return false + } catch (error) { + return isMissingQueryClientError(error) + } + }, timeoutMs) +} diff --git a/packages/lit-query/src/tests/type-inference.test.ts b/packages/lit-query/src/tests/type-inference.test.ts new file mode 100644 index 00000000000..e8c6c3c525d --- /dev/null +++ b/packages/lit-query/src/tests/type-inference.test.ts @@ -0,0 +1,210 @@ +import { + dataTagSymbol, + QueryClient, + type DefinedQueryObserverResult, + type QueryObserverResult, +} from '@tanstack/query-core' +import { describe, expectTypeOf, it } from 'vitest' +import { createMutationController } from '../createMutationController.js' +import { createQueriesController } from '../createQueriesController.js' +import { createInfiniteQueryController } from '../createInfiniteQueryController.js' +import { createQueryController } from '../createQueryController.js' +import { infiniteQueryOptions } from '../infiniteQueryOptions.js' +import { mutationOptions } from '../mutationOptions.js' +import { queryOptions } from '../queryOptions.js' +import { TestControllerHost } from './testHost.js' + +describe('type inference', () => { + it('L1: createQueriesController preserves tuple/combine inference', () => { + const client = new QueryClient() + const host = new TestControllerHost() + const expectTupleResult = ( + value: [QueryObserverResult, QueryObserverResult], + ) => value + const expectDefinedInitialDataTuple = ( + value: [DefinedQueryObserverResult<{ id: number; name: string }>], + ) => value + const expectMappedQueriesResult = ( + value: [ + ...Array>, + QueryObserverResult, + ], + ) => value + + const tupleResult = createQueriesController( + host, + { + queries: [ + { + queryKey: ['type-inference', 'tuple-number'] as const, + queryFn: async () => 1, + }, + { + queryKey: ['type-inference', 'tuple-string'] as const, + queryFn: async () => 'x', + }, + ] as const, + }, + client, + ) + + const tupleData = expectTupleResult(tupleResult()) + expectTypeOf(tupleData[0].data).toEqualTypeOf() + expectTypeOf(tupleData[1].data).toEqualTypeOf() + + const combinedResult = createQueriesController( + host, + { + queries: [ + { + queryKey: ['type-inference', 'combined-number'] as const, + queryFn: async () => 7, + }, + { + queryKey: ['type-inference', 'combined-string'] as const, + queryFn: async () => 'ok', + }, + ] as const, + combine: (result) => ({ + first: result[0].data, + second: result[1].data, + }), + }, + client, + ) + + expectTypeOf(combinedResult().first).toEqualTypeOf() + expectTypeOf(combinedResult().second).toEqualTypeOf() + + const definedInitialDataResult = createQueriesController( + host, + { + queries: [ + queryOptions({ + queryKey: ['type-inference', 'defined-initial-data'] as const, + queryFn: async () => ({ id: 4, name: 'Marie' }), + initialData: { id: 0, name: 'Seed' }, + }), + ] as const, + }, + client, + ) + + const definedInitialDataTuple = expectDefinedInitialDataTuple( + definedInitialDataResult(), + ) + expectTypeOf(definedInitialDataTuple[0].data).toEqualTypeOf<{ + id: number + name: string + }>() + + const definedInitialDataCombined = createQueriesController( + host, + { + queries: [ + queryOptions({ + queryKey: [ + 'type-inference', + 'defined-initial-data-combine', + ] as const, + queryFn: async () => ({ id: 5, name: 'Katherine' }), + initialData: { id: 1, name: 'Init' }, + }), + ] as const, + combine: (result) => result[0].data.name, + }, + client, + ) + + const definedInitialDataCombinedValue: string = definedInitialDataCombined() + expectTypeOf(definedInitialDataCombinedValue).toEqualTypeOf() + + const numberQueries = [1, 2, 3].map((value) => + queryOptions({ + queryKey: ['type-inference', 'mapped-number', value] as const, + queryFn: async () => value, + }), + ) + const mappedQueriesResult = createQueriesController( + host, + { + queries: [ + ...numberQueries, + queryOptions({ + queryKey: ['type-inference', 'mapped-boolean'] as const, + queryFn: async () => true, + }), + ], + }, + client, + ) + + const mappedQueriesData = expectMappedQueriesResult(mappedQueriesResult()) + expectTypeOf(mappedQueriesData[0].data).toEqualTypeOf< + number | boolean | undefined + >() + }) + + it('L2: helper option generics preserve controller inference', () => { + const client = new QueryClient() + const host = new TestControllerHost() + + const query = createQueryController( + host, + queryOptions({ + queryKey: ['type-inference', 'query'] as const, + queryFn: async () => ({ id: 1, name: 'Ada' }), + }), + client, + ) + expectTypeOf(query().data).toEqualTypeOf< + { id: number; name: string } | undefined + >() + + const mutation = createMutationController( + host, + mutationOptions({ + mutationFn: async (input: { id: number }) => input.id.toString(), + }), + client, + ) + expectTypeOf(mutation().data).toEqualTypeOf() + expectTypeOf(mutation().variables).toEqualTypeOf< + { id: number } | undefined + >() + + const queryOpts = queryOptions({ + queryKey: ['type-inference', 'query-options'] as const, + queryFn: async () => ({ id: 2, name: 'Grace' }), + }) + expectTypeOf(queryOpts.queryKey[dataTagSymbol]).toEqualTypeOf<{ + id: number + name: string + }>() + const cachedData = client.getQueryData(queryOpts.queryKey) + expectTypeOf(cachedData).toEqualTypeOf< + { id: number; name: string } | undefined + >() + const updatedData = client.setQueryData(queryOpts.queryKey, { + id: 3, + name: 'Lin', + }) + expectTypeOf(updatedData).toEqualTypeOf< + { id: number; name: string } | undefined + >() + + const infinite = createInfiniteQueryController( + host, + infiniteQueryOptions({ + queryKey: ['type-inference', 'infinite'] as const, + initialPageParam: 0, + queryFn: async () => ({ page: 1 }), + getNextPageParam: (lastPage) => lastPage.page + 1, + }), + client, + ) + expectTypeOf(infinite().data?.pages).toEqualTypeOf< + Array<{ page: number }> | undefined + >() + }) +}) diff --git a/packages/lit-query/src/types.ts b/packages/lit-query/src/types.ts new file mode 100644 index 00000000000..f56121835eb --- /dev/null +++ b/packages/lit-query/src/types.ts @@ -0,0 +1,59 @@ +import type { + DefaultError, + InfiniteData, + MutationObserverResult, + QueryKey, + QueryObserverResult, +} from '@tanstack/query-core' +import type { Accessor } from './accessor.js' +import type { CreateInfiniteQueryOptions } from './createInfiniteQueryController.js' +import type { CreateMutationOptions } from './createMutationController.js' +import type { + CreateQueriesControllerOptions, + CreateQueriesResults, +} from './createQueriesController.js' +import type { CreateQueryOptions } from './createQueryController.js' + +export type QueryControllerOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +> = Accessor< + CreateQueryOptions +> + +export type QueryControllerResult< + TData = unknown, + TError = DefaultError, +> = QueryObserverResult + +export type InfiniteQueryControllerOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = Accessor< + CreateInfiniteQueryOptions +> + +export type MutationControllerOptions< + TData = unknown, + TError = DefaultError, + TVariables = void, + TOnMutateResult = unknown, +> = Accessor> + +export type MutationControllerResult< + TData = unknown, + TError = DefaultError, + TVariables = void, + TOnMutateResult = unknown, +> = MutationObserverResult + +export type QueriesControllerOptions< + TQueryOptions extends Array = Array, + TCombinedResult = CreateQueriesResults, +> = Accessor> diff --git a/packages/lit-query/src/useIsFetching.ts b/packages/lit-query/src/useIsFetching.ts new file mode 100644 index 00000000000..4f9ec48f750 --- /dev/null +++ b/packages/lit-query/src/useIsFetching.ts @@ -0,0 +1,122 @@ +import type { QueryClient, QueryFilters } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { BaseController } from './controllers/BaseController.js' + +export type IsFetchingAccessor = ValueAccessor & { destroy: () => void } + +class IsFetchingController extends BaseController { + private queryClient: QueryClient | undefined + private unsubscribe: (() => void) | undefined + + constructor( + host: ReactiveControllerHost, + private readonly filters: Accessor = {}, + queryClient?: QueryClient, + ) { + super(host, 0, queryClient) + + if (!queryClient) { + return + } + + this.queryClient = queryClient + this.result = this.computeValue() + } + + protected onConnected(): void { + if (!this.syncClient()) { + this.setResult(0) + return + } + + this.subscribe() + this.setResult(this.computeValue()) + } + + protected onDisconnected(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + this.syncClient() + } + + protected onHostUpdate(): void { + if (typeof this.filters !== 'function') { + return + } + + this.setResult(this.syncClient() ? this.computeValue() : 0) + } + + protected onQueryClientChanged(): void { + if (!this.syncClient()) { + this.setResult(0) + return + } + + if (this.connectedState) { + this.subscribe() + this.setResult(this.computeValue()) + } + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = undefined + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = nextClient + return true + } + + private subscribe(): void { + if (!this.queryClient) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.queryClient.getQueryCache().subscribe(() => { + this.setResult(this.computeValue()) + }) + } + + private computeValue(): number { + if (!this.queryClient) { + return 0 + } + + return this.queryClient.isFetching(readAccessor(this.filters)) + } +} + +export function useIsFetching( + host: ReactiveControllerHost, + filters: Accessor = {}, + queryClient?: QueryClient, +): IsFetchingAccessor { + const controller = new IsFetchingController(host, filters, queryClient) + return Object.assign( + createValueAccessor(() => controller.current), + { + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/useIsMutating.ts b/packages/lit-query/src/useIsMutating.ts new file mode 100644 index 00000000000..97b31cf1d49 --- /dev/null +++ b/packages/lit-query/src/useIsMutating.ts @@ -0,0 +1,122 @@ +import type { MutationFilters, QueryClient } from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { BaseController } from './controllers/BaseController.js' + +export type IsMutatingAccessor = ValueAccessor & { destroy: () => void } + +class IsMutatingController extends BaseController { + private queryClient: QueryClient | undefined + private unsubscribe: (() => void) | undefined + + constructor( + host: ReactiveControllerHost, + private readonly filters: Accessor = {}, + queryClient?: QueryClient, + ) { + super(host, 0, queryClient) + + if (!queryClient) { + return + } + + this.queryClient = queryClient + this.result = this.computeValue() + } + + protected onConnected(): void { + if (!this.syncClient()) { + this.setResult(0) + return + } + + this.subscribe() + this.setResult(this.computeValue()) + } + + protected onDisconnected(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + this.syncClient() + } + + protected onHostUpdate(): void { + if (typeof this.filters !== 'function') { + return + } + + this.setResult(this.syncClient() ? this.computeValue() : 0) + } + + protected onQueryClientChanged(): void { + if (!this.syncClient()) { + this.setResult(0) + return + } + + if (this.connectedState) { + this.subscribe() + this.setResult(this.computeValue()) + } + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = undefined + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = nextClient + return true + } + + private subscribe(): void { + if (!this.queryClient) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.queryClient.getMutationCache().subscribe(() => { + this.setResult(this.computeValue()) + }) + } + + private computeValue(): number { + if (!this.queryClient) { + return 0 + } + + return this.queryClient.isMutating(readAccessor(this.filters)) + } +} + +export function useIsMutating( + host: ReactiveControllerHost, + filters: Accessor = {}, + queryClient?: QueryClient, +): IsMutatingAccessor { + const controller = new IsMutatingController(host, filters, queryClient) + return Object.assign( + createValueAccessor(() => controller.current), + { + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/src/useMutationState.ts b/packages/lit-query/src/useMutationState.ts new file mode 100644 index 00000000000..99e6bdbb021 --- /dev/null +++ b/packages/lit-query/src/useMutationState.ts @@ -0,0 +1,156 @@ +import type { + Mutation, + MutationFilters, + MutationState, + QueryClient, +} from '@tanstack/query-core' +import type { ReactiveControllerHost } from 'lit' +import { + createValueAccessor, + readAccessor, + type Accessor, + type ValueAccessor, +} from './accessor.js' +import { BaseController } from './controllers/BaseController.js' + +export type MutationStateOptions = { + filters?: Accessor + select?: (mutation: Mutation) => TResult +} + +export type MutationStateAccessor = ValueAccessor & { + destroy: () => void +} + +class MutationStateController extends BaseController { + private queryClient: QueryClient | undefined + private unsubscribe: (() => void) | undefined + + constructor( + host: ReactiveControllerHost, + private readonly options: MutationStateOptions, + queryClient?: QueryClient, + ) { + super(host, [], queryClient) + + if (!queryClient) { + return + } + + this.queryClient = queryClient + this.result = this.computeState() + } + + protected onConnected(): void { + if (!this.syncClient()) { + this.setResult([]) + return + } + + this.subscribe() + this.setResult(this.computeState()) + } + + protected onDisconnected(): void { + this.unsubscribe?.() + this.unsubscribe = undefined + this.syncClient() + } + + protected onHostUpdate(): void { + if (!this.shouldRefreshOnHostUpdate()) { + return + } + + this.setResult(this.syncClient() ? this.computeState() : []) + } + + protected onQueryClientChanged(): void { + if (!this.syncClient()) { + this.setResult([]) + return + } + + if (this.connectedState) { + this.subscribe() + this.setResult(this.computeState()) + } + } + + private syncClient(): boolean { + const nextClient = this.tryGetQueryClient() + if (!nextClient) { + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = undefined + return false + } + + if (nextClient === this.queryClient) { + return true + } + + this.unsubscribe?.() + this.unsubscribe = undefined + this.queryClient = nextClient + return true + } + + private subscribe(): void { + if (!this.queryClient) { + return + } + + if (this.unsubscribe) { + return + } + + this.unsubscribe = this.queryClient.getMutationCache().subscribe(() => { + this.setResult(this.computeState()) + }) + } + + private shouldRefreshOnHostUpdate(): boolean { + return ( + typeof this.options.filters === 'function' || + typeof this.options.select === 'function' + ) + } + + private computeState(): TResult[] { + if (!this.queryClient) { + return [] + } + + const filters = this.options.filters + ? readAccessor(this.options.filters) + : undefined + + const select = this.options.select + const mutations = this.queryClient.getMutationCache().findAll(filters) + + return mutations.map((mutation) => { + if (select) { + return select(mutation) + } + + return mutation.state as TResult + }) + } +} + +export function useMutationState< + TResult = MutationState, +>( + host: ReactiveControllerHost, + options: MutationStateOptions = {}, + queryClient?: QueryClient, +): MutationStateAccessor { + const controller = new MutationStateController(host, options, queryClient) + return Object.assign( + createValueAccessor(() => controller.current), + { + destroy: () => controller.destroy(), + }, + ) +} diff --git a/packages/lit-query/tsconfig.build.cjs.json b/packages/lit-query/tsconfig.build.cjs.json new file mode 100644 index 00000000000..f6a332575d4 --- /dev/null +++ b/packages/lit-query/tsconfig.build.cjs.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "customConditions": null, + "module": "CommonJS", + "moduleResolution": "Node", + "outDir": "dist-cjs", + "declaration": false, + "declarationMap": false + }, + "exclude": ["src/tests/**/*.ts", "dist", "dist-cjs", "node_modules"] +} diff --git a/packages/lit-query/tsconfig.build.json b/packages/lit-query/tsconfig.build.json new file mode 100644 index 00000000000..5a1dd43717c --- /dev/null +++ b/packages/lit-query/tsconfig.build.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "incremental": false, + "composite": false, + "customConditions": [], + "module": "ESNext", + "moduleResolution": "Bundler", + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/tests/**/*.ts", "dist", "node_modules"] +} diff --git a/packages/lit-query/tsconfig.json b/packages/lit-query/tsconfig.json new file mode 100644 index 00000000000..9608d648e8c --- /dev/null +++ b/packages/lit-query/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM"], + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noUncheckedIndexedAccess": true, + "forceConsistentCasingInFileNames": true, + "emitDeclarationOnly": false, + "rootDir": ".", + "outDir": "dist-ts" + }, + "include": ["src/**/*.ts", "*.config.*", "package.json"], + "exclude": ["dist", "node_modules"], + "references": [{ "path": "../query-core" }] +} diff --git a/packages/lit-query/vitest.config.ts b/packages/lit-query/vitest.config.ts new file mode 100644 index 00000000000..0ab94757be4 --- /dev/null +++ b/packages/lit-query/vitest.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + // 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: { + dir: './src', + watch: false, + environment: 'jsdom', + include: ['tests/**/*.test.ts'], + coverage: { + enabled: false, + }, + typecheck: { enabled: true }, + restoreMocks: true, + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab5be43d95e..d596327e123 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ importers: version: 0.3.1(typescript@5.9.3) '@tanstack/vite-config': specifier: 0.4.3 - version: 0.4.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 0.4.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/jest-dom': specifier: ^6.8.0 version: 6.9.1 @@ -68,10 +68,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitest/coverage-istanbul': specifier: 4.0.6 - version: 4.0.6(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.0.6(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@vitest/eslint-plugin': specifier: ^1.4.0 - version: 1.6.14(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 1.6.14(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) esbuild-plugin-file-path-extensions: specifier: ^2.1.4 version: 2.1.4 @@ -122,7 +122,7 @@ importers: version: 0.2.15 tsup: specifier: ^8.4.0 - version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: 5.9.3 version: 5.9.3 @@ -149,10 +149,10 @@ importers: version: typescript@6.0.1-rc vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: ^4.0.18 - version: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/angular/auto-refetching: dependencies: @@ -183,7 +183,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -223,7 +223,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -269,7 +269,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -312,7 +312,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -352,7 +352,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -395,7 +395,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -435,7 +435,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -478,7 +478,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -521,7 +521,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -564,7 +564,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -604,7 +604,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -615,6 +615,72 @@ importers: specifier: 5.8.3 version: 5.8.3 + examples/lit/basic: + dependencies: + '@tanstack/lit-query': + specifier: ^0.1.0 + version: link:../../../packages/lit-query + '@tanstack/query-core': + specifier: ^5.99.0 + version: link:../../../packages/query-core + lit: + specifier: ^3.3.1 + version: 3.3.2 + devDependencies: + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: ^6.4.1 + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + examples/lit/pagination: + dependencies: + '@tanstack/lit-query': + specifier: ^0.1.0 + version: link:../../../packages/lit-query + '@tanstack/query-core': + specifier: ^5.99.0 + version: link:../../../packages/query-core + lit: + specifier: ^3.3.1 + version: 3.3.2 + devDependencies: + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: ^6.4.1 + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + examples/lit/ssr: + dependencies: + '@lit-labs/ssr': + specifier: ^3.3.0 + version: 3.3.1 + '@tanstack/lit-query': + specifier: ^0.1.0 + version: link:../../../packages/lit-query + '@tanstack/query-core': + specifier: ^5.99.0 + version: link:../../../packages/query-core + lit: + specifier: ^3.3.1 + version: 3.3.2 + devDependencies: + '@lit-labs/ssr-client': + specifier: ^1.1.7 + version: 1.1.8 + tsx: + specifier: ^4.19.0 + version: 4.21.0 + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: ^6.4.1 + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + examples/preact/simple: dependencies: '@tanstack/preact-query': @@ -626,7 +692,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) eslint: specifier: ^9.36.0 version: 9.39.4(jiti@2.6.1) @@ -638,7 +704,7 @@ importers: version: 5.9.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/algolia: dependencies: @@ -669,13 +735,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/auto-refetching: dependencies: @@ -737,13 +803,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/basic-graphql-request: dependencies: @@ -768,10 +834,10 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/chat: dependencies: @@ -790,10 +856,10 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.0.14 - version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) tailwindcss: specifier: ^4.0.14 version: 4.2.2 @@ -802,7 +868,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/default-query-function: dependencies: @@ -821,13 +887,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/devtools-panel: dependencies: @@ -846,13 +912,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/eslint-legacy: dependencies: @@ -886,13 +952,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/eslint-plugin-demo: dependencies: @@ -1094,13 +1160,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/optimistic-updates-cache: dependencies: @@ -1203,13 +1269,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/prefetching: dependencies: @@ -1344,13 +1410,13 @@ importers: version: 1.2.3 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/rick-morty: dependencies: @@ -1375,10 +1441,10 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.1.13 - version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) tailwindcss: specifier: ^4.1.13 version: 4.2.2 @@ -1387,7 +1453,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/shadow-dom: dependencies: @@ -1412,13 +1478,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/simple: dependencies: @@ -1437,13 +1503,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/star-wars: dependencies: @@ -1468,10 +1534,10 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.1.13 - version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) tailwindcss: specifier: ^4.1.13 version: 4.2.2 @@ -1480,7 +1546,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/react/suspense: dependencies: @@ -1505,13 +1571,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/solid/astro: dependencies: @@ -1520,16 +1586,16 @@ importers: version: 0.9.8(prettier@3.8.1)(typescript@5.8.3) '@astrojs/node': specifier: ^9.1.3 - version: 9.5.5(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3)) + version: 9.5.5(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3)) '@astrojs/solid-js': specifier: ^5.0.7 - version: 5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@1.9.12)(terser@5.46.1)(yaml@2.8.3) + version: 5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@1.9.12)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@astrojs/tailwind': specifier: ^6.0.2 - version: 6.0.2(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3)) + version: 6.0.2(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3)) '@astrojs/vercel': specifier: ^8.1.3 - version: 8.2.11(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(rollup@4.60.1)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3)) + version: 8.2.11(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(rollup@4.60.1)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3)) '@tanstack/solid-query': specifier: ^5.99.0 version: link:../../../packages/solid-query @@ -1538,13 +1604,13 @@ importers: version: link:../../../packages/solid-query-devtools astro: specifier: ^5.5.6 - version: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3) + version: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3) solid-js: specifier: ^1.9.7 version: 1.9.12 tailwindcss: specifier: ^3.4.7 - version: 3.4.19(yaml@2.8.3) + version: 3.4.19(tsx@4.21.0)(yaml@2.8.3) typescript: specifier: 5.8.3 version: 5.8.3 @@ -1566,10 +1632,10 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/basic-graphql-request: dependencies: @@ -1594,10 +1660,10 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/default-query-function: dependencies: @@ -1616,10 +1682,10 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/offline: dependencies: @@ -1647,10 +1713,10 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/simple: dependencies: @@ -1672,10 +1738,10 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) examples/solid/solid-start-streaming: dependencies: @@ -1687,7 +1753,7 @@ importers: version: 0.15.4(solid-js@1.9.12) '@solidjs/start': specifier: ^1.1.3 - version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/solid-query': specifier: ^5.99.0 version: link:../../../packages/solid-query @@ -1699,7 +1765,7 @@ importers: version: 1.9.12 vinxi: specifier: ^0.5.3 - version: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/auto-refetching: dependencies: @@ -1712,13 +1778,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1730,7 +1796,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/basic: dependencies: @@ -1749,13 +1815,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1767,7 +1833,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/load-more-infinite-scroll: dependencies: @@ -1780,13 +1846,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1798,7 +1864,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/optimistic-updates: dependencies: @@ -1811,13 +1877,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1829,7 +1895,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/playground: dependencies: @@ -1842,13 +1908,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1860,7 +1926,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/simple: dependencies: @@ -1873,7 +1939,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tsconfig/svelte': specifier: ^5.0.4 version: 5.0.8 @@ -1888,7 +1954,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/ssr: dependencies: @@ -1901,13 +1967,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1919,7 +1985,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/star-wars: dependencies: @@ -1932,16 +1998,16 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^6.1.0 - version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@sveltejs/kit': specifier: ^2.42.2 - version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tailwindcss/vite': specifier: ^4.1.13 - version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) svelte: specifier: ^5.39.3 version: 5.55.1 @@ -1956,7 +2022,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/vue/basic: dependencies: @@ -1972,13 +2038,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/vue/dependent-queries: dependencies: @@ -1991,13 +2057,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/vue/persister: dependencies: @@ -2022,13 +2088,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) examples/vue/simple: dependencies: @@ -2044,13 +2110,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) integrations/angular-cli-20: dependencies: @@ -2087,7 +2153,7 @@ importers: devDependencies: '@angular/build': specifier: ^20.0.0 - version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3) + version: 20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) '@angular/cli': specifier: ^20.0.0 version: 20.3.22(@types/node@22.19.15)(chokidar@4.0.3) @@ -2098,6 +2164,25 @@ importers: specifier: ~5.8.2 version: 5.8.3 + integrations/lit-vite: + dependencies: + '@tanstack/lit-query': + specifier: workspace:* + version: link:../../packages/lit-query + '@tanstack/query-core': + specifier: workspace:* + version: link:../../packages/query-core + lit: + specifier: ^3.3.1 + version: 3.3.2 + vite: + specifier: ^6.4.1 + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + devDependencies: + typescript: + specifier: 5.8.3 + version: 5.8.3 + integrations/react-next-14: dependencies: '@tanstack/react-query': @@ -2195,7 +2280,7 @@ importers: version: link:../../packages/react-query-devtools '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) react: specifier: ^19.0.0 version: 19.2.4 @@ -2204,7 +2289,7 @@ importers: version: 19.2.4(react@19.2.4) vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) integrations/react-webpack-4: dependencies: @@ -2296,16 +2381,16 @@ importers: version: 1.9.12 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) integrations/svelte-vite: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/svelte-query': specifier: workspace:* version: link:../../packages/svelte-query @@ -2317,7 +2402,7 @@ importers: version: 5.55.1 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) integrations/vue-vite: dependencies: @@ -2330,13 +2415,13 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) typescript: specifier: 5.8.3 version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue-tsc: specifier: ^2.2.8 version: 2.2.12(typescript@5.8.3) @@ -2373,13 +2458,13 @@ importers: version: 7.8.2 vite-plugin-dts: specifier: 4.2.3 - version: 4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite-plugin-externalize-deps: specifier: ^0.9.0 - version: 0.9.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 0.9.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.4(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@tanstack/query-devtools': specifier: workspace:* @@ -2451,6 +2536,28 @@ importers: specifier: ^5.0.0 version: 5.0.2 + packages/lit-query: + dependencies: + '@lit/context': + specifier: ^1.1.6 + version: 1.1.6 + '@tanstack/query-core': + specifier: workspace:* + version: link:../query-core + lit: + specifier: ^3.3.1 + version: 3.3.2 + devDependencies: + '@eslint/js': + specifier: ^9.36.0 + version: 9.39.4 + globals: + specifier: ^17.4.0 + version: 17.5.0 + typescript-eslint: + specifier: 8.58.1 + version: 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + packages/preact-query: dependencies: '@tanstack/query-core': @@ -2459,7 +2566,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/query-persist-client-core': specifier: workspace:* version: link:../query-persist-client-core @@ -2499,7 +2606,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/preact-query': specifier: workspace:* version: link:../preact-query @@ -2530,7 +2637,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: ^2.10.2 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/preact-query': specifier: workspace:* version: link:../preact-query @@ -2586,7 +2693,7 @@ importers: version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 @@ -2649,10 +2756,10 @@ importers: version: 2.2.6 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/query-persist-client-core: dependencies: @@ -2715,7 +2822,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) cpy-cli: specifier: ^5.0.0 version: 5.0.0 @@ -2749,7 +2856,7 @@ importers: version: 19.2.14 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 @@ -2767,7 +2874,7 @@ importers: version: 19.2.14 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) next: specifier: ^16.0.1 version: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0) @@ -2798,7 +2905,7 @@ importers: version: 19.2.14 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) npm-run-all2: specifier: ^5.0.0 version: 5.0.2 @@ -2826,10 +2933,10 @@ importers: version: 1.9.12 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/solid-query-devtools: dependencies: @@ -2851,10 +2958,10 @@ importers: version: 1.9.12 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/solid-query-persist-client: dependencies: @@ -2879,10 +2986,10 @@ importers: version: 1.9.12 tsup-preset-solid: specifier: ^2.2.0 - version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3)) + version: 2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) packages/svelte-query: dependencies: @@ -2895,13 +3002,13 @@ importers: version: 2.5.7(svelte@5.55.1)(typescript@5.9.3) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/query-test-utils': specifier: workspace:* version: link:../query-test-utils '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@typescript-eslint/parser': specifier: 8.58.1 version: 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) @@ -2932,7 +3039,7 @@ importers: version: 2.5.7(svelte@5.55.1)(typescript@5.9.3) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/svelte-query': specifier: workspace:* version: link:../svelte-query @@ -2963,7 +3070,7 @@ importers: version: 2.5.7(svelte@5.55.1)(typescript@5.9.3) '@sveltejs/vite-plugin-svelte': specifier: ^5.1.1 - version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + version: 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/query-test-utils': specifier: workspace:* version: link:../query-test-utils @@ -2972,7 +3079,7 @@ importers: version: link:../svelte-query '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))) + version: 5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) '@typescript-eslint/parser': specifier: 8.58.1 version: 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) @@ -3009,7 +3116,7 @@ importers: version: link:../query-test-utils '@vitejs/plugin-vue': specifier: ^5.2.4 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3)) '@vue/composition-api': specifier: 1.7.2 version: 1.7.2(vue@3.5.31(typescript@5.9.3)) @@ -3037,7 +3144,7 @@ importers: version: link:../vue-query '@vitejs/plugin-vue': specifier: ^5.2.4 - version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) + version: 5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3)) eslint-plugin-vue: specifier: ^10.5.0 version: 10.8.0(@stylistic/eslint-plugin@5.10.0(eslint@9.39.4(jiti@2.6.1)))(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) @@ -3046,7 +3153,7 @@ importers: version: 5.8.3 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + version: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: specifier: ^3.4.27 version: 3.5.31(typescript@5.8.3) @@ -4963,89 +5070,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -5354,6 +5477,22 @@ packages: '@inquirer/prompts': '>= 3 < 8' listr2: 9.0.1 + '@lit-labs/ssr-client@1.1.8': + resolution: {integrity: sha512-PjGh81oKsoI64m3IDjTqqjhC7dr2uC/o0jrllUb5gRAyp/RlAHxapgJrjq9kWz97faCHLQ8jUlTi6tGm+8fgyA==} + + '@lit-labs/ssr-dom-shim@1.5.1': + resolution: {integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==} + + '@lit-labs/ssr@3.3.1': + resolution: {integrity: sha512-JlF1PempxvzrGEpRFrF+Ki0MHzR3HA51SK8Zv0cFpW9p0bPW4k0FeCwrElCu371UEpXF7RcaE2wgYaE1az0XKg==} + engines: {node: '>=13.9.0'} + + '@lit/context@1.1.6': + resolution: {integrity: sha512-M26qDE6UkQbZA2mQ3RjJ3Gzd8TxP+/0obMgE5HfkfLhEEyYE3Bui4A5XHiGPjy0MUGAyxB3QgVuw2ciS0kHn6A==} + + '@lit/reactive-element@2.1.2': + resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} + '@lmdb/lmdb-darwin-arm64@3.4.2': resolution: {integrity: sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==} cpu: [arm64] @@ -5504,42 +5643,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -5631,72 +5777,84 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-gnu@15.5.14': resolution: {integrity: sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-gnu@16.2.2': resolution: {integrity: sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@14.2.33': resolution: {integrity: sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-arm64-musl@15.5.14': resolution: {integrity: sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-arm64-musl@16.2.2': resolution: {integrity: sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@14.2.33': resolution: {integrity: sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-gnu@15.5.14': resolution: {integrity: sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-gnu@16.2.2': resolution: {integrity: sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@14.2.33': resolution: {integrity: sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-linux-x64-musl@15.5.14': resolution: {integrity: sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-linux-x64-musl@16.2.2': resolution: {integrity: sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@14.2.33': resolution: {integrity: sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==} @@ -5820,21 +5978,25 @@ packages: resolution: {integrity: sha512-wgpPaTpQKl+cCkSuE5zamTVrg14mRvT+bLAeN/yHSUgMztvGxwl3Ll+K9DgEcktBo1PLECTWNkVaW8IAsJm4Rg==} cpu: [arm64] os: [linux] + libc: [glibc] '@nx/nx-linux-arm64-musl@22.1.3': resolution: {integrity: sha512-o9XmQehSPR2y0RD4evD+Ob3lNFuwsFOL5upVJqZ3rcE6GkJIFPg8SwEP5FaRIS5MwS04fxnek20NZ18BHjjV/g==} cpu: [arm64] os: [linux] + libc: [musl] '@nx/nx-linux-x64-gnu@22.1.3': resolution: {integrity: sha512-ekcinyDNTa2huVe02T2SFMR8oArohozRbMGO19zftbObXXI4dLdoAuLNb3vK9Pe4vYOpkhfxBVkZvcWMmx7JdA==} cpu: [x64] os: [linux] + libc: [glibc] '@nx/nx-linux-x64-musl@22.1.3': resolution: {integrity: sha512-CqpRIJeIgELCqIgjtSsYnnLi6G0uqjbp/Pw9d7w4im4/NmJXqaE9gxpdHA1eowXLgAy9W1LkfzCPS8Q2IScPuQ==} cpu: [x64] os: [linux] + libc: [musl] '@nx/nx-win32-arm64-msvc@22.1.3': resolution: {integrity: sha512-YbuWb8KQsAR9G0+7b4HA16GV962/VWtRcdS7WY2yaScmPT2W5rObl528Y2j4DuB0j/MVZj12qJKrYfUyjL+UJA==} @@ -5905,48 +6067,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-arm64-musl@0.121.0': resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-riscv64-musl@0.121.0': resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-parser/binding-linux-s390x-gnu@0.121.0': resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-gnu@0.121.0': resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-parser/binding-linux-x64-musl@0.121.0': resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxc-parser/binding-openharmony-arm64@0.121.0': resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==} @@ -6019,41 +6189,49 @@ packages: resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.19.1': resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.19.1': resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.19.1': resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.19.1': resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.19.1': resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==} @@ -6112,36 +6290,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.3.0': resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} @@ -6177,6 +6361,9 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@parse5/tools@0.3.0': + resolution: {integrity: sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -6477,131 +6664,157 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.60.1': resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -7003,24 +7216,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.2': resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} @@ -7430,41 +7647,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -7600,9 +7825,23 @@ packages: vitest: optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.2': resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.4.1 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@4.1.2': resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} peerDependencies: @@ -7614,18 +7853,33 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.2': resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.1.2': resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.1.2': resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.2': resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.2': resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} @@ -8710,6 +8964,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -8745,6 +9003,10 @@ packages: charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -9267,6 +9529,10 @@ packages: cyclist@1.0.2: resolution: {integrity: sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -9358,6 +9624,10 @@ packages: dedent-js@1.0.1: resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -10210,6 +10480,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + fetch-retry@4.1.1: resolution: {integrity: sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==} @@ -10355,6 +10629,10 @@ packages: engines: {node: '>=18.3.0'} hasBin: true + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -10555,6 +10833,10 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -11684,48 +11966,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.27.0: resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.27.0: resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.27.0: resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.27.0: resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} @@ -11785,6 +12075,15 @@ packages: resolution: {integrity: sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==} engines: {node: '>=20.0.0'} + lit-element@4.2.2: + resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} + + lit-html@3.3.2: + resolution: {integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==} + + lit@3.3.2: + resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==} + lmdb@3.4.2: resolution: {integrity: sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==} hasBin: true @@ -11886,6 +12185,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -12400,7 +12702,7 @@ packages: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + resolution: {integrity: sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=} msgpackr-extract@3.0.3: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} @@ -12571,6 +12873,11 @@ packages: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-emoji@2.2.0: resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} @@ -12591,6 +12898,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.4.0: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} @@ -13139,6 +13450,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + pbkdf2@3.1.5: resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} engines: {node: '>= 0.10'} @@ -14961,10 +15276,22 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} @@ -15107,6 +15434,11 @@ packages: typescript: optional: true + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + tty-browserify@0.0.0: resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==} @@ -15586,6 +15918,11 @@ packages: resolution: {integrity: sha512-82Qm+EG/b2PRFBvXBbz1lgWBGcd9totIL6SJhnrZYfakjloTVG9+5l6gfO6dbCCtztm5pqWFzLY0qpZ3H3ww/w==} hasBin: true + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-plugin-dts@4.2.3: resolution: {integrity: sha512-O5NalzHANQRwVw1xj8KQun3Bv8OSDAlNJXrnqoAz10BOuW8FVvY5g4ygj+DlJZL5mtSPuMu9vd3OfrdW5d4k6w==} engines: {node: ^14.18.0 || >=16.0.0} @@ -15677,6 +16014,34 @@ packages: vite: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^22.15.3 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@4.1.2: resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -16458,7 +16823,7 @@ snapshots: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) tslib: 2.8.1 - '@angular/build@20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(typescript@5.8.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(yaml@2.8.3)': + '@angular/build@20.3.22(@angular/compiler-cli@20.3.18(@angular/compiler@20.3.18)(typescript@5.8.3))(@angular/compiler@20.3.18)(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(@angular/platform-browser@20.3.18(@angular/animations@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@angular/common@20.3.18(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0))(rxjs@7.8.2))(@angular/core@20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0)))(@types/node@22.19.15)(chokidar@4.0.3)(jiti@2.6.1)(lightningcss@1.32.0)(postcss@8.5.8)(tailwindcss@4.2.2)(terser@5.46.1)(tslib@2.8.1)(tsx@4.21.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2003.22(chokidar@4.0.3) @@ -16468,7 +16833,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 '@inquirer/confirm': 5.1.14(@types/node@22.19.15) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) beasties: 0.3.5 browserslist: 4.28.2 esbuild: 0.27.4 @@ -16488,7 +16853,7 @@ snapshots: tinyglobby: 0.2.14 tslib: 2.8.1 typescript: 5.8.3 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) watchpack: 2.4.4 optionalDependencies: '@angular/core': 20.3.18(@angular/compiler@20.3.18)(rxjs@7.8.2)(zone.js@0.15.0) @@ -16496,7 +16861,7 @@ snapshots: lmdb: 3.4.2 postcss: 8.5.8 tailwindcss: 4.2.2 - vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - chokidar @@ -16707,10 +17072,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/node@9.5.5(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3))': + '@astrojs/node@9.5.5(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))': dependencies: '@astrojs/internal-helpers': 0.7.6 - astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3) + astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3) send: 1.2.1 server-destroy: 1.0.1 transitivePeerDependencies: @@ -16720,11 +17085,11 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/solid-js@5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@1.9.12)(terser@5.46.1)(yaml@2.8.3)': + '@astrojs/solid-js@5.1.3(@testing-library/jest-dom@6.9.1)(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(solid-js@1.9.12)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': dependencies: solid-js: 1.9.12 - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@testing-library/jest-dom' - '@types/node' @@ -16740,13 +17105,13 @@ snapshots: - tsx - yaml - '@astrojs/tailwind@6.0.2(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3))': + '@astrojs/tailwind@6.0.2(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))': dependencies: - astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3) + astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3) autoprefixer: 10.4.27(postcss@8.5.8) postcss: 8.5.8 postcss-load-config: 4.0.2(postcss@8.5.8) - tailwindcss: 3.4.19(yaml@2.8.3) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - ts-node @@ -16762,14 +17127,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/vercel@8.2.11(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(rollup@4.60.1)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3))': + '@astrojs/vercel@8.2.11(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(rollup@4.60.1)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3))': dependencies: '@astrojs/internal-helpers': 0.7.4 - '@vercel/analytics': 1.6.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3)) + '@vercel/analytics': 1.6.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3)) '@vercel/functions': 2.2.13 '@vercel/nft': 0.30.3(rollup@4.60.1) '@vercel/routing-utils': 5.3.3 - astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3) + astro: 5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3) esbuild: 0.27.4 tinyglobby: 0.2.15 transitivePeerDependencies: @@ -19280,6 +19645,36 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@lit-labs/ssr-client@1.1.8': + dependencies: + '@lit/reactive-element': 2.1.2 + lit: 3.3.2 + lit-html: 3.3.2 + + '@lit-labs/ssr-dom-shim@1.5.1': {} + + '@lit-labs/ssr@3.3.1': + dependencies: + '@lit-labs/ssr-client': 1.1.8 + '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit/reactive-element': 2.1.2 + '@parse5/tools': 0.3.0 + '@types/node': 22.19.15 + enhanced-resolve: 5.20.1 + lit: 3.3.2 + lit-element: 4.2.2 + lit-html: 3.3.2 + node-fetch: 3.3.2 + parse5: 7.3.0 + + '@lit/context@1.1.6': + dependencies: + '@lit/reactive-element': 2.1.2 + + '@lit/reactive-element@2.1.2': + dependencies: + '@lit-labs/ssr-dom-shim': 1.5.1 + '@lmdb/lmdb-darwin-arm64@3.4.2': optional: true @@ -19914,6 +20309,10 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6 + '@parse5/tools@0.3.0': + dependencies: + parse5: 7.3.0 + '@pkgjs/parseargs@0.11.0': optional: true @@ -19933,19 +20332,19 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.60.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vite-prerender-plugin: 0.5.13(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-prerender-plugin: 0.5.13(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -19960,7 +20359,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.0)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.29.0)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.3 @@ -19968,7 +20367,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.0 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -20645,11 +21044,11 @@ snapshots: dependencies: solid-js: 1.9.12 - '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@tanstack/server-functions-plugin': 1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@tanstack/server-functions-plugin': 1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) cookie-es: 2.0.0 defu: 6.1.4 error-stack-parser: 2.1.4 @@ -20661,8 +21060,8 @@ snapshots: source-map-js: 1.2.1 terracotta: 1.1.0(solid-js@1.9.12) tinyglobby: 0.2.15 - vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@testing-library/jest-dom' - solid-js @@ -20694,15 +21093,15 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-auto@6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))': + '@sveltejs/adapter-auto@6.1.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -20714,16 +21113,16 @@ snapshots: set-cookie-parser: 3.1.0 sirv: 3.0.2 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: typescript: 5.8.3 optional: true - '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -20735,7 +21134,7 @@ snapshots: set-cookie-parser: 3.1.0 sirv: 3.0.2 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: typescript: 5.8.3 @@ -20750,49 +21149,49 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) debug: 4.4.3 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color optional: true - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) debug: 4.4.3 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) debug: 4.4.3 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color optional: true - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) debug: 4.4.3 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 5.55.1 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -20879,14 +21278,14 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - '@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@tanstack/directive-functions-plugin@1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@tanstack/directive-functions-plugin@1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.0 @@ -20895,7 +21294,7 @@ snapshots: '@tanstack/router-utils': 1.161.6 babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -20940,7 +21339,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/server-functions-plugin@1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@tanstack/server-functions-plugin@1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.0 @@ -20949,7 +21348,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/directive-functions-plugin': 1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@tanstack/directive-functions-plugin': 1.121.21(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 transitivePeerDependencies: @@ -20964,13 +21363,13 @@ snapshots: transitivePeerDependencies: - typescript - '@tanstack/vite-config@0.4.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@tanstack/vite-config@0.4.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: rollup-plugin-preserve-directives: 0.4.0(rollup@4.60.1) - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vite-plugin-dts: 4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) - vite-plugin-externalize-deps: 0.10.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) - vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-dts: 4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-plugin-externalize-deps: 0.10.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@types/node' - rollup @@ -21054,14 +21453,14 @@ snapshots: dependencies: svelte: 5.55.1 - '@testing-library/svelte@5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))': + '@testing-library/svelte@5.3.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/svelte-core': 1.0.0(svelte@5.55.1) svelte: 5.55.1 optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tsconfig/svelte@5.0.8': {} @@ -21271,6 +21670,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + '@typescript-eslint/visitor-keys': 8.58.1 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.1-rc) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.58.1 @@ -21295,6 +21710,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.58.1(typescript@5.8.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.8.3) @@ -21313,6 +21740,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.58.1(typescript@6.0.1-rc)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/rule-tester@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) @@ -21340,6 +21776,10 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.1-rc)': + dependencies: + typescript: 6.0.1-rc + '@typescript-eslint/type-utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.58.1 @@ -21364,6 +21804,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.1-rc) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.58.1': {} '@typescript-eslint/typescript-estree@8.58.1(typescript@5.8.3)': @@ -21396,6 +21848,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.1-rc)': + dependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@6.0.1-rc) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) @@ -21418,6 +21885,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.1-rc) + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.58.1': dependencies: '@typescript-eslint/types': 8.58.1 @@ -21496,9 +21974,9 @@ snapshots: '@urql/core': 5.2.0(graphql@16.13.2) wonka: 6.3.6 - '@vercel/analytics@1.6.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3))': + '@vercel/analytics@1.6.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0))(react@19.2.4)(svelte@5.55.1)(vue@3.5.31(typescript@5.8.3))': optionalDependencies: - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.8.3)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) next: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.90.0) react: 19.2.4 svelte: 5.55.1 @@ -21578,7 +22056,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.2 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/parser': 7.29.2 acorn: 8.16.0 @@ -21589,24 +22067,24 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vinxi/server-components@0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@vinxi/server-components@0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) acorn: 8.16.0 acorn-loose: 8.5.2 acorn-typescript: 1.4.13(acorn@8.16.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vinxi: 0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitejs/plugin-basic-ssl@2.1.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -21614,21 +22092,21 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.8.3))': dependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.31(typescript@5.8.3) - '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3))': dependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.31(typescript@5.9.3) - '@vitest/coverage-istanbul@4.0.6(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))': + '@vitest/coverage-istanbul@4.0.6(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.4.3 @@ -21639,11 +22117,11 @@ snapshots: istanbul-reports: 3.2.0 magicast: 0.3.5 tinyrainbow: 3.1.0 - vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.6.14(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)))': + '@vitest/eslint-plugin@1.6.14(@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)(vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))': dependencies: '@typescript-eslint/scope-manager': 8.58.1 '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) @@ -21651,10 +22129,19 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) typescript: 5.9.3 - vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + optional: true + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 @@ -21664,24 +22151,53 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3))': + '@vitest/mocker@3.2.4(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.14(@types/node@22.19.15)(typescript@5.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + optional: true + + '@vitest/mocker@4.1.2(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.14(@types/node@22.19.15)(typescript@5.9.3) - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + optional: true '@vitest/pretty-format@4.1.2': dependencies: tinyrainbow: 3.1.0 + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + optional: true + '@vitest/runner@4.1.2': dependencies: '@vitest/utils': 4.1.2 pathe: 2.0.3 + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + optional: true + '@vitest/snapshot@4.1.2': dependencies: '@vitest/pretty-format': 4.1.2 @@ -21689,8 +22205,20 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + optional: true + '@vitest/spy@4.1.2': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + optional: true + '@vitest/utils@4.1.2': dependencies: '@vitest/pretty-format': 4.1.2 @@ -22442,7 +22970,7 @@ snapshots: astring@1.9.0: {} - astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(typescript@5.8.3)(yaml@2.8.3): + astro@5.18.1(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3): dependencies: '@astrojs/compiler': 2.13.1 '@astrojs/internal-helpers': 0.7.6 @@ -22499,8 +23027,8 @@ snapshots: unist-util-visit: 5.1.0 unstorage: 1.17.5(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1) vfile: 6.0.3 - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -23181,6 +23709,15 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + optional: true + chai@6.2.2: {} chalk@2.4.2: @@ -23208,6 +23745,9 @@ snapshots: charenc@0.0.2: {} + check-error@2.1.3: + optional: true + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -23835,6 +24375,8 @@ snapshots: cyclist@1.0.2: {} + data-uri-to-buffer@4.0.1: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -23896,6 +24438,9 @@ snapshots: dedent-js@1.0.1: {} + deep-eql@5.0.2: + optional: true + deep-equal@2.2.3: dependencies: array-buffer-byte-length: 1.0.2 @@ -25096,6 +25641,11 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fetch-retry@4.1.1: {} fflate@0.8.2: {} @@ -25255,6 +25805,10 @@ snapshots: dependencies: fd-package-json: 2.0.0 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + forwarded@0.2.0: {} fraction.js@5.3.4: {} @@ -25462,6 +26016,8 @@ snapshots: globals@16.5.0: {} + globals@17.5.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -26876,6 +27432,22 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 + lit-element@4.2.2: + dependencies: + '@lit-labs/ssr-dom-shim': 1.5.1 + '@lit/reactive-element': 2.1.2 + lit-html: 3.3.2 + + lit-html@3.3.2: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.2: + dependencies: + '@lit/reactive-element': 2.1.2 + lit-element: 4.2.2 + lit-html: 3.3.2 + lmdb@3.4.2: dependencies: msgpackr: 1.11.9 @@ -26989,6 +27561,9 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: + optional: true + lower-case@2.0.2: dependencies: tslib: 2.8.1 @@ -28146,6 +28721,8 @@ snapshots: dependencies: minimatch: 3.1.5 + node-domexception@1.0.0: {} + node-emoji@2.2.0: dependencies: '@sindresorhus/is': 4.6.0 @@ -28166,6 +28743,12 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-forge@1.4.0: {} node-gyp-build-optional-packages@5.2.2: @@ -28905,6 +29488,9 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: + optional: true + pbkdf2@3.1.5: dependencies: create-hash: 1.2.0 @@ -29004,20 +29590,22 @@ snapshots: optionalDependencies: postcss: 8.5.8 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.8 + tsx: 4.21.0 yaml: 2.8.3 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 postcss: 8.5.8 + tsx: 4.21.0 yaml: 2.8.3 postcss-media-query-parser@0.2.3: {} @@ -30880,7 +31468,7 @@ snapshots: tagged-tag@1.0.0: {} - tailwindcss@3.4.19(yaml@2.8.3): + tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -30899,7 +31487,7 @@ snapshots: postcss: 8.5.8 postcss-import: 15.1.0(postcss@8.5.8) postcss-js: 4.1.0(postcss@8.5.8) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.3) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3) postcss-nested: 6.2.0(postcss@8.5.8) postcss-selector-parser: 6.1.2 resolve: 1.22.11 @@ -31071,8 +31659,17 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinypool@1.1.1: + optional: true + + tinyrainbow@2.0.0: + optional: true + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: + optional: true + tldts-core@6.1.86: {} tldts-core@7.0.27: {} @@ -31153,6 +31750,10 @@ snapshots: dependencies: typescript: 5.9.3 + ts-api-utils@2.5.0(typescript@6.0.1-rc): + dependencies: + typescript: 6.0.1-rc + ts-declaration-location@1.0.7(typescript@5.9.3): dependencies: picomatch: 4.0.4 @@ -31184,16 +31785,16 @@ snapshots: tslib@2.8.1: {} - tsup-preset-solid@2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3)): + tsup-preset-solid@2.2.0(esbuild@0.27.4)(solid-js@1.9.12)(tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)): dependencies: esbuild-plugin-solid: 0.5.0(esbuild@0.27.4)(solid-js@1.9.12) - tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + tsup: 8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) transitivePeerDependencies: - esbuild - solid-js - supports-color - tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3): + tsup@8.5.1(@microsoft/api-extractor@7.47.7(@types/node@22.19.15))(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.4) cac: 6.7.14 @@ -31204,7 +31805,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.3) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3) resolve-from: 5.0.0 rollup: 4.60.1 source-map: 0.7.6 @@ -31222,6 +31823,13 @@ snapshots: - tsx - yaml + tsx@4.21.0: + dependencies: + esbuild: 0.27.4 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 + tty-browserify@0.0.0: {} tuf-js@4.1.0: @@ -31341,6 +31949,17 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-eslint@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc): + dependencies: + '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + '@typescript-eslint/typescript-estree': 8.58.1(typescript@6.0.1-rc) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.1-rc) + eslint: 9.39.4(jiti@2.6.1) + typescript: 6.0.1-rc + transitivePeerDependencies: + - supports-color + typescript@5.3.3: {} typescript@5.4.2: {} @@ -31701,7 +32320,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3): + vinxi@0.5.11(@types/node@22.19.15)(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -31735,7 +32354,7 @@ snapshots: unctx: 2.5.0 unenv: 1.10.0 unstorage: 1.17.5(@vercel/functions@2.2.13)(db0@0.3.4)(idb-keyval@6.2.2)(ioredis@5.10.1) - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) zod: 4.3.6 transitivePeerDependencies: - '@azure/app-configuration' @@ -31783,7 +32402,29 @@ snapshots: - xml2js - yaml - vite-plugin-dts@4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + optional: true + + vite-plugin-dts@4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@microsoft/api-extractor': 7.47.7(@types/node@22.19.15) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) @@ -31796,13 +32437,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-plugin-dts@4.2.3(@types/node@22.19.15)(rollup@4.60.1)(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@microsoft/api-extractor': 7.47.7(@types/node@22.19.15) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) @@ -31815,21 +32456,21 @@ snapshots: magic-string: 0.30.21 typescript: 6.0.1-rc optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-externalize-deps@0.10.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-plugin-externalize-deps@0.10.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-externalize-deps@0.9.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-plugin-externalize-deps@0.9.0(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 @@ -31837,14 +32478,14 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.12 solid-refresh: 0.6.3(solid-js@1.9.12) - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 @@ -31852,14 +32493,14 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.12 solid-refresh: 0.6.3(solid-js@1.9.12) - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) - vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.13(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-prerender-plugin@0.5.13(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -31867,31 +32508,31 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - typescript - vite-tsconfig-paths@5.1.4(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vite-tsconfig-paths@5.1.4(typescript@6.0.1-rc)(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.1-rc) optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - typescript - vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3): + vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -31906,9 +32547,10 @@ snapshots: lightningcss: 1.32.0 sass: 1.90.0 terser: 5.46.1 + tsx: 4.21.0 yaml: 2.8.3 - vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3): + vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -31923,20 +32565,65 @@ snapshots: lightningcss: 1.32.0 sass: 1.90.0 terser: 5.46.1 + tsx: 4.21.0 yaml: 2.8.3 - vitefu@1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vitefu@1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@1.21.7)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu@1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vitefu@1.1.2(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.32.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.12.14(@types/node@22.19.15)(typescript@5.8.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.13 + '@types/node': 22.19.15 + jsdom: 27.4.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + optional: true - vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)): + vitest@4.1.2(@types/node@22.19.15)(jsdom@27.4.0)(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(msw@2.12.14(@types/node@22.19.15)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -31953,7 +32640,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(yaml@2.8.3) + vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.15 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c5f243dfaab..34482c7ec4f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ packages: - 'examples/angular/*' - 'examples/react/*' - 'examples/preact/*' + - 'examples/lit/*' - 'examples/solid/*' - 'examples/svelte/*' - 'examples/vue/*'