@oxyhq/services
Version:
173 lines (160 loc) • 5.11 kB
JavaScript
;
/**
* Mutation Factory - Creates standardized mutations with optimistic updates
*
* This factory reduces boilerplate code for mutations that follow the common pattern:
* 1. Cancel outgoing queries
* 2. Snapshot previous data
* 3. Apply optimistic update
* 4. On error: rollback and show toast
* 5. On success: update cache, stores, and invalidate queries
*/
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from "../queries/queryKeys.js";
import { toast } from '../../../lib/sonner';
import { useAuthStore } from "../../stores/authStore.js";
/**
* Configuration for creating a standard profile mutation
*/
/**
* Creates a standard profile mutation with optimistic updates
*
* @example
* ```ts
* const updateProfile = createProfileMutation({
* mutationFn: (updates) => oxyServices.updateProfile(updates),
* optimisticUpdate: (user, updates) => updates,
* errorMessage: 'Failed to update profile',
* });
* ```
*/
export function createProfileMutation(config, queryClient, activeSessionId) {
const {
mutationFn,
cancelQueryKeys = [],
optimisticUpdate,
errorMessage = 'Operation failed',
successMessage,
updateAuthStore = true,
invalidateUserQueries: shouldInvalidateUserQueries = true,
invalidateAccountQueries: shouldInvalidateAccountQueries = true,
onSuccess: customOnSuccess
} = config;
return {
mutationFn,
onMutate: async variables => {
// Cancel queries that might conflict
await queryClient.cancelQueries({
queryKey: queryKeys.accounts.current()
});
for (const key of cancelQueryKeys) {
await queryClient.cancelQueries({
queryKey: key
});
}
// Snapshot previous user data
const previousUser = queryClient.getQueryData(queryKeys.accounts.current());
// Apply optimistic update if provided
if (previousUser && optimisticUpdate) {
const updates = optimisticUpdate(previousUser, variables);
const optimisticUser = {
...previousUser,
...updates
};
queryClient.setQueryData(queryKeys.accounts.current(), optimisticUser);
if (activeSessionId) {
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), optimisticUser);
}
}
return {
previousUser
};
},
onError: (error, _variables, context) => {
// Rollback optimistic update
if (context?.previousUser) {
queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
if (activeSessionId) {
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
}
}
// Show error toast
const message = typeof errorMessage === 'function' ? errorMessage(error) : error instanceof Error ? error.message : errorMessage;
toast.error(message);
},
onSuccess: (data, variables) => {
// Update cache with server response
queryClient.setQueryData(queryKeys.accounts.current(), data);
if (activeSessionId) {
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
}
// Update authStore for immediate UI updates
if (updateAuthStore) {
useAuthStore.getState().setUser(data);
}
// Invalidate related queries
if (shouldInvalidateUserQueries) {
invalidateUserQueries(queryClient);
}
if (shouldInvalidateAccountQueries) {
invalidateAccountQueries(queryClient);
}
// Show success toast if configured
if (successMessage) {
toast.success(successMessage);
}
// Call custom onSuccess handler
if (customOnSuccess) {
customOnSuccess(data, variables, queryClient);
}
}
};
}
/**
* Configuration for creating a generic mutation (non-profile)
*/
/**
* Creates a generic mutation with optimistic updates
*/
export function createGenericMutation(config, queryClient) {
const {
mutationFn,
queryKey,
optimisticData,
errorMessage = 'Operation failed',
successMessage,
invalidateQueries = []
} = config;
return {
mutationFn,
onMutate: async variables => {
await queryClient.cancelQueries({
queryKey
});
const previous = queryClient.getQueryData(queryKey);
if (optimisticData) {
queryClient.setQueryData(queryKey, optimisticData(previous, variables));
}
return {
previous
};
},
onError: (error, _variables, context) => {
if (context?.previous !== undefined) {
queryClient.setQueryData(queryKey, context.previous);
}
toast.error(error instanceof Error ? error.message : errorMessage);
},
onSuccess: data => {
queryClient.setQueryData(queryKey, data);
for (const key of invalidateQueries) {
queryClient.invalidateQueries({
queryKey: key
});
}
if (successMessage) {
toast.success(successMessage);
}
}
};
}
//# sourceMappingURL=mutationFactory.js.map