From 79bed2bf91bdca7f8fe7958dd67b91741a7ccb42 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Tue, 21 Oct 2025 01:49:23 +0000 Subject: [PATCH 1/2] feat: start TanStack Query React integration --- packages/tanstack-query/package.json | 18 +- packages/tanstack-query/src/react.ts | 120 ++-- packages/tanstack-query/src/runtime/common.ts | 128 ++++ packages/tanstack-query/src/runtime/index.ts | 5 + packages/tanstack-query/src/runtime/react.ts | 23 + packages/tanstack-query/test/react.test.tsx | 69 ++ packages/tanstack-query/test/schema.ts | 98 +++ packages/tanstack-query/test/setup.ts | 1 + packages/tanstack-query/tsconfig.json | 10 +- packages/tanstack-query/vitest.config.ts | 9 + pnpm-lock.yaml | 633 +++++++++++++++++- pnpm-workspace.yaml | 4 + 12 files changed, 1062 insertions(+), 56 deletions(-) create mode 100644 packages/tanstack-query/src/runtime/common.ts create mode 100644 packages/tanstack-query/src/runtime/index.ts create mode 100644 packages/tanstack-query/src/runtime/react.ts create mode 100644 packages/tanstack-query/test/react.test.tsx create mode 100644 packages/tanstack-query/test/schema.ts create mode 100644 packages/tanstack-query/test/setup.ts create mode 100644 packages/tanstack-query/vitest.config.ts diff --git a/packages/tanstack-query/package.json b/packages/tanstack-query/package.json index 92cb74de..82cf362c 100644 --- a/packages/tanstack-query/package.json +++ b/packages/tanstack-query/package.json @@ -28,15 +28,29 @@ "@zenstackhq/runtime": "workspace:*" }, "devDependencies": { + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", "@zenstackhq/eslint-config": "workspace:*", - "@zenstackhq/typescript-config": "workspace:*" + "@zenstackhq/typescript-config": "workspace:*", + "jsdom": "^27.0.1" }, "peerDependencies": { - "@tanstack/react-query": "^5.0.0" + "@tanstack/react-query": "^5.0.0", + "react": "catalog:", + "react-dom": "catalog:" }, "peerDependenciesMeta": { "@tanstack/react-query": { "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true } } } diff --git a/packages/tanstack-query/src/react.ts b/packages/tanstack-query/src/react.ts index c87115d5..886cbeb7 100644 --- a/packages/tanstack-query/src/react.ts +++ b/packages/tanstack-query/src/react.ts @@ -1,29 +1,29 @@ -import type { - DefaultError, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult, +import { + useMutation, + useQuery, + type DefaultError, + type UseMutationOptions, + type UseMutationResult, + type UseQueryOptions, + type UseQueryResult, } from '@tanstack/react-query'; import type { CreateArgs, FindArgs, ModelResult, SelectSubset } from '@zenstackhq/runtime'; -import type { GetModels, SchemaDef } from '@zenstackhq/runtime/schema'; +import { type GetModels, type SchemaDef } from '@zenstackhq/runtime/schema'; +import { useContext } from 'react'; +import { getQueryKey, type MutationMethod, type MutationOperation, type QueryOperation } from './runtime/common'; +import { RequestHandlerContext } from './runtime/react'; -export type toHooks = { - [Model in GetModels as Uncapitalize]: ToModelHooks; +export type useHooks = { + [Model in GetModels as Uncapitalize]: UseModelHooks; }; -type ToModelHooks> = { - findMany>( +type UseModelHooks> = { + useFindFirst>( args?: SelectSubset>, - options?: Omit[]>, 'queryKey'>, - ): UseQueryResult[]>; - - findFirst>( - args?: SelectSubset>, - options?: Omit[]>, 'queryKey'>, + options?: Omit[]>, 'queryKey' | 'queryFn'>, ): UseQueryResult | null>; - create>( + useCreate>( options?: UseMutationOptions, DefaultError, T>, ): UseMutationResult, DefaultError, T>; }; @@ -32,47 +32,77 @@ function uncapitalize(s: string) { return s.charAt(0).toLowerCase() + s.slice(1); } -export function toHooks(schema: Schema): toHooks { +export function useHooks(schema: Schema): useHooks { return Object.entries(schema.models).reduce( (acc, [model, _]) => Object.assign(acc, { - [uncapitalize(model)]: toModelHooks(schema, model as GetModels), + [uncapitalize(model)]: useModelHooks(schema, model as GetModels), }), - {} as toHooks, + {} as useHooks, ); } -function toModelHooks>(schema: Schema, model: Model): any { +function useModelHooks>(schema: Schema, model: Model): any { const modelDef = schema.models[model]; if (!modelDef) { throw new Error(`Model ${model} not found in schema`); } return { - findMany: () => { - return { - data: [], - isLoading: false, - isError: false, - }; - }, + useFindFirst: useModelQuery(schema, model, 'findFirst'), + useCreate: useModelMutation(schema, model, 'create', 'POST'), + }; +} - findFirst: () => { - return { - data: null, - isLoading: false, - isError: false, - }; - }, +export function useModelQuery< + Schema extends SchemaDef, + Model extends GetModels, +>( + schema: Schema, + model: Model, + operation: QueryOperation, +) { + const context = useContext(RequestHandlerContext); + if (!context) { + throw new Error('Missing context'); + } + + const queryKey = getQueryKey(schema, model, operation, {}); + const query = useQuery({ + queryKey, + queryFn: async () => { + const response = await context.fetch!(context.endpoint!); - create: () => { - return { - mutate: async () => { - return null; - }, - isLoading: false, - isError: false, - }; + return response; }, - }; + }); + + return query; } + +export function useModelMutation< + Schema extends SchemaDef, + Model extends GetModels, +>( + schema: Schema, + model: Model, + operation: MutationOperation, + method: MutationMethod, +) { + const context = useContext(RequestHandlerContext); + if (!context) { + throw new Error('Missing context'); + } + + const mutation = useMutation({ + mutationFn: async () => { + const response = await context.fetch!(context.endpoint!, { + method, + }); + + return response; + } + }); + + return mutation; +} \ No newline at end of file diff --git a/packages/tanstack-query/src/runtime/common.ts b/packages/tanstack-query/src/runtime/common.ts new file mode 100644 index 00000000..940f82e3 --- /dev/null +++ b/packages/tanstack-query/src/runtime/common.ts @@ -0,0 +1,128 @@ +import type { GetModels, SchemaDef } from '@zenstackhq/runtime/schema'; +import type { CrudOperation, FindArgs, AggregateArgs, CountArgs, CreateArgs, UpdateArgs, CreateManyArgs, UpdateManyArgs, CreateManyAndReturnArgs, DeleteArgs, DeleteManyArgs, FindUniqueArgs, GroupByArgs, UpdateManyAndReturnArgs, UpsertArgs } from '@zenstackhq/runtime'; + +/** + * The default query endpoint. + */ +export const DEFAULT_QUERY_ENDPOINT = '/api/model'; + +/** + * Prefix for TanStack Query keys. + */ +export const QUERY_KEY_PREFIX = 'zenstack'; + +/** + * Function signature for `fetch`. + */ +export type FetchFn = (url: string, options?: RequestInit) => Promise; + +/** + * Context type for configuring the hooks. + */ +export type APIContext = { + /** + * The endpoint to use for the queries. + */ + endpoint?: string; + + /** + * A custom fetch function for sending the HTTP requests. + */ + fetch?: FetchFn; + + /** + * If logging is enabled. + */ + logging?: boolean; +}; + +/** + * Extra query options. + */ +export type ExtraQueryOptions = { + /** + * Whether this is an infinite query. Defaults to `false`. + */ + infinite?: boolean; + + /** + * Whether to opt-in to optimistic updates for this query. Defaults to `true`. + */ + optimisticUpdate?: boolean; +}; + +export type CrudOperationTypeMap> = { + findFirst: FindArgs, + findMany: FindArgs, + findUnique: FindUniqueArgs, + create: CreateArgs, + createMany: CreateManyArgs, + createManyAndReturn: CreateManyAndReturnArgs, + upsert: UpsertArgs, + update: UpdateArgs, + updateMany: UpdateManyArgs, + updateManyAndReturn: UpdateManyAndReturnArgs, + delete: DeleteArgs, + deleteMany: DeleteManyArgs, + count: CountArgs, + aggregate: AggregateArgs, + groupBy: GroupByArgs, +}; + +export type QueryOperation = Extract< + CrudOperation, + 'findFirst' | 'findMany' | 'findUnique' | 'count' | 'aggregate' | 'groupBy' +>; + +export type MutationOperation = Exclude; + +export type MutationMethod = 'POST' | 'PUT' | 'DELETE'; + +export type QueryKey< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends CrudOperation, +> = [ + prefix: typeof QUERY_KEY_PREFIX, + model: Model, + operation: CrudOperation, + args: CrudOperationTypeMap[Operation], + extraOptions: ExtraQueryOptions, + ]; + +export function getQueryKey< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends CrudOperation, +>( + schema: Schema, + model: Model, + operation: CrudOperation, + args: CrudOperationTypeMap[Operation], + + extraOptions: ExtraQueryOptions = { + infinite: false, + optimisticUpdate: true, + }, +): QueryKey { + const modelDef = schema.models[model]; + if (!modelDef) { + throw new Error(`Model ${model} not found in schema`); + } + + return [QUERY_KEY_PREFIX, model, operation, args, extraOptions] +} + +export function isZenStackQueryKey( + queryKey: readonly unknown[] +): queryKey is QueryKey, CrudOperation> { + if (queryKey.length < 5) { + return false; + } + + if (queryKey[0] !== QUERY_KEY_PREFIX) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/packages/tanstack-query/src/runtime/index.ts b/packages/tanstack-query/src/runtime/index.ts new file mode 100644 index 00000000..bd281cc7 --- /dev/null +++ b/packages/tanstack-query/src/runtime/index.ts @@ -0,0 +1,5 @@ +export { + getQueryKey, + type ExtraQueryOptions, + type FetchFn, +} from './common'; \ No newline at end of file diff --git a/packages/tanstack-query/src/runtime/react.ts b/packages/tanstack-query/src/runtime/react.ts new file mode 100644 index 00000000..e7953ab4 --- /dev/null +++ b/packages/tanstack-query/src/runtime/react.ts @@ -0,0 +1,23 @@ +import { DEFAULT_QUERY_ENDPOINT, type APIContext } from './common'; +import { createContext, useContext } from 'react'; + +/** + * Context for configuring react hooks. + */ +export const RequestHandlerContext = createContext({ + endpoint: DEFAULT_QUERY_ENDPOINT, + fetch, +}); + +/** + * Hooks context. + */ +export function getHooksContext() { + const { endpoint, ...rest } = useContext(RequestHandlerContext); + return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest }; +} + +/** + * Context provider. + */ +export const Provider = RequestHandlerContext.Provider; \ No newline at end of file diff --git a/packages/tanstack-query/test/react.test.tsx b/packages/tanstack-query/test/react.test.tsx new file mode 100644 index 00000000..5432f038 --- /dev/null +++ b/packages/tanstack-query/test/react.test.tsx @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; +import { Provider } from '../src/runtime/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { cleanup, renderHook, waitFor } from '@testing-library/react'; +import { type ReactNode } from 'react'; +import { afterEach, vi } from 'vitest'; +import { useModelMutation, useModelQuery } from '../src/react'; +import { schema } from './schema'; + +const ENDPOINT_MOCK = 'http://localhost:3000'; + +afterEach(() => { + cleanup(); +}); + +const mockFetch = vi.fn(function (url: string, options: RequestInit) { + return Response.json({ + url, + options, + }); +}); + +const queryClient = new QueryClient(); + +function wrapper({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} + +describe('useModelQuery', () => { + it('should be able to findFirst', async () => { + const { result } = renderHook(() => ( + useModelQuery(schema, 'User', 'findFirst') + ), { + wrapper, + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(mockFetch).toHaveBeenCalledWith(ENDPOINT_MOCK); + }); +}); + +describe('useModelMutation', () => { + it('should be able to create', async () => { + const { result } = renderHook(() => ( + useModelMutation(schema, 'User', 'create', 'POST') + ), { + wrapper, + }); + + await waitFor(() => result.current.mutateAsync()); + + expect(mockFetch).toHaveBeenCalledWith(ENDPOINT_MOCK, { + method: 'POST', + }); + }); +}); \ No newline at end of file diff --git a/packages/tanstack-query/test/schema.ts b/packages/tanstack-query/test/schema.ts new file mode 100644 index 00000000..d70e82cc --- /dev/null +++ b/packages/tanstack-query/test/schema.ts @@ -0,0 +1,98 @@ +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/runtime/schema"; +export const schema = { + provider: { + type: "postgresql" + }, + models: { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("uuid") }] }], + default: ExpressionUtils.call("uuid") + }, + name: { + name: "name", + type: "String" + }, + email: { + name: "email", + type: "String", + unique: true, + attributes: [{ name: "@unique" }] + }, + createdAt: { + name: "createdAt", + type: "DateTime", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], + default: ExpressionUtils.call("now") + }, + updatedAt: { + name: "updatedAt", + type: "DateTime", + updatedAt: true, + attributes: [{ name: "@updatedAt" }] + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "author" } + } + }, + attributes: [ + { name: "@@map", args: [{ name: "name", value: ExpressionUtils.literal("users") }] } + ], + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + email: { type: "String" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + title: { + name: "title", + type: "String" + }, + published: { + name: "published", + type: "Boolean", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(false) }] }], + default: false + }, + author: { + name: "author", + type: "User", + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }, { name: "onDelete", value: ExpressionUtils.literal("Cascade") }] }], + relation: { opposite: "posts", fields: ["authorId"], references: ["id"], onDelete: "Cascade" } + }, + authorId: { + name: "authorId", + type: "String", + foreignKeyFor: [ + "author" + ] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + }, + authType: "User", + plugins: {} +} as const satisfies SchemaDef; +export type SchemaType = typeof schema; diff --git a/packages/tanstack-query/test/setup.ts b/packages/tanstack-query/test/setup.ts new file mode 100644 index 00000000..331666ce --- /dev/null +++ b/packages/tanstack-query/test/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; \ No newline at end of file diff --git a/packages/tanstack-query/tsconfig.json b/packages/tanstack-query/tsconfig.json index e7ce31be..cca5d3c3 100644 --- a/packages/tanstack-query/tsconfig.json +++ b/packages/tanstack-query/tsconfig.json @@ -1,4 +1,12 @@ { "extends": "@zenstackhq/typescript-config/base.json", - "include": ["src/**/*.ts", "test/**/*.ts"] + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "test/react.test.tsx" + ], + "compilerOptions": { + "jsx": "react-jsx", + "skipLibCheck": true + } } diff --git a/packages/tanstack-query/vitest.config.ts b/packages/tanstack-query/vitest.config.ts new file mode 100644 index 00000000..21dfde95 --- /dev/null +++ b/packages/tanstack-query/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + globals: true, + setupFiles: './test/setup.ts', + }, +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa32c4a2..781c2834 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,12 @@ catalogs: '@types/node': specifier: ^20.17.24 version: 20.17.24 + '@types/react': + specifier: ^18 + version: 18.3.26 + '@types/react-dom': + specifier: ^18 + version: 18.3.7 '@types/tmp': specifier: ^0.2.6 version: 0.2.6 @@ -79,7 +85,7 @@ importers: version: 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@20.17.24)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/node@20.17.24)(jiti@2.4.2)(jsdom@27.0.1(postcss@8.5.6))(tsx@4.20.3)(yaml@2.8.0) yaml: specifier: ^2.8.0 version: 2.8.0 @@ -413,17 +419,41 @@ importers: dependencies: '@tanstack/react-query': specifier: ^5.0.0 - version: 5.81.0(react@19.1.0) + version: 5.81.0(react@18.3.1) '@zenstackhq/runtime': specifier: workspace:* version: link:../runtime + react: + specifier: 'catalog:' + version: 18.3.1 + react-dom: + specifier: 'catalog:' + version: 18.3.1(react@18.3.1) devDependencies: + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: 'catalog:' + version: 18.3.26 + '@types/react-dom': + specifier: 'catalog:' + version: 18.3.7(@types/react@18.3.26) '@zenstackhq/eslint-config': specifier: workspace:* version: link:../config/eslint-config '@zenstackhq/typescript-config': specifier: workspace:* version: link:../config/typescript-config + jsdom: + specifier: ^27.0.1 + version: 27.0.1(postcss@8.5.6) packages/testtools: dependencies: @@ -612,6 +642,30 @@ importers: packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@4.0.5': + resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + + '@asamuzakjp/dom-selector@6.7.2': + resolution: {integrity: sha512-ccKogJI+0aiDhOahdjANIc9SDixSud1gbwdVrhn7kMopAtLXqsz9MKmQQtIl6Y5aC2IYq+j4dz/oedL2AVMmVQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -627,6 +681,40 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.14': + resolution: {integrity: sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.23.1': resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} @@ -1232,6 +1320,32 @@ packages: peerDependencies: react: ^18 || ^19 + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} @@ -1259,6 +1373,17 @@ packages: '@types/pluralize@0.0.33': resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.26': + resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==} + '@types/semver@7.7.0': resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} @@ -1380,6 +1505,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1395,6 +1524,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -1405,6 +1538,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1419,6 +1555,9 @@ packages: resolution: {integrity: sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==} engines: {node: 20.x || 22.x || 23.x || 24.x} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -1561,6 +1700,24 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@5.3.1: + resolution: {integrity: sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==} + engines: {node: '>=20'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -1573,6 +1730,9 @@ packages: decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -1602,6 +1762,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -1609,6 +1773,12 @@ packages: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -1636,6 +1806,10 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1860,10 +2034,26 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + https-proxy-agent@5.0.0: resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} engines: {node: '>= 6'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1883,6 +2073,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1909,6 +2103,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -1934,6 +2131,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -1941,6 +2141,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@27.0.1: + resolution: {integrity: sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==} + engines: {node: '>=20'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2017,6 +2226,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} @@ -2027,6 +2240,14 @@ packages: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -2034,6 +2255,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2050,6 +2274,10 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.0.1: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} @@ -2163,6 +2391,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2327,6 +2558,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prisma@6.14.0: resolution: {integrity: sha512-QEuCwxu+Uq9BffFw7in8In+WfbSUN0ewnaSUKloLkbJd42w6EyFckux4M0f7VwwHlM3A8ssaz4OyniCXlsn0WA==} engines: {node: '>=18.18'} @@ -2360,8 +2595,16 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} readable-stream@3.6.2: @@ -2372,6 +2615,14 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2396,12 +2647,25 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -2476,6 +2740,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -2496,6 +2764,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tar-fs@2.1.2: resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} @@ -2535,6 +2806,13 @@ packages: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + tldts-core@7.0.17: + resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==} + + tldts@7.0.17: + resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==} + hasBin: true + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -2546,9 +2824,17 @@ packages: toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2768,12 +3054,32 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -2802,6 +3108,25 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -2826,6 +3151,36 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@4.0.5': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.7.2': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/runtime@7.28.4': {} + '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 @@ -2843,6 +3198,30 @@ snapshots: '@chevrotain/utils@11.0.3': {} + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/css-tokenizer@3.0.4': {} + '@esbuild/aix-ppc64@0.23.1': optional: true @@ -3247,10 +3626,42 @@ snapshots: '@tanstack/query-core@5.81.0': {} - '@tanstack/react-query@5.81.0(react@19.1.0)': + '@tanstack/react-query@5.81.0(react@18.3.1)': dependencies: '@tanstack/query-core': 5.81.0 - react: 19.1.0 + react: 18.3.1 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + '@types/react-dom': 18.3.7(@types/react@18.3.26) + + '@types/aria-query@5.0.4': {} '@types/better-sqlite3@7.6.13': dependencies: @@ -3280,6 +3691,17 @@ snapshots: '@types/pluralize@0.0.33': {} + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.26)': + dependencies: + '@types/react': 18.3.26 + + '@types/react@18.3.26': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.1.3 + '@types/semver@7.7.0': {} '@types/sql.js@1.4.9': @@ -3443,6 +3865,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3458,12 +3882,18 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} any-promise@1.3.0: {} argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + assertion-error@2.0.1: {} balanced-match@1.0.2: {} @@ -3475,6 +3905,10 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -3625,12 +4059,36 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + cssstyle@5.3.1(postcss@8.5.6): + dependencies: + '@asamuzakjp/css-color': 4.0.5 + '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) + css-tree: 3.1.0 + transitivePeerDependencies: + - postcss + + csstype@3.1.3: {} + + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + debug@4.4.1: dependencies: ms: 2.1.3 decimal.js@10.4.3: {} + decimal.js@10.6.0: {} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -3655,10 +4113,16 @@ snapshots: defu@6.1.4: {} + dequal@2.0.3: {} + destr@2.0.5: {} detect-libc@2.0.3: {} + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dotenv@16.6.1: {} dunder-proto@1.0.1: @@ -3684,6 +4148,8 @@ snapshots: dependencies: once: 1.4.0 + entities@6.0.1: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3985,6 +4451,17 @@ snapshots: dependencies: function-bind: 1.1.2 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.0: dependencies: agent-base: 6.0.2 @@ -3992,6 +4469,17 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -4005,6 +4493,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inherits@2.0.4: {} ini@1.3.8: {} @@ -4021,6 +4511,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-unicode-supported@0.1.0: {} isarray@2.0.5: {} @@ -4041,12 +4533,42 @@ snapshots: joycon@3.1.1: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} js-yaml@4.1.0: dependencies: argparse: 2.0.1 + jsdom@27.0.1(postcss@8.5.6): + dependencies: + '@asamuzakjp/dom-selector': 6.7.2 + cssstyle: 5.3.1(postcss@8.5.6) + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - postcss + - supports-color + - utf-8-validate + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -4128,18 +4650,28 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.1.4: {} lru-cache@10.4.3: {} lru-cache@11.1.0: {} + lru-cache@11.2.2: {} + + lz-string@1.5.0: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 math-intrinsics@1.1.0: {} + mdn-data@2.12.2: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -4151,6 +4683,8 @@ snapshots: mimic-response@3.1.0: {} + min-indent@1.0.1: {} + minimatch@10.0.1: dependencies: brace-expansion: 2.0.2 @@ -4269,6 +4803,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4414,6 +4952,12 @@ snapshots: prettier@3.5.3: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + prisma@6.14.0(typescript@5.8.3): dependencies: '@prisma/config': 6.14.0 @@ -4448,7 +4992,17 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react@19.1.0: {} + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@17.0.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 readable-stream@3.6.2: dependencies: @@ -4458,6 +5012,13 @@ snapshots: readdirp@4.1.2: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -4497,12 +5058,24 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.44.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + semver@7.7.2: {} set-function-length@1.2.2: @@ -4572,6 +5145,10 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} @@ -4594,6 +5171,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tar-fs@2.1.2: dependencies: chownr: 1.1.4 @@ -4634,6 +5213,12 @@ snapshots: tinyspy@4.0.3: {} + tldts-core@7.0.17: {} + + tldts@7.0.17: + dependencies: + tldts-core: 7.0.17 + tmp@0.2.3: {} to-regex-range@5.0.1: @@ -4642,10 +5227,18 @@ snapshots: toposort@2.0.2: {} + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.17 + tr46@1.0.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} ts-api-utils@2.1.0(typescript@5.8.3): @@ -4798,7 +5391,7 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 - vitest@3.2.4(@types/node@20.17.24)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/node@20.17.24)(jiti@2.4.2)(jsdom@27.0.1(postcss@8.5.6))(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -4825,6 +5418,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.17.24 + jsdom: 27.0.1(postcss@8.5.6) transitivePeerDependencies: - jiti - less @@ -4862,12 +5456,29 @@ snapshots: vscode-uri@3.0.8: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + wcwidth@1.0.1: dependencies: defaults: 1.0.4 webidl-conversions@4.0.2: {} + webidl-conversions@8.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 @@ -4899,6 +5510,12 @@ snapshots: wrappy@1.0.2: {} + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xtend@4.0.2: {} yaml@2.8.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9dc25b4c..ca1fc680 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,10 @@ catalog: prisma: ^6.10.0 langium: 3.5.0 langium-cli: 3.5.0 + react: ^18 + react-dom: ^18 + '@types/react': ^18 + '@types/react-dom': ^18 ts-pattern: ^5.7.1 typescript: ^5.8.0 '@types/node': ^20.17.24 From 814f4757a120b682a902c9c5d14f490ec9d0eb27 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 24 Oct 2025 09:57:31 +0000 Subject: [PATCH 2/2] Add ModelQuery and ModelMutation --- packages/tanstack-query/src/react.ts | 78 ++++++++++++---- packages/tanstack-query/src/runtime/common.ts | 52 +++++++++-- packages/tanstack-query/test/react.test.tsx | 48 ++++++---- packages/tanstack-query/test/schema.ts | 92 +++++++++---------- 4 files changed, 177 insertions(+), 93 deletions(-) diff --git a/packages/tanstack-query/src/react.ts b/packages/tanstack-query/src/react.ts index 886cbeb7..29803e7f 100644 --- a/packages/tanstack-query/src/react.ts +++ b/packages/tanstack-query/src/react.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { useMutation, useQuery, @@ -7,25 +8,25 @@ import { type UseQueryOptions, type UseQueryResult, } from '@tanstack/react-query'; -import type { CreateArgs, FindArgs, ModelResult, SelectSubset } from '@zenstackhq/runtime'; +import { type CreateArgs, type FindArgs, type ModelResult, type SelectSubset } from '@zenstackhq/runtime'; import { type GetModels, type SchemaDef } from '@zenstackhq/runtime/schema'; import { useContext } from 'react'; -import { getQueryKey, type MutationMethod, type MutationOperation, type QueryOperation } from './runtime/common'; +import { getQueryKey, type MutationMethod, type MutationOperation, type OperationArgs, type OperationResult, type QueryOperation } from './runtime/common'; import { RequestHandlerContext } from './runtime/react'; export type useHooks = { [Model in GetModels as Uncapitalize]: UseModelHooks; }; -type UseModelHooks> = { +export type UseModelHooks> = { useFindFirst>( args?: SelectSubset>, options?: Omit[]>, 'queryKey' | 'queryFn'>, ): UseQueryResult | null>; - useCreate>( - options?: UseMutationOptions, DefaultError, T>, - ): UseMutationResult, DefaultError, T>; + // useCreate>( + // options?: Omit, DefaultError, T>, 'mutationFn'>, + // ): UseMutationResult, DefaultError, T>; }; function uncapitalize(s: string) { @@ -34,7 +35,7 @@ function uncapitalize(s: string) { export function useHooks(schema: Schema): useHooks { return Object.entries(schema.models).reduce( - (acc, [model, _]) => + (acc, [model]) => Object.assign(acc, { [uncapitalize(model)]: useModelHooks(schema, model as GetModels), }), @@ -42,65 +43,104 @@ export function useHooks(schema: Schema): useHooks>(schema: Schema, model: Model): any { +export function useModelHooks< + Schema extends SchemaDef, + Model extends GetModels, +>( + schema: Schema, + model: Model, +): UseModelHooks { const modelDef = schema.models[model]; if (!modelDef) { throw new Error(`Model ${model} not found in schema`); } return { - useFindFirst: useModelQuery(schema, model, 'findFirst'), - useCreate: useModelMutation(schema, model, 'create', 'POST'), - }; + useFindFirst: >( + args: SelectSubset>, + options?: Omit[]>, 'queryKey' | 'queryFn'>, + ) => { + return useModelQuery(schema, model, 'findFirst', args); + }, + + // useCreate: >( + // options?: Omit, DefaultError, T>, 'mutationFn'>, + // ) => { + // return useModelMutation(schema, model, 'create', 'POST'); + // }, + } } +export type ModelQuery< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends QueryOperation, + Result extends OperationResult, +> = UseQueryResult + export function useModelQuery< Schema extends SchemaDef, Model extends GetModels, + Operation extends QueryOperation, + Args extends OperationArgs, + Result extends OperationResult, >( schema: Schema, model: Model, - operation: QueryOperation, + operation: Operation, + args: Args, ) { const context = useContext(RequestHandlerContext); if (!context) { throw new Error('Missing context'); } - const queryKey = getQueryKey(schema, model, operation, {}); - const query = useQuery({ + const queryKey = getQueryKey(schema, model, operation, args); + const argsQuery = encodeURIComponent(JSON.stringify(args)); + const query = useQuery({ queryKey, queryFn: async () => { const response = await context.fetch!(context.endpoint!); - return response; + return response as unknown as Result; }, }); return query; } +export type ModelMutation< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends MutationOperation, + Args extends OperationArgs, + Result extends OperationResult, +> = UseMutationResult; + export function useModelMutation< Schema extends SchemaDef, Model extends GetModels, + Operation extends MutationOperation, + Args extends OperationArgs, + Result extends OperationResult, >( schema: Schema, model: Model, - operation: MutationOperation, + operation: Operation, method: MutationMethod, -) { +): ModelMutation { const context = useContext(RequestHandlerContext); if (!context) { throw new Error('Missing context'); } - const mutation = useMutation({ + const mutation = useMutation({ mutationFn: async () => { const response = await context.fetch!(context.endpoint!, { method, }); - return response; + return response as unknown as Result; } }); diff --git a/packages/tanstack-query/src/runtime/common.ts b/packages/tanstack-query/src/runtime/common.ts index 940f82e3..c4afb2fb 100644 --- a/packages/tanstack-query/src/runtime/common.ts +++ b/packages/tanstack-query/src/runtime/common.ts @@ -1,5 +1,5 @@ +import type { AggregateArgs, AggregateResult, BatchResult, CountArgs, CountResult, CreateArgs, CreateManyAndReturnArgs, CreateManyArgs, CrudOperation, DeleteArgs, DeleteManyArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, GroupByResult, ModelResult, UpdateArgs, UpdateManyAndReturnArgs, UpdateManyArgs, UpsertArgs } from '@zenstackhq/runtime'; import type { GetModels, SchemaDef } from '@zenstackhq/runtime/schema'; -import type { CrudOperation, FindArgs, AggregateArgs, CountArgs, CreateArgs, UpdateArgs, CreateManyArgs, UpdateManyArgs, CreateManyAndReturnArgs, DeleteArgs, DeleteManyArgs, FindUniqueArgs, GroupByArgs, UpdateManyAndReturnArgs, UpsertArgs } from '@zenstackhq/runtime'; /** * The default query endpoint. @@ -51,9 +51,9 @@ export type ExtraQueryOptions = { optimisticUpdate?: boolean; }; -export type CrudOperationTypeMap> = { - findFirst: FindArgs, - findMany: FindArgs, +export type CrudOperationArgsMap> = { + findFirst: FindFirstArgs, + findMany: FindManyArgs, findUnique: FindUniqueArgs, create: CreateArgs, createMany: CreateManyArgs, @@ -69,6 +69,24 @@ export type CrudOperationTypeMap, }; +export type CrudOperationResultsMap> = { + findFirst: ModelResult['findFirst']> | null; + findMany: ModelResult['findMany']>[]; + findUnique: ModelResult['findUnique']>; + create: ModelResult['create']>; + createMany: BatchResult; + createManyAndReturn: ModelResult['createManyAndReturn']>[]; + upsert: ModelResult['upsert']>; + update: ModelResult['update']>; + updateMany: BatchResult; + updateManyAndReturn: ModelResult['updateManyAndReturn']>[]; + delete: ModelResult['delete']>; + deleteMany: BatchResult; + count: CountResult['count']>; + aggregate: AggregateResult['aggregate']>; + groupBy: GroupByResult['groupBy']>; +}; + export type QueryOperation = Extract< CrudOperation, 'findFirst' | 'findMany' | 'findUnique' | 'count' | 'aggregate' | 'groupBy' @@ -82,29 +100,43 @@ export type QueryKey< Schema extends SchemaDef, Model extends GetModels, Operation extends CrudOperation, + Args extends OperationArgs, > = [ prefix: typeof QUERY_KEY_PREFIX, model: Model, - operation: CrudOperation, - args: CrudOperationTypeMap[Operation], + operation: Operation, + args: Args, extraOptions: ExtraQueryOptions, ]; +export type OperationArgs< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends CrudOperation, +> = CrudOperationArgsMap[Operation]; + +export type OperationResult< + Schema extends SchemaDef, + Model extends GetModels, + Operation extends CrudOperation, +> = CrudOperationResultsMap[Operation]; + export function getQueryKey< Schema extends SchemaDef, Model extends GetModels, Operation extends CrudOperation, + Args extends OperationArgs, >( schema: Schema, model: Model, - operation: CrudOperation, - args: CrudOperationTypeMap[Operation], + operation: Operation, + args: Args, extraOptions: ExtraQueryOptions = { infinite: false, optimisticUpdate: true, }, -): QueryKey { +): QueryKey { const modelDef = schema.models[model]; if (!modelDef) { throw new Error(`Model ${model} not found in schema`); @@ -115,7 +147,7 @@ export function getQueryKey< export function isZenStackQueryKey( queryKey: readonly unknown[] -): queryKey is QueryKey, CrudOperation> { +): queryKey is QueryKey, CrudOperation, any> { if (queryKey.length < 5) { return false; } diff --git a/packages/tanstack-query/test/react.test.tsx b/packages/tanstack-query/test/react.test.tsx index 5432f038..b09cd646 100644 --- a/packages/tanstack-query/test/react.test.tsx +++ b/packages/tanstack-query/test/react.test.tsx @@ -1,10 +1,9 @@ -import { describe, expect, it } from 'vitest'; -import { Provider } from '../src/runtime/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { cleanup, renderHook, waitFor } from '@testing-library/react'; import { type ReactNode } from 'react'; -import { afterEach, vi } from 'vitest'; -import { useModelMutation, useModelQuery } from '../src/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { useModelQuery } from '../src/react'; +import { Provider } from '../src/runtime/react'; import { schema } from './schema'; const ENDPOINT_MOCK = 'http://localhost:3000'; @@ -41,7 +40,13 @@ function wrapper({ children }: { children: ReactNode }) { describe('useModelQuery', () => { it('should be able to findFirst', async () => { const { result } = renderHook(() => ( - useModelQuery(schema, 'User', 'findFirst') + useModelQuery(schema, 'User', 'findFirst', { + where: { + email: { + equals: '', + }, + } + }) ), { wrapper, }); @@ -52,18 +57,25 @@ describe('useModelQuery', () => { }); }); -describe('useModelMutation', () => { - it('should be able to create', async () => { - const { result } = renderHook(() => ( - useModelMutation(schema, 'User', 'create', 'POST') - ), { - wrapper, - }); +// describe('useModelMutation', () => { +// it('should be able to create', async () => { +// const { result } = renderHook(() => ( +// useModelMutation(schema, 'User', 'create', 'POST') +// ), { +// wrapper, +// }); - await waitFor(() => result.current.mutateAsync()); +// await waitFor(async () => { +// await result.current.mutateAsync({ +// data: { +// email: '', +// name: '', +// } +// }); +// }); - expect(mockFetch).toHaveBeenCalledWith(ENDPOINT_MOCK, { - method: 'POST', - }); - }); -}); \ No newline at end of file +// expect(mockFetch).toHaveBeenCalledWith(ENDPOINT_MOCK, { +// method: 'POST', +// }); +// }); +// }); \ No newline at end of file diff --git a/packages/tanstack-query/test/schema.ts b/packages/tanstack-query/test/schema.ts index d70e82cc..fecdb758 100644 --- a/packages/tanstack-query/test/schema.ts +++ b/packages/tanstack-query/test/schema.ts @@ -1,98 +1,98 @@ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/runtime/schema"; +import { type SchemaDef, ExpressionUtils } from '@zenstackhq/runtime/schema'; export const schema = { provider: { - type: "postgresql" + type: 'postgresql' }, models: { User: { - name: "User", + name: 'User', fields: { id: { - name: "id", - type: "String", + name: 'id', + type: 'String', id: true, - attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("uuid") }] }], - default: ExpressionUtils.call("uuid") + attributes: [{ name: '@id' }, { name: '@default', args: [{ name: 'value', value: ExpressionUtils.call('uuid') }] }], + default: ExpressionUtils.call('uuid') }, name: { - name: "name", - type: "String" + name: 'name', + type: 'String' }, email: { - name: "email", - type: "String", + name: 'email', + type: 'String', unique: true, - attributes: [{ name: "@unique" }] + attributes: [{ name: '@unique' }] }, createdAt: { - name: "createdAt", - type: "DateTime", - attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], - default: ExpressionUtils.call("now") + name: 'createdAt', + type: 'DateTime', + attributes: [{ name: '@default', args: [{ name: 'value', value: ExpressionUtils.call('now') }] }], + default: ExpressionUtils.call('now') }, updatedAt: { - name: "updatedAt", - type: "DateTime", + name: 'updatedAt', + type: 'DateTime', updatedAt: true, - attributes: [{ name: "@updatedAt" }] + attributes: [{ name: '@updatedAt' }] }, posts: { - name: "posts", - type: "Post", + name: 'posts', + type: 'Post', array: true, - relation: { opposite: "author" } + relation: { opposite: 'author' } } }, attributes: [ - { name: "@@map", args: [{ name: "name", value: ExpressionUtils.literal("users") }] } + { name: '@@map', args: [{ name: 'name', value: ExpressionUtils.literal('users') }] } ], - idFields: ["id"], + idFields: ['id'], uniqueFields: { - id: { type: "String" }, - email: { type: "String" } + id: { type: 'String' }, + email: { type: 'String' } } }, Post: { - name: "Post", + name: 'Post', fields: { id: { - name: "id", - type: "String", + name: 'id', + type: 'String', id: true, - attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], - default: ExpressionUtils.call("cuid") + attributes: [{ name: '@id' }, { name: '@default', args: [{ name: 'value', value: ExpressionUtils.call('cuid') }] }], + default: ExpressionUtils.call('cuid') }, title: { - name: "title", - type: "String" + name: 'title', + type: 'String' }, published: { - name: "published", - type: "Boolean", - attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(false) }] }], + name: 'published', + type: 'Boolean', + attributes: [{ name: '@default', args: [{ name: 'value', value: ExpressionUtils.literal(false) }] }], default: false }, author: { - name: "author", - type: "User", - attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }, { name: "onDelete", value: ExpressionUtils.literal("Cascade") }] }], - relation: { opposite: "posts", fields: ["authorId"], references: ["id"], onDelete: "Cascade" } + name: 'author', + type: 'User', + attributes: [{ name: '@relation', args: [{ name: 'fields', value: ExpressionUtils.array([ExpressionUtils.field('authorId')]) }, { name: 'references', value: ExpressionUtils.array([ExpressionUtils.field('id')]) }, { name: 'onDelete', value: ExpressionUtils.literal('Cascade') }] }], + relation: { opposite: 'posts', fields: ['authorId'], references: ['id'], onDelete: 'Cascade' } }, authorId: { - name: "authorId", - type: "String", + name: 'authorId', + type: 'String', foreignKeyFor: [ - "author" + 'author' ] } }, - idFields: ["id"], + idFields: ['id'], uniqueFields: { - id: { type: "String" } + id: { type: 'String' } } } }, - authType: "User", + authType: 'User', plugins: {} } as const satisfies SchemaDef; export type SchemaType = typeof schema;