import { createLogger } from '@sim/logger' import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { requestJson } from '@/lib/api/client/request' import { deleteSkillContract, listSkillsContract, type Skill, upsertSkillsContract, } from '@/lib/api/contracts' const logger = createLogger('SkillsQueries') export const SKILL_LIST_STALE_TIME = 60 * 1000 export type SkillDefinition = Skill /** * Query key factories for skills queries */ export const skillsKeys = { all: ['skills'] as const, lists: () => [...skillsKeys.all, 'list'] as const, list: (workspaceId: string) => [...skillsKeys.lists(), workspaceId] as const, } /** * Fetch skills for a workspace */ async function fetchSkills(workspaceId: string, signal?: AbortSignal): Promise { const { data } = await requestJson(listSkillsContract, { query: { workspaceId }, signal, }) return data } /** * Hook to fetch skills for a workspace */ export function useSkills(workspaceId: string) { return useQuery({ queryKey: skillsKeys.list(workspaceId), queryFn: ({ signal }) => fetchSkills(workspaceId, signal), enabled: !!workspaceId, staleTime: SKILL_LIST_STALE_TIME, placeholderData: keepPreviousData, }) } /** * Create skill mutation. On success the created skill is merged into the list * cache so consumers (e.g. the integration detail page's "Added" state) reflect * it immediately, before the invalidation refetch lands. */ interface CreateSkillParams { workspaceId: string skill: { name: string description: string content: string } } export function useCreateSkill() { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ workspaceId, skill: s }: CreateSkillParams) => { logger.info(`Creating skill: ${s.name} in workspace ${workspaceId}`) const { data } = await requestJson(upsertSkillsContract, { body: { skills: [{ name: s.name, description: s.description, content: s.content }], workspaceId, }, }) logger.info(`Created skill: ${s.name}`) return data }, onSuccess: (data, variables) => { queryClient.setQueryData( skillsKeys.list(variables.workspaceId), (prev) => { const byId = new Map((prev ?? []).map((skill) => [skill.id, skill])) for (const skill of data) byId.set(skill.id, skill) return Array.from(byId.values()) } ) }, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: skillsKeys.list(variables.workspaceId) }) }, }) } /** * Update skill mutation */ interface UpdateSkillParams { workspaceId: string skillId: string updates: { name?: string description?: string content?: string } } export function useUpdateSkill() { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ workspaceId, skillId, updates }: UpdateSkillParams) => { logger.info(`Updating skill: ${skillId} in workspace ${workspaceId}`) const currentSkills = queryClient.getQueryData( skillsKeys.list(workspaceId) ) const currentSkill = currentSkills?.find((s) => s.id === skillId) if (!currentSkill) { throw new Error('Skill not found') } const { data } = await requestJson(upsertSkillsContract, { body: { skills: [ { id: skillId, name: updates.name ?? currentSkill.name, description: updates.description ?? currentSkill.description, content: updates.content ?? currentSkill.content, }, ], workspaceId, }, }) logger.info(`Updated skill: ${skillId}`) return data }, onMutate: async ({ workspaceId, skillId, updates }) => { await queryClient.cancelQueries({ queryKey: skillsKeys.list(workspaceId) }) const previousSkills = queryClient.getQueryData( skillsKeys.list(workspaceId) ) if (previousSkills) { queryClient.setQueryData( skillsKeys.list(workspaceId), previousSkills.map((s) => s.id === skillId ? { ...s, name: updates.name ?? s.name, description: updates.description ?? s.description, content: updates.content ?? s.content, } : s ) ) } return { previousSkills } }, onError: (_err, variables, context) => { if (context?.previousSkills) { queryClient.setQueryData(skillsKeys.list(variables.workspaceId), context.previousSkills) } }, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: skillsKeys.list(variables.workspaceId) }) }, }) } /** * Delete skill mutation */ interface DeleteSkillParams { workspaceId: string skillId: string } export function useDeleteSkill() { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ workspaceId, skillId }: DeleteSkillParams) => { logger.info(`Deleting skill: ${skillId}`) const data = await requestJson(deleteSkillContract, { query: { id: skillId, workspaceId }, }) logger.info(`Deleted skill: ${skillId}`) return data }, onMutate: async ({ workspaceId, skillId }) => { await queryClient.cancelQueries({ queryKey: skillsKeys.list(workspaceId) }) const previousSkills = queryClient.getQueryData( skillsKeys.list(workspaceId) ) if (previousSkills) { queryClient.setQueryData( skillsKeys.list(workspaceId), previousSkills.filter((s) => s.id !== skillId) ) } return { previousSkills } }, onError: (_err, variables, context) => { if (context?.previousSkills) { queryClient.setQueryData(skillsKeys.list(variables.workspaceId), context.previousSkills) } }, onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: skillsKeys.list(variables.workspaceId) }) }, }) }