diff --git a/package.json b/package.json index a8eb656c3..1d830a1af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.2.0", + "version": "2.2.1", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index e1547b907..e754ae7aa 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.2.0" +version = "2.2.1" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index d94e641c4..d97e0fd04 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.2.0", + "version": "2.2.1", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index 5703ef590..4fd5bd2bc 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.2.0", + "version": "2.2.1", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index 5e5253ac1..c695f0115 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "2.2.0", + "version": "2.2.1", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index cff7fcd0d..96ebd46a6 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 9483c87da..662e109a6 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 6611d6789..44782eb36 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/tanstack-query/src/runtime-v5/vue.ts b/packages/plugins/tanstack-query/src/runtime-v5/vue.ts index 5f4cbf406..f62fd78c9 100644 --- a/packages/plugins/tanstack-query/src/runtime-v5/vue.ts +++ b/packages/plugins/tanstack-query/src/runtime-v5/vue.ts @@ -1 +1,212 @@ -export * from '../runtime/vue'; +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, + type InfiniteData, + type QueryKey, + type UseInfiniteQueryOptions, + type UseMutationOptions, + type UseQueryOptions, +} from '@tanstack/vue-query'; +import type { ModelMeta } from '@zenstackhq/runtime/cross'; +import { computed, inject, provide, toValue, type ComputedRef, type MaybeRefOrGetter } from 'vue'; +import { + APIContext, + DEFAULT_QUERY_ENDPOINT, + fetcher, + getQueryKey, + makeUrl, + marshal, + setupInvalidation, + setupOptimisticUpdate, + type ExtraMutationOptions, + type ExtraQueryOptions, + type FetchFn, +} from '../runtime/common'; + +export { APIContext as RequestHandlerContext } from '../runtime/common'; + +export const VueQueryContextKey = 'zenstack-vue-query-context'; + +/** + * Provide context for the generated TanStack Query hooks. + */ +export function provideHooksContext(context: APIContext) { + provide(VueQueryContextKey, context); +} + +/** + * Hooks context. + */ +export function getHooksContext() { + const { endpoint, ...rest } = inject(VueQueryContextKey, { + endpoint: DEFAULT_QUERY_ENDPOINT, + fetch: undefined, + logging: false, + }); + return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest }; +} + +/** + * Creates a vue-query query. + * + * @param model The name of the model under query. + * @param url The request URL. + * @param args The request args object, URL-encoded and appended as "?q=" parameter + * @param options The vue-query options object + * @param fetch The fetch function to use for sending the HTTP request + * @returns useQuery hook + */ +export function useModelQuery( + model: string, + url: string, + args?: MaybeRefOrGetter | ComputedRef, + options?: + | MaybeRefOrGetter, 'queryKey'> & ExtraQueryOptions> + | ComputedRef, 'queryKey'> & ExtraQueryOptions>, + fetch?: FetchFn +) { + const queryOptions = computed(() => { + const optionsValue = toValue< + (Omit, 'queryKey'> & ExtraQueryOptions) | undefined + >(options); + return { + queryKey: getQueryKey(model, url, args, { + infinite: false, + optimisticUpdate: optionsValue?.optimisticUpdate !== false, + }), + queryFn: ({ queryKey }: { queryKey: QueryKey }) => { + const [_prefix, _model, _op, args] = queryKey; + const reqUrl = makeUrl(url, toValue(args)); + return fetcher(reqUrl, undefined, fetch, false); + }, + ...optionsValue, + }; + }); + return useQuery(queryOptions); +} + +/** + * Creates a vue-query infinite query. + * + * @param model The name of the model under query. + * @param url The request URL. + * @param args The initial request args object, URL-encoded and appended as "?q=" parameter + * @param options The vue-query infinite query options object + * @param fetch The fetch function to use for sending the HTTP request + * @returns useInfiniteQuery hook + */ +export function useInfiniteModelQuery( + model: string, + url: string, + args?: MaybeRefOrGetter | ComputedRef, + options?: + | MaybeRefOrGetter< + Omit>, 'queryKey' | 'initialPageParam'> + > + | ComputedRef< + Omit>, 'queryKey' | 'initialPageParam'> + >, + fetch?: FetchFn +) { + // CHECKME: vue-query's `useInfiniteQuery`'s input typing seems wrong + const queryOptions: any = computed(() => ({ + queryKey: getQueryKey(model, url, args, { infinite: true, optimisticUpdate: false }), + queryFn: ({ queryKey, pageParam }: { queryKey: QueryKey; pageParam?: unknown }) => { + const [_prefix, _model, _op, args] = queryKey; + const reqUrl = makeUrl(url, pageParam ?? toValue(args)); + return fetcher(reqUrl, undefined, fetch, false); + }, + initialPageParam: toValue(args), + ...toValue(options), + })); + + return useInfiniteQuery>(queryOptions); +} + +/** + * 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 fetch The fetch function to use for sending the HTTP request + * @param checkReadBack Whether to check for read back errors and return undefined if found. + * @returns useMutation hooks + */ +export function useModelMutation< + TArgs, + TError, + R = any, + C extends boolean = boolean, + Result = C extends true ? R | undefined : R +>( + model: string, + method: 'POST' | 'PUT' | 'DELETE', + url: string, + modelMeta: ModelMeta, + options?: + | MaybeRefOrGetter< + Omit, 'mutationFn'> & ExtraMutationOptions + > + | ComputedRef, 'mutationFn'> & ExtraMutationOptions>, + fetch?: FetchFn, + checkReadBack?: C +) { + const queryClient = useQueryClient(); + 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), + }), + }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; + + const optionsValue = toValue< + (Omit, 'mutationFn'> & ExtraMutationOptions) | undefined + >(options); + // TODO: figure out the typing problem + const finalOptions: any = computed(() => ({ ...optionsValue, mutationFn })); + const operation = url.split('/').pop(); + const invalidateQueries = optionsValue?.invalidateQueries !== false; + const optimisticUpdate = !!optionsValue?.optimisticUpdate; + + if (operation) { + const { logging } = getHooksContext(); + if (invalidateQueries) { + setupInvalidation( + model, + operation, + modelMeta, + toValue(finalOptions), + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } + + if (optimisticUpdate) { + setupOptimisticUpdate( + model, + operation, + modelMeta, + toValue(finalOptions), + queryClient.getQueryCache().getAll(), + (queryKey, data) => queryClient.setQueryData(queryKey, data), + invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined, + logging + ); + } + } + return useMutation(finalOptions); +} diff --git a/packages/plugins/tanstack-query/src/runtime/vue.ts b/packages/plugins/tanstack-query/src/runtime/vue.ts index b024d8940..5627b830b 100644 --- a/packages/plugins/tanstack-query/src/runtime/vue.ts +++ b/packages/plugins/tanstack-query/src/runtime/vue.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { InfiniteData } from '@tanstack/react-query-v5'; import { useInfiniteQuery, useMutation, useQuery, useQueryClient, + type InfiniteData, type QueryKey, type UseInfiniteQueryOptions, type UseMutationOptions, @@ -124,7 +124,7 @@ export function useInfiniteModelQuery( ...toValue(options), })); - return useInfiniteQuery>(queryOptions); + return useInfiniteQuery(queryOptions); } /** diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index a91ea4e45..4e40c56cf 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 9a6bfb923..053da9a58 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.2.0", + "version": "2.2.1", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index b78d49bfe..44955cdec 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database", - "version": "2.2.0", + "version": "2.2.1", "author": { "name": "ZenStack Team" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 4d1dedde7..e8e474f8d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 11c41bae1..1b62e2d68 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.2.0", + "version": "2.2.1", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 8c1a9dc5e..a23b36293 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.2.0", + "version": "2.2.1", "description": "ZenStack Test Tools", "main": "index.js", "private": true,