diff --git a/packages/query-core/package.json b/packages/query-core/package.json index c15e999..32ac117 100644 --- a/packages/query-core/package.json +++ b/packages/query-core/package.json @@ -45,7 +45,7 @@ "@tuyau/core": "^1.0.0-beta.9" }, "devDependencies": { - "@tanstack/query-core": "^5.90.20", + "@tanstack/query-core": "^5.96.2", "@tuyau/core": "workspace:*" }, "tsup": { diff --git a/packages/react-query/package.json b/packages/react-query/package.json index 1787332..c7737b7 100644 --- a/packages/react-query/package.json +++ b/packages/react-query/package.json @@ -51,7 +51,7 @@ "@adonisjs/core": "^7.0.1", "@faker-js/faker": "^10.3.0", "@happy-dom/global-registrator": "^20.8.4", - "@tanstack/react-query": "^5.90.21", + "@tanstack/react-query": "^5.96.2", "@testing-library/react": "^16.3.2", "@tuyau/core": "workspace:*", "@types/react": "^19.2.14", diff --git a/packages/svelte-query/bin/test.ts b/packages/svelte-query/bin/test.ts new file mode 100644 index 0000000..0d24f06 --- /dev/null +++ b/packages/svelte-query/bin/test.ts @@ -0,0 +1,23 @@ +import { assert } from '@japa/assert' +import { snapshot } from '@japa/snapshot' +import { fileSystem } from '@japa/file-system' +import { expectTypeOf } from '@japa/expect-type' +import { processCLIArgs, configure, run } from '@japa/runner' + +import { HappyDom } from '../tests/helpers/happy_dom_env.ts' + +processCLIArgs(process.argv.slice(2)) +configure({ + files: ['tests/**/*.spec.ts'], + plugins: [assert(), expectTypeOf(), fileSystem({ autoClean: true }), snapshot()], + setup: [ + () => { + HappyDom.init() + return () => { + HappyDom.destroy() + } + }, + ], +}) + +run() diff --git a/packages/svelte-query/package.json b/packages/svelte-query/package.json new file mode 100644 index 0000000..55677e6 --- /dev/null +++ b/packages/svelte-query/package.json @@ -0,0 +1,79 @@ +{ + "name": "@tuyau/svelte-query", + "type": "module", + "version": "1.1.0", + "description": "Svelte Tanstack Query integration for Tuyau", + "author": "Julien Ripouteau ", + "license": "MIT", + "homepage": "https://github.com/Julien-R44/tuyau#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/Julien-R44/tuyau.git", + "directory": "packages/svelte-query" + }, + "bugs": { + "url": "https://github.com/Julien-R44/tuyau/issues" + }, + "keywords": [ + "adonisjs", + "typescript", + "typesafe", + "svelte-query", + "tanstack-query", + "tuyau", + "svelte" + ], + "exports": { + ".": "./build/index.js" + }, + "main": "build/index.js", + "files": [ + "build" + ], + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "lint": "oxlint .", + "typecheck": "tsc --noEmit", + "build": "tsup-node", + "test": "pnpm quick:test && pnpm test:integration", + "quick:test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts --force-exit", + "test:integration": "vitest run", + "checks": "pnpm lint && pnpm typecheck" + }, + "dependencies": { + "@tuyau/query-core": "workspace:*" + }, + "peerDependencies": { + "@tanstack/svelte-query": "^6.1.0", + "@tuyau/core": "^1.0.0-beta.9", + "svelte": "^5.0.0" + }, + "devDependencies": { + "@adonisjs/core": "^7.0.1", + "@faker-js/faker": "^10.3.0", + "@happy-dom/global-registrator": "^20.8.4", + "@tanstack/query-core": "^5.96.2", + "@tanstack/svelte-query": "^6.1.13", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tuyau/core": "workspace:*", + "nock": "^14.0.11", + "svelte": "^5.25.0", + "vitest": "^4.1.0" + }, + "tsup": { + "entry": [ + "./src/index.ts" + ], + "outDir": "./build", + "clean": true, + "format": "esm", + "dts": true, + "target": "esnext" + }, + "publishConfig": { + "access": "public", + "tag": "latest" + } +} diff --git a/packages/svelte-query/src/context.ts b/packages/svelte-query/src/context.ts new file mode 100644 index 0000000..a26cbdc --- /dev/null +++ b/packages/svelte-query/src/context.ts @@ -0,0 +1,88 @@ +import { setContext, getContext } from 'svelte' +import type { Tuyau } from '@tuyau/core/client' +import type { AdonisEndpoint, InferRoutes, TuyauRegistry } from '@tuyau/core/types' + +import { createTuyauSvelteQueryClient } from './main.ts' +import type { TuyauSvelteQuery } from './types/common.ts' + +const TUYAU_KEY = Symbol('tuyau') +const TUYAU_CLIENT_KEY = Symbol('tuyau-client') + +/** + * Options for setting up Tuyau context in a Svelte component tree + */ +export interface SetTuyauContextOptions< + TRegistry extends TuyauRegistry, + TRoutes extends Record = InferRoutes, +> { + client: Tuyau +} + +/** + * Sets up the Tuyau context in a Svelte component tree. + * Must be called during component initialization (in ` + * ``` + */ +export function setTuyauContext< + TRegistry extends TuyauRegistry, + TRoutes extends Record = InferRoutes, +>(options: SetTuyauContextOptions) { + const queryClient = createTuyauSvelteQueryClient({ client: options.client }) + setContext(TUYAU_KEY, queryClient) + setContext(TUYAU_CLIENT_KEY, options.client) + return queryClient +} + +/** + * Retrieves the Tuyau Svelte Query client from context. + * Must be called during component initialization. + * + * @example + * ```svelte + * + * ``` + */ +export function useTuyau< + TRegistry extends TuyauRegistry, + TRoutes extends Record = InferRoutes, +>(): TuyauSvelteQuery { + const ctx = getContext | undefined>(TUYAU_KEY) + if (!ctx) { + throw new Error( + 'useTuyau() must be called during component initialization after setTuyauContext()', + ) + } + return ctx +} + +/** + * Retrieves the raw Tuyau client from context. + * Must be called during component initialization. + */ +export function useTuyauClient< + TRegistry extends TuyauRegistry, + TRoutes extends Record = InferRoutes, +>(): Tuyau { + const client = getContext | undefined>(TUYAU_CLIENT_KEY) + if (!client) { + throw new Error( + 'useTuyauClient() must be called during component initialization after setTuyauContext()', + ) + } + return client +} diff --git a/packages/svelte-query/src/index.ts b/packages/svelte-query/src/index.ts new file mode 100644 index 0000000..0c6857e --- /dev/null +++ b/packages/svelte-query/src/index.ts @@ -0,0 +1,6 @@ +export * from './types/common.ts' +export * from './main.ts' +export * from './mutation.ts' +export * from './query.ts' +export * from './infinite_query.ts' +export * from './context.ts' diff --git a/packages/svelte-query/src/infinite_query.ts b/packages/svelte-query/src/infinite_query.ts new file mode 100644 index 0000000..fd4386b --- /dev/null +++ b/packages/svelte-query/src/infinite_query.ts @@ -0,0 +1,47 @@ +import { Tuyau } from '@tuyau/core/client' +import { createInfiniteQueryFn } from '@tuyau/query-core' +import type { TuyauQueryKey, TuyauRequestOptions } from '@tuyau/query-core' +import type { SkipToken } from '@tanstack/query-core' +import type { RawRequestArgs } from '@tuyau/core/types' +import type { CreateInfiniteQueryOptions } from '@tanstack/svelte-query' + +import type { AnyTuyauInfiniteQueryOptionsIn } from './types/common.ts' + +/** + * Internal options for building an infinite query options object + */ +interface TuyauInfiniteQueryOptionsOptions { + request: RawRequestArgs | SkipToken + opts?: AnyTuyauInfiniteQueryOptionsIn + queryKey: TuyauQueryKey + routeName: string + client: Tuyau + globalOptions?: TuyauRequestOptions +} + +/** + * Builds a TanStack Svelte Query `infiniteQueryOptions` object from Tuyau route information. + * Delegates queryFn creation to the shared `createInfiniteQueryFn` from `@tuyau/query-core`. + * + * @tanstack/svelte-query's barrel export re-exports .svelte component files + * (HydrationBoundary, QueryClientProvider) which Node.js cannot load without + * a Svelte compiler. Since `infiniteQueryOptions` is a pure identity function + * (it just returns its input for type inference), we construct the result directly + */ +export function tuyauInfiniteQueryOptions( + options: TuyauInfiniteQueryOptionsOptions, +): CreateInfiniteQueryOptions & { queryKey: TuyauQueryKey } { + const { request, routeName, opts, queryKey, client, globalOptions } = options + + const queryFn = createInfiniteQueryFn({ request, routeName, opts, client, globalOptions }) + + return { + ...opts, + queryKey, + queryFn, + + initialPageParam: opts?.initialPageParam ?? 1, + getNextPageParam: opts?.getNextPageParam ?? (() => null), + getPreviousPageParam: opts?.getPreviousPageParam, + } as any +} diff --git a/packages/svelte-query/src/main.ts b/packages/svelte-query/src/main.ts new file mode 100644 index 0000000..9296aa5 --- /dev/null +++ b/packages/svelte-query/src/main.ts @@ -0,0 +1,96 @@ +import { Tuyau } from '@tuyau/core/client' +import { buildKey, segmentsToRouteName, getMutationKeyInternal } from '@tuyau/query-core' +import type { QueryFilters } from '@tanstack/query-core' +import type { + AdonisEndpoint, + InferRoutes, + InferTree, + RawRequestArgs, + TuyauRegistry, +} from '@tuyau/core/types' +import type { TuyauQueryKey, TuyauRequestOptions } from '@tuyau/query-core' + +import { tuyauQueryOptions } from './query.ts' +import { tuyauInfiniteQueryOptions } from './infinite_query.ts' +import { tuyauMutationOptions } from './mutation.ts' +import type { TransformToSvelteQuery } from './types/common.ts' + +/** + * Creates a type-safe TanStack Svelte Query client from a Tuyau client instance. + * Returns a Proxy-based object that mirrors the API route tree and exposes + * `queryOptions`, `mutationOptions`, `infiniteQueryOptions`, and key/filter + * helpers on each endpoint node. + * + * GET/HEAD endpoints get query and infinite query methods. + * Other methods (POST, PUT, etc.) get mutation methods. + * All nodes get `pathKey` and `pathFilter` for cache operations + */ +export function createTuyauSvelteQueryClient< + Reg extends TuyauRegistry, + Tree = InferTree, + Routes extends Record = InferRoutes, +>(options: { + client: Tuyau + globalOptions?: TuyauRequestOptions +}): TransformToSvelteQuery { + const { client, globalOptions } = options + + function makeSvelteQueryNamed(segments: string[]): any { + const routeName = segmentsToRouteName(segments) + const decoratedEndpoint = { + queryOptions: (request: RawRequestArgs, opts?: any) => { + return tuyauQueryOptions({ + opts, + client, + request, + routeName, + globalOptions, + queryKey: buildKey({ segments, request, type: 'query' }), + }) + }, + + queryKey: (request: RawRequestArgs) => buildKey({ segments, request, type: 'query' }), + queryFilter: (request?: RawRequestArgs, filters?: QueryFilters) => ({ + queryKey: buildKey({ segments, request, type: 'query' }), + ...filters, + }), + + infiniteQueryOptions: (request: RawRequestArgs, opts?: any) => { + return tuyauInfiniteQueryOptions({ + opts, + client, + request, + routeName, + globalOptions, + queryKey: buildKey({ segments, request, type: 'infinite' }), + }) + }, + + infiniteQueryKey: (request: RawRequestArgs) => + buildKey({ segments, request, type: 'infinite' }), + infiniteQueryFilter: (request?: RawRequestArgs, filters?: QueryFilters) => ({ + queryKey: buildKey({ segments, request, type: 'infinite' }), + ...filters, + }), + + mutationOptions: (opts?: any) => tuyauMutationOptions({ opts, client, routeName }), + mutationKey: () => getMutationKeyInternal({ segments }), + + pathKey: () => buildKey({ segments, type: 'any' }), + pathFilter: (filters?: QueryFilters) => ({ + queryKey: buildKey({ segments, type: 'any' }), + ...filters, + }), + } + + return new Proxy(decoratedEndpoint, { + get: (target, prop) => { + if (typeof prop === 'symbol') return undefined + if (prop in target) return target[prop as keyof typeof target] + return makeSvelteQueryNamed([...segments, String(prop)]) + }, + }) + } + + return makeSvelteQueryNamed([]) as TransformToSvelteQuery +} diff --git a/packages/svelte-query/src/mutation.ts b/packages/svelte-query/src/mutation.ts new file mode 100644 index 0000000..7d97dca --- /dev/null +++ b/packages/svelte-query/src/mutation.ts @@ -0,0 +1,74 @@ +import type { SchemaEndpoint, RawRequestArgs, ErrorOf, ResponseOf } from '@tuyau/core/types' +import type { CreateMutationOptions } from '@tanstack/svelte-query' +import { getMutationKeyInternal, createMutationFn } from '@tuyau/query-core' +import type { DistributiveOmit, TuyauQueryBaseOptions, TuyauMutationKey } from '@tuyau/query-core' +import { Tuyau } from '@tuyau/core/client' + +type ReservedOptions = 'mutationKey' | 'mutationFn' + +/** + * User-facing mutation options input. Extends TanStack's mutation observer + * options but omits `mutationKey` and `mutationFn` which are auto-generated + */ +export interface TuyauMutationOptionsIn + extends + DistributiveOmit, ReservedOptions>, + TuyauQueryBaseOptions {} + +/** + * Fully resolved mutation options ready to be passed to `createMutation`. + * Includes the auto-generated `mutationKey` and `mutationFn` + */ +export interface TuyauMutationOptionsOut< + TInput, + TError, + TOutput, + TContext, +> extends CreateMutationOptions { + mutationKey: TuyauMutationKey +} + +/** + * Callable interface exposed on mutation endpoints. + * Returns fully resolved mutation options from user-provided options + */ +export interface TuyauSvelteMutationOptions { + ( + opts?: TuyauMutationOptionsIn, ErrorOf, ResponseOf, TContext>, + ): TuyauMutationOptionsOut, ErrorOf, ResponseOf, TContext> +} + +/** + * Decorates mutation endpoints with `mutationOptions` and `mutationKey` methods + */ +export interface DecorateMutationFn { + mutationOptions: TuyauSvelteMutationOptions + mutationKey: () => TuyauMutationKey +} + +/** + * Internal options for building a mutation options object + */ +export interface TuyauMutationOptionsOptions { + opts?: TuyauMutationOptionsIn + routeName: string + client: Tuyau +} + +/** + * Builds a TanStack Svelte Query `mutationOptions` object from Tuyau route information. + * Delegates mutationFn creation to the shared `createMutationFn` from `@tuyau/query-core`. + * + * @tanstack/svelte-query's barrel export re-exports .svelte component files + * (HydrationBoundary, QueryClientProvider) which Node.js cannot load without + * a Svelte compiler. Since `mutationOptions` is a pure identity function (it just + * returns its input for type inference), we construct the result directly + */ +export function tuyauMutationOptions( + options: TuyauMutationOptionsOptions, +): TuyauMutationOptionsOut { + const { opts, routeName, client } = options + const mutationKey = getMutationKeyInternal({ segments: routeName.split('.') }) + const mutationFn = createMutationFn({ opts, routeName, client }) + return { ...opts, mutationKey, mutationFn } +} diff --git a/packages/svelte-query/src/query.ts b/packages/svelte-query/src/query.ts new file mode 100644 index 0000000..55fe466 --- /dev/null +++ b/packages/svelte-query/src/query.ts @@ -0,0 +1,25 @@ +import { createQueryFn } from '@tuyau/query-core' +import type { CreateQueryFnOptions, TuyauQueryKey } from '@tuyau/query-core' +import type { CreateQueryOptions } from '@tanstack/svelte-query' + +/** + * Internal options for building a TanStack Query options object + */ +export type TuyauQueryOptionsOptions = CreateQueryFnOptions & { queryKey: TuyauQueryKey } + +/** + * Builds a TanStack Svelte Query `queryOptions` object from Tuyau route information. + * Delegates queryFn creation to the shared `createQueryFn` from `@tuyau/query-core`. + * + * @tanstack/svelte-query's barrel export re-exports .svelte component files + * (HydrationBoundary, QueryClientProvider) which Node.js cannot load without + * a Svelte compiler. Since `queryOptions` is a pure identity function (it just + * returns its input for type inference), we construct the result directly + */ +export function tuyauQueryOptions( + options: TuyauQueryOptionsOptions, +): CreateQueryOptions & { queryKey: TuyauQueryKey } { + const { queryKey, ...fnOptions } = options + const queryFn = createQueryFn(fnOptions) + return { ...options.opts, queryKey, queryFn } as any +} diff --git a/packages/svelte-query/src/types/common.ts b/packages/svelte-query/src/types/common.ts new file mode 100644 index 0000000..3cf24ab --- /dev/null +++ b/packages/svelte-query/src/types/common.ts @@ -0,0 +1,184 @@ +import type { SchemaEndpoint } from '@tuyau/core/types' +import type { DataTag } from '@tanstack/query-core' +import type { DefinedInitialDataOptions, UndefinedInitialDataOptions } from '@tanstack/svelte-query' +import type { CreateInfiniteQueryOptions } from '@tanstack/svelte-query' +import type { InitialDataFunction, NonUndefinedGuard } from '@tanstack/query-core' +import type { + TuyauQueryKey, + TuyauMutationKey, + TuyauRequestOptions, + TuyauQueryBaseOptions, + DecorateRouterKeyable, + DistributiveOmit, + WithRequired, +} from '@tuyau/query-core' + +import type { DecorateQueryFn } from './query.ts' +import type { DecorateMutationFn } from '../mutation.ts' +import type { DecorateInfiniteQueryFn } from './infinite_query.ts' + +export type { + TuyauQueryKey, + TuyauMutationKey, + TuyauQueryBaseOptions, + DecorateRouterKeyable, + DistributiveOmit, + WithRequired, + TuyauRequestOptions, +} +export type { QueryType } from '@tuyau/query-core' + +type ReservedQueryOptions = 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash' + +/** + * Query options input when `initialData` is defined. + * Omits reserved keys that are auto-generated by Tuyau + */ +export type DefinedTuyauQueryOptionsIn = DistributiveOmit< + DefinedInitialDataOptions, + ReservedQueryOptions +> & + TuyauQueryBaseOptions + +/** + * Query options input when `initialData` is undefined (default case). + * Omits reserved keys that are auto-generated by Tuyau + */ +export type UndefinedTuyauQueryOptionsIn = DistributiveOmit< + UndefinedInitialDataOptions, + ReservedQueryOptions +> & + TuyauQueryBaseOptions + +/** + * Fully resolved query options with defined initial data, + * ready to be passed to Svelte's `createQuery`. + * The `queryKey` is branded with `DataTag` for type-safe cache access + */ +export type DefinedTuyauQueryOptionsOut = DefinedInitialDataOptions< + TQueryFnData, + TError, + TData, + TuyauQueryKey +> & { + queryKey: DataTag +} + +/** + * Fully resolved query options with undefined initial data, + * ready to be passed to Svelte's `createQuery`. + * The `queryKey` is branded with `DataTag` for type-safe cache access + */ +export type UndefinedTuyauQueryOptionsOut = + UndefinedInitialDataOptions & { + queryKey: DataTag + } + +type ReservedInfiniteQueryOptions = 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash' + +/** + * Tuyau-specific infinite query option allowing the user to specify + * which request parameter holds the page value + */ +interface TuyauInfiniteQueryBaseOptionsIn { + /** + * The key used for the page parameter in the request query. + * For example, if your API expects `?page=1`, set this to `'page'`. + * If your API expects `?cursor=abc`, set this to `'cursor'`. + * Defaults to `'page'` + */ + pageParamKey?: string +} + +/** + * Infinite query options input when `initialData` is undefined (default case). + * Svelte Query does not export dedicated Defined/Undefined infinite variants, + * so we construct them from `CreateInfiniteQueryOptions` directly + */ +export type UndefinedTuyauInfiniteQueryOptionsIn = DistributiveOmit< + CreateInfiniteQueryOptions, + ReservedInfiniteQueryOptions +> & + TuyauQueryBaseOptions & + TuyauInfiniteQueryBaseOptionsIn & { + initialData?: undefined + } + +/** + * Fully resolved infinite query options with undefined initial data, + * ready to be passed to Svelte's `createInfiniteQuery` + */ +export type UndefinedTuyauInfiniteQueryOptionsOut = + CreateInfiniteQueryOptions & { + queryKey: DataTag + } + +/** + * Infinite query options input when `initialData` is defined + */ +export type DefinedTuyauInfiniteQueryOptionsIn = DistributiveOmit< + CreateInfiniteQueryOptions, + ReservedInfiniteQueryOptions +> & + TuyauQueryBaseOptions & + TuyauInfiniteQueryBaseOptionsIn & { + initialData: + | NonUndefinedGuard + | InitialDataFunction> + } + +/** + * Fully resolved infinite query options with defined initial data, + * ready to be passed to Svelte's `createInfiniteQuery` + */ +export type DefinedTuyauInfiniteQueryOptionsOut = + CreateInfiniteQueryOptions & { + queryKey: DataTag + } + +/** + * Union of all infinite query options input types. + * Used internally when the exact initialData variant is not known + */ +export type AnyTuyauInfiniteQueryOptionsIn = + | UndefinedTuyauInfiniteQueryOptionsIn + | DefinedTuyauInfiniteQueryOptionsIn + +type EndpointKeys = 'methods' | 'pattern' | 'types' + +/** + * Determines if an endpoint uses query methods (GET/HEAD) or mutation methods + */ +type IsQueryMethod = E['methods'][number] extends infer M + ? M extends 'GET' | 'HEAD' + ? true + : false + : false + +/** + * Resolves the available TanStack Query decorators for an endpoint. + * GET/HEAD endpoints get query + infinite query methods. + * Other methods (POST, PUT, etc.) get mutation methods. + * All endpoints get path-level key/filter methods + */ +export type EndpointNode = (IsQueryMethod extends true + ? DecorateQueryFn & DecorateInfiniteQueryFn + : DecorateMutationFn) & + DecorateRouterKeyable + +/** + * Recursively transforms a route tree into a Svelte Query client type. + * Each endpoint node gets query/mutation decorators, and nested + * route segments are recursively transformed. Every level also + * gets path-level key/filter methods via `DecorateRouterKeyable` + */ +export type TransformToSvelteQuery = { + [K in keyof T]: T[K] extends SchemaEndpoint + ? EndpointNode & TransformToSvelteQuery> + : TransformToSvelteQuery +} & DecorateRouterKeyable + +/** + * Top-level type for a Tuyau Svelte Query client + */ +export type TuyauSvelteQuery> = TransformToSvelteQuery diff --git a/packages/svelte-query/src/types/infinite_query.ts b/packages/svelte-query/src/types/infinite_query.ts new file mode 100644 index 0000000..0b29c5c --- /dev/null +++ b/packages/svelte-query/src/types/infinite_query.ts @@ -0,0 +1,72 @@ +import type { ErrorOf, RawRequestArgs, ResponseOf, SchemaEndpoint } from '@tuyau/core/types' +import type { DataTag, InfiniteData, QueryFilters, SkipToken } from '@tanstack/query-core' +import type { WithRequired, TuyauQueryKey } from '@tuyau/query-core' + +import type { + DefinedTuyauInfiniteQueryOptionsIn, + DefinedTuyauInfiniteQueryOptionsOut, + UndefinedTuyauInfiniteQueryOptionsIn, + UndefinedTuyauInfiniteQueryOptionsOut, +} from './common.ts' + +/** + * Callable interface for building infinite query options on a given endpoint. + * Provides overloads for different usage scenarios: + * - With undefined initial data (default) + * - With defined initial data + * - Without arguments + * - With skipToken for conditional queries + */ +export interface TuyauSvelteInfiniteQueryOptions { + /** + * With request input and undefined initial data (default case) + */ + >>( + input: RawRequestArgs, + opts: UndefinedTuyauInfiniteQueryOptionsIn, ErrorOf, TData>, + ): UndefinedTuyauInfiniteQueryOptionsOut, ErrorOf, TData> + + /** + * With defined initial data + */ + >>( + input: RawRequestArgs | SkipToken, + opts: DefinedTuyauInfiniteQueryOptionsIn, ErrorOf, TData>, + ): DefinedTuyauInfiniteQueryOptionsOut, ErrorOf, TData> + + /** + * No arguments + */ + (): UndefinedTuyauInfiniteQueryOptionsOut< + ResponseOf, + ErrorOf, + InfiniteData> + > + + /** + * With skipToken or conditional input and options + */ + >>( + input: RawRequestArgs | SkipToken, + opts: UndefinedTuyauInfiniteQueryOptionsIn, ErrorOf, TData>, + ): UndefinedTuyauInfiniteQueryOptionsOut, ErrorOf, TData> +} + +/** + * Decorates query endpoints (GET/HEAD) with `infiniteQueryOptions`, + * `infiniteQueryKey`, and `infiniteQueryFilter` methods for use + * with Svelte's `createInfiniteQuery` + */ +export interface DecorateInfiniteQueryFn { + infiniteQueryOptions: TuyauSvelteInfiniteQueryOptions + infiniteQueryKey: ( + args?: RawRequestArgs, + ) => DataTag>> + infiniteQueryFilter: ( + args?: RawRequestArgs, + filters?: QueryFilters>>>, + ) => WithRequired< + QueryFilters>>>, + 'queryKey' + > +} diff --git a/packages/svelte-query/src/types/query.ts b/packages/svelte-query/src/types/query.ts new file mode 100644 index 0000000..a936d19 --- /dev/null +++ b/packages/svelte-query/src/types/query.ts @@ -0,0 +1,69 @@ +import type { ErrorOf, RawRequestArgs, ResponseOf, SchemaEndpoint } from '@tuyau/core/types' +import type { DataTag, QueryFilters, SkipToken } from '@tanstack/query-core' +import type { WithRequired, TuyauQueryKey } from '@tuyau/query-core' + +import type { + DefinedTuyauQueryOptionsIn, + DefinedTuyauQueryOptionsOut, + UndefinedTuyauQueryOptionsIn, + UndefinedTuyauQueryOptionsOut, +} from './common.ts' + +/** + * Callable interface for building query options on a given endpoint. + * Provides overloads for different usage scenarios: + * - With defined initial data + * - With optional request args + * - Without arguments + * - With skipToken for conditional queries + */ +export interface TuyauSvelteQueryOptions { + /** + * With defined initial data + */ + >( + input: RawRequestArgs | SkipToken, + opts: DefinedTuyauQueryOptionsIn, TData, ErrorOf>, + ): DefinedTuyauQueryOptionsOut, TData, ErrorOf> + + /** + * Without initial data (default case) + */ + >( + input?: RawRequestArgs, + opts?: UndefinedTuyauQueryOptionsIn, TData, ErrorOf>, + ): UndefinedTuyauQueryOptionsOut, TData, ErrorOf> + + /** + * No arguments + */ + (): UndefinedTuyauQueryOptionsOut, ResponseOf, ErrorOf> + + /** + * With skipToken only (conditional query) + */ + ( + input: SkipToken, + ): UndefinedTuyauQueryOptionsOut, ResponseOf, ErrorOf> + + /** + * With skipToken or conditional input and optional options + */ + >( + input: RawRequestArgs | SkipToken, + opts?: UndefinedTuyauQueryOptionsIn, TData, ErrorOf>, + ): UndefinedTuyauQueryOptionsOut, TData, ErrorOf> +} + +/** + * Decorates query endpoints (GET/HEAD) with `queryOptions`, `queryKey`, + * and `queryFilter` methods for use with Svelte's `createQuery` + */ +export interface DecorateQueryFn { + queryOptions: TuyauSvelteQueryOptions + queryKey: (args?: RawRequestArgs) => DataTag> + queryFilter: ( + args?: RawRequestArgs, + filters?: QueryFilters>>, + ) => WithRequired>>, 'queryKey'> +} diff --git a/packages/svelte-query/tests/error_typings.spec.ts b/packages/svelte-query/tests/error_typings.spec.ts new file mode 100644 index 0000000..c202d15 --- /dev/null +++ b/packages/svelte-query/tests/error_typings.spec.ts @@ -0,0 +1,91 @@ +import { test } from '@japa/runner' +import { createTuyau } from '@tuyau/core/client' +import type { RouteWithRegistry } from '@tuyau/core/types' +import { errorRegistry } from '@tuyau/query-core/test-helpers' + +import { createTuyauSvelteQueryClient } from '../src/index.ts' + +test.group('Typing | errors', () => { + test('queryOptions exposes typed error narrowing', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: errorRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + tuyau.contacts.show.queryOptions({ params: { id: '1' } }, { enabled: false }) + + type QueryError = RouteWithRegistry.Error + + const assertTypedError = (error: QueryError) => { + if (error.isStatus(404)) { + expectTypeOf(error.response).toEqualTypeOf<{ message: string; id: string }>() + } + + if (error.isStatus(422)) { + expectTypeOf(error.response).toEqualTypeOf<{ field: string }>() + } + } + + expectTypeOf(assertTypedError).toBeCallableWith({} as QueryError) + }) + + test('mutationOptions exposes typed error narrowing', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: errorRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + tuyau.contacts.store.mutationOptions({ + onError: (error) => { + if (error.isStatus(409)) { + expectTypeOf(error.response).toEqualTypeOf<{ + message: string + existingId: number + }>() + } + + if (error.isStatus(422)) { + expectTypeOf(error.response).toEqualTypeOf<{ field: string }>() + } + }, + }) + + type MutationError = RouteWithRegistry.Error + + const assertTypedError = (error: MutationError) => { + if (error.isStatus(409)) { + expectTypeOf(error.response).toEqualTypeOf<{ message: string; existingId: number }>() + } + + if (error.isStatus(422)) { + expectTypeOf(error.response).toEqualTypeOf<{ field: string }>() + } + } + + expectTypeOf(assertTypedError).toBeCallableWith({} as MutationError) + }) + + test('infiniteQueryOptions exposes typed error narrowing', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: errorRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + tuyau.articles.index.infiniteQueryOptions( + { query: { page: 1 } }, + { + enabled: false, + initialPageParam: 1, + getNextPageParam: (lastPage) => lastPage.nextCursor, + }, + ) + + type InfiniteQueryError = RouteWithRegistry.Error + + const assertTypedError = (error: InfiniteQueryError) => { + if (error.isStatus(404)) { + expectTypeOf(error.response).toEqualTypeOf<{ message: string }>() + } + + if (error.isStatus(422)) { + expectTypeOf(error.response).toEqualTypeOf<{ field: string }>() + } + } + + expectTypeOf(assertTypedError).toBeCallableWith({} as InfiniteQueryError) + }) +}) diff --git a/packages/svelte-query/tests/fixtures/index.ts b/packages/svelte-query/tests/fixtures/index.ts new file mode 100644 index 0000000..3906029 --- /dev/null +++ b/packages/svelte-query/tests/fixtures/index.ts @@ -0,0 +1,2 @@ +export { defaultRegistry } from '@tuyau/query-core/test-helpers' +export type { ApiDefinition } from '@tuyau/query-core/test-helpers' diff --git a/packages/svelte-query/tests/helpers/happy_dom_env.ts b/packages/svelte-query/tests/helpers/happy_dom_env.ts new file mode 100644 index 0000000..1afdf86 --- /dev/null +++ b/packages/svelte-query/tests/helpers/happy_dom_env.ts @@ -0,0 +1,15 @@ +import { GlobalRegistrator } from '@happy-dom/global-registrator' + +export class HappyDom { + static init() { + GlobalRegistrator.register({ + url: 'http://localhost:3000', + width: 1920, + height: 1080, + }) + } + + static destroy() { + GlobalRegistrator.unregister() + } +} diff --git a/packages/svelte-query/tests/helpers/index.ts b/packages/svelte-query/tests/helpers/index.ts new file mode 100644 index 0000000..4a13332 --- /dev/null +++ b/packages/svelte-query/tests/helpers/index.ts @@ -0,0 +1,5 @@ +import { QueryClient } from '@tanstack/query-core' + +export const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) diff --git a/packages/svelte-query/tests/infinite_query.spec.ts b/packages/svelte-query/tests/infinite_query.spec.ts new file mode 100644 index 0000000..6b77c68 --- /dev/null +++ b/packages/svelte-query/tests/infinite_query.spec.ts @@ -0,0 +1,173 @@ +import nock from 'nock' +import { test } from '@japa/runner' +import { createTuyau } from '@tuyau/core/client' +import { skipToken } from '@tanstack/query-core' + +import { defaultRegistry } from './fixtures/index.ts' +import { createTuyauSvelteQueryClient } from '../src/index.ts' +import { withRequestCapture } from '@tuyau/query-core/test-helpers' + +test.group('Infinite Query', () => { + test('query keys', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.articles.index.infiniteQueryOptions( + { query: { page: 1, limit: 10 } }, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: (lastPage: any) => lastPage.nextCursor, + }, + ) + + assert.deepEqual(options.queryKey, [ + ['articles', 'index'], + { request: { query: { page: 1, limit: 10 } }, type: 'infinite' }, + ]) + + assert.deepEqual( + options.queryKey, + tuyau.articles.index.infiniteQueryKey({ query: { page: 1, limit: 10 } }), + ) + }) + + test('should have queryFn as a function', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.articles.index.infiniteQueryOptions( + { query: { page: 1, limit: 10 } }, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: (lastPage: any) => lastPage.nextCursor, + }, + ) + + assert.isFunction(options.queryFn) + }) + + test('should have default initialPageParam and getNextPageParam', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.articles.index.infiniteQueryOptions( + { query: { limit: 10 } }, + {} as any, + ) + + assert.equal(options.initialPageParam, 1) + assert.isFunction(options.getNextPageParam) + assert.isNull(options.getNextPageParam()) + }) +}) + +test.group('Infinite Query | skipToken', () => { + test('should handle skipToken in infiniteQueryOptions', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.articles.index.infiniteQueryOptions(skipToken, { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: () => null, + }) + + assert.isObject(options) + assert.property(options, 'queryKey') + assert.equal(typeof options.queryFn, 'symbol') + }) + + test('should work with conditional infinite queries', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + let shouldFetch = false + const getConditionalOptions = () => + tuyau.articles.index.infiniteQueryOptions( + shouldFetch ? { query: { page: 1, limit: 10 } } : skipToken, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: () => null, + }, + ) + + const options1: any = getConditionalOptions() + assert.equal(typeof options1.queryFn, 'symbol') + + shouldFetch = true + const options2: any = getConditionalOptions() + assert.notEqual(typeof options2.queryFn, 'symbol') + assert.isFunction(options2.queryFn) + }) +}) + +test.group('Infinite Query | Ky options', () => { + test('should pass timeout option to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/articles') + .query({ page: 1, limit: 10 }) + .reply(200, { data: [{ id: 1, title: 'Timeout Test' }], nextCursor: null }) + + const options: any = tuyau.articles.index.infiniteQueryOptions( + { query: { limit: 10 } }, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: (lastPage: any) => lastPage.nextCursor, + tuyau: { timeout: 60_000 }, + }, + ) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + pageParam: 1, + }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 60_000) + assert.equal(lastRequest?.options.retry, 0) + }) + + test('abortOnUnmount should not be passed to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/articles') + .query({ page: 1, limit: 10 }) + .reply(200, { data: [{ id: 1, title: 'Abort Test' }], nextCursor: null }) + + const options: any = tuyau.articles.index.infiniteQueryOptions( + { query: { limit: 10 } }, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: (lastPage: any) => lastPage.nextCursor, + tuyau: { abortOnUnmount: true, timeout: 5000 }, + }, + ) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + pageParam: 1, + }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 5000) + assert.notProperty(lastRequest?.options, 'abortOnUnmount') + }) +}) diff --git a/packages/svelte-query/tests/integration/infinite_query.svelte.test.ts b/packages/svelte-query/tests/integration/infinite_query.svelte.test.ts new file mode 100644 index 0000000..783e327 --- /dev/null +++ b/packages/svelte-query/tests/integration/infinite_query.svelte.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { createInfiniteQuery, skipToken } from '@tanstack/svelte-query' +import { createTuyau } from '@tuyau/core/client' +import nock from 'nock' + +import { defaultRegistry } from '../fixtures/index.ts' +import { createTuyauSvelteQueryClient } from '../../src/main.ts' +import { withEffectRoot, createTestQueryClient, settle } from './utils.svelte.ts' + +describe('Infinite Query | createInfiniteQuery', () => { + let queryClient = createTestQueryClient() + + beforeEach(() => { + queryClient = createTestQueryClient() + }) + + afterEach(() => { + queryClient.clear() + nock.cleanAll() + }) + + it( + 'basic e2e pagination', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/articles') + .query({ page: 1, limit: 10 }) + .reply(200, { data: [{ id: 1, title: 'Article 1' }], nextCursor: 2 }) + + const query = createInfiniteQuery( + () => + tuyau.articles.index.infiniteQueryOptions( + { query: { page: 1, limit: 10 } }, + { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: (lastPage: any) => lastPage.nextCursor, + }, + ), + () => queryClient, + ) + + expect(query.isPending).toBe(true) + + await settle() + + expect(query.isSuccess).toBe(true) + expect(query.data!.pages).toHaveLength(1) + expect(query.data!.pages[0].data[0].title).toBe('Article 1') + }), + ) + + it( + 'skipToken prevents fetching', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const query = createInfiniteQuery( + () => + tuyau.articles.index.infiniteQueryOptions(skipToken, { + pageParamKey: 'page', + initialPageParam: 1, + getNextPageParam: () => null, + }), + () => queryClient, + ) + + expect(query.isFetching).toBe(false) + expect(query.data).toBeUndefined() + }), + ) +}) diff --git a/packages/svelte-query/tests/integration/mutation.svelte.test.ts b/packages/svelte-query/tests/integration/mutation.svelte.test.ts new file mode 100644 index 0000000..21a59e9 --- /dev/null +++ b/packages/svelte-query/tests/integration/mutation.svelte.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { createMutation } from '@tanstack/svelte-query' +import { createTuyau } from '@tuyau/core/client' +import nock from 'nock' + +import { defaultRegistry } from '../fixtures/index.ts' +import { createTuyauSvelteQueryClient } from '../../src/main.ts' +import { withEffectRoot, createTestQueryClient, settle } from './utils.svelte.ts' + +describe('Mutation | createMutation', () => { + let queryClient = createTestQueryClient() + + beforeEach(() => { + queryClient = createTestQueryClient() + }) + + afterEach(() => { + queryClient.clear() + nock.cleanAll() + }) + + it( + 'basic e2e mutation', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333').post('/users').reply(201, { id: 1, name: 'foo' }) + + const mutation = createMutation( + () => tuyau.users.store.mutationOptions(), + () => queryClient, + ) + + mutation.mutate({ body: { name: 'foo' } }) + + await settle() + + expect(mutation.isSuccess).toBe(true) + }), + ) + + it( + 'onSuccess callback fires', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + let onSuccessCalled = false + + nock('http://localhost:3333').post('/users').reply(201, { id: 101, name: 'bar' }) + + const mutation = createMutation( + () => + tuyau.users.store.mutationOptions({ + onSuccess: () => { + onSuccessCalled = true + }, + }), + () => queryClient, + ) + + mutation.mutate({ body: { name: 'bar' } }) + + await settle() + + expect(mutation.isSuccess).toBe(true) + expect(onSuccessCalled).toBe(true) + }), + ) +}) diff --git a/packages/svelte-query/tests/integration/query.svelte.test.ts b/packages/svelte-query/tests/integration/query.svelte.test.ts new file mode 100644 index 0000000..c6dd9b4 --- /dev/null +++ b/packages/svelte-query/tests/integration/query.svelte.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { createQuery, skipToken } from '@tanstack/svelte-query' +import { createTuyau } from '@tuyau/core/client' +import nock from 'nock' + +import { defaultRegistry } from '../fixtures/index.ts' +import { createTuyauSvelteQueryClient } from '../../src/main.ts' +import { withEffectRoot, createTestQueryClient, settle } from './utils.svelte.ts' + +describe('Query | createQuery', () => { + let queryClient = createTestQueryClient() + + beforeEach(() => { + queryClient = createTestQueryClient() + }) + + afterEach(() => { + queryClient.clear() + nock.cleanAll() + }) + + it( + 'basic e2e fetch', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/users') + .query({ email: 'fo@ok.com' }) + .reply(200, [{ id: 1, name: 'foo' }]) + + const query = createQuery( + () => tuyau.users.index.queryOptions({ query: { email: 'fo@ok.com' } }), + () => queryClient, + ) + + expect(query.isPending).toBe(true) + + await settle() + + expect(query.isSuccess).toBe(true) + expect(query.data).toEqual([{ id: 1, name: 'foo' }]) + }), + ) + + it( + 'with initial data', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const query = createQuery( + () => + tuyau.users.index.queryOptions({}, { initialData: () => [{ id: 1, name: 'initial' }] }), + () => queryClient, + ) + + expect(query.data).toEqual([{ id: 1, name: 'initial' }]) + }), + ) + + it( + 'skipToken prevents fetching', + withEffectRoot(async () => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const query = createQuery( + () => tuyau.users.index.queryOptions(skipToken), + () => queryClient, + ) + + expect(query.isFetching).toBe(false) + expect(query.data).toBeUndefined() + }), + ) +}) diff --git a/packages/svelte-query/tests/integration/utils.svelte.ts b/packages/svelte-query/tests/integration/utils.svelte.ts new file mode 100644 index 0000000..1012bce --- /dev/null +++ b/packages/svelte-query/tests/integration/utils.svelte.ts @@ -0,0 +1,28 @@ +import { QueryClient } from '@tanstack/query-core' +import { flushSync } from 'svelte' + +export function withEffectRoot(fn: () => void | Promise) { + return async () => { + let promise: void | Promise = Promise.resolve() + const cleanup = $effect.root(() => { + promise = fn() + }) + await promise + cleanup() + } +} + +export function createTestQueryClient() { + return new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }) +} + +/** + * Wait for nock-intercepted requests to resolve and Svelte reactive state to update. + * Uses real setTimeout (not fake timers) since nock uses the real async pipeline. + */ +export async function settle(ms = 100) { + await new Promise((resolve) => setTimeout(resolve, ms)) + flushSync() +} diff --git a/packages/svelte-query/tests/mutation.spec.ts b/packages/svelte-query/tests/mutation.spec.ts new file mode 100644 index 0000000..03383ce --- /dev/null +++ b/packages/svelte-query/tests/mutation.spec.ts @@ -0,0 +1,105 @@ +import nock from 'nock' +import { test } from '@japa/runner' +import { createTuyau } from '@tuyau/core/client' + +import { defaultRegistry } from './fixtures/index.ts' +import { TuyauMutationKey } from '../src/types/common.ts' +import { createTuyauSvelteQueryClient } from '../src/index.ts' +import { withRequestCapture } from '@tuyau/query-core/test-helpers' + +test.group('Mutation | Options', () => { + test('mutationOptions should create valid mutation options object', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options = tuyau.users.store.mutationOptions({ + onSuccess: () => {}, + onError: () => {}, + }) + + assert.isObject(options) + assert.property(options, 'mutationKey') + assert.property(options, 'mutationFn') + assert.property(options, 'onSuccess') + assert.property(options, 'onError') + assert.isArray(options.mutationKey) + assert.isFunction(options.mutationFn) + }) + + test('mutationKey should generate consistent keys', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const key1 = tuyau.users.store.mutationKey() + const key2 = tuyau.users.store.mutationKey() + + assert.deepEqual(key1, key2) + assert.deepEqual(key1, [['users', 'store']]) + }) + + test('mutationKey should return TuyauMutationKey type', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const mutationKey = tuyau.do.something.mutationKey() + expectTypeOf(mutationKey).toMatchTypeOf() + expectTypeOf(mutationKey[0]).toMatchTypeOf() + }) +}) + +test.group('Mutation | Ky retry disabled', () => { + test('should pass retry: 0 to disable Ky retries', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333').post('/users').reply(201, { id: 1, name: 'retry-test' }) + + const options: any = tuyau.users.store.mutationOptions() + + await options.mutationFn({ body: { name: 'retry-test' } }) + + assert.equal(capture.getLastRequest()?.options.retry, 0) + }) +}) + +test.group('Mutation | Ky options', () => { + test('should pass timeout option to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333').post('/users').reply(201, { id: 1, name: 'timeout-test' }) + + const options: any = tuyau.users.store.mutationOptions({ + tuyau: { timeout: 60_000 }, + }) + + await options.mutationFn({ body: { name: 'timeout-test' } }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 60_000) + assert.equal(lastRequest?.options.retry, 0) + }) + + test('abortOnUnmount should not be passed to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333').post('/users').reply(201, { id: 1, name: 'abort-test' }) + + const options: any = tuyau.users.store.mutationOptions({ + tuyau: { abortOnUnmount: true, timeout: 5000 }, + }) + + await options.mutationFn({ body: { name: 'abort-test' } }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 5000) + assert.notProperty(lastRequest?.options, 'abortOnUnmount') + }) +}) diff --git a/packages/svelte-query/tests/query.spec.ts b/packages/svelte-query/tests/query.spec.ts new file mode 100644 index 0000000..b4016cb --- /dev/null +++ b/packages/svelte-query/tests/query.spec.ts @@ -0,0 +1,417 @@ +import nock from 'nock' +import { test } from '@japa/runner' +import { createTuyau } from '@tuyau/core/client' +import { skipToken } from '@tanstack/query-core' + +import { defaultRegistry } from './fixtures/index.ts' +import type { TuyauQueryKey } from '../src/types/common.ts' +import { createTuyauSvelteQueryClient } from '../src/main.ts' +import { withRequestCapture } from '@tuyau/query-core/test-helpers' +import { queryClient } from './helpers/index.ts' + +test.group('Query | queryOptions', () => { + test('basic', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const result: any = tuyau.users.index.queryOptions( + { query: { name: 'foo' } }, + { initialData: () => [{ id: 1, name: 'foo' }], staleTime: 1000 }, + ) + + assert.isFunction(result.queryFn) + assert.deepEqual(result.staleTime, 1000) + assert.deepEqual(result.queryKey, [ + ['users', 'index'], + { request: { query: { name: 'foo' } }, type: 'query' }, + ]) + }) + + test('with route param call', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const result: any = tuyau.users.show.queryOptions({ params: { id: '1' } }) + + assert.isFunction(result.queryFn) + assert.deepEqual(result.queryKey, [ + ['users', 'show'], + { request: { params: { id: '1' } }, type: 'query' }, + ]) + }) +}) + +test.group('Query | queryKey', () => { + test('basic', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const r1 = tuyau.users.index.queryKey({ query: { name: 'foo' } }) + const r2 = tuyau.users.index.queryKey() + const r3 = tuyau.users.comments.index.queryKey({ params: { userId: '1' } }) + + assert.deepEqual(r1, [ + ['users', 'index'], + { request: { query: { name: 'foo' } }, type: 'query' }, + ]) + assert.deepEqual(r2, [['users', 'index'], { type: 'query' }]) + assert.deepEqual(r3, [ + ['users', 'comments', 'index'], + { request: { params: { userId: '1' } }, type: 'query' }, + ]) + }) +}) + +test.group('Query | pathKey', () => { + test('basic', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const r1 = tuyau.users.pathKey() + const r4 = tuyau.users.comments.pathKey() + const r5 = tuyau.users.comments.index.pathKey() + const r6 = tuyau.users.show.pathKey() + + assert.deepEqual(r1, [['users']]) + assert.deepEqual(r4, [['users', 'comments']]) + assert.deepEqual(r5, [['users', 'comments', 'index']]) + assert.deepEqual(r6, [['users', 'show']]) + }) +}) + +test.group('Query | Filters', () => { + test('queryFilter should merge filters with queryKey', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const filter = tuyau.users.index.queryFilter({ query: { name: 'foo' } }, { stale: true }) + + assert.property(filter, 'queryKey') + assert.property(filter, 'stale') + assert.equal(filter.stale, true) + assert.deepEqual(filter.queryKey, [ + ['users', 'index'], + { request: { query: { name: 'foo' } }, type: 'query' }, + ]) + }) + + test('pathFilter should merge filters with pathKey', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const filter = tuyau.users.pathFilter({ stale: true }) + + assert.property(filter, 'queryKey') + assert.property(filter, 'stale') + assert.equal(filter.stale, true) + assert.deepEqual(filter.queryKey, [['users']]) + }) +}) + +test.group('Query | Skip Token', () => { + test('should handle skipToken in queryOptions', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.users.index.queryOptions(skipToken) + + assert.isObject(options) + assert.property(options, 'queryKey') + assert.equal(typeof options.queryFn, 'symbol') + }) + + test('should work with conditional queries', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + let shouldFetch = false + const getConditionalOptions = () => + tuyau.users.index.queryOptions(shouldFetch ? { query: { name: 'foo' } } : skipToken) + + const options1: any = getConditionalOptions() + assert.equal(typeof options1.queryFn, 'symbol') + + shouldFetch = true + const options2: any = getConditionalOptions() + assert.notEqual(typeof options2.queryFn, 'symbol') + assert.isFunction(options2.queryFn) + }) +}) + +test.group('Query | Route Parameters', () => { + test('should handle single route parameters', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.users.show.queryOptions({ params: { id: '1' } }) + const queryKey = tuyau.users.show.queryKey({ params: { id: '1' } }) + + assert.deepEqual(options.queryKey, [ + ['users', 'show'], + { request: { params: { id: '1' } }, type: 'query' }, + ]) + assert.deepEqual(queryKey, [ + ['users', 'show'], + { request: { params: { id: '1' } }, type: 'query' }, + ]) + }) + + test('should handle nested route parameters', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.posts.comments.likes.detail.queryOptions({ + params: { postId: '1', commentId: '2', likeId: '3' }, + query: { foo: 'bar' }, + }) + + const queryKey = tuyau.posts.comments.likes.detail.queryKey({ + params: { postId: '1', commentId: '2', likeId: '3' }, + query: { foo: 'bar' }, + }) + + assert.deepEqual(options.queryKey, [ + ['posts', 'comments', 'likes', 'detail'], + { + request: { + params: { postId: '1', commentId: '2', likeId: '3' }, + query: { foo: 'bar' }, + }, + type: 'query', + }, + ]) + assert.deepEqual(queryKey, options.queryKey) + }) +}) + +test.group('Query | CamelCase to snake_case conversion', () => { + test('should convert camelCase route segments to snake_case for route lookup', async ({ + assert, + }) => { + let capturedRouteName: string | undefined + + const client = createTuyau({ + baseUrl: 'http://localhost:3333', + registry: defaultRegistry, + }) as any + + const originalRequest = client.request.bind(client) + client.request = async (routeName: string, opts?: any) => { + capturedRouteName = routeName + return originalRequest(routeName as any, opts) + } + + const tuyau = createTuyauSvelteQueryClient({ client }) + + // @ts-ignore + const options: any = tuyau.products.byCategory.queryOptions({ + params: { category: 'electronics' }, + }) + + try { + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + client: queryClient, + } as any) + } catch { + // Ignore network errors + } + + assert.equal(capturedRouteName, 'products.by_category') + }) + + test('should preserve query key with camelCase segments for cache consistency', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const queryKey = tuyau.products.byCategory.queryKey({ params: { category: 'electronics' } }) + + assert.deepEqual(queryKey, [ + ['products', 'byCategory'], + { request: { params: { category: 'electronics' } }, type: 'query' }, + ]) + }) +}) + +test.group('Types | Query Types', () => { + test('queryOptions should return correct types', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const undefinedOptions = tuyau.users.index.queryOptions({ query: { name: 'foo' } }) + expectTypeOf(undefinedOptions.queryKey).toMatchTypeOf() + + const definedOptions = tuyau.users.index.queryOptions( + { query: { name: 'foo' } }, + { initialData: () => [{ id: 1, name: 'foo' }] }, + ) + expectTypeOf(definedOptions.queryKey).toMatchTypeOf() + + tuyau.users.index.queryOptions(skipToken) + }) + + test('queryKey should return TuyauQueryKey type', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const queryKey = tuyau.users.index.queryKey({ query: { name: 'foo' } }) + expectTypeOf(queryKey).toMatchTypeOf() + expectTypeOf(queryKey[0]).toMatchTypeOf() + }) + + test('pathKey should return TuyauQueryKey type', ({ expectTypeOf }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const pathKey = tuyau.users.pathKey() + expectTypeOf(pathKey).toMatchTypeOf() + expectTypeOf(pathKey[0]).toMatchTypeOf() + }) +}) + +test.group('Query | Advanced Options', () => { + test('should support custom query options', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const options: any = tuyau.users.index.queryOptions( + { query: { name: 'foo' } }, + { + staleTime: 5000, + retry: 3, + enabled: false, + }, + ) + + assert.equal(options.staleTime, 5000) + assert.equal(options.retry, 3) + assert.equal(options.enabled, false) + }) + + test('should support initialData with correct types', ({ assert }) => { + const client = createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }) + const tuyau = createTuyauSvelteQueryClient({ client }) + + const initialData = [{ id: 1, name: 'initial' }] + const options: any = tuyau.users.index.queryOptions( + { query: { name: 'foo' } }, + { initialData: () => initialData }, + ) + + assert.isFunction(options.initialData) + assert.deepEqual((options.initialData as Function)(), initialData) + }) +}) + +test.group('Query | Ky retry disabled', () => { + test('should pass retry: 0 to disable Ky retries', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/users') + .query({ name: 'retry-test' }) + .reply(200, [{ id: 1, name: 'retry-test' }]) + + const options: any = tuyau.users.index.queryOptions({ query: { name: 'retry-test' } }) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + client: queryClient, + }) + + assert.equal(capture.getLastRequest()?.options.retry, 0) + }) +}) + +test.group('Query | Ky options', () => { + test('should pass timeout option to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/users') + .query({ name: 'timeout-test' }) + .reply(200, [{ id: 1, name: 'timeout-test' }]) + + const options: any = tuyau.users.index.queryOptions( + { query: { name: 'timeout-test' } }, + { tuyau: { timeout: 60_000 } }, + ) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + client: queryClient, + }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 60_000) + assert.equal(lastRequest?.options.retry, 0) + }) + + test('should pass custom headers option to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/users') + .query({ name: 'headers-test' }) + .reply(200, [{ id: 1, name: 'headers-test' }]) + + const options: any = tuyau.users.index.queryOptions( + { query: { name: 'headers-test' } }, + { tuyau: { headers: { 'X-Custom-Header': 'custom-value' } } }, + ) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + client: queryClient, + }) + + assert.deepEqual(capture.getLastRequest()?.options.headers, { + 'X-Custom-Header': 'custom-value', + }) + }) + + test('abortOnUnmount should not be passed to client.request', async ({ assert }) => { + const { client, capture } = withRequestCapture( + createTuyau({ baseUrl: 'http://localhost:3333', registry: defaultRegistry }), + ) + const tuyau = createTuyauSvelteQueryClient({ client }) + + nock('http://localhost:3333') + .get('/users') + .query({ name: 'abort-test' }) + .reply(200, [{ id: 1, name: 'abort-test' }]) + + const options: any = tuyau.users.index.queryOptions( + { query: { name: 'abort-test' } }, + { tuyau: { abortOnUnmount: true, timeout: 5000 } }, + ) + + await options.queryFn!({ + queryKey: options.queryKey, + meta: undefined, + signal: new AbortController().signal, + client: queryClient, + }) + + const lastRequest = capture.getLastRequest() + assert.equal(lastRequest?.options.timeout, 5000) + assert.notProperty(lastRequest?.options, 'abortOnUnmount') + }) +}) diff --git a/packages/svelte-query/tsconfig.json b/packages/svelte-query/tsconfig.json new file mode 100644 index 0000000..609535c --- /dev/null +++ b/packages/svelte-query/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@adonisjs/tsconfig/tsconfig.package.json", + "compilerOptions": { + "lib": ["DOM", "ESNext"], + "rootDir": "./", + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "outDir": "./build" + }, + "exclude": ["tests/integration", "vitest.config.ts"] +} diff --git a/packages/svelte-query/vitest.config.ts b/packages/svelte-query/vitest.config.ts new file mode 100644 index 0000000..5e87004 --- /dev/null +++ b/packages/svelte-query/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], + test: { + include: ['tests/integration/**/*.svelte.test.ts'], + environment: 'happy-dom', + }, +}) diff --git a/packages/vue-query/package.json b/packages/vue-query/package.json index 93355cc..9ff4caf 100644 --- a/packages/vue-query/package.json +++ b/packages/vue-query/package.json @@ -52,8 +52,8 @@ "@adonisjs/core": "^7.0.1", "@faker-js/faker": "^10.3.0", "@happy-dom/global-registrator": "^20.8.4", - "@tanstack/query-core": "^5.90.20", - "@tanstack/vue-query": "^5.92.9", + "@tanstack/query-core": "^5.96.2", + "@tanstack/vue-query": "^5.96.2", "@tuyau/core": "workspace:*", "@vue/test-utils": "^2.4.6", "nock": "^14.0.11", diff --git a/playgrounds/basic/package.json b/playgrounds/basic/package.json index b219903..83a2a28 100644 --- a/playgrounds/basic/package.json +++ b/playgrounds/basic/package.json @@ -45,7 +45,7 @@ "@adonisjs/vite": "^5.1.0", "@inertiajs/react": "^2.3.18", "@tailwindcss/vite": "^4.2.1", - "@tanstack/react-query": "^5.90.21", + "@tanstack/react-query": "^5.96.2", "@tuyau/core": "workspace:*", "@tuyau/react-query": "workspace:*", "@vinejs/vine": "^4.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6f1bbe..4dafaa5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,8 +106,8 @@ importers: packages/query-core: devDependencies: '@tanstack/query-core': - specifier: ^5.90.20 - version: 5.90.20 + specifier: ^5.96.2 + version: 5.96.2 '@tuyau/core': specifier: workspace:* version: link:../core @@ -128,8 +128,8 @@ importers: specifier: ^20.8.4 version: 20.8.4 '@tanstack/react-query': - specifier: ^5.90.21 - version: 5.90.21(react@19.2.4) + specifier: ^5.96.2 + version: 5.96.2(react@19.2.4) '@testing-library/react': specifier: ^16.3.2 version: 16.3.2(@testing-library/dom@10.4.0)(@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) @@ -168,6 +168,43 @@ importers: specifier: ^14.0.11 version: 14.0.11 + packages/svelte-query: + dependencies: + '@tuyau/query-core': + specifier: workspace:* + version: link:../query-core + devDependencies: + '@adonisjs/core': + specifier: ^7.0.1 + version: 7.0.1(@adonisjs/assembler@8.0.1(typescript@5.9.3))(@vinejs/vine@4.3.0)(edge.js@6.5.0)(pino-pretty@13.1.3)(youch@4.1.0) + '@faker-js/faker': + specifier: ^10.3.0 + version: 10.3.0 + '@happy-dom/global-registrator': + specifier: ^20.8.4 + version: 20.8.4 + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + '@tanstack/query-core': + specifier: ^5.96.2 + version: 5.96.2 + '@tanstack/svelte-query': + specifier: ^6.1.13 + version: 6.1.13(svelte@5.55.2) + '@tuyau/core': + specifier: workspace:* + version: link:../core + nock: + specifier: ^14.0.11 + version: 14.0.11 + svelte: + specifier: ^5.25.0 + version: 5.55.2 + vitest: + specifier: ^4.1.0 + version: 4.1.3(@types/node@25.5.0)(happy-dom@20.8.4)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + packages/vue-query: dependencies: '@tuyau/query-core': @@ -184,11 +221,11 @@ importers: specifier: ^20.8.4 version: 20.8.4 '@tanstack/query-core': - specifier: ^5.90.20 - version: 5.90.20 + specifier: ^5.96.2 + version: 5.96.2 '@tanstack/vue-query': - specifier: ^5.92.9 - version: 5.92.9(vue@3.5.30(typescript@5.9.3)) + specifier: ^5.96.2 + version: 5.96.2(vue@3.5.30(typescript@5.9.3)) '@tuyau/core': specifier: workspace:* version: link:../core @@ -238,8 +275,8 @@ importers: specifier: ^4.2.1 version: 4.2.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) '@tanstack/react-query': - specifier: ^5.90.21 - version: 5.90.21(react@19.2.4) + specifier: ^5.96.2 + version: 5.96.2(react@19.2.4) '@tuyau/core': specifier: workspace:* version: link:../../packages/core @@ -334,10 +371,6 @@ importers: packages: - '@aashutoshrathi/word-wrap@1.2.6': - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - '@adobe/css-tools@4.4.3': resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} @@ -1448,8 +1481,8 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/config-helpers@0.4.2': @@ -1460,8 +1493,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.3': @@ -1488,20 +1521,16 @@ packages: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.2': - resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} '@img/colour@1.1.0': @@ -2304,6 +2333,26 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2': + resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@sveltejs/vite-plugin-svelte@6.2.4': + resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + '@swc/core-darwin-arm64@1.15.18': resolution: {integrity: sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==} engines: {node: '>=10'} @@ -2481,16 +2530,21 @@ packages: resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} engines: {node: '>=12'} - '@tanstack/query-core@5.90.20': - resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + '@tanstack/query-core@5.96.2': + resolution: {integrity: sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==} - '@tanstack/react-query@5.90.21': - resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==} + '@tanstack/react-query@5.96.2': + resolution: {integrity: sha512-sYyzzJT4G0g02azzJ8o55VFFV31XvFpdUpG+unxS0vSaYsJnSPKGoI6WdPwUucJL1wpgGfwfmntNX/Ub1uOViA==} peerDependencies: react: ^18 || ^19 - '@tanstack/vue-query@5.92.9': - resolution: {integrity: sha512-jjAZcqKveyX0C4w/6zUqbnqk/XzuxNWaFsWjGTJWULVFizUNeLGME2gf9vVSDclIyiBhR13oZJPPs6fJgfpIJQ==} + '@tanstack/svelte-query@6.1.13': + resolution: {integrity: sha512-A/BB9BuRoPSEJZqVUU5sqcAgi13XdfldlJtd8XmbwAXr/fxEMfpSEMYmIxLmUb/s9EVYRJi3bew9OjZddkx0cg==} + peerDependencies: + svelte: ^5.25.0 + + '@tanstack/vue-query@5.96.2': + resolution: {integrity: sha512-mDJoLG1kElNu0vFf8m+sdzka15lWXUZ3CXYVOuivHVdrbjjs5fI0o8i1iqAtIGGL5q+sek4XTbPeWW327zOgtQ==} peerDependencies: '@vue/composition-api': ^1.1.2 vue: ^2.6.0 || ^3.3.0 @@ -2598,6 +2652,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} @@ -2695,6 +2752,35 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@vitest/expect@4.1.3': + resolution: {integrity: sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==} + + '@vitest/mocker@4.1.3': + resolution: {integrity: sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.3': + resolution: {integrity: sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==} + + '@vitest/runner@4.1.3': + resolution: {integrity: sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==} + + '@vitest/snapshot@4.1.3': + resolution: {integrity: sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==} + + '@vitest/spy@4.1.3': + resolution: {integrity: sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==} + + '@vitest/utils@4.1.3': + resolution: {integrity: sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==} + '@vue/compiler-core@3.5.30': resolution: {integrity: sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==} @@ -2752,8 +2838,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} @@ -2795,6 +2881,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + arkregex@0.0.4: resolution: {integrity: sha512-biS/FkvSwQq59TZ453piUp8bxMui11pgOMV9WHAnli1F8o0ayNCZzUwQadL/bGIUic5TkS/QlPcyMuI8ZIwedQ==} @@ -2826,6 +2916,10 @@ packages: axios@1.13.6: resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2864,6 +2958,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.13: + resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -2942,6 +3039,10 @@ packages: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2997,6 +3098,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + code-block-writer@13.0.3: resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} @@ -3197,6 +3302,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devalue@5.7.0: + resolution: {integrity: sha512-qCvc8m7cImp1QDCsiY+C2EdSBWSj7Ucfoq87scSdYboDiIKdvMtFbH1U2VReBls6WMhMaUOoK3ZJEDNG/7zm3w==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3294,6 +3402,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -3392,6 +3503,9 @@ packages: jiti: optional: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} @@ -3405,10 +3519,13 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} + esrap@2.2.4: + resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -3420,6 +3537,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -3527,8 +3647,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} flattie@1.1.1: resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} @@ -3593,10 +3713,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-east-asian-width@1.5.0: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} @@ -3627,6 +3743,9 @@ packages: get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + getopts@2.3.0: resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} @@ -3747,8 +3866,8 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} import-meta-resolve@4.2.0: @@ -3844,6 +3963,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -4111,6 +4233,9 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4223,6 +4348,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -4315,6 +4443,9 @@ packages: object-to-formdata@4.5.1: resolution: {integrity: sha512-QiM9D0NiU5jV6J6tjE1g7b4Z2tcUnKs1OPUi4iMb2zH+7jwlcUrASghgkFk9GtzqNNq8rTQJtT8AzjBAvLoNMw==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -4334,8 +4465,8 @@ packages: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} - optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} outdent@0.5.0: @@ -4839,6 +4970,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -4918,10 +5052,16 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -4937,10 +5077,6 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.0: - resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} - engines: {node: '>=20'} - string-width@8.2.0: resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} engines: {node: '>=20'} @@ -5015,6 +5151,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@5.55.2: + resolution: {integrity: sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw==} + engines: {node: '>=18'} + synckit@0.11.12: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5077,12 +5217,19 @@ packages: timekeeper@2.3.1: resolution: {integrity: sha512-LeQRS7/4JcC0PgdSFnfUiStQEdiuySlCj/5SJ18D+T1n9BoY7PxKFfCwLulpHXoLUFr67HxBddQdEX47lDGx1g==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -5091,6 +5238,10 @@ packages: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tmp-cache@1.1.0: resolution: {integrity: sha512-j040fkL/x+XAZQ9K3bKGEPwgYhOZNBQLa3NXEADUiuno9C+3N2JJA4bVPDREixp604G3/vTXWA3DIPpA9lu1RQ==} engines: {node: '>=6'} @@ -5307,6 +5458,55 @@ packages: yaml: optional: true + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@4.1.3: + resolution: {integrity: sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.3 + '@vitest/browser-preview': 4.1.3 + '@vitest/browser-webdriverio': 4.1.3 + '@vitest/coverage-istanbul': 4.1.3 + '@vitest/coverage-v8': 4.1.3 + '@vitest/ui': 4.1.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vue-component-type-helpers@2.2.12: resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} @@ -5338,6 +5538,15 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + workerd@1.20260312.1: resolution: {integrity: sha512-nNpPkw9jaqo79B+iBCOiksx+N62xC+ETIfyzofUEdY3cSOHJg6oNnVSHm7vHevzVblfV76c8Gr0cXHEapYMBEg==} engines: {node: '>=16'} @@ -5444,9 +5653,10 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} -snapshots: + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} - '@aashutoshrathi/word-wrap@1.2.6': {} +snapshots: '@adobe/css-tools@4.4.3': {} @@ -5454,7 +5664,7 @@ snapshots: dependencies: '@poppinss/cliui': 6.7.0 '@poppinss/hooks': 7.3.0 - '@poppinss/macroable': 1.1.0 + '@poppinss/macroable': 1.1.1 '@poppinss/prompts': 3.1.6 '@poppinss/utils': 7.0.1 fastest-levenshtein: 1.0.16 @@ -5468,7 +5678,7 @@ snapshots: '@adonisjs/config': 6.1.0 '@adonisjs/fold': 11.0.0 '@poppinss/hooks': 7.3.0 - '@poppinss/macroable': 1.1.0 + '@poppinss/macroable': 1.1.1 '@poppinss/utils': 7.0.1 glob-parent: 6.0.2 tempura: 0.4.1 @@ -5516,7 +5726,7 @@ snapshots: '@adonisjs/bodyparser@11.0.0(@adonisjs/http-server@8.0.0(@adonisjs/application@9.0.0(@adonisjs/assembler@8.0.1(typescript@5.9.3))(@adonisjs/config@6.1.0)(@adonisjs/fold@11.0.0))(@adonisjs/events@10.1.0(@adonisjs/application@9.0.0(@adonisjs/assembler@8.0.1(typescript@5.9.3))(@adonisjs/config@6.1.0)(@adonisjs/fold@11.0.0))(@adonisjs/fold@11.0.0))(@adonisjs/fold@11.0.0)(@adonisjs/logger@7.1.0(pino-pretty@13.1.3))(@boringnode/encryption@1.0.0)(youch@4.1.0))': dependencies: '@adonisjs/http-server': 8.0.0(@adonisjs/application@9.0.0(@adonisjs/assembler@8.0.1(typescript@5.9.3))(@adonisjs/config@6.1.0)(@adonisjs/fold@11.0.0))(@adonisjs/events@10.1.0(@adonisjs/application@9.0.0(@adonisjs/assembler@8.0.1(typescript@5.9.3))(@adonisjs/config@6.1.0)(@adonisjs/fold@11.0.0))(@adonisjs/fold@11.0.0))(@adonisjs/fold@11.0.0)(@adonisjs/logger@7.1.0(pino-pretty@13.1.3))(@boringnode/encryption@1.0.0)(youch@4.1.0) - '@poppinss/macroable': 1.1.0 + '@poppinss/macroable': 1.1.1 '@poppinss/middleware': 3.2.7 '@poppinss/multiparty': 3.0.0 '@poppinss/qs': 6.15.0 @@ -5550,7 +5760,7 @@ snapshots: '@boringnode/encryption': 1.0.0 '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.7.0 - '@poppinss/macroable': 1.1.0 + '@poppinss/macroable': 1.1.1 '@poppinss/utils': 7.0.1 '@sindresorhus/is': 7.2.0 '@types/he': 1.2.3 @@ -6409,11 +6619,11 @@ snapshots: '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -6425,16 +6635,16 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.12.6 + ajv: 6.14.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.1 - minimatch: 3.1.2 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -6460,16 +6670,14 @@ snapshots: '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.2': {} + '@humanwhocodes/retry@0.4.3': {} '@img/colour@1.1.0': {} @@ -6617,11 +6825,11 @@ snapshots: '@japa/core@10.4.0': dependencies: '@poppinss/hooks': 7.3.0 - '@poppinss/macroable': 1.1.0 + '@poppinss/macroable': 1.1.1 '@poppinss/string': 1.7.1 async-retry: 1.3.3 emittery: 1.2.0 - string-width: 8.1.0 + string-width: 8.2.0 '@japa/errors-printer@4.1.4': dependencies: @@ -6699,7 +6907,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -7082,6 +7290,27 @@ snapshots: estraverse: 5.3.0 picomatch: 4.0.3 + '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)))(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2))': + dependencies: + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + obug: 2.1.1 + svelte: 5.55.2 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2) + + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)))(svelte@5.55.2)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.1 + svelte: 5.55.2 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2) + vitefu: 1.1.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + '@swc/core-darwin-arm64@1.15.18': optional: true @@ -7206,17 +7435,22 @@ snapshots: dependencies: remove-accents: 0.5.0 - '@tanstack/query-core@5.90.20': {} + '@tanstack/query-core@5.96.2': {} - '@tanstack/react-query@5.90.21(react@19.2.4)': + '@tanstack/react-query@5.96.2(react@19.2.4)': dependencies: - '@tanstack/query-core': 5.90.20 + '@tanstack/query-core': 5.96.2 react: 19.2.4 - '@tanstack/vue-query@5.92.9(vue@3.5.30(typescript@5.9.3))': + '@tanstack/svelte-query@6.1.13(svelte@5.55.2)': + dependencies: + '@tanstack/query-core': 5.96.2 + svelte: 5.55.2 + + '@tanstack/vue-query@5.96.2(vue@3.5.30(typescript@5.9.3))': dependencies: '@tanstack/match-sorter-utils': 8.19.4 - '@tanstack/query-core': 5.90.20 + '@tanstack/query-core': 5.96.2 '@vue/devtools-api': 6.6.4 vue: 3.5.30(typescript@5.9.3) vue-demi: 0.14.10(vue@3.5.30(typescript@5.9.3)) @@ -7331,6 +7565,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/trusted-types@2.0.7': {} + '@types/validator@13.15.10': {} '@types/whatwg-mimetype@3.0.2': {} @@ -7481,6 +7717,47 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@4.1.3': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.3 + '@vitest/utils': 4.1.3 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2))': + dependencies: + '@vitest/spy': 4.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2) + + '@vitest/pretty-format@4.1.3': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.3': + dependencies: + '@vitest/utils': 4.1.3 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.3': + dependencies: + '@vitest/pretty-format': 4.1.3 + '@vitest/utils': 4.1.3 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.3': {} + + '@vitest/utils@4.1.3': + dependencies: + '@vitest/pretty-format': 4.1.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@vue/compiler-core@3.5.30': dependencies: '@babel/parser': 7.29.0 @@ -7554,7 +7831,7 @@ snapshots: acorn@8.15.0: {} - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -7591,6 +7868,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.1: {} + arkregex@0.0.4: dependencies: '@ark/util': 0.56.0 @@ -7623,6 +7902,8 @@ snapshots: transitivePeerDependencies: - debug + axobject-query@4.1.0: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -7661,6 +7942,11 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@1.1.13: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -7735,6 +8021,8 @@ snapshots: chai@6.2.1: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -7798,6 +8086,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + code-block-writer@13.0.3: {} color-convert@2.0.1: @@ -7944,6 +8234,8 @@ snapshots: detect-libc@2.1.2: {} + devalue@5.7.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -8037,6 +8329,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -8194,17 +8488,17 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.3 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.6 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.2 + '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -8212,7 +8506,7 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -8223,14 +8517,16 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 - optionator: 0.9.3 + optionator: 0.9.4 optionalDependencies: jiti: 2.6.1 transitivePeerDependencies: - supports-color + esm-env@1.2.2: {} + esm@3.2.25: {} espree@10.4.0: @@ -8241,10 +8537,15 @@ snapshots: esprima@4.0.1: {} - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 + esrap@2.2.4: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@typescript-eslint/types': 8.56.1 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -8253,6 +8554,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} @@ -8358,10 +8663,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.3.1: {} + flatted@3.4.2: {} flattie@1.1.1: {} @@ -8411,8 +8716,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-east-asian-width@1.5.0: {} get-intrinsic@1.2.7: @@ -8450,6 +8753,11 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + optional: true + getopts@2.3.0: {} github-from-package@0.0.0: {} @@ -8580,7 +8888,7 @@ snapshots: ignore@7.0.5: {} - import-fresh@3.3.0: + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 @@ -8646,6 +8954,10 @@ snapshots: is-plain-obj@4.1.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + is-stream@4.0.1: {} is-subdir@1.2.0: @@ -8872,6 +9184,8 @@ snapshots: load-tsconfig@0.2.5: {} + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -8969,6 +9283,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.13 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -9048,6 +9366,8 @@ snapshots: object-to-formdata@4.5.1: {} + obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} on-finished@2.4.1: @@ -9071,14 +9391,14 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.0 - optionator@0.9.3: + optionator@0.9.4: dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 + word-wrap: 1.2.5 outdent@0.5.0: {} @@ -9269,7 +9589,7 @@ snapshots: dependencies: confbox: 0.1.8 mlly: 1.7.4 - pathe: 2.0.2 + pathe: 2.0.3 pluralize@8.0.0: {} @@ -9645,6 +9965,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -9714,8 +10036,12 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.0.0: {} + strict-event-emitter@0.5.1: {} string-width@4.2.3: @@ -9736,11 +10062,6 @@ snapshots: get-east-asian-width: 1.5.0 strip-ansi: 7.1.2 - string-width@8.1.0: - dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - string-width@8.2.0: dependencies: get-east-asian-width: 1.5.0 @@ -9806,6 +10127,25 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte@5.55.2: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@types/estree': 1.0.8 + '@types/trusted-types': 2.0.7 + acorn: 8.15.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.7.0 + esm-env: 1.2.2 + esrap: 2.2.4 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + synckit@0.11.12: dependencies: '@pkgr/core': 0.2.9 @@ -9868,10 +10208,14 @@ snapshots: timekeeper@2.3.1: {} + tinybench@2.9.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.1: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -9879,6 +10223,8 @@ snapshots: tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} + tmp-cache@1.1.0: {} to-regex-range@5.0.1: @@ -9945,7 +10291,7 @@ snapshots: tsx@4.19.2: dependencies: esbuild: 0.23.1 - get-tsconfig: 4.13.6 + get-tsconfig: 4.13.7 optionalDependencies: fsevents: 2.3.3 optional: true @@ -10058,6 +10404,38 @@ snapshots: lightningcss: 1.31.1 tsx: 4.19.2 + vitefu@1.1.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)): + optionalDependencies: + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2) + + vitest@4.1.3(@types/node@25.5.0)(happy-dom@20.8.4)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)): + dependencies: + '@vitest/expect': 4.1.3 + '@vitest/mocker': 4.1.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2)) + '@vitest/pretty-format': 4.1.3 + '@vitest/runner': 4.1.3 + '@vitest/snapshot': 4.1.3 + '@vitest/spy': 4.1.3 + '@vitest/utils': 4.1.3 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.19.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.0 + happy-dom: 20.8.4 + transitivePeerDependencies: + - msw + vue-component-type-helpers@2.2.12: {} vue-demi@0.14.10(vue@3.5.30(typescript@5.9.3)): @@ -10080,6 +10458,13 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + workerd@1.20260312.1: optionalDependencies: '@cloudflare/workerd-darwin-64': 1.20260312.1 @@ -10189,3 +10574,5 @@ snapshots: '@speed-highlight/core': 1.2.14 cookie: 1.0.2 youch-core: 0.3.3 + + zimmerframe@1.1.4: {}