diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 644418e0c..49055f690 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -99,15 +99,20 @@ "@tanstack/svelte-query": "^4.29.7", "@tanstack/svelte-query-v5": "npm:@tanstack/svelte-query@^5.0.0", "@tanstack/vue-query": "^4.37.0", + "@testing-library/react": "^14.0.0", "@types/jest": "^29.5.0", + "@types/nock": "^11.1.0", "@types/node": "^18.0.0", "@types/react": "18.2.0", "@types/semver": "^7.3.13", "@types/tmp": "^0.2.3", "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", - "jest": "^29.5.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "nock": "^13.3.6", "react": "18.2.0", + "react-test-renderer": "^18.2.0", "replace-in-file": "^7.0.1", "rimraf": "^3.0.2", "svelte": "^4.2.1", diff --git a/packages/plugins/tanstack-query/src/generator.ts b/packages/plugins/tanstack-query/src/generator.ts index d5c45b87c..f7c886068 100644 --- a/packages/plugins/tanstack-query/src/generator.ts +++ b/packages/plugins/tanstack-query/src/generator.ts @@ -2,6 +2,7 @@ import type { DMMF } from '@prisma/generator-helper'; import { PluginError, PluginOptions, + generateModelMeta as _generateModelMeta, createProject, getDataModels, getPrismaClientImportSpec, @@ -44,6 +45,8 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. throw new PluginError(options.name, `Unsupported version "${version}": use "v4" or "v5"`); } + await generateModelMeta(project, outDir, models); + generateIndex(project, outDir, models, target, version); models.forEach((dataModel) => { @@ -158,11 +161,11 @@ function generateMutationHook( { name: `_mutation`, initializer: ` - ${httpVerb}Mutation<${argsType}, ${ + mutate<${argsType}, ${ overrideReturnType ?? model - }, ${checkReadBack}>('${model}', \`\${endpoint}/${lowerCaseFirst( + }, ${checkReadBack}>('${model}', '${httpVerb.toUpperCase()}', \`\${endpoint}/${lowerCaseFirst( model - )}/${operation}\`, options, fetch, invalidateQueries, ${checkReadBack}) + )}/${operation}\`, metadata, options, fetch, invalidateQueries, ${checkReadBack}) `, }, ], @@ -438,6 +441,10 @@ function generateModelHooks( } } +async function generateModelMeta(project: Project, outDir: string, models: DataModel[]) { + await _generateModelMeta(project, models, path.join(outDir, '__model_meta.ts'), false, true); +} + function generateIndex( project: Project, outDir: string, @@ -445,9 +452,10 @@ function generateIndex( target: string, version: TanStackVersion ) { + const runtimeImportBase = makeRuntimeImportBase(version); const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); - const runtimeImportBase = makeRuntimeImportBase(version); + sf.addStatements(`export { getQueryKey } from '${runtimeImportBase}';`); switch (target) { case 'react': sf.addStatements(`export { Provider } from '${runtimeImportBase}/react';`); @@ -477,8 +485,9 @@ function makeGetContext(target: TargetFramework) { function makeBaseImports(target: TargetFramework, version: TanStackVersion) { const runtimeImportBase = makeRuntimeImportBase(version); const shared = [ - `import { query, infiniteQuery, postMutation, putMutation, deleteMutation } from '${runtimeImportBase}/${target}';`, + `import { query, infiniteQuery, mutate } from '${runtimeImportBase}/${target}';`, `import type { PickEnumerable, CheckSelect } from '${runtimeImportBase}';`, + `import metadata from './__model_meta';`, ]; switch (target) { case 'react': diff --git a/packages/plugins/tanstack-query/src/runtime-v5/index.ts b/packages/plugins/tanstack-query/src/runtime-v5/index.ts index c7b30ba34..302b775fc 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/index.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/index.ts @@ -1,2 +1,2 @@ export * from '../runtime/prisma-types'; -export type { FetchFn } from '../runtime/common'; +export { type FetchFn, getQueryKey } from '../runtime/common'; diff --git a/packages/plugins/tanstack-query/src/runtime-v5/react.ts b/packages/plugins/tanstack-query/src/runtime-v5/react.ts index b95c2530b..d3ea22f53 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/react.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/react.ts @@ -5,20 +5,20 @@ import { useQuery, useQueryClient, type InfiniteData, - type MutateFunction, - type QueryClient, type UseInfiniteQueryOptions, type UseMutationOptions, type UseQueryOptions, } from '@tanstack/react-query-v5'; -import { createContext } from 'react'; +import type { ModelMeta } from '@zenstackhq/runtime/cross'; +import { createContext, useContext } from 'react'; import { DEFAULT_QUERY_ENDPOINT, FetchFn, - QUERY_KEY_PREFIX, fetcher, + getQueryKey, makeUrl, marshal, + setupInvalidation, type APIContext, } from '../runtime/common'; @@ -53,7 +53,7 @@ export function query( ) { const reqUrl = makeUrl(url, args); return useQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); @@ -76,7 +76,7 @@ export function infiniteQuery( fetch?: FetchFn ) { return useInfiniteQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: ({ pageParam }) => { return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, @@ -84,128 +84,46 @@ export function infiniteQuery( }); } -/** - * Creates a POST mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function postMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a PUT mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function putMutation( +export function mutate( model: string, + method: 'POST' | 'PUT' | 'DELETE', url: string, + modelMeta: ModelMeta, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'PUT', + const mutationFn = (data: any) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { headers: { 'content-type': 'application/json', }, body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a DELETE mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function deleteMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - makeUrl(url, data), - { - method: 'DELETE', - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -function mergeOptions( - model: string, - options: Omit, 'mutationFn'> | undefined, - invalidateQueries: boolean, - mutationFn: MutateFunction, - queryClient: QueryClient -): UseMutationOptions { - const result = { ...options, mutationFn }; - if (options?.onSuccess || invalidateQueries) { - result.onSuccess = (...args) => { - if (invalidateQueries) { - queryClient.invalidateQueries({ queryKey: [QUERY_KEY_PREFIX + model] }); - } - return options?.onSuccess?.(...args); + }), }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; + + const finalOptions = { ...options, mutationFn }; + if (invalidateQueries) { + const { logging } = useContext(RequestHandlerContext); + const operation = url.split('/').pop(); + if (operation) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } } - return result; + + return useMutation(finalOptions); } diff --git a/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts b/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts index 8f25ff0ee..883417013 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/svelte.ts @@ -6,15 +6,14 @@ import { useQueryClient, type CreateInfiniteQueryOptions, type InfiniteData, - type MutateFunction, type MutationOptions, - type QueryClient, type StoreOrVal, } from '@tanstack/svelte-query-v5'; import { QueryOptions } from '@tanstack/vue-query'; -import { setContext } from 'svelte'; +import { ModelMeta } from '@zenstackhq/runtime/cross'; +import { getContext, setContext } from 'svelte'; import { Readable, derived } from 'svelte/store'; -import { APIContext, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal } from '../runtime/common'; +import { APIContext, FetchFn, fetcher, getQueryKey, makeUrl, marshal, setupInvalidation } from '../runtime/common'; export { APIContext as RequestHandlerContext } from '../runtime/common'; @@ -47,7 +46,7 @@ export function query( fetch?: FetchFn ) { const reqUrl = makeUrl(url, args); - const queryKey = [QUERY_KEY_PREFIX + model, url, args]; + const queryKey = getQueryKey(model, url, args); const queryFn = () => fetcher(reqUrl, undefined, fetch, false); let mergedOpt: any; @@ -87,7 +86,7 @@ export function infiniteQuery( options: StoreOrVal>, 'queryKey'>>, fetch?: FetchFn ) { - const queryKey = [QUERY_KEY_PREFIX + model, url, args]; + const queryKey = getQueryKey(model, url, args); const queryFn = ({ pageParam }: { pageParam: unknown }) => fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); @@ -120,124 +119,53 @@ function isStore(opt: unknown): opt is Readable { * Creates a POST mutation with svelte-query. * * @param model The name of the model under mutation. + * @param method The HTTP method. + * @param modelMeta The model metadata. * @param url The request URL. * @param options The svelte-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function postMutation( +export function mutate( model: string, + method: 'POST' | 'PUT' | 'DELETE', url: string, + modelMeta: ModelMeta, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'POST', + const mutationFn = (data: any) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { headers: { 'content-type': 'application/json', }, body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); - return mutation; -} - -/** - * Creates a PUT mutation with svelte-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The svelte-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function putMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'PUT', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); - return mutation; -} - -/** - * Creates a DELETE mutation with svelte-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The svelte-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function deleteMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - makeUrl(url, data), - { - method: 'DELETE', - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); - return mutation; -} - -function mergeOptions( - model: string, - options: Omit, 'mutationFn'> | undefined, - invalidateQueries: boolean, - mutationFn: MutateFunction, - queryClient: QueryClient -): MutationOptions { - const result = { ...options, mutationFn }; - if (options?.onSuccess || invalidateQueries) { - result.onSuccess = (...args) => { - if (invalidateQueries) { - queryClient.invalidateQueries({ queryKey: [QUERY_KEY_PREFIX + model] }); - } - return options?.onSuccess?.(...args); + }), }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; + + const finalOptions = { ...options, mutationFn }; + if (invalidateQueries) { + const { logging } = getContext(SvelteQueryContextKey); + const operation = url.split('/').pop(); + if (operation) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } } - return result; + + return createMutation(finalOptions); } diff --git a/packages/plugins/tanstack-query/src/runtime/common.ts b/packages/plugins/tanstack-query/src/runtime/common.ts index d64c1ed25..26b02bf14 100644 --- a/packages/plugins/tanstack-query/src/runtime/common.ts +++ b/packages/plugins/tanstack-query/src/runtime/common.ts @@ -1,5 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { deserialize, serialize } from '@zenstackhq/runtime/browser'; +import { + NestedReadVisitor, + NestedWriteVisitor, + type ModelMeta, + type PrismaWriteActionType, +} from '@zenstackhq/runtime/cross'; import * as crossFetch from 'cross-fetch'; /** @@ -10,7 +17,7 @@ export const DEFAULT_QUERY_ENDPOINT = '/api/model'; /** * Prefix for react-query keys. */ -export const QUERY_KEY_PREFIX = 'zenstack:'; +export const QUERY_KEY_PREFIX = 'zenstack'; /** * Function signature for `fetch`. @@ -30,6 +37,11 @@ export type APIContext = { * A custom fetch function for sending the HTTP requests. */ fetch?: FetchFn; + + /** + * If logging is enabled. + */ + logging?: boolean; }; export async function fetcher( @@ -68,6 +80,24 @@ export async function fetcher( } } +type QueryKey = [string /* prefix */, string /* model */, string /* operation */, unknown /* args */]; + +/** + * Computes query key for the given model, operation and query args. + * @param model Model name. + * @param urlOrOperation Prisma operation (e.g, `findMany`) or request URL. If it's a URL, the last path segment will be used as the operation name. + * @param args Prisma query arguments. + * @returns Query key + */ +export function getQueryKey(model: string, urlOrOperation: string, args: unknown): QueryKey { + if (!urlOrOperation) { + throw new Error('Invalid urlOrOperation'); + } + const operation = urlOrOperation.split('/').pop(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return [QUERY_KEY_PREFIX, model, operation!, args]; +} + export function marshal(value: unknown) { const { data, meta } = serialize(value); if (meta) { @@ -99,3 +129,113 @@ export function makeUrl(url: string, args: unknown) { } return result; } + +type InvalidationPredicate = ({ queryKey }: { queryKey: readonly unknown[] }) => boolean; + +// sets up invalidation hook for a mutation +export function setupInvalidation( + model: string, + operation: string, + modelMeta: ModelMeta, + options: { onSuccess?: (...args: any[]) => any }, + invalidate: (predicate: InvalidationPredicate) => Promise, + logging = false +) { + const origOnSuccess = options?.onSuccess; + options.onSuccess = async (...args: unknown[]) => { + const [_, variables] = args; + const predicate = await getInvalidationPredicate( + model, + operation as PrismaWriteActionType, + variables, + modelMeta, + logging + ); + await invalidate(predicate); + return origOnSuccess?.(...args); + }; +} + +// gets a predicate for evaluating whether a query should be invalidated +async function getInvalidationPredicate( + model: string, + operation: PrismaWriteActionType, + mutationArgs: any, + modelMeta: ModelMeta, + logging = false +) { + const mutatedModels = await collectMutatedModels(model, operation, mutationArgs, modelMeta); + + return ({ queryKey }: { queryKey: readonly unknown[] }) => { + const [_model, queryModel, queryOp, args] = queryKey as QueryKey; + + if (mutatedModels.includes(queryModel)) { + // direct match + if (logging) { + console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`); + } + return true; + } + + if (args) { + // traverse query args to find nested reads that match the model under mutation + if (queryOp.startsWith('find') && findNestedRead(queryModel, mutatedModels, modelMeta, args)) { + if (logging) { + console.log(`Invalidating query [${queryKey}] due to mutation "${model}.${operation}"`); + } + return true; + } + } + + return false; + }; +} + +// find nested reads that match the given models +function findNestedRead(visitingModel: string, targetModels: string[], modelMeta: ModelMeta, args: any) { + let found = false; + const visitor = new NestedReadVisitor(modelMeta, { + field: (model) => { + if (targetModels.includes(model)) { + // found a match + found = true; + // stop visiting + return false; + } else { + return true; + } + }, + }); + + visitor.visit(visitingModel, args); + + return found; +} + +// collect the models being mutated in the given mutation args +async function collectMutatedModels( + model: string, + operation: PrismaWriteActionType, + mutationArgs: any, + modelMeta: ModelMeta +) { + const result = new Set(); + const addModel = (model: string) => void result.add(model); + + const visitor = new NestedWriteVisitor(modelMeta, { + create: addModel, + createMany: addModel, + connectOrCreate: addModel, + connect: addModel, + disconnect: addModel, + set: addModel, + update: addModel, + updateMany: addModel, + upsert: addModel, + delete: addModel, + deleteMany: addModel, + }); + + await visitor.visit(model, operation, mutationArgs); + return [...result]; +} diff --git a/packages/plugins/tanstack-query/src/runtime/index.ts b/packages/plugins/tanstack-query/src/runtime/index.ts index d263dca77..909c0c4bf 100644 --- a/packages/plugins/tanstack-query/src/runtime/index.ts +++ b/packages/plugins/tanstack-query/src/runtime/index.ts @@ -1,2 +1,2 @@ export * from './prisma-types'; -export type { FetchFn } from './common'; +export { type FetchFn, getQueryKey } from './common'; diff --git a/packages/plugins/tanstack-query/src/runtime/react.ts b/packages/plugins/tanstack-query/src/runtime/react.ts index 6c3a4739a..e0fcccd52 100644 --- a/packages/plugins/tanstack-query/src/runtime/react.ts +++ b/packages/plugins/tanstack-query/src/runtime/react.ts @@ -4,20 +4,20 @@ import { useMutation, useQuery, useQueryClient, - type MutateFunction, - type QueryClient, type UseInfiniteQueryOptions, type UseMutationOptions, type UseQueryOptions, } from '@tanstack/react-query'; -import { createContext } from 'react'; +import type { ModelMeta } from '@zenstackhq/runtime/cross'; +import { createContext, useContext } from 'react'; import { DEFAULT_QUERY_ENDPOINT, FetchFn, - QUERY_KEY_PREFIX, fetcher, + getQueryKey, makeUrl, marshal, + setupInvalidation, type APIContext, } from './common'; @@ -27,6 +27,7 @@ import { export const RequestHandlerContext = createContext({ endpoint: DEFAULT_QUERY_ENDPOINT, fetch: undefined, + logging: false, }); /** @@ -52,7 +53,7 @@ export function query( ) { const reqUrl = makeUrl(url, args); return useQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); @@ -75,7 +76,7 @@ export function infiniteQuery( fetch?: FetchFn ) { return useInfiniteQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: ({ pageParam }) => { return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, @@ -84,127 +85,56 @@ export function infiniteQuery( } /** - * Creates a POST mutation with react-query. + * Creates a mutation with react-query. * * @param model The name of the model under mutation. + * @param method The HTTP method. + * @param modelMeta The model metadata. * @param url The request URL. * @param options The react-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function postMutation( +export function mutate( model: string, + method: 'POST' | 'PUT' | 'DELETE', url: string, + modelMeta: ModelMeta, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'POST', + const mutationFn = (data: any) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { headers: { 'content-type': 'application/json', }, body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a PUT mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function putMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'PUT', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a DELETE mutation with react-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The react-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function deleteMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - makeUrl(url, data), - { - method: 'DELETE', - }, - fetch, - checkReadBack - ) as Promise; - - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -function mergeOptions( - model: string, - options: Omit, 'mutationFn'> | undefined, - invalidateQueries: boolean, - mutationFn: MutateFunction, - queryClient: QueryClient -): UseMutationOptions { - const result = { ...options, mutationFn }; - if (options?.onSuccess || invalidateQueries) { - result.onSuccess = (...args) => { - if (invalidateQueries) { - queryClient.invalidateQueries([QUERY_KEY_PREFIX + model]); - } - return options?.onSuccess?.(...args); + }), }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; + + const finalOptions = { ...options, mutationFn }; + if (invalidateQueries) { + const { logging } = useContext(RequestHandlerContext); + const operation = url.split('/').pop(); + if (operation) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } } - return result; + + return useMutation(finalOptions); } diff --git a/packages/plugins/tanstack-query/src/runtime/svelte.ts b/packages/plugins/tanstack-query/src/runtime/svelte.ts index 24ea57448..edd3b3c3a 100644 --- a/packages/plugins/tanstack-query/src/runtime/svelte.ts +++ b/packages/plugins/tanstack-query/src/runtime/svelte.ts @@ -10,8 +10,9 @@ import { type QueryClient, type QueryOptions, } from '@tanstack/svelte-query'; -import { setContext } from 'svelte'; -import { APIContext, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal } from './common'; +import { ModelMeta } from '@zenstackhq/runtime/cross'; +import { getContext, setContext } from 'svelte'; +import { APIContext, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal, setupInvalidation } from './common'; export { APIContext as RequestHandlerContext } from './common'; @@ -78,37 +79,55 @@ export function infiniteQuery( * Creates a POST mutation with svelte-query. * * @param model The name of the model under mutation. + * @param method The HTTP method. + * @param modelMeta The model metadata. * @param url The request URL. * @param options The svelte-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function postMutation( +export function mutate( model: string, + method: 'POST' | 'PUT' | 'DELETE', url: string, + modelMeta: ModelMeta, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'POST', + const mutationFn = (data: any) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { headers: { 'content-type': 'application/json', }, body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; + }), + }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; - const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = createMutation(finalOptions); - return mutation; + const finalOptions = { ...options, mutationFn }; + if (invalidateQueries) { + const { logging } = getContext(SvelteQueryContextKey); + const operation = url.split('/').pop(); + if (operation) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } + } + + return createMutation(finalOptions); } /** diff --git a/packages/plugins/tanstack-query/src/runtime/vue.ts b/packages/plugins/tanstack-query/src/runtime/vue.ts index 2ea162649..312573cf6 100644 --- a/packages/plugins/tanstack-query/src/runtime/vue.ts +++ b/packages/plugins/tanstack-query/src/runtime/vue.ts @@ -5,14 +5,22 @@ import { useMutation, useQuery, useQueryClient, - type MutateFunction, - type QueryClient, type UseInfiniteQueryOptions, type UseMutationOptions, type UseQueryOptions, } from '@tanstack/vue-query'; +import type { ModelMeta } from '@zenstackhq/runtime/cross'; import { inject, provide } from 'vue'; -import { APIContext, DEFAULT_QUERY_ENDPOINT, FetchFn, QUERY_KEY_PREFIX, fetcher, makeUrl, marshal } from './common'; +import { + APIContext, + DEFAULT_QUERY_ENDPOINT, + FetchFn, + fetcher, + getQueryKey, + makeUrl, + marshal, + setupInvalidation, +} from './common'; export { APIContext as RequestHandlerContext } from './common'; @@ -29,6 +37,7 @@ export function getContext() { return inject(VueQueryContextKey, { endpoint: DEFAULT_QUERY_ENDPOINT, fetch: undefined, + logging: false, }); } @@ -44,7 +53,7 @@ export function getContext() { export function query(model: string, url: string, args?: unknown, options?: UseQueryOptions, fetch?: FetchFn) { const reqUrl = makeUrl(url, args); return useQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: () => fetcher(reqUrl, undefined, fetch, false), ...options, }); @@ -67,7 +76,7 @@ export function infiniteQuery( fetch?: FetchFn ) { return useInfiniteQuery({ - queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryKey: getQueryKey(model, url, args), queryFn: ({ pageParam }) => { return fetcher(makeUrl(url, pageParam ?? args), undefined, fetch, false); }, @@ -76,130 +85,56 @@ export function infiniteQuery( } /** - * Creates a POST mutation with vue-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The vue-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function postMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - // TODO: figure out the typing problem - const finalOptions: any = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a PUT mutation with vue-query. + * Creates a mutation with vue-query. * * @param model The name of the model under mutation. + * @param method The HTTP method. + * @param modelMeta The model metadata. * @param url The request URL. * @param options The vue-query options. * @param invalidateQueries Whether to invalidate queries after mutation. * @returns useMutation hooks */ -export function putMutation( +export function mutate( model: string, + method: 'POST' | 'PUT' | 'DELETE', url: string, + modelMeta: ModelMeta, options?: Omit, 'mutationFn'>, fetch?: FetchFn, invalidateQueries = true, checkReadBack?: C ) { const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - url, - { - method: 'PUT', + const mutationFn = (data: any) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { headers: { 'content-type': 'application/json', }, body: marshal(data), - }, - fetch, - checkReadBack - ) as Promise; - - // TODO: figure out the typing problem - const finalOptions: any = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -/** - * Creates a DELETE mutation with vue-query. - * - * @param model The name of the model under mutation. - * @param url The request URL. - * @param options The vue-query options. - * @param invalidateQueries Whether to invalidate queries after mutation. - * @returns useMutation hooks - */ -export function deleteMutation( - model: string, - url: string, - options?: Omit, 'mutationFn'>, - fetch?: FetchFn, - invalidateQueries = true, - checkReadBack?: C -) { - const queryClient = useQueryClient(); - const mutationFn = (data: any) => - fetcher( - makeUrl(url, data), - { - method: 'DELETE', - }, - fetch, - checkReadBack - ) as Promise; + }), + }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; // TODO: figure out the typing problem - const finalOptions: any = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); - const mutation = useMutation(finalOptions); - return mutation; -} - -function mergeOptions( - model: string, - options: Omit, 'mutationFn'> | undefined, - invalidateQueries: boolean, - mutationFn: MutateFunction, - queryClient: QueryClient -): UseMutationOptions { - const result = { ...options, mutationFn }; - if (options?.onSuccess || invalidateQueries) { - result.onSuccess = (...args) => { - if (invalidateQueries) { - queryClient.invalidateQueries([QUERY_KEY_PREFIX + model]); - } - return options?.onSuccess?.(...args); - }; + const finalOptions: any = { ...options, mutationFn }; + if (invalidateQueries) { + const { logging } = getContext(); + const operation = url.split('/').pop(); + if (operation) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } } - return result; + return useMutation(finalOptions); } diff --git a/packages/plugins/tanstack-query/tests/react-hooks-v5.test.tsx b/packages/plugins/tanstack-query/tests/react-hooks-v5.test.tsx new file mode 100644 index 000000000..5be4e87dc --- /dev/null +++ b/packages/plugins/tanstack-query/tests/react-hooks-v5.test.tsx @@ -0,0 +1,283 @@ +/** + * @jest-environment jsdom + */ + +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query-v5'; +import { renderHook, waitFor, act } from '@testing-library/react'; +import nock from 'nock'; +import React from 'react'; +import { QUERY_KEY_PREFIX } from '../src/runtime/common'; +import { RequestHandlerContext, mutate, query } from '../src/runtime-v5/react'; +import { modelMeta } from './test-model-meta'; + +describe('Tanstack Query React Hooks V5 Test', () => { + function createWrapper() { + const queryClient = new QueryClient(); + const Provider = RequestHandlerContext.Provider; + const wrapper = ({ children }: { children: React.ReactElement }) => ( + + {/* @ts-ignore */} + {children} + + ); + return { queryClient, wrapper }; + } + + function makeUrl(model: string, operation: string, args?: unknown) { + let r = `http://localhost/api/model/${model}/${operation}`; + if (args) { + r += `?q=${encodeURIComponent(JSON.stringify(args))}`; + } + return r; + } + + beforeEach(() => { + nock.cleanAll(); + }); + + it('simple query', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + nock(makeUrl('User', 'findUnique', queryArgs)).get(/.*/).reply(200, { + data, + }); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + expect(result.current.data).toMatchObject(data); + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData).toMatchObject(data); + }); + }); + + it('independent mutation and query', async () => { + const { wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + let queryCount = 0; + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + queryCount++; + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject({ name: 'foo' }); + }); + + nock(makeUrl('Post', 'create')) + .post(/.*/) + .reply(200, () => { + console.log('Mutating data'); + return { data: { id: '1', title: 'post1' } }; + }); + + const { result: mutationResult } = renderHook( + () => mutate('Post', 'POST', makeUrl('Post', 'create'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ ...queryArgs, data: { title: 'post1' } })); + + await waitFor(() => { + // no refetch caused by invalidation + expect(queryCount).toBe(1); + }); + }); + + it('create and invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const data: any[] = []; + + nock(makeUrl('User', 'findMany')) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findMany')), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toHaveLength(0); + }); + + nock(makeUrl('User', 'create')) + .post(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.push({ id: '1', name: 'foo' }); + return { data: data[0] }; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'POST', makeUrl('User', 'create'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ data: { name: 'foo' } })); + + await waitFor(() => { + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findMany', undefined]); + expect(cacheData).toHaveLength(1); + }); + }); + + it('update and invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject({ name: 'foo' }); + }); + + nock(makeUrl('User', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.name = 'bar'; + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'PUT', makeUrl('User', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ ...queryArgs, data: { name: 'bar' } })); + + await waitFor(() => { + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData).toMatchObject({ name: 'bar' }); + }); + }); + + it('top-level mutation and nested-read invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' }, include: { posts: true } }; + const data = { posts: [{ id: '1', title: 'post1' }] }; + + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject(data); + }); + + nock(makeUrl('Post', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.posts[0].title = 'post2'; + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('Post', 'PUT', makeUrl('Post', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { name: 'post2' } })); + + await waitFor(() => { + const cacheData: any = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData.posts[0].title).toBe('post2'); + }); + }); + + it('nested mutation and top-level-read invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const data = [{ id: '1', title: 'post1', ownerId: '1' }]; + + nock(makeUrl('Post', 'findMany')) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('Post', makeUrl('Post', 'findMany')), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject(data); + }); + + nock(makeUrl('User', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.push({ id: '2', title: 'post2', ownerId: '1' }); + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'PUT', makeUrl('User', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => + mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { create: { title: 'post2' } } } }) + ); + + await waitFor(() => { + const cacheData: any = queryClient.getQueryData([QUERY_KEY_PREFIX, 'Post', 'findMany', undefined]); + expect(cacheData).toHaveLength(2); + }); + }); +}); diff --git a/packages/plugins/tanstack-query/tests/react-hooks.test.tsx b/packages/plugins/tanstack-query/tests/react-hooks.test.tsx new file mode 100644 index 000000000..55b780751 --- /dev/null +++ b/packages/plugins/tanstack-query/tests/react-hooks.test.tsx @@ -0,0 +1,283 @@ +/** + * @jest-environment jsdom + */ + +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor, act } from '@testing-library/react'; +import nock from 'nock'; +import React from 'react'; +import { QUERY_KEY_PREFIX } from '../src/runtime/common'; +import { RequestHandlerContext, mutate, query } from '../src/runtime/react'; +import { modelMeta } from './test-model-meta'; + +describe('Tanstack Query React Hooks Test', () => { + function createWrapper() { + const queryClient = new QueryClient(); + const Provider = RequestHandlerContext.Provider; + const wrapper = ({ children }: { children: React.ReactElement }) => ( + + {/* @ts-ignore */} + {children} + + ); + return { queryClient, wrapper }; + } + + function makeUrl(model: string, operation: string, args?: unknown) { + let r = `http://localhost/api/model/${model}/${operation}`; + if (args) { + r += `?q=${encodeURIComponent(JSON.stringify(args))}`; + } + return r; + } + + beforeEach(() => { + nock.cleanAll(); + }); + + it('simple query', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + nock(makeUrl('User', 'findUnique', queryArgs)).get(/.*/).reply(200, { + data, + }); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + expect(result.current.data).toMatchObject(data); + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData).toMatchObject(data); + }); + }); + + it('independent mutation and query', async () => { + const { wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + let queryCount = 0; + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + queryCount++; + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject({ name: 'foo' }); + }); + + nock(makeUrl('Post', 'create')) + .post(/.*/) + .reply(200, () => { + console.log('Mutating data'); + return { data: { id: '1', title: 'post1' } }; + }); + + const { result: mutationResult } = renderHook( + () => mutate('Post', 'POST', makeUrl('Post', 'create'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ ...queryArgs, data: { title: 'post1' } })); + + await waitFor(() => { + // no refetch caused by invalidation + expect(queryCount).toBe(1); + }); + }); + + it('create and invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const data: any[] = []; + + nock(makeUrl('User', 'findMany')) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findMany')), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toHaveLength(0); + }); + + nock(makeUrl('User', 'create')) + .post(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.push({ id: '1', name: 'foo' }); + return { data: data[0] }; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'POST', makeUrl('User', 'create'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ data: { name: 'foo' } })); + + await waitFor(() => { + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findMany', undefined]); + expect(cacheData).toHaveLength(1); + }); + }); + + it('update and invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' } }; + const data = { id: '1', name: 'foo' }; + + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject({ name: 'foo' }); + }); + + nock(makeUrl('User', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.name = 'bar'; + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'PUT', makeUrl('User', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ ...queryArgs, data: { name: 'bar' } })); + + await waitFor(() => { + const cacheData = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData).toMatchObject({ name: 'bar' }); + }); + }); + + it('top-level mutation and nested-read invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const queryArgs = { where: { id: '1' }, include: { posts: true } }; + const data = { posts: [{ id: '1', title: 'post1' }] }; + + nock(makeUrl('User', 'findUnique', queryArgs)) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('User', makeUrl('User', 'findUnique'), queryArgs), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject(data); + }); + + nock(makeUrl('Post', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.posts[0].title = 'post2'; + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('Post', 'PUT', makeUrl('Post', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => mutationResult.current.mutate({ where: { id: '1' }, data: { name: 'post2' } })); + + await waitFor(() => { + const cacheData: any = queryClient.getQueryData([QUERY_KEY_PREFIX, 'User', 'findUnique', queryArgs]); + expect(cacheData.posts[0].title).toBe('post2'); + }); + }); + + it('nested mutation and top-level-read invalidation', async () => { + const { queryClient, wrapper } = createWrapper(); + + const data = [{ id: '1', title: 'post1', ownerId: '1' }]; + + nock(makeUrl('Post', 'findMany')) + .get(/.*/) + .reply(200, () => { + console.log('Querying data:', JSON.stringify(data)); + return { data }; + }) + .persist(); + + const { result } = renderHook(() => query('Post', makeUrl('Post', 'findMany')), { + wrapper, + }); + await waitFor(() => { + expect(result.current.data).toMatchObject(data); + }); + + nock(makeUrl('User', 'update')) + .put(/.*/) + .reply(200, () => { + console.log('Mutating data'); + data.push({ id: '2', title: 'post2', ownerId: '1' }); + return data; + }); + + const { result: mutationResult } = renderHook( + () => mutate('User', 'PUT', makeUrl('User', 'update'), modelMeta), + { + wrapper, + } + ); + + act(() => + mutationResult.current.mutate({ where: { id: '1' }, data: { posts: { create: { title: 'post2' } } } }) + ); + + await waitFor(() => { + const cacheData: any = queryClient.getQueryData([QUERY_KEY_PREFIX, 'Post', 'findMany', undefined]); + expect(cacheData).toHaveLength(2); + }); + }); +}); diff --git a/packages/plugins/tanstack-query/tests/test-model-meta.ts b/packages/plugins/tanstack-query/tests/test-model-meta.ts new file mode 100644 index 000000000..5e1a852a4 --- /dev/null +++ b/packages/plugins/tanstack-query/tests/test-model-meta.ts @@ -0,0 +1,47 @@ +import { ModelMeta } from '@zenstackhq/runtime/cross'; + +const fieldDefaults = { + isId: false, + isDataModel: false, + isArray: false, + isOptional: true, + attributes: [], + isRelationOwner: false, + isForeignKey: false, +}; + +export const modelMeta: ModelMeta = { + fields: { + user: { + id: { + ...fieldDefaults, + type: 'String', + isId: true, + name: 'id', + isOptional: false, + }, + name: { ...fieldDefaults, type: 'String', name: 'name' }, + email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false }, + posts: { + ...fieldDefaults, + type: 'Post', + isDataModel: true, + isArray: true, + name: 'posts', + }, + }, + post: { + id: { + ...fieldDefaults, + type: 'String', + isId: true, + name: 'id', + isOptional: false, + }, + title: { ...fieldDefaults, type: 'String', name: 'title' }, + owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true }, + ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true }, + }, + }, + uniqueConstraints: {}, +}; diff --git a/packages/plugins/tanstack-query/tsconfig.json b/packages/plugins/tanstack-query/tsconfig.json index c47be7288..9e4f772c5 100644 --- a/packages/plugins/tanstack-query/tsconfig.json +++ b/packages/plugins/tanstack-query/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "lib": ["ESNext", "DOM"], - "outDir": "dist" + "outDir": "dist", + "jsx": "react" }, "include": ["src/**/*.ts"], "exclude": ["src/runtime"] diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 1869fa80f..3982e64c1 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -9,8 +9,8 @@ }, "scripts": { "clean": "rimraf dist", - "build": "pnpm lint && pnpm clean && tsc && tsup-node && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", - "watch": "concurrently \"tsc --watch\" \"tsup-node --watch\"", + "build": "pnpm lint && pnpm clean && tsc && tsup-node --config ./tsup-browser.config.ts && tsup-node --config ./tsup-cross.config.ts && copyfiles ./package.json ./README.md ../../LICENSE dist && copyfiles -u1 'res/**/*' dist && pnpm pack dist --pack-destination '../../../.build'", + "watch": "concurrently \"tsc --watch\" \"tsup-node --config ./tsup-browser.config.ts --watch\" \"tsup-node --config ./tsup-cross.config.ts --watch\"", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build", "publish-dev": "pnpm publish --tag dev" @@ -41,6 +41,12 @@ "require": "./browser/index.js", "default": "./browser/index.js", "types": "./browser/index.d.ts" + }, + "./cross": { + "import": "./cross/index.mjs", + "require": "./cross/index.js", + "default": "./cross/index.js", + "types": "./cross/index.d.ts" } }, "publishConfig": { diff --git a/packages/runtime/src/cross/index.ts b/packages/runtime/src/cross/index.ts new file mode 100644 index 000000000..e7567a504 --- /dev/null +++ b/packages/runtime/src/cross/index.ts @@ -0,0 +1,5 @@ +export * from './model-meta'; +export * from './nested-read-visitor'; +export * from './nested-write-visitor'; +export * from './types'; +export * from './utils'; diff --git a/packages/runtime/src/cross/model-meta.ts b/packages/runtime/src/cross/model-meta.ts new file mode 100644 index 000000000..498635892 --- /dev/null +++ b/packages/runtime/src/cross/model-meta.ts @@ -0,0 +1,97 @@ +import { lowerCaseFirst } from 'lower-case-first'; + +/** + * Runtime information of a data model or field attribute + */ +export type RuntimeAttribute = { + name: string; + args: Array<{ name?: string; value: unknown }>; +}; + +/** + * Runtime information of a data model field + */ +export type FieldInfo = { + /** + * Field name + */ + name: string; + + /** + * Field type name + */ + type: string; + + /** + * If the field is an ID field or part of a multi-field ID + */ + isId: boolean; + + /** + * If the field type is a data model (or an optional/array of data model) + */ + isDataModel: boolean; + + /** + * If the field is an array + */ + isArray: boolean; + + /** + * If the field is optional + */ + isOptional: boolean; + + /** + * Attributes on the field + */ + attributes: RuntimeAttribute[]; + + /** + * If the field is a relation field, the field name of the reverse side of the relation + */ + backLink?: string; + + /** + * If the field is the owner side of a relation + */ + isRelationOwner: boolean; + + /** + * If the field is a foreign key field + */ + isForeignKey: boolean; + + /** + * Mapping from foreign key field names to relation field names + */ + foreignKeyMapping?: Record; +}; + +/** + * Metadata for a model-level unique constraint + * e.g.: @@unique([a, b]) + */ +export type UniqueConstraint = { name: string; fields: string[] }; + +/** + * ZModel data model metadata + */ +export type ModelMeta = { + fields: Record>; + uniqueConstraints: Record>; +}; + +/** + * Resolves a model field to its metadata. Returns undefined if not found. + */ +export function resolveField(modelMeta: ModelMeta, model: string, field: string): FieldInfo | undefined { + return modelMeta.fields[lowerCaseFirst(model)]?.[field]; +} + +/** + * Gets all fields of a model. + */ +export function getFields(modelMeta: ModelMeta, model: string) { + return modelMeta.fields[lowerCaseFirst(model)]; +} diff --git a/packages/runtime/src/cross/nested-read-visitor.ts b/packages/runtime/src/cross/nested-read-visitor.ts new file mode 100644 index 000000000..1e2e25be8 --- /dev/null +++ b/packages/runtime/src/cross/nested-read-visitor.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { resolveField, type FieldInfo, type ModelMeta } from './model-meta'; + +export type NestedReadVisitorCallback = { + field?: ( + model: string, + field: FieldInfo | undefined, + kind: 'include' | 'select' | undefined, + args: unknown + ) => void | boolean; +}; + +/** + * Visitor for nested read payload. + */ +export class NestedReadVisitor { + constructor(private readonly modelMeta: ModelMeta, private readonly callback: NestedReadVisitorCallback) {} + + doVisit(model: string, field: FieldInfo | undefined, kind: 'include' | 'select' | undefined, args: unknown) { + if (this.callback.field) { + const r = this.callback.field(model, field, kind, args); + if (r === false) { + return; + } + } + + if (!args || typeof args !== 'object') { + return; + } + + let selectInclude: any; + let nextKind: 'select' | 'include' | undefined; + if ((args as any).select) { + selectInclude = (args as any).select; + nextKind = 'select'; + } else if ((args as any).include) { + selectInclude = (args as any).include; + nextKind = 'include'; + } + + if (selectInclude && typeof selectInclude === 'object') { + for (const [k, v] of Object.entries(selectInclude)) { + const field = resolveField(this.modelMeta, model, k); + if (field) { + this.doVisit(field.type, field, nextKind, v); + } + } + } + } + + visit(model: string, args: unknown) { + this.doVisit(model, undefined, undefined, args); + } +} diff --git a/packages/runtime/src/enhancements/nested-write-visitor.ts b/packages/runtime/src/cross/nested-write-visitor.ts similarity index 93% rename from packages/runtime/src/enhancements/nested-write-visitor.ts rename to packages/runtime/src/cross/nested-write-visitor.ts index 667ebf5f2..9b2a9a628 100644 --- a/packages/runtime/src/enhancements/nested-write-visitor.ts +++ b/packages/runtime/src/cross/nested-write-visitor.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { FieldInfo, PrismaWriteActionType, PrismaWriteActions } from '../types'; +import type { FieldInfo, ModelMeta } from './model-meta'; import { resolveField } from './model-meta'; -import { ModelMeta } from './types'; +import { MaybePromise, PrismaWriteActionType, PrismaWriteActions } from './types'; import { enumerate, getModelFields } from './utils'; type NestingPathItem = { field?: FieldInfo; model: string; where: any; unique: boolean }; @@ -34,58 +34,66 @@ export type NestedWriteVisitorContext = { * to let the visitor traverse it instead of its original children. */ export type NestedWriterVisitorCallback = { - create?: (model: string, args: any[], context: NestedWriteVisitorContext) => Promise; + create?: (model: string, args: any[], context: NestedWriteVisitorContext) => MaybePromise; createMany?: ( model: string, args: { data: any; skipDuplicates?: boolean }, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; connectOrCreate?: ( model: string, args: { where: object; create: any }, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; - connect?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; + connect?: ( + model: string, + args: object, + context: NestedWriteVisitorContext + ) => MaybePromise; - disconnect?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; + disconnect?: ( + model: string, + args: object, + context: NestedWriteVisitorContext + ) => MaybePromise; - set?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; + set?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise; - update?: (model: string, args: object, context: NestedWriteVisitorContext) => Promise; + update?: (model: string, args: object, context: NestedWriteVisitorContext) => MaybePromise; updateMany?: ( model: string, args: { where?: object; data: any }, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; upsert?: ( model: string, args: { where: object; create: any; update: any }, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; delete?: ( model: string, args: object | boolean, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; deleteMany?: ( model: string, args: any | object, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; field?: ( field: FieldInfo, action: PrismaWriteActionType, data: any, context: NestedWriteVisitorContext - ) => Promise; + ) => MaybePromise; }; /** diff --git a/packages/runtime/src/cross/types.ts b/packages/runtime/src/cross/types.ts new file mode 100644 index 000000000..614865f14 --- /dev/null +++ b/packages/runtime/src/cross/types.ts @@ -0,0 +1,26 @@ +/** + * Prisma write operation kinds + */ +export const PrismaWriteActions = [ + 'create', + 'createMany', + 'connectOrCreate', + 'update', + 'updateMany', + 'upsert', + 'connect', + 'disconnect', + 'set', + 'delete', + 'deleteMany', +] as const; + +/** + * Prisma write operation kinds + */ +export type PrismaWriteActionType = (typeof PrismaWriteActions)[number]; + +/** + * Maybe promise + */ +export type MaybePromise = T | Promise | PromiseLike; diff --git a/packages/runtime/src/cross/utils.ts b/packages/runtime/src/cross/utils.ts new file mode 100644 index 000000000..6fc23812b --- /dev/null +++ b/packages/runtime/src/cross/utils.ts @@ -0,0 +1,44 @@ +/** + * Gets field names in a data model entity, filtering out internal fields. + */ +export function getModelFields(data: object) { + return data ? Object.keys(data) : []; +} + +/** + * Array or scalar + */ +export type Enumerable = T | Array; + +/** + * Uniformly enumerates an array or scalar. + */ +export function enumerate(x: Enumerable) { + if (x === null || x === undefined) { + return []; + } else if (Array.isArray(x)) { + return x; + } else { + return [x]; + } +} + +/** + * Zip two arrays or scalars. + */ +export function zip(x: Enumerable, y: Enumerable): Array<[T1, T2]> { + if (Array.isArray(x)) { + if (!Array.isArray(y)) { + throw new Error('x and y should be both array or both scalar'); + } + if (x.length !== y.length) { + throw new Error('x and y should have the same length'); + } + return x.map((_, i) => [x[i], y[i]] as [T1, T2]); + } else { + if (Array.isArray(y)) { + throw new Error('x and y should be both array or both scalar'); + } + return [[x, y]]; + } +} diff --git a/packages/runtime/src/enhancements/index.ts b/packages/runtime/src/enhancements/index.ts index df45e34c1..25b150a71 100644 --- a/packages/runtime/src/enhancements/index.ts +++ b/packages/runtime/src/enhancements/index.ts @@ -1,10 +1,9 @@ -export * from './model-meta'; -export * from './nested-write-visitor'; +export * from '../cross'; +export * from './enhance'; export * from './omit'; export * from './password'; export * from './policy'; export * from './preset'; -export * from './enhance'; export * from './types'; export * from './utils'; export * from './where-visitor'; diff --git a/packages/runtime/src/enhancements/model-data-visitor.ts b/packages/runtime/src/enhancements/model-data-visitor.ts index 50a6dfbe3..78c40b75c 100644 --- a/packages/runtime/src/enhancements/model-data-visitor.ts +++ b/packages/runtime/src/enhancements/model-data-visitor.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { resolveField } from './model-meta'; -import { ModelMeta } from './types'; +import { resolveField, type ModelMeta } from '../cross'; /** * Callback for @see ModelDataVisitor. diff --git a/packages/runtime/src/enhancements/model-meta.ts b/packages/runtime/src/enhancements/model-meta.ts deleted file mode 100644 index 626a7f26e..000000000 --- a/packages/runtime/src/enhancements/model-meta.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { lowerCaseFirst } from 'lower-case-first'; -import { FieldInfo } from '../types'; -import { ModelMeta } from './types'; - -/** - * Resolves a model field to its metadata. Returns undefined if not found. - */ -export function resolveField(modelMeta: ModelMeta, model: string, field: string): FieldInfo | undefined { - return modelMeta.fields[lowerCaseFirst(model)]?.[field]; -} - -/** - * Gets all fields of a model. - */ -export function getFields(modelMeta: ModelMeta, model: string) { - return modelMeta.fields[lowerCaseFirst(model)]; -} diff --git a/packages/runtime/src/enhancements/omit.ts b/packages/runtime/src/enhancements/omit.ts index 236151981..654be80fc 100644 --- a/packages/runtime/src/enhancements/omit.ts +++ b/packages/runtime/src/enhancements/omit.ts @@ -1,12 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { enumerate, getModelFields, resolveField, type ModelMeta } from '../cross'; import { getDefaultModelMeta } from '../loader'; import { DbClientContract } from '../types'; -import { resolveField } from './model-meta'; import { DefaultPrismaProxyHandler, makeProxy } from './proxy'; -import { CommonEnhancementOptions, ModelMeta } from './types'; -import { enumerate, getModelFields } from './utils'; +import { CommonEnhancementOptions } from './types'; /** * Options for @see withOmit diff --git a/packages/runtime/src/enhancements/password.ts b/packages/runtime/src/enhancements/password.ts index 154e14c83..34844dfb4 100644 --- a/packages/runtime/src/enhancements/password.ts +++ b/packages/runtime/src/enhancements/password.ts @@ -3,11 +3,11 @@ import { hash } from 'bcryptjs'; import { DEFAULT_PASSWORD_SALT_LENGTH } from '../constants'; +import { NestedWriteVisitor, type ModelMeta, type PrismaWriteActionType } from '../cross'; import { getDefaultModelMeta } from '../loader'; -import { DbClientContract, PrismaWriteActionType } from '../types'; -import { NestedWriteVisitor } from './nested-write-visitor'; +import { DbClientContract } from '../types'; import { DefaultPrismaProxyHandler, PrismaProxyActions, makeProxy } from './proxy'; -import { CommonEnhancementOptions, ModelMeta } from './types'; +import { CommonEnhancementOptions } from './types'; /** * Options for @see withPassword diff --git a/packages/runtime/src/enhancements/policy/handler.ts b/packages/runtime/src/enhancements/policy/handler.ts index cd098706c..5593c9c77 100644 --- a/packages/runtime/src/enhancements/policy/handler.ts +++ b/packages/runtime/src/enhancements/policy/handler.ts @@ -4,13 +4,19 @@ import { lowerCaseFirst } from 'lower-case-first'; import { upperCaseFirst } from 'upper-case-first'; import { fromZodError } from 'zod-validation-error'; import { CrudFailureReason, PRISMA_TX_FLAG } from '../../constants'; -import { AuthUser, DbClientContract, DbOperations, FieldInfo, PolicyOperationKind } from '../../types'; +import { + NestedWriteVisitor, + NestedWriteVisitorContext, + enumerate, + resolveField, + type FieldInfo, + type ModelMeta, +} from '../../cross'; +import { AuthUser, DbClientContract, DbOperations, PolicyOperationKind } from '../../types'; import { ModelDataVisitor } from '../model-data-visitor'; -import { resolveField } from '../model-meta'; -import { NestedWriteVisitor, NestedWriteVisitorContext } from '../nested-write-visitor'; import { PrismaProxyHandler } from '../proxy'; -import type { ModelMeta, PolicyDef, ZodSchemas } from '../types'; -import { enumerate, formatObject, getIdFields, prismaClientValidationError } from '../utils'; +import type { PolicyDef, ZodSchemas } from '../types'; +import { formatObject, getIdFields, prismaClientValidationError } from '../utils'; import { Logger } from './logger'; import { PolicyUtil } from './policy-utils'; import { createDeferredPromise } from './promise'; diff --git a/packages/runtime/src/enhancements/policy/index.ts b/packages/runtime/src/enhancements/policy/index.ts index 5cb49c113..efe6f8f75 100644 --- a/packages/runtime/src/enhancements/policy/index.ts +++ b/packages/runtime/src/enhancements/policy/index.ts @@ -3,11 +3,12 @@ import semver from 'semver'; import { PRISMA_MINIMUM_VERSION } from '../../constants'; +import { ModelMeta } from '../../cross'; import { getDefaultModelMeta, getDefaultPolicy, getDefaultZodSchemas } from '../../loader'; import { AuthUser, DbClientContract } from '../../types'; import { hasAllFields } from '../../validation'; import { makeProxy } from '../proxy'; -import type { CommonEnhancementOptions, ModelMeta, PolicyDef, ZodSchemas } from '../types'; +import type { CommonEnhancementOptions, PolicyDef, ZodSchemas } from '../types'; import { getIdFields } from '../utils'; import { PolicyProxyHandler } from './handler'; diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index 2e72e1de6..cfb6103f3 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -14,20 +14,25 @@ import { PRE_UPDATE_VALUE_SELECTOR, PrismaErrorCode, } from '../../constants'; -import { AuthUser, DbClientContract, DbOperations, FieldInfo, PolicyOperationKind } from '../../types'; -import { getVersion } from '../../version'; -import { getFields, resolveField } from '../model-meta'; -import { NestedWriteVisitorContext } from '../nested-write-visitor'; -import type { InputCheckFunc, ModelMeta, PolicyDef, ReadFieldCheckFunc, ZodSchemas } from '../types'; import { enumerate, + getFields, + getModelFields, + resolveField, + zip, + type FieldInfo, + type ModelMeta, + type NestedWriteVisitorContext, +} from '../../cross'; +import { AuthUser, DbClientContract, DbOperations, PolicyOperationKind } from '../../types'; +import { getVersion } from '../../version'; +import type { InputCheckFunc, PolicyDef, ReadFieldCheckFunc, ZodSchemas } from '../types'; +import { formatObject, getIdFields, - getModelFields, prismaClientKnownRequestError, prismaClientUnknownRequestError, prismaClientValidationError, - zip, } from '../utils'; import { Logger } from './logger'; diff --git a/packages/runtime/src/enhancements/proxy.ts b/packages/runtime/src/enhancements/proxy.ts index 37593a6b6..ab55a2aad 100644 --- a/packages/runtime/src/enhancements/proxy.ts +++ b/packages/runtime/src/enhancements/proxy.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { PRISMA_PROXY_ENHANCER, PRISMA_TX_FLAG } from '../constants'; -import { DbClientContract } from '../types'; +import type { ModelMeta } from '../cross'; +import type { DbClientContract } from '../types'; import { createDeferredPromise } from './policy/promise'; -import { ModelMeta } from './types'; /** * Prisma batch write operation result diff --git a/packages/runtime/src/enhancements/types.ts b/packages/runtime/src/enhancements/types.ts index 6645951db..f9342f455 100644 --- a/packages/runtime/src/enhancements/types.ts +++ b/packages/runtime/src/enhancements/types.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { z } from 'zod'; -import type { DbOperations, FieldInfo, PolicyOperationKind, QueryContext } from '../types'; import { - FIELD_LEVEL_READ_CHECKER_SELECTOR, - PRE_UPDATE_VALUE_SELECTOR, FIELD_LEVEL_READ_CHECKER_PREFIX, + FIELD_LEVEL_READ_CHECKER_SELECTOR, FIELD_LEVEL_UPDATE_GUARD_PREFIX, HAS_FIELD_LEVEL_POLICY_FLAG, + PRE_UPDATE_VALUE_SELECTOR, } from '../constants'; +import type { DbOperations, PolicyOperationKind, QueryContext } from '../types'; /** * Common options for PrismaClient enhancements @@ -19,20 +19,6 @@ export interface CommonEnhancementOptions { loadPath?: string; } -/** - * Metadata for a model-level unique constraint - * e.g.: @@unique([a, b]) - */ -export type UniqueConstraint = { name: string; fields: string[] }; - -/** - * ZModel data model metadata - */ -export type ModelMeta = { - fields: Record>; - uniqueConstraints: Record>; -}; - /** * Function for getting policy guard with a given context */ diff --git a/packages/runtime/src/enhancements/utils.ts b/packages/runtime/src/enhancements/utils.ts index faabed365..ea939f873 100644 --- a/packages/runtime/src/enhancements/utils.ts +++ b/packages/runtime/src/enhancements/utils.ts @@ -3,15 +3,8 @@ import { lowerCaseFirst } from 'lower-case-first'; import path from 'path'; import * as util from 'util'; -import { DbClientContract } from '../types'; -import { ModelMeta } from './types'; - -/** - * Gets field names in a data model entity, filtering out internal fields. - */ -export function getModelFields(data: object) { - return data ? Object.keys(data) : []; -} +import type { ModelMeta } from '../cross'; +import type { DbClientContract } from '../types'; /** * Gets id fields for the given model. @@ -32,44 +25,6 @@ export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound return result; } -/** - * Array or scalar - */ -export type Enumerable = T | Array; - -/** - * Uniformly enumerates an array or scalar. - */ -export function enumerate(x: Enumerable) { - if (x === null || x === undefined) { - return []; - } else if (Array.isArray(x)) { - return x; - } else { - return [x]; - } -} - -/** - * Zip two arrays or scalars. - */ -export function zip(x: Enumerable, y: Enumerable): Array<[T1, T2]> { - if (Array.isArray(x)) { - if (!Array.isArray(y)) { - throw new Error('x and y should be both array or both scalar'); - } - if (x.length !== y.length) { - throw new Error('x and y should have the same length'); - } - return x.map((_, i) => [x[i], y[i]] as [T1, T2]); - } else { - if (Array.isArray(y)) { - throw new Error('x and y should be both array or both scalar'); - } - return [[x, y]]; - } -} - /** * Formats an object for pretty printing. */ diff --git a/packages/runtime/src/enhancements/where-visitor.ts b/packages/runtime/src/enhancements/where-visitor.ts index bb806c29d..17a7ec3bc 100644 --- a/packages/runtime/src/enhancements/where-visitor.ts +++ b/packages/runtime/src/enhancements/where-visitor.ts @@ -1,10 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { FieldInfo } from '../types'; -import { resolveField } from './model-meta'; -import { ModelMeta } from './types'; -import { enumerate } from './utils'; +import { enumerate, resolveField, type FieldInfo, type ModelMeta } from '../cross'; /** * Context for visiting diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 445703991..e143cacfa 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -56,87 +56,6 @@ export type QueryContext = { preValue?: any; }; -export type RuntimeAttribute = { - name: string; - args: Array<{ name?: string; value: unknown }>; -}; - -/** - * Runtime information of a data model field - */ -export type FieldInfo = { - /** - * Field name - */ - name: string; - - /** - * Field type name - */ - type: string; - - /** - * If the field is an ID field or part of a multi-field ID - */ - isId: boolean; - - /** - * If the field type is a data model (or an optional/array of data model) - */ - isDataModel: boolean; - - /** - * If the field is an array - */ - isArray: boolean; - - /** - * If the field is optional - */ - isOptional: boolean; - - /** - * Attributes on the field - */ - attributes: RuntimeAttribute[]; - - /** - * If the field is a relation field, the field name of the reverse side of the relation - */ - backLink?: string; - - /** - * If the field is the owner side of a relation - */ - isRelationOwner: boolean; - - /** - * If the field is a foreign key field - */ - isForeignKey: boolean; - - /** - * Mapping from foreign key field names to relation field names - */ - foreignKeyMapping?: Record; -}; - export type DbClientContract = Record & { $transaction: (action: (tx: Record) => Promise, options?: unknown) => Promise; }; - -export const PrismaWriteActions = [ - 'create', - 'createMany', - 'connectOrCreate', - 'update', - 'updateMany', - 'upsert', - 'connect', - 'disconnect', - 'set', - 'delete', - 'deleteMany', -] as const; - -export type PrismaWriteActionType = (typeof PrismaWriteActions)[number]; diff --git a/packages/runtime/tsconfig.json b/packages/runtime/tsconfig.json index 712e397b6..28708fe42 100644 --- a/packages/runtime/tsconfig.json +++ b/packages/runtime/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "dist" }, "include": ["src/**/*.ts"], - "exclude": ["src/browser"] + "exclude": ["src/browser", "src/cross"] } diff --git a/packages/runtime/tsup.config.ts b/packages/runtime/tsup-browser.config.ts similarity index 100% rename from packages/runtime/tsup.config.ts rename to packages/runtime/tsup-browser.config.ts diff --git a/packages/runtime/tsup-cross.config.ts b/packages/runtime/tsup-cross.config.ts new file mode 100644 index 000000000..2ab45a71b --- /dev/null +++ b/packages/runtime/tsup-cross.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/cross/index.ts'], + outDir: 'dist/cross', + splitting: false, + sourcemap: true, + clean: true, + dts: true, + format: ['cjs', 'esm'], +}); diff --git a/packages/schema/src/plugins/model-meta/index.ts b/packages/schema/src/plugins/model-meta/index.ts index 2c0148751..c143b56b1 100644 --- a/packages/schema/src/plugins/model-meta/index.ts +++ b/packages/schema/src/plugins/model-meta/index.ts @@ -1,35 +1,12 @@ -import { - ArrayExpr, - DataModel, - DataModelField, - isArrayExpr, - isBooleanLiteral, - isDataModel, - isNumberLiteral, - isReferenceExpr, - isStringLiteral, - ReferenceExpr, -} from '@zenstackhq/language/ast'; -import type { RuntimeAttribute } from '@zenstackhq/runtime'; import { createProject, - emitProject, - getAttributeArg, - getAttributeArgs, + generateModelMeta, getDataModels, - getLiteral, - hasAttribute, - isForeignKeyField, - isIdField, PluginError, PluginFunction, - resolved, resolvePath, - saveProject, } from '@zenstackhq/sdk'; -import { lowerCaseFirst } from 'lower-case-first'; import path from 'path'; -import { CodeBlockWriter, VariableDeclarationKind } from 'ts-morph'; import { getDefaultOutputFolder } from '../plugin-utils'; export const name = 'Model Metadata'; @@ -39,20 +16,12 @@ const run: PluginFunction = async (model, options, _dmmf, globalOptions) => { if (!output) { throw new PluginError(options.name, `Unable to determine output path, not running plugin`); } - output = resolvePath(output, options); + output = resolvePath(output, options); + const outFile = path.join(output, 'model-meta.ts'); const dataModels = getDataModels(model); - const project = createProject(); - const sf = project.createSourceFile(path.join(output, 'model-meta.ts'), undefined, { overwrite: true }); - sf.addStatements('/* eslint-disable */'); - sf.addVariableStatement({ - declarationKind: VariableDeclarationKind.Const, - declarations: [{ name: 'metadata', initializer: (writer) => generateModelMetadata(dataModels, writer) }], - }); - sf.addStatements('export default metadata;'); - let shouldCompile = true; if (typeof options.compile === 'boolean') { // explicit override @@ -62,208 +31,7 @@ const run: PluginFunction = async (model, options, _dmmf, globalOptions) => { shouldCompile = globalOptions.compile; } - if (!shouldCompile || options.preserveTsFiles === true) { - // save ts files - await saveProject(project); - } - if (shouldCompile) { - await emitProject(project); - } + await generateModelMeta(project, dataModels, outFile, shouldCompile, options.preserveTsFiles === true); }; -function generateModelMetadata(dataModels: DataModel[], writer: CodeBlockWriter) { - writer.block(() => { - writer.write('fields:'); - writer.block(() => { - for (const model of dataModels) { - writer.write(`${lowerCaseFirst(model.name)}:`); - writer.block(() => { - for (const f of model.fields) { - const backlink = getBackLink(f); - const fkMapping = generateForeignKeyMapping(f); - writer.write(`${f.name}: { - name: "${f.name}", - type: "${ - f.type.reference - ? f.type.reference.$refText - : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - f.type.type! - }", - isId: ${isIdField(f)}, - isDataModel: ${isDataModel(f.type.reference?.ref)}, - isArray: ${f.type.array}, - isOptional: ${f.type.optional}, - attributes: ${JSON.stringify(getFieldAttributes(f))}, - backLink: ${backlink ? "'" + backlink.name + "'" : 'undefined'}, - isRelationOwner: ${isRelationOwner(f, backlink)}, - isForeignKey: ${isForeignKeyField(f)}, - foreignKeyMapping: ${fkMapping ? JSON.stringify(fkMapping) : 'undefined'} - },`); - } - }); - writer.write(','); - } - }); - writer.write(','); - - writer.write('uniqueConstraints:'); - writer.block(() => { - for (const model of dataModels) { - writer.write(`${lowerCaseFirst(model.name)}:`); - writer.block(() => { - for (const constraint of getUniqueConstraints(model)) { - writer.write(`${constraint.name}: { - name: "${constraint.name}", - fields: ${JSON.stringify(constraint.fields)} - },`); - } - }); - writer.write(','); - } - }); - writer.write(','); - }); -} - -function getBackLink(field: DataModelField) { - if (!field.type.reference?.ref || !isDataModel(field.type.reference?.ref)) { - return undefined; - } - - const relName = getRelationName(field); - - const sourceModel = field.$container as DataModel; - const targetModel = field.type.reference.ref as DataModel; - - for (const otherField of targetModel.fields) { - if (otherField.type.reference?.ref === sourceModel) { - if (relName) { - const otherRelName = getRelationName(otherField); - if (relName === otherRelName) { - return otherField; - } - } else { - return otherField; - } - } - } - return undefined; -} - -function getRelationName(field: DataModelField) { - const relAttr = field.attributes.find((attr) => attr.decl.ref?.name === 'relation'); - const relName = relAttr && relAttr.args?.[0] && getLiteral(relAttr.args?.[0].value); - return relName; -} - -function getFieldAttributes(field: DataModelField): RuntimeAttribute[] { - return field.attributes - .map((attr) => { - const args: Array<{ name?: string; value: unknown }> = []; - for (const arg of attr.args) { - if (isNumberLiteral(arg.value)) { - let v = parseInt(arg.value.value); - if (isNaN(v)) { - v = parseFloat(arg.value.value); - } - if (isNaN(v)) { - throw new Error(`Invalid number literal: ${arg.value.value}`); - } - args.push({ name: arg.name, value: v }); - } else if (isStringLiteral(arg.value) || isBooleanLiteral(arg.value)) { - args.push({ name: arg.name, value: arg.value.value }); - } else { - // attributes with non-literal args are skipped - return undefined; - } - } - return { name: resolved(attr.decl).name, args }; - }) - .filter((d): d is RuntimeAttribute => !!d); -} - -function getUniqueConstraints(model: DataModel) { - const constraints: Array<{ name: string; fields: string[] }> = []; - - // model-level constraints - for (const attr of model.attributes.filter( - (attr) => attr.decl.ref?.name === '@@unique' || attr.decl.ref?.name === '@@id' - )) { - const argsMap = getAttributeArgs(attr); - if (argsMap.fields) { - const fieldNames = (argsMap.fields as ArrayExpr).items.map( - (item) => resolved((item as ReferenceExpr).target).name - ); - let constraintName = argsMap.name && getLiteral(argsMap.name); - if (!constraintName) { - // default constraint name is fields concatenated with underscores - constraintName = fieldNames.join('_'); - } - constraints.push({ name: constraintName, fields: fieldNames }); - } - } - - // field-level constraints - for (const field of model.fields) { - if (hasAttribute(field, '@id') || hasAttribute(field, '@unique')) { - constraints.push({ name: field.name, fields: [field.name] }); - } - } - - return constraints; -} - -function isRelationOwner(field: DataModelField, backLink: DataModelField | undefined) { - if (!isDataModel(field.type.reference?.ref)) { - return false; - } - - if (!backLink) { - // CHECKME: can this really happen? - return true; - } - - if (!hasAttribute(field, '@relation') && !hasAttribute(backLink, '@relation')) { - // if neither side has `@relation` attribute, it's an implicit many-to-many relation, - // both sides are owners - return true; - } - - return holdsForeignKey(field); -} - -function holdsForeignKey(field: DataModelField) { - const relation = field.attributes.find((attr) => attr.decl.ref?.name === '@relation'); - if (!relation) { - return false; - } - const fields = getAttributeArg(relation, 'fields'); - return !!fields; -} - -function generateForeignKeyMapping(field: DataModelField) { - const relation = field.attributes.find((attr) => attr.decl.ref?.name === '@relation'); - if (!relation) { - return undefined; - } - const fields = getAttributeArg(relation, 'fields'); - const references = getAttributeArg(relation, 'references'); - if (!isArrayExpr(fields) || !isArrayExpr(references) || fields.items.length !== references.items.length) { - return undefined; - } - - const fieldNames = fields.items.map((item) => (isReferenceExpr(item) ? item.target.$refText : undefined)); - const referenceNames = references.items.map((item) => (isReferenceExpr(item) ? item.target.$refText : undefined)); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result: Record = {}; - referenceNames.forEach((name, i) => { - if (name) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - result[name] = fieldNames[i]!; - } - }); - return result; -} - export default run; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 68ceaf680..4d8b43aaf 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -24,6 +24,7 @@ "@prisma/internals-v5": "npm:@prisma/internals@^5.0.0", "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", + "lower-case-first": "^2.0.2", "prettier": "^2.8.3", "semver": "^7.3.8", "ts-morph": "^16.0.0", diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 05d630ba5..43accf73e 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,7 +1,8 @@ export * from './code-gen'; export * from './constants'; +export { generate as generateModelMeta } from './model-meta-generator'; +export * from './policy'; +export * from './prisma'; export * from './types'; export * from './utils'; -export * from './policy'; export * from './validation'; -export * from './prisma'; diff --git a/packages/sdk/src/model-meta-generator.ts b/packages/sdk/src/model-meta-generator.ts new file mode 100644 index 000000000..e9e41385d --- /dev/null +++ b/packages/sdk/src/model-meta-generator.ts @@ -0,0 +1,245 @@ +import { + ArrayExpr, + DataModel, + DataModelField, + isArrayExpr, + isBooleanLiteral, + isDataModel, + isNumberLiteral, + isReferenceExpr, + isStringLiteral, + ReferenceExpr, +} from '@zenstackhq/language/ast'; +import type { RuntimeAttribute } from '@zenstackhq/runtime'; +import { lowerCaseFirst } from 'lower-case-first'; +import { CodeBlockWriter, Project, VariableDeclarationKind } from 'ts-morph'; +import { + emitProject, + getAttributeArg, + getAttributeArgs, + getLiteral, + hasAttribute, + isForeignKeyField, + isIdField, + resolved, + saveProject, +} from '.'; + +export async function generate( + project: Project, + models: DataModel[], + output: string, + compile: boolean, + preserveTsFiles: boolean +) { + const sf = project.createSourceFile(output, undefined, { overwrite: true }); + sf.addStatements('/* eslint-disable */'); + sf.addVariableStatement({ + declarationKind: VariableDeclarationKind.Const, + declarations: [{ name: 'metadata', initializer: (writer) => generateModelMetadata(models, writer) }], + }); + sf.addStatements('export default metadata;'); + + if (!compile || preserveTsFiles) { + // save ts files + await saveProject(project); + } + if (compile) { + await emitProject(project); + } +} + +function generateModelMetadata(dataModels: DataModel[], writer: CodeBlockWriter) { + writer.block(() => { + writer.write('fields:'); + writer.block(() => { + for (const model of dataModels) { + writer.write(`${lowerCaseFirst(model.name)}:`); + writer.block(() => { + for (const f of model.fields) { + const backlink = getBackLink(f); + const fkMapping = generateForeignKeyMapping(f); + writer.write(`${f.name}: { + name: "${f.name}", + type: "${ + f.type.reference + ? f.type.reference.$refText + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + f.type.type! + }", + isId: ${isIdField(f)}, + isDataModel: ${isDataModel(f.type.reference?.ref)}, + isArray: ${f.type.array}, + isOptional: ${f.type.optional}, + attributes: ${JSON.stringify(getFieldAttributes(f))}, + backLink: ${backlink ? "'" + backlink.name + "'" : 'undefined'}, + isRelationOwner: ${isRelationOwner(f, backlink)}, + isForeignKey: ${isForeignKeyField(f)}, + foreignKeyMapping: ${fkMapping ? JSON.stringify(fkMapping) : 'undefined'} + },`); + } + }); + writer.write(','); + } + }); + writer.write(','); + + writer.write('uniqueConstraints:'); + writer.block(() => { + for (const model of dataModels) { + writer.write(`${lowerCaseFirst(model.name)}:`); + writer.block(() => { + for (const constraint of getUniqueConstraints(model)) { + writer.write(`${constraint.name}: { + name: "${constraint.name}", + fields: ${JSON.stringify(constraint.fields)} + },`); + } + }); + writer.write(','); + } + }); + writer.write(','); + }); +} + +function getBackLink(field: DataModelField) { + if (!field.type.reference?.ref || !isDataModel(field.type.reference?.ref)) { + return undefined; + } + + const relName = getRelationName(field); + + const sourceModel = field.$container as DataModel; + const targetModel = field.type.reference.ref as DataModel; + + for (const otherField of targetModel.fields) { + if (otherField.type.reference?.ref === sourceModel) { + if (relName) { + const otherRelName = getRelationName(otherField); + if (relName === otherRelName) { + return otherField; + } + } else { + return otherField; + } + } + } + return undefined; +} + +function getRelationName(field: DataModelField) { + const relAttr = field.attributes.find((attr) => attr.decl.ref?.name === 'relation'); + const relName = relAttr && relAttr.args?.[0] && getLiteral(relAttr.args?.[0].value); + return relName; +} + +function getFieldAttributes(field: DataModelField): RuntimeAttribute[] { + return field.attributes + .map((attr) => { + const args: Array<{ name?: string; value: unknown }> = []; + for (const arg of attr.args) { + if (isNumberLiteral(arg.value)) { + let v = parseInt(arg.value.value); + if (isNaN(v)) { + v = parseFloat(arg.value.value); + } + if (isNaN(v)) { + throw new Error(`Invalid number literal: ${arg.value.value}`); + } + args.push({ name: arg.name, value: v }); + } else if (isStringLiteral(arg.value) || isBooleanLiteral(arg.value)) { + args.push({ name: arg.name, value: arg.value.value }); + } else { + // attributes with non-literal args are skipped + return undefined; + } + } + return { name: resolved(attr.decl).name, args }; + }) + .filter((d): d is RuntimeAttribute => !!d); +} + +function getUniqueConstraints(model: DataModel) { + const constraints: Array<{ name: string; fields: string[] }> = []; + + // model-level constraints + for (const attr of model.attributes.filter( + (attr) => attr.decl.ref?.name === '@@unique' || attr.decl.ref?.name === '@@id' + )) { + const argsMap = getAttributeArgs(attr); + if (argsMap.fields) { + const fieldNames = (argsMap.fields as ArrayExpr).items.map( + (item) => resolved((item as ReferenceExpr).target).name + ); + let constraintName = argsMap.name && getLiteral(argsMap.name); + if (!constraintName) { + // default constraint name is fields concatenated with underscores + constraintName = fieldNames.join('_'); + } + constraints.push({ name: constraintName, fields: fieldNames }); + } + } + + // field-level constraints + for (const field of model.fields) { + if (hasAttribute(field, '@id') || hasAttribute(field, '@unique')) { + constraints.push({ name: field.name, fields: [field.name] }); + } + } + + return constraints; +} + +function isRelationOwner(field: DataModelField, backLink: DataModelField | undefined) { + if (!isDataModel(field.type.reference?.ref)) { + return false; + } + + if (!backLink) { + // CHECKME: can this really happen? + return true; + } + + if (!hasAttribute(field, '@relation') && !hasAttribute(backLink, '@relation')) { + // if neither side has `@relation` attribute, it's an implicit many-to-many relation, + // both sides are owners + return true; + } + + return holdsForeignKey(field); +} + +function holdsForeignKey(field: DataModelField) { + const relation = field.attributes.find((attr) => attr.decl.ref?.name === '@relation'); + if (!relation) { + return false; + } + const fields = getAttributeArg(relation, 'fields'); + return !!fields; +} + +function generateForeignKeyMapping(field: DataModelField) { + const relation = field.attributes.find((attr) => attr.decl.ref?.name === '@relation'); + if (!relation) { + return undefined; + } + const fields = getAttributeArg(relation, 'fields'); + const references = getAttributeArg(relation, 'references'); + if (!isArrayExpr(fields) || !isArrayExpr(references) || fields.items.length !== references.items.length) { + return undefined; + } + + const fieldNames = fields.items.map((item) => (isReferenceExpr(item) ? item.target.$refText : undefined)); + const referenceNames = references.items.map((item) => (isReferenceExpr(item) ? item.target.$refText : undefined)); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: Record = {}; + referenceNames.forEach((name, i) => { + if (name) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + result[name] = fieldNames[i]!; + } + }); + return result; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 356c1db1d..22657bf2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -248,7 +248,7 @@ importers: version: 4.29.7(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query-v5': specifier: npm:@tanstack/react-query@^5.0.0 - version: /@tanstack/react-query@5.0.5(react@18.2.0) + version: /@tanstack/react-query@5.0.5(react-dom@18.2.0)(react@18.2.0) '@tanstack/svelte-query': specifier: ^4.29.7 version: 4.29.7(svelte@4.2.1) @@ -258,9 +258,15 @@ importers: '@tanstack/vue-query': specifier: ^4.37.0 version: 4.37.0(vue@3.3.4) + '@testing-library/react': + specifier: ^14.0.0 + version: 14.0.0(react-dom@18.2.0)(react@18.2.0) '@types/jest': specifier: ^29.5.0 version: 29.5.0 + '@types/nock': + specifier: ^11.1.0 + version: 11.1.0 '@types/node': specifier: ^18.0.0 version: 18.0.0 @@ -280,11 +286,20 @@ importers: specifier: ^2.4.1 version: 2.4.1 jest: - specifier: ^29.5.0 - version: 29.5.0(@types/node@18.0.0)(ts-node@10.9.1) + specifier: ^29.7.0 + version: 29.7.0(@types/node@18.0.0) + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0 + nock: + specifier: ^13.3.6 + version: 13.3.6 react: specifier: 18.2.0 version: 18.2.0 + react-test-renderer: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) replace-in-file: specifier: ^7.0.1 version: 7.0.1 @@ -299,7 +314,7 @@ importers: version: 2.0.3(react@18.2.0) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4) + version: 29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.7.0)(typescript@4.9.4) typescript: specifier: ^4.9.4 version: 4.9.4 @@ -655,6 +670,9 @@ importers: '@zenstackhq/runtime': specifier: workspace:* version: link:../runtime/dist + lower-case-first: + specifier: ^2.0.2 + version: 2.0.2 prettier: specifier: ^2.8.3 version: 2.8.3 @@ -974,17 +992,12 @@ packages: dependencies: '@babel/highlight': 7.22.20 chalk: 2.4.2 - dev: true /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.22.5 - - /@babel/compat-data@7.22.5: - resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==} - engines: {node: '>=6.9.0'} dev: true /@babel/compat-data@7.22.9: @@ -992,29 +1005,6 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/core@7.22.5: - resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.5 - '@babel/generator': 7.22.5 - '@babel/helper-compilation-targets': 7.22.5(@babel/core@7.22.5) - '@babel/helper-module-transforms': 7.22.5 - '@babel/helpers': 7.22.5 - '@babel/parser': 7.23.0 - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 - '@babel/types': 7.22.5 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core@7.23.2: resolution: {integrity: sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==} engines: {node: '>=6.9.0'} @@ -1048,16 +1038,6 @@ packages: jsesc: 2.5.2 dev: true - /@babel/generator@7.22.9: - resolution: {integrity: sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.0 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.23.0: resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} engines: {node: '>=6.9.0'} @@ -1086,20 +1066,6 @@ packages: semver: 6.3.1 dev: true - /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.22.5 - '@babel/core': 7.22.5 - '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 - lru-cache: 5.1.1 - semver: 6.3.1 - dev: true - /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.2): resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} engines: {node: '>=6.9.0'} @@ -1172,22 +1138,6 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/helper-module-transforms@7.22.5: - resolution: {integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-module-imports': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.5 - '@babel/helper-validator-identifier': 7.22.20 - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.2 - '@babel/types': 7.23.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-transforms@7.23.0(@babel/core@7.23.2): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} engines: {node: '>=6.9.0'} @@ -1262,33 +1212,17 @@ packages: /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier@7.22.5: resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-option@7.22.5: - resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helpers@7.22.5: - resolution: {integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.2 - '@babel/types': 7.23.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helpers@7.23.2: resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} engines: {node: '>=6.9.0'} @@ -1307,7 +1241,6 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/highlight@7.22.5: resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} @@ -1316,6 +1249,7 @@ packages: '@babel/helper-validator-identifier': 7.22.5 chalk: 2.4.2 js-tokens: 4.0.0 + dev: true /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} @@ -1325,58 +1259,48 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.5): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.2): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.5): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.2): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - - /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -1390,77 +1314,67 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.5): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.2): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.5): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.2): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.5): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.2): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - dev: true - - /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.5): - resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -1508,21 +1422,12 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/template@7.22.5: - resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/parser': 7.23.0 - '@babel/types': 7.23.0 - dev: true - /@babel/traverse@7.22.5: resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.22.5 - '@babel/generator': 7.22.5 + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.0 '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-function-name': 7.22.5 '@babel/helper-hoist-variables': 7.22.5 @@ -1535,24 +1440,6 @@ packages: - supports-color dev: true - /@babel/traverse@7.22.8: - resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.5 - '@babel/generator': 7.22.9 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.0 - '@babel/types': 7.23.0 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/traverse@7.23.2: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} @@ -2754,11 +2641,23 @@ packages: resolution: {integrity: sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 chalk: 4.1.2 - jest-message-util: 29.5.0 - jest-util: 29.5.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 slash: 3.0.0 dev: true @@ -2775,7 +2674,7 @@ packages: '@jest/reporters': 29.5.0 '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -2804,14 +2703,67 @@ packages: - ts-node dev: true + /@jest/core@29.7.0: + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@18.0.0) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /@jest/environment@29.5.0: resolution: {integrity: sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/fake-timers': 29.5.0 - '@jest/types': 29.5.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 - jest-mock: 29.5.0 + jest-mock: 29.7.0 + dev: true + + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + jest-mock: 29.7.0 dev: true /@jest/expect-utils@29.5.0: @@ -2821,6 +2773,13 @@ packages: jest-get-type: 29.4.3 dev: true + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + dev: true + /@jest/expect@29.5.0: resolution: {integrity: sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2831,26 +2790,60 @@ packages: - supports-color dev: true + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/fake-timers@29.5.0: resolution: {integrity: sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 '@types/node': 18.0.0 - jest-message-util: 29.5.0 - jest-mock: 29.5.0 - jest-util: 29.5.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 18.0.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 dev: true /@jest/globals@29.5.0: resolution: {integrity: sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.5.0 + '@jest/environment': 29.7.0 '@jest/expect': 29.5.0 - '@jest/types': 29.5.0 - jest-mock: 29.5.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 transitivePeerDependencies: - supports-color dev: true @@ -2868,7 +2861,7 @@ packages: '@jest/console': 29.5.0 '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 '@types/node': 18.0.0 chalk: 4.1.2 @@ -2881,8 +2874,8 @@ packages: istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 - jest-message-util: 29.5.0 - jest-util: 29.5.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 jest-worker: 29.5.0 slash: 3.0.0 string-length: 4.0.2 @@ -2892,6 +2885,43 @@ packages: - supports-color dev: true + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.18 + '@types/node': 18.0.0 + chalk: 4.1.2 + collect-v8-coverage: 1.0.1 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 6.0.1 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.5 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/schemas@29.4.3: resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2899,6 +2929,13 @@ packages: '@sinclair/typebox': 0.25.24 dev: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jest/source-map@29.4.3: resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2908,12 +2945,31 @@ packages: graceful-fs: 4.2.11 dev: true + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.18 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + /@jest/test-result@29.5.0: resolution: {integrity: sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + dev: true + + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 dev: true @@ -2928,12 +2984,22 @@ packages: slash: 3.0.0 dev: true + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + dev: true + /@jest/transform@29.5.0: resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.5 - '@jest/types': 29.5.0 + '@babel/core': 7.23.2 + '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 @@ -2942,7 +3008,7 @@ packages: graceful-fs: 4.2.11 jest-haste-map: 29.5.0 jest-regex-util: 29.4.3 - jest-util: 29.5.0 + jest-util: 29.7.0 micromatch: 4.0.5 pirates: 4.0.6 slash: 3.0.0 @@ -2951,7 +3017,30 @@ packages: - supports-color dev: true - /@jest/types@29.5.0: + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.23.2 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.18 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@29.5.0: resolution: {integrity: sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: @@ -2963,6 +3052,18 @@ packages: chalk: 4.1.2 dev: true + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.0.0 + '@types/yargs': 17.0.24 + chalk: 4.1.2 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -4165,6 +4266,10 @@ packages: resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@sinonjs/commons@3.0.0: resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} dependencies: @@ -4310,7 +4415,7 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: true - /@tanstack/react-query@5.0.5(react@18.2.0): + /@tanstack/react-query@5.0.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZG0Q4HZ0iuI8mWiZ2/MdVYPHbrmAVhMn7+gLOkxJh6zLIgCL4luSZlohzN5Xt4MjxfxxWioO1nemwpudaTsmQg==} peerDependencies: react: ^18.0.0 @@ -4324,6 +4429,7 @@ packages: dependencies: '@tanstack/query-core': 5.0.5 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: true /@tanstack/svelte-query@4.29.7(svelte@4.2.1): @@ -4360,6 +4466,39 @@ packages: vue-demi: 0.13.11(vue@3.3.4) dev: true + /@testing-library/dom@9.3.3: + resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} + engines: {node: '>=14'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/runtime': 7.22.5 + '@types/aria-query': 5.0.3 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + + /@testing-library/react@14.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==} + engines: {node: '>=14'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.22.5 + '@testing-library/dom': 9.3.3 + '@types/react-dom': 18.2.14 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + /@trpc/client@10.32.0(@trpc/server@10.32.0): resolution: {integrity: sha512-hrj6XV84nE6DH5AmsPJ0JzIRcx3f6zU0tvS705DnZtNBOoMk7Lx+wX+KCDxj4DxLsTXC+roAxZxWwAB6/LGsXA==} peerDependencies: @@ -4439,6 +4578,10 @@ packages: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} dev: true + /@types/aria-query@5.0.3: + resolution: {integrity: sha512-0Z6Tr7wjKJIk4OUEjVUQMtyunLDy339vcMaj38Kpj6jM2OE1p3S4kXExKZ7a3uXQAPCoy3sbrP1wibDKaf39oA==} + dev: true + /@types/async-exit-hook@2.0.0: resolution: {integrity: sha512-RNjIyjnVZdcP5a1zeIPb5c0hq2nbJc/NOCLNKUAqeCw+J5z2zMcINISn9wybCWhczHnUu3VSUFy7ZCO6ir4ZRw==} dev: true @@ -4592,6 +4735,14 @@ packages: pretty-format: 29.5.0 dev: true + /@types/jsdom@20.0.1: + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + dependencies: + '@types/node': 18.0.0 + '@types/tough-cookie': 4.0.4 + parse5: 7.1.2 + dev: true + /@types/json-schema@7.0.12: resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true @@ -4618,6 +4769,15 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false + /@types/nock@11.1.0: + resolution: {integrity: sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==} + deprecated: This is a stub types definition. nock provides its own type definitions, so you do not need this installed. + dependencies: + nock: 13.3.6 + transitivePeerDependencies: + - supports-color + dev: true + /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true @@ -4660,6 +4820,12 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true + /@types/react-dom@18.2.14: + resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==} + dependencies: + '@types/react': 18.2.0 + dev: true + /@types/react@18.2.0: resolution: {integrity: sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==} dependencies: @@ -4732,6 +4898,10 @@ packages: resolution: {integrity: sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==} dev: true + /@types/tough-cookie@4.0.4: + resolution: {integrity: sha512-95Sfz4nvMAb0Nl9DTxN3j64adfwfbBPEYq14VN7zT5J5O2M9V6iZMIIQU1U+pJyl9agHYHNCqhCXgyEtIRRa5A==} + dev: true + /@types/uuid@8.3.4: resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} dev: true @@ -5291,8 +5461,8 @@ packages: '@babel/core': 7.23.2 '@babel/helper-module-imports': 7.22.5 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.2) - '@babel/template': 7.22.5 - '@babel/traverse': 7.22.8 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 '@babel/types': 7.23.0 '@vue/babel-helper-vue-transform-on': 1.1.5 camelcase: 6.3.0 @@ -5389,6 +5559,10 @@ packages: resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} dev: true + /abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + dev: true + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true @@ -5412,6 +5586,13 @@ packages: negotiator: 0.6.3 dev: true + /acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + dependencies: + acorn: 8.10.0 + acorn-walk: 8.2.0 + dev: true + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -5647,6 +5828,12 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + dependencies: + deep-equal: 2.2.2 + dev: true + /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -5812,17 +5999,35 @@ packages: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} dev: true - /babel-jest@29.5.0(@babel/core@7.22.5): + /babel-jest@29.5.0(@babel/core@7.23.2): resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@jest/transform': 29.5.0 '@types/babel__core': 7.20.1 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.5.0(@babel/core@7.22.5) + babel-preset-jest: 29.5.0(@babel/core@7.23.2) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-jest@29.7.0(@babel/core@7.23.2): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.23.2 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.1 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.23.2) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -5853,35 +6058,56 @@ packages: '@types/babel__traverse': 7.20.1 dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.5): + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 + '@types/babel__core': 7.20.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.2): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.5) - dev: true - - /babel-preset-jest@29.5.0(@babel/core@7.22.5): + '@babel/core': 7.23.2 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.2) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.2) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.2) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.2) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.2) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.2) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.2) + dev: true + + /babel-preset-jest@29.5.0(@babel/core@7.23.2): resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 babel-plugin-jest-hoist: 29.5.0 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.23.2): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.2 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) dev: true /balanced-match@1.0.2: @@ -6679,6 +6905,25 @@ packages: readable-stream: 3.6.2 dev: true + /create-jest@29.7.0(@types/node@18.0.0): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@18.0.0) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true @@ -6831,6 +7076,21 @@ packages: css-tree: 2.2.1 dev: true + /cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + dev: true + + /cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true + + /cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + dependencies: + cssom: 0.3.8 + dev: true + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: true @@ -6866,6 +7126,15 @@ packages: engines: {node: '>=12.17'} dev: true + /data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + dev: true + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -6931,6 +7200,15 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true + /dedent@1.5.1: + resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} @@ -6938,6 +7216,29 @@ packages: type-detect: 4.0.8 dev: true + /deep-equal@2.2.2: + resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.1 + is-arguments: 1.1.1 + is-array-buffer: 3.0.2 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.9 + dev: true + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -7064,6 +7365,11 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -7087,6 +7393,10 @@ packages: esutils: 2.0.3 dev: true + /dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dev: true + /dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dependencies: @@ -7099,6 +7409,13 @@ packages: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} dev: true + /domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + dependencies: + webidl-conversions: 7.0.0 + dev: true + /domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} @@ -7270,6 +7587,20 @@ packages: which-typed-array: 1.1.9 dev: true + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + dev: true + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -7651,6 +7982,18 @@ packages: engines: {node: '>=12'} dev: true + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + /eslint-plugin-jest@27.1.7(@typescript-eslint/eslint-plugin@5.42.0)(eslint@8.27.0)(jest@29.5.0)(typescript@4.8.4): resolution: {integrity: sha512-0QVzf+og4YI1Qr3UoprkqqhezAZjFffdi62b0IurkCXMqPtRW84/UT4CKsYT80h/D82LA9avjO/80Ou1LdgbaQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8020,6 +8363,17 @@ packages: jest-util: 29.5.0 dev: true + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + dev: true + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -8804,6 +9158,13 @@ packages: lru-cache: 6.0.0 dev: true + /html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + dependencies: + whatwg-encoding: 2.0.0 + dev: true + /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true @@ -8833,6 +9194,17 @@ packages: toidentifier: 1.0.1 dev: true + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /http-proxy-agent@7.0.0: resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} engines: {node: '>= 14'} @@ -8857,6 +9229,16 @@ packages: transitivePeerDependencies: - supports-color + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /https-proxy-agent@7.0.0: resolution: {integrity: sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==} engines: {node: '>= 14'} @@ -8901,6 +9283,13 @@ packages: safer-buffer: 2.1.2 dev: true + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -9000,6 +9389,14 @@ packages: resolution: {integrity: sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==} dev: true + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -9103,6 +9500,10 @@ packages: engines: {node: '>=8'} dev: false + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + /is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true @@ -9142,6 +9543,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + dev: true + /is-primitive@3.0.1: resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} engines: {node: '>=0.10.0'} @@ -9176,6 +9581,10 @@ packages: engines: {node: '>=10'} dev: false + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -9234,12 +9643,23 @@ packages: engines: {node: '>=10'} dev: false + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 dev: true + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + /is-what@4.1.15: resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==} engines: {node: '>=12.13'} @@ -9262,6 +9682,10 @@ packages: /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -9283,7 +9707,7 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/parser': 7.23.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 @@ -9292,6 +9716,19 @@ packages: - supports-color dev: true + /istanbul-lib-instrument@6.0.1: + resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.23.2 + '@babel/parser': 7.23.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-lib-report@3.0.0: resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} engines: {node: '>=8'} @@ -9328,14 +9765,23 @@ packages: p-limit: 3.1.0 dev: true + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + dev: true + /jest-circus@29.5.0: resolution: {integrity: sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.5.0 + '@jest/environment': 29.7.0 '@jest/expect': 29.5.0 '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 chalk: 4.1.2 co: 4.6.0 @@ -9343,12 +9789,12 @@ packages: is-generator-fn: 2.1.0 jest-each: 29.5.0 jest-matcher-utils: 29.5.0 - jest-message-util: 29.5.0 + jest-message-util: 29.7.0 jest-runtime: 29.5.0 jest-snapshot: 29.5.0 - jest-util: 29.5.0 + jest-util: 29.7.0 p-limit: 3.1.0 - pretty-format: 29.5.0 + pretty-format: 29.7.0 pure-rand: 6.0.2 slash: 3.0.0 stack-utils: 2.0.6 @@ -9356,6 +9802,35 @@ packages: - supports-color dev: true + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.0.2 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-cli@29.5.0(@types/node@18.0.0)(ts-node@10.9.1): resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9368,7 +9843,7 @@ packages: dependencies: '@jest/core': 29.5.0(ts-node@10.9.1) '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 @@ -9384,6 +9859,34 @@ packages: - ts-node dev: true + /jest-cli@29.7.0(@types/node@18.0.0): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@18.0.0) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@18.0.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-config@29.5.0(@types/node@18.0.0)(ts-node@10.9.1): resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9396,11 +9899,11 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@jest/test-sequencer': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 - babel-jest: 29.5.0(@babel/core@7.22.5) + babel-jest: 29.5.0(@babel/core@7.23.2) chalk: 4.1.2 ci-info: 3.8.0 deepmerge: 4.3.1 @@ -9412,11 +9915,11 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.5.0 jest-runner: 29.5.0 - jest-util: 29.5.0 + jest-util: 29.7.0 jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.5.0 + pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 ts-node: 10.9.1(@types/node@18.0.0)(typescript@4.8.4) @@ -9424,6 +9927,46 @@ packages: - supports-color dev: true + /jest-config@29.7.0(@types/node@18.0.0): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.23.2 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + babel-jest: 29.7.0(@babel/core@7.23.2) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@29.5.0: resolution: {integrity: sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9431,7 +9974,17 @@ packages: chalk: 4.1.2 diff-sequences: 29.4.3 jest-get-type: 29.4.3 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + dev: true + + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true /jest-docblock@29.4.3: @@ -9441,27 +9994,80 @@ packages: detect-newline: 3.1.0 dev: true + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + /jest-each@29.5.0: resolution: {integrity: sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.4.3 - jest-util: 29.5.0 - pretty-format: 29.5.0 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 18.0.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate dev: true /jest-environment-node@29.5.0: resolution: {integrity: sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.5.0 - '@jest/fake-timers': 29.5.0 - '@jest/types': 29.5.0 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 - jest-mock: 29.5.0 - jest-util: 29.5.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 dev: true /jest-fetch-mock@3.0.3: @@ -9478,18 +10084,23 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /jest-haste-map@29.5.0: resolution: {integrity: sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 '@types/node': 18.0.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 29.4.3 - jest-util: 29.5.0 + jest-util: 29.7.0 jest-worker: 29.5.0 micromatch: 4.0.5 walker: 1.0.8 @@ -9497,12 +10108,39 @@ packages: fsevents: 2.3.3 dev: true + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.6 + '@types/node': 18.0.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /jest-leak-detector@29.5.0: resolution: {integrity: sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.4.3 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + dev: true + + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true /jest-matcher-utils@29.5.0: @@ -9512,7 +10150,17 @@ packages: chalk: 4.1.2 jest-diff: 29.5.0 jest-get-type: 29.4.3 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + dev: true + + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true /jest-message-util@29.5.0: @@ -9520,12 +10168,27 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.22.5 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.5 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.22.13 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 dev: true @@ -9534,9 +10197,18 @@ packages: resolution: {integrity: sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 - jest-util: 29.5.0 + jest-util: 29.7.0 + dev: true + + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + jest-util: 29.7.0 dev: true /jest-pnp-resolver@1.2.3(jest-resolve@29.5.0): @@ -9551,17 +10223,44 @@ packages: jest-resolve: 29.5.0 dev: true + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.7.0 + dev: true + /jest-regex-util@29.4.3: resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /jest-resolve-dependencies@29.5.0: - resolution: {integrity: sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==} + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-resolve-dependencies@29.5.0: + resolution: {integrity: sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.4.3 + jest-snapshot: 29.5.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - jest-regex-util: 29.4.3 - jest-snapshot: 29.5.0 + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color dev: true @@ -9574,13 +10273,28 @@ packages: graceful-fs: 4.2.11 jest-haste-map: 29.5.0 jest-pnp-resolver: 1.2.3(jest-resolve@29.5.0) - jest-util: 29.5.0 + jest-util: 29.7.0 jest-validate: 29.5.0 resolve: 1.22.2 resolve.exports: 2.0.2 slash: 3.0.0 dev: true + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.2 + resolve.exports: 2.0.2 + slash: 3.0.0 + dev: true + /jest-runner@29.5.0: resolution: {integrity: sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9589,7 +10303,7 @@ packages: '@jest/environment': 29.5.0 '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 chalk: 4.1.2 emittery: 0.13.1 @@ -9598,10 +10312,10 @@ packages: jest-environment-node: 29.5.0 jest-haste-map: 29.5.0 jest-leak-detector: 29.5.0 - jest-message-util: 29.5.0 + jest-message-util: 29.7.0 jest-resolve: 29.5.0 jest-runtime: 29.5.0 - jest-util: 29.5.0 + jest-util: 29.7.0 jest-watcher: 29.5.0 jest-worker: 29.5.0 p-limit: 3.1.0 @@ -9610,6 +10324,35 @@ packages: - supports-color dev: true + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + /jest-runtime@29.5.0: resolution: {integrity: sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9620,7 +10363,7 @@ packages: '@jest/source-map': 29.4.3 '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 chalk: 4.1.2 cjs-module-lexer: 1.2.3 @@ -9628,12 +10371,42 @@ packages: glob: 7.2.3 graceful-fs: 4.2.11 jest-haste-map: 29.5.0 - jest-message-util: 29.5.0 + jest-message-util: 29.7.0 jest-mock: 29.5.0 jest-regex-util: 29.4.3 jest-resolve: 29.5.0 jest-snapshot: 29.5.0 - jest-util: 29.5.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: @@ -9644,28 +10417,56 @@ packages: resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 '@babel/generator': 7.22.5 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.5) - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.5) + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.2) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.23.2) '@babel/traverse': 7.22.5 '@babel/types': 7.22.5 '@jest/expect-utils': 29.5.0 '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/babel__traverse': 7.20.1 '@types/prettier': 2.7.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) chalk: 4.1.2 expect: 29.5.0 graceful-fs: 4.2.11 jest-diff: 29.5.0 jest-get-type: 29.4.3 jest-matcher-utils: 29.5.0 - jest-message-util: 29.5.0 - jest-util: 29.5.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 natural-compare: 1.4.0 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.23.2 + '@babel/generator': 7.23.0 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.2) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.23.2) + '@babel/types': 7.23.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.2) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 semver: 7.5.4 transitivePeerDependencies: - supports-color @@ -9683,16 +10484,40 @@ packages: picomatch: 2.3.1 dev: true + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: true + /jest-validate@29.5.0: resolution: {integrity: sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 29.4.3 leven: 3.1.0 - pretty-format: 29.5.0 + pretty-format: 29.7.0 + dev: true + + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 dev: true /jest-watcher@29.5.0: @@ -9700,12 +10525,26 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 '@types/node': 18.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 29.5.0 + jest-util: 29.7.0 + string-length: 4.0.2 + dev: true + + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 string-length: 4.0.2 dev: true @@ -9714,7 +10553,17 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@types/node': 18.0.0 - jest-util: 29.5.0 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 18.0.0 + jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -9739,6 +10588,27 @@ packages: - ts-node dev: true + /jest@29.7.0(@types/node@18.0.0): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@18.0.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jiti@1.20.0: resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} hasBin: true @@ -9771,6 +10641,47 @@ packages: argparse: 2.0.1 dev: true + /jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + dependencies: + abab: 2.0.6 + acorn: 8.10.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.2 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.0 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.7 + parse5: 7.1.2 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.14.2 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -9792,6 +10703,10 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + /json-to-ast@2.1.0: resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==} engines: {node: '>= 4'} @@ -10107,6 +11022,11 @@ packages: dependencies: yallist: 4.0.0 + /lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: true + /magic-string-ast@0.3.0: resolution: {integrity: sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==} engines: {node: '>=16.14.0'} @@ -10686,6 +11606,17 @@ packages: tslib: 2.6.0 dev: false + /nock@13.3.6: + resolution: {integrity: sha512-lT6YuktKroUFM+27mubf2uqQZVy2Jf+pfGzuh9N6VwdHlFoZqvi4zyxFTVR1w/ChPqGY6yxGehHp6C3wqCASCw==} + engines: {node: '>= 10.13'} + dependencies: + debug: 4.3.4 + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: true + /node-abi@3.45.0: resolution: {integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==} engines: {node: '>=10'} @@ -10949,6 +11880,10 @@ packages: - xml2js dev: true + /nwsapi@2.2.7: + resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} + dev: true + /nypm@0.3.3: resolution: {integrity: sha512-FHoxtTscAE723e80d2M9cJRb4YVjL82Ra+ZV+YqC6rfNZUWahi+ZhPF+krnR+bdMvibsfHCtgKXnZf5R6kmEPA==} engines: {node: ^14.16.0 || >=16.10.0} @@ -10969,6 +11904,14 @@ packages: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} dev: true + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + dev: true + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -11196,7 +12139,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -11949,6 +12892,15 @@ packages: react-is: 18.2.0 dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /printj@1.3.1: resolution: {integrity: sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==} engines: {node: '>=0.8'} @@ -12005,6 +12957,11 @@ packages: kleur: 3.0.3 sisteransi: 1.0.5 + /propagate@2.0.1: + resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} + engines: {node: '>= 8'} + dev: true + /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} dev: false @@ -12029,6 +12986,10 @@ packages: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: true + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} requiresBuild: true @@ -12061,6 +13022,10 @@ packages: side-channel: 1.0.4 dev: true + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-lit@1.5.0: resolution: {integrity: sha512-IslToJ4eiCEE9xwMzq3viOO5nH8sUWUCwoElrhNMozzr9IIt2qqvB4I+uHu/zJTQVqc9R5DFwok4ijNK1pU3fA==} dev: true @@ -12151,6 +13116,16 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /react-shallow-renderer@16.15.0(react@18.2.0): + resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + object-assign: 4.1.1 + react: 18.2.0 + react-is: 18.2.0 + dev: true + /react-ssr-prepass@1.5.0(react@18.2.0): resolution: {integrity: sha512-yFNHrlVEReVYKsLI5lF05tZoHveA5pGzjFbFJY/3pOqqjGOmMmqx83N4hIjN2n6E1AOa+eQEUxs3CgRnPmT0RQ==} peerDependencies: @@ -12159,6 +13134,17 @@ packages: react: 18.2.0 dev: true + /react-test-renderer@18.2.0(react@18.2.0): + resolution: {integrity: sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==} + peerDependencies: + react: ^18.2.0 + dependencies: + react: 18.2.0 + react-is: 18.2.0 + react-shallow-renderer: 16.15.0(react@18.2.0) + scheduler: 0.23.0 + dev: true + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -12373,6 +13359,10 @@ packages: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -12519,6 +13509,13 @@ packages: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + dependencies: + xmlchars: 2.2.0 + dev: true + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -12868,6 +13865,13 @@ packages: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: true + /stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.5 + dev: true + /stream-read-all@3.0.1: resolution: {integrity: sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A==} engines: {node: '>=10'} @@ -13205,6 +14209,10 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: true + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + dev: true + /table-layout@1.0.2: resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==} engines: {node: '>=8.0.0'} @@ -13420,6 +14428,16 @@ packages: engines: {node: '>=6'} dev: true + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.0 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -13429,6 +14447,13 @@ packages: punycode: 2.3.0 dev: true + /tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.3.0 + dev: true + /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -13518,7 +14543,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.0.5(@babel/core@7.22.5)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4): + /ts-jest@29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4): resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13539,7 +14564,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.23.2 bs-logger: 0.2.6 esbuild: 0.18.13 fast-json-stable-stringify: 2.1.0 @@ -13553,7 +14578,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.4): + /ts-jest@29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.5): resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13584,11 +14609,11 @@ packages: lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.5.3 - typescript: 4.9.4 + typescript: 4.9.5 yargs-parser: 21.1.1 dev: true - /ts-jest@29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.5.0)(typescript@4.9.5): + /ts-jest@29.0.5(@babel/core@7.23.2)(esbuild@0.18.13)(jest@29.7.0)(typescript@4.9.4): resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -13613,13 +14638,13 @@ packages: bs-logger: 0.2.6 esbuild: 0.18.13 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@18.0.0)(ts-node@10.9.1) + jest: 29.7.0(@types/node@18.0.0) jest-util: 29.5.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.5.3 - typescript: 4.9.5 + typescript: 4.9.4 yargs-parser: 21.1.1 dev: true @@ -14021,6 +15046,11 @@ packages: engines: {node: '>= 4.0.0'} dev: true + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} @@ -14196,6 +15226,13 @@ packages: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /url-pattern@1.0.3: resolution: {integrity: sha512-uQcEj/2puA4aq1R3A2+VNVBgaWYR24FdWjl7VNW83rnWftlhyzOZ/tBjezRiC2UkIzuxC8Top3IekN3vUf1WxA==} engines: {node: '>=0.12.0'} @@ -14329,7 +15366,7 @@ packages: vue-tsc: optional: true dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.22.13 ansi-escapes: 4.3.2 chalk: 4.1.2 chokidar: 3.5.3 @@ -14609,6 +15646,13 @@ packages: '@vue/shared': 3.3.4 dev: true + /w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -14635,6 +15679,11 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: true + /webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} @@ -14644,10 +15693,30 @@ packages: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} dev: true + /whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + dependencies: + iconv-lite: 0.6.3 + dev: true + /whatwg-fetch@3.6.2: resolution: {integrity: sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==} dev: true + /whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + dev: true + + /whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + dev: true + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -14676,6 +15745,15 @@ packages: is-symbol: 1.0.4 dev: true + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true @@ -14766,6 +15844,24 @@ packages: signal-exit: 3.0.7 dev: true + /ws@8.14.2: + resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} + 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 + dev: true + + /xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: true + /xml2js@0.5.0: resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} @@ -14779,6 +15875,10 @@ packages: engines: {node: '>=4.0'} dev: true + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + dev: true + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}