Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fb96362
Merge pull request #478 from zenstackhq/dev
ymc9 Dec 12, 2025
d35b939
Merge pull request #486 from zenstackhq/dev
ymc9 Dec 13, 2025
39fb7d3
Merge pull request #489 from zenstackhq/dev
ymc9 Dec 14, 2025
69dcf6b
Merge pull request #500 from zenstackhq/dev
ymc9 Dec 14, 2025
3f3ffbe
Merge pull request #508 from zenstackhq/dev
ymc9 Dec 16, 2025
da6cf60
Merge pull request #517 from zenstackhq/dev
ymc9 Dec 18, 2025
e371ec9
Merge pull request #521 from zenstackhq/dev
ymc9 Dec 18, 2025
2966af1
Merge pull request #529 from zenstackhq/dev
ymc9 Dec 24, 2025
e71bee7
Merge pull request #532 from zenstackhq/dev
ymc9 Dec 24, 2025
de795f5
Merge pull request #545 from zenstackhq/dev
ymc9 Dec 30, 2025
9b95c29
feat(cli): implement watch mode for generate
DoctorFTB Dec 30, 2025
4895949
chore(root): update pnpm-lock.yaml
DoctorFTB Jan 5, 2026
469cb26
chore(cli): track all model declaration and removed paths, logs in pa…
DoctorFTB Jan 5, 2026
92814ca
fix(cli): typo, unused double array from
DoctorFTB Jan 5, 2026
c9f31ea
fix(orm): preserve zod validation errors when validating custom json …
ymc9 Jan 6, 2026
b625c50
update
ymc9 Jan 6, 2026
3d8f203
Merge branch 'fix/issue-558' into dev
ymc9 Jan 6, 2026
835a01b
chore(cli): move import, fix parallel generation on watch
DoctorFTB Jan 7, 2026
ce76178
Merge branch 'dev' of github.com:DoctorFTB/zenstack-v3 into dev
DoctorFTB Jan 7, 2026
f969ec4
feat(common-helpers): implement single-debounce
DoctorFTB Jan 7, 2026
87a95f0
chore(cli): use single-debounce for debouncing
DoctorFTB Jan 7, 2026
d966e2a
feat(common-helpers): implement single-debounce
DoctorFTB Jan 7, 2026
385e791
fix(common-helpers): re run single-debounce
DoctorFTB Jan 7, 2026
cf61959
Merge branch 'dev' of https://github.com/zenstackhq/zenstack-v3 into dev
ymc9 Jan 8, 2026
326b846
fix(tanstack): avoid invalidating queries for custom proc mutations
ymc9 Jan 8, 2026
b6d58c5
add missing file
ymc9 Jan 8, 2026
ecebbe5
fix formatting
ymc9 Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/clients/tanstack-query/src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CUSTOM_PROC_ROUTE_NAME = '$procs';
125 changes: 68 additions & 57 deletions packages/clients/tanstack-query/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import type {
import type { GetModels, SchemaDef } from '@zenstackhq/schema';
import { createContext, useContext } from 'react';
import { getAllQueries, invalidateQueriesMatchingPredicate } from './common/client';
import { CUSTOM_PROC_ROUTE_NAME } from './common/constants';
import { getQueryKey } from './common/query-key';
import type {
ExtraMutationOptions,
Expand Down Expand Up @@ -350,30 +351,36 @@ export function useClientQueries<Schema extends SchemaDef, Options extends Query

const procedures = (schema as any).procedures as Record<string, { mutation?: boolean }> | undefined;
if (procedures) {
const buildProcedureHooks = (endpointModel: '$procs') => {
const buildProcedureHooks = () => {
return Object.keys(procedures).reduce((acc, name) => {
const procDef = procedures[name];
if (procDef?.mutation) {
acc[name] = {
useMutation: (hookOptions?: any) =>
useInternalMutation(schema, endpointModel, 'POST', name, { ...options, ...hookOptions }),
useInternalMutation(schema, CUSTOM_PROC_ROUTE_NAME, 'POST', name, {
...options,
...hookOptions,
}),
};
} else {
acc[name] = {
useQuery: (args?: any, hookOptions?: any) =>
useInternalQuery(schema, endpointModel, name, args, { ...options, ...hookOptions }),
useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, {
...options,
...hookOptions,
}),
useSuspenseQuery: (args?: any, hookOptions?: any) =>
useInternalSuspenseQuery(schema, endpointModel, name, args, {
useInternalSuspenseQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, {
...options,
...hookOptions,
}),
useInfiniteQuery: (args?: any, hookOptions?: any) =>
useInternalInfiniteQuery(schema, endpointModel, name, args, {
useInternalInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, {
...options,
...hookOptions,
}),
useSuspenseInfiniteQuery: (args?: any, hookOptions?: any) =>
useInternalSuspenseInfiniteQuery(schema, endpointModel, name, args, {
useInternalSuspenseInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, {
...options,
...hookOptions,
}),
Expand All @@ -383,7 +390,7 @@ export function useClientQueries<Schema extends SchemaDef, Options extends Query
}, {} as any);
};

(result as any).$procs = buildProcedureHooks('$procs');
(result as any).$procs = buildProcedureHooks();
}

return result;
Expand Down Expand Up @@ -645,64 +652,68 @@ export function useInternalMutation<TArgs, R = any>(
};

const finalOptions = { ...options, mutationFn };
const invalidateQueries = options?.invalidateQueries !== false;
const optimisticUpdate = !!options?.optimisticUpdate;

if (!optimisticUpdate) {
// if optimistic update is not enabled, invalidate related queries on success
if (invalidateQueries) {
const invalidator = createInvalidator(
if (model !== CUSTOM_PROC_ROUTE_NAME) {
// not a custom procedure, set up optimistic update and invalidation

const invalidateQueries = options?.invalidateQueries !== false;
const optimisticUpdate = !!options?.optimisticUpdate;

if (!optimisticUpdate) {
// if optimistic update is not enabled, invalidate related queries on success
if (invalidateQueries) {
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);
const origOnSuccess = finalOptions.onSuccess;
finalOptions.onSuccess = async (...args) => {
// execute invalidator prior to user-provided onSuccess
await invalidator(...args);

// call user-provided onSuccess
await origOnSuccess?.(...args);
};
}
} else {
// schedule optimistic update on mutate
const optimisticUpdater = createOptimisticUpdater(
model,
operation,
schema,
(predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate),
{ optimisticDataProvider: finalOptions.optimisticDataProvider },
() => getAllQueries(queryClient),
logging,
);
const origOnSuccess = finalOptions.onSuccess;
finalOptions.onSuccess = async (...args) => {
// execute invalidator prior to user-provided onSuccess
await invalidator(...args);
const origOnMutate = finalOptions.onMutate;
finalOptions.onMutate = async (...args) => {
// execute optimistic update
await optimisticUpdater(...args);

// call user-provided onSuccess
await origOnSuccess?.(...args);
// call user-provided onMutate
return origOnMutate?.(...args);
};
}
} else {
// schedule optimistic update on mutate
const optimisticUpdater = createOptimisticUpdater(
model,
operation,
schema,
{ optimisticDataProvider: finalOptions.optimisticDataProvider },
() => getAllQueries(queryClient),
logging,
);
const origOnMutate = finalOptions.onMutate;
finalOptions.onMutate = async (...args) => {
// execute optimistic update
await optimisticUpdater(...args);

// call user-provided onMutate
return origOnMutate?.(...args);
};

if (invalidateQueries) {
// invalidate related queries on settled (success or error)
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);
const origOnSettled = finalOptions.onSettled;
finalOptions.onSettled = async (...args) => {
// execute invalidator prior to user-provided onSettled
await invalidator(...args);

// call user-provided onSettled
return origOnSettled?.(...args);
};
if (invalidateQueries) {
// invalidate related queries on settled (success or error)
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);
const origOnSettled = finalOptions.onSettled;
finalOptions.onSettled = async (...args) => {
// execute invalidator prior to user-provided onSettled
await invalidator(...args);

// call user-provided onSettled
return origOnSettled?.(...args);
};
}
}
}

Expand Down
137 changes: 77 additions & 60 deletions packages/clients/tanstack-query/src/svelte/index.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import type {
import type { GetModels, SchemaDef } from '@zenstackhq/schema';
import { getContext, setContext } from 'svelte';
import { getAllQueries, invalidateQueriesMatchingPredicate } from '../common/client';
import { CUSTOM_PROC_ROUTE_NAME } from '../common/constants';
import { getQueryKey } from '../common/query-key';
import type {
ExtraMutationOptions,
Expand Down Expand Up @@ -297,27 +298,39 @@ export function useClientQueries<Schema extends SchemaDef, Options extends Query

const procedures = (schema as any).procedures as Record<string, { mutation?: boolean }> | undefined;
if (procedures) {
const buildProcedureHooks = (endpointModel: '$procs') => {
const buildProcedureHooks = () => {
return Object.keys(procedures).reduce((acc, name) => {
const procDef = procedures[name];
if (procDef?.mutation) {
acc[name] = {
useMutation: (hookOptions?: any) =>
useInternalMutation(schema, endpointModel, 'POST', name, merge(options, hookOptions)),
useInternalMutation(
schema,
CUSTOM_PROC_ROUTE_NAME,
'POST',
name,
merge(options, hookOptions),
),
};
} else {
acc[name] = {
useQuery: (args?: any, hookOptions?: any) =>
useInternalQuery(schema, endpointModel, name, args, merge(options, hookOptions)),
useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, merge(options, hookOptions)),
useInfiniteQuery: (args?: any, hookOptions?: any) =>
useInternalInfiniteQuery(schema, endpointModel, name, args, merge(options, hookOptions)),
useInternalInfiniteQuery(
schema,
CUSTOM_PROC_ROUTE_NAME,
name,
args,
merge(options, hookOptions),
),
};
}
return acc;
}, {} as any);
};

(result as any).$procs = buildProcedureHooks('$procs');
(result as any).$procs = buildProcedureHooks();
}

return result;
Expand Down Expand Up @@ -533,70 +546,74 @@ export function useInternalMutation<TArgs, R = any>(
mutationFn,
};

if (!optimisticUpdate) {
// if optimistic update is not enabled, invalidate related queries on success
if (invalidateQueries) {
const invalidator = createInvalidator(
if (model !== CUSTOM_PROC_ROUTE_NAME) {
// not a custom procedure, set up optimistic update and invalidation

if (!optimisticUpdate) {
// if optimistic update is not enabled, invalidate related queries on success
if (invalidateQueries) {
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate: InvalidationPredicate) =>
// @ts-ignore
invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);

// execute invalidator prior to user-provided onSuccess
const origOnSuccess = optionsValue?.onSuccess;
const wrappedOnSuccess: typeof origOnSuccess = async (...args) => {
await invalidator(...args);
await origOnSuccess?.(...args);
};
result.onSuccess = wrappedOnSuccess;
}
} else {
const optimisticUpdater = createOptimisticUpdater(
model,
operation,
schema,
(predicate: InvalidationPredicate) =>
// @ts-ignore
invalidateQueriesMatchingPredicate(queryClient, predicate),
{ optimisticDataProvider: optionsValue?.optimisticDataProvider },
// @ts-ignore
() => getAllQueries(queryClient),
logging,
);

// execute invalidator prior to user-provided onSuccess
const origOnSuccess = optionsValue?.onSuccess;
const wrappedOnSuccess: typeof origOnSuccess = async (...args) => {
await invalidator(...args);
await origOnSuccess?.(...args);
};
result.onSuccess = wrappedOnSuccess;
}
} else {
const optimisticUpdater = createOptimisticUpdater(
model,
operation,
schema,
{ optimisticDataProvider: optionsValue?.optimisticDataProvider },
// @ts-ignore
() => getAllQueries(queryClient),
logging,
);

const origOnMutate = optionsValue.onMutate;
const wrappedOnMutate: typeof origOnMutate = async (...args) => {
// execute optimistic updater prior to user-provided onMutate
await optimisticUpdater(...args);

// call user-provided onMutate
return origOnMutate?.(...args);
};

result.onMutate = wrappedOnMutate;
const origOnMutate = optionsValue.onMutate;
const wrappedOnMutate: typeof origOnMutate = async (...args) => {
// execute optimistic updater prior to user-provided onMutate
await optimisticUpdater(...args);

if (invalidateQueries) {
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate: InvalidationPredicate) =>
// @ts-ignore
invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);
const origOnSettled = optionsValue.onSettled;
const wrappedOnSettled: typeof origOnSettled = async (...args) => {
// execute invalidator prior to user-provided onSettled
await invalidator(...args);

// call user-provided onSettled
await origOnSettled?.(...args);
// call user-provided onMutate
return origOnMutate?.(...args);
};

// replace onSettled in mergedOpt
result.onSettled = wrappedOnSettled;
result.onMutate = wrappedOnMutate;

if (invalidateQueries) {
const invalidator = createInvalidator(
model,
operation,
schema,
(predicate: InvalidationPredicate) =>
// @ts-ignore
invalidateQueriesMatchingPredicate(queryClient, predicate),
logging,
);
const origOnSettled = optionsValue.onSettled;
const wrappedOnSettled: typeof origOnSettled = async (...args) => {
// execute invalidator prior to user-provided onSettled
await invalidator(...args);

// call user-provided onSettled
await origOnSettled?.(...args);
};

// replace onSettled in mergedOpt
result.onSettled = wrappedOnSettled;
}
}
}

Expand Down
Loading
Loading