feat: initial release v0.3.0
This commit is contained in:
208
src/lib/query/hooks/useTaskStatus.ts
Normal file
208
src/lib/query/hooks/useTaskStatus.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { queryKeys } from '../keys'
|
||||
import { apiFetch } from '@/lib/api-fetch'
|
||||
|
||||
export type TaskItem = {
|
||||
id: string
|
||||
type: string
|
||||
targetType: string
|
||||
targetId: string
|
||||
episodeId?: string | null
|
||||
status: string
|
||||
progress?: number | null
|
||||
errorCode?: string | null
|
||||
errorMessage?: string | null
|
||||
error?: {
|
||||
code: string
|
||||
message: string
|
||||
retryable: boolean
|
||||
category: string
|
||||
userMessageKey: string
|
||||
details?: Record<string, unknown> | null
|
||||
} | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
const ACTIVE_STATUS = ['queued', 'processing'] as const
|
||||
const SNAPSHOT_STATUS = ['queued', 'processing', 'completed', 'failed'] as const
|
||||
|
||||
function buildTaskSearch(params: {
|
||||
projectId: string
|
||||
targetType?: string
|
||||
targetId?: string
|
||||
type?: string[]
|
||||
statuses: readonly string[]
|
||||
limit?: number
|
||||
}) {
|
||||
const search = new URLSearchParams()
|
||||
search.set('projectId', params.projectId)
|
||||
if (params.targetType) search.set('targetType', params.targetType)
|
||||
if (params.targetId) search.set('targetId', params.targetId)
|
||||
for (const status of params.statuses) {
|
||||
search.append('status', status)
|
||||
}
|
||||
if (typeof params.limit === 'number') {
|
||||
search.set('limit', String(params.limit))
|
||||
}
|
||||
for (const taskType of params.type || []) {
|
||||
search.append('type', taskType)
|
||||
}
|
||||
return search
|
||||
}
|
||||
|
||||
export function useTaskList(params: {
|
||||
projectId?: string | null
|
||||
targetType?: string | null
|
||||
targetId?: string | null
|
||||
type?: string[]
|
||||
statuses?: string[]
|
||||
limit?: number
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const enabled = (params.enabled ?? true) && !!params.projectId
|
||||
const statusKey = (params.statuses || []).slice().sort().join(',')
|
||||
const typeKey = (params.type || []).slice().sort().join(',')
|
||||
const queryKey = [
|
||||
...queryKeys.tasks.all(params.projectId || ''),
|
||||
params.targetType || '',
|
||||
params.targetId || '',
|
||||
statusKey,
|
||||
typeKey,
|
||||
params.limit ?? '',
|
||||
] as const
|
||||
|
||||
return useQuery({
|
||||
queryKey,
|
||||
enabled,
|
||||
staleTime: 5000,
|
||||
queryFn: async () => {
|
||||
const search = buildTaskSearch({
|
||||
projectId: params.projectId!,
|
||||
targetType: params.targetType || undefined,
|
||||
targetId: params.targetId || undefined,
|
||||
type: params.type,
|
||||
statuses: (params.statuses || SNAPSHOT_STATUS),
|
||||
limit: params.limit,
|
||||
})
|
||||
const res = await apiFetch(`/api/tasks?${search}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch tasks')
|
||||
const data = await res.json()
|
||||
return (data.tasks || []) as TaskItem[]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useActiveTasks(params: {
|
||||
projectId?: string | null
|
||||
targetType?: string | null
|
||||
targetId?: string | null
|
||||
type?: string[]
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const enabled = (params.enabled ?? true) && !!params.projectId
|
||||
const typeKey = (params.type || []).slice().sort().join(',')
|
||||
const queryKey = params.targetType && params.targetId
|
||||
? [...queryKeys.tasks.target(params.projectId || '', params.targetType, params.targetId), typeKey] as const
|
||||
: [...queryKeys.tasks.all(params.projectId || ''), typeKey] as const
|
||||
|
||||
return useQuery({
|
||||
queryKey,
|
||||
enabled,
|
||||
staleTime: 5000,
|
||||
queryFn: async () => {
|
||||
const search = buildTaskSearch({
|
||||
projectId: params.projectId!,
|
||||
targetType: params.targetType || undefined,
|
||||
targetId: params.targetId || undefined,
|
||||
type: params.type,
|
||||
statuses: ACTIVE_STATUS,
|
||||
})
|
||||
const res = await apiFetch(`/api/tasks?${search}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch active tasks')
|
||||
const data = await res.json()
|
||||
return (data.tasks || []) as TaskItem[]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useTaskSnapshot(params: {
|
||||
projectId?: string | null
|
||||
targetType?: string | null
|
||||
targetId?: string | null
|
||||
enabled?: boolean
|
||||
type?: string[]
|
||||
}) {
|
||||
const enabled = (params.enabled ?? true) && !!params.projectId && !!params.targetType && !!params.targetId
|
||||
const typeKey = (params.type || []).slice().sort().join(',')
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeys.tasks.snapshot(params.projectId || '', params.targetType || '', params.targetId || '', typeKey),
|
||||
enabled,
|
||||
staleTime: 5000,
|
||||
queryFn: async () => {
|
||||
const search = buildTaskSearch({
|
||||
projectId: params.projectId!,
|
||||
targetType: params.targetType || undefined,
|
||||
targetId: params.targetId || undefined,
|
||||
type: params.type,
|
||||
statuses: SNAPSHOT_STATUS,
|
||||
limit: 1,
|
||||
})
|
||||
const res = await apiFetch(`/api/tasks?${search}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch task snapshot')
|
||||
const data = await res.json()
|
||||
const tasks = (data.tasks || []) as TaskItem[]
|
||||
return tasks[0] || null
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useTaskStatus(params: {
|
||||
projectId?: string | null
|
||||
targetType?: string | null
|
||||
targetId?: string | null
|
||||
enabled?: boolean
|
||||
type?: string[]
|
||||
}) {
|
||||
const query = useActiveTasks({
|
||||
projectId: params.projectId,
|
||||
targetType: params.targetType,
|
||||
targetId: params.targetId,
|
||||
enabled: params.enabled,
|
||||
type: params.type,
|
||||
})
|
||||
const snapshotQuery = useTaskSnapshot({
|
||||
projectId: params.projectId,
|
||||
targetType: params.targetType,
|
||||
targetId: params.targetId,
|
||||
enabled: params.enabled,
|
||||
type: params.type,
|
||||
})
|
||||
|
||||
const data = useMemo(() => {
|
||||
const tasks = query.data || []
|
||||
const latest = snapshotQuery.data || tasks[0] || null
|
||||
const lastFailed = latest?.status === 'failed' ? (latest.error || null) : null
|
||||
return {
|
||||
active: tasks,
|
||||
hasActive: tasks.length > 0,
|
||||
latest,
|
||||
lastFailed,
|
||||
lastTerminal: lastFailed,
|
||||
// Backward compatibility: keep lastError but only represent FAILED.
|
||||
lastError: lastFailed,
|
||||
}
|
||||
}, [query.data, snapshotQuery.data])
|
||||
|
||||
return {
|
||||
...query,
|
||||
isFetching: query.isFetching || snapshotQuery.isFetching,
|
||||
isError: query.isError || snapshotQuery.isError,
|
||||
error: query.error || snapshotQuery.error,
|
||||
data,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user