diff --git a/apps/studio/components/interfaces/Workers/RuntimeBadge.tsx b/apps/studio/components/interfaces/Workers/RuntimeBadge.tsx
index 92a743b2799..96775239540 100644
--- a/apps/studio/components/interfaces/Workers/RuntimeBadge.tsx
+++ b/apps/studio/components/interfaces/Workers/RuntimeBadge.tsx
@@ -1,10 +1,9 @@
import { cn } from 'ui'
-import { getRuntimeMeta } from './Workers.constants'
-import type { WorkerRuntime } from './Workers.types'
+import { formatRuntime, getRuntimeMeta } from './Workers.utils'
interface RuntimeBadgeProps {
- runtime: WorkerRuntime
+ runtime: string | undefined
className?: string
}
@@ -12,8 +11,10 @@ export const RuntimeBadge = ({ runtime, className }: RuntimeBadgeProps) => {
const meta = getRuntimeMeta(runtime)
return (
-
- {meta.label}
+
+ {formatRuntime(runtime)}
)
}
diff --git a/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx b/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx
index 91e0bf95fda..8ad3a227375 100644
--- a/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx
+++ b/apps/studio/components/interfaces/Workers/WorkerStatePill.tsx
@@ -1,16 +1,16 @@
import { cn } from 'ui'
-import { WORKER_STATE_META } from './Workers.constants'
-import type { WorkerState } from './Workers.types'
+import { getWorkerStateMeta } from './Workers.constants'
+import type { Worker } from './Workers.types'
interface WorkerStatePillProps {
- state: WorkerState
+ worker: Worker
className?: string
}
-export const WorkerStatePill = ({ state, className }: WorkerStatePillProps) => {
- const meta = WORKER_STATE_META[state]
- const isPulsing = state === 'deploying' || state === 'draining' || state === 'resuming'
+export const WorkerStatePill = ({ worker, className }: WorkerStatePillProps) => {
+ const meta = getWorkerStateMeta(worker)
+ const isPulsing = worker.buildState === 'building' || worker.isDeleting
return (
diff --git a/apps/studio/components/interfaces/Workers/Workers.constants.ts b/apps/studio/components/interfaces/Workers/Workers.constants.ts
index 02e01bf4e0f..c76000c4816 100644
--- a/apps/studio/components/interfaces/Workers/Workers.constants.ts
+++ b/apps/studio/components/interfaces/Workers/Workers.constants.ts
@@ -1,4 +1,4 @@
-import type { WorkerRuntime, WorkerSize, WorkerState } from './Workers.types'
+import type { Worker, WorkerBuildState } from './Workers.types'
// Locked to one region at alpha, so nothing in the UI lets you change it.
export const WORKERS_REGION = 'us-west-1'
@@ -8,127 +8,74 @@ export const WORKERS_REGION_SHORT = 'US West'
// Private workers share this URL shape; access is enforced by the gateway, not the path.
export const workerGatewayUrl = (name: string) => `https://workers.supabase.co/v1/${name}`
+export const LISTENING_PORT = 8080
+
export interface RuntimeMeta {
- value: WorkerRuntime
label: string
- cli: string
baseImage: string
entrypoint: string
swatchClassName: string
}
-export const RUNTIMES: RuntimeMeta[] = [
- {
- value: 'node',
+// Keyed by the `spec.runtime` values the Management API returns.
+export const RUNTIMES: Record = {
+ node: {
label: 'Node.js 24',
- cli: 'node',
baseImage: 'node:24-slim',
entrypoint: 'node index.js',
swatchClassName: 'bg-[#5FA04E]',
},
- {
- value: 'deno',
+ deno: {
label: 'Deno 2',
- cli: 'deno',
baseImage: 'denoland/deno:latest',
entrypoint: 'deno run main.ts',
swatchClassName: 'bg-[#70FFAF]',
},
- {
- value: 'dockerfile',
- label: 'Dockerfile',
- cli: 'dockerfile',
- baseImage: 'From ./Dockerfile',
- entrypoint: 'CMD from Dockerfile',
- swatchClassName: 'bg-[#2496ED]',
- },
- {
- value: 'bun',
+ bun: {
label: 'Bun 1',
- cli: 'bun',
baseImage: 'oven/bun:latest',
entrypoint: 'bun run index.ts',
swatchClassName: 'bg-[#FBF0DF]',
},
- {
- value: 'python',
+ python: {
label: 'Python 3.14',
- cli: 'python',
baseImage: 'python:3.14-slim',
entrypoint: 'python main.py',
swatchClassName: 'bg-[#3776AB]',
},
-]
-
-export const getRuntimeMeta = (runtime: WorkerRuntime): RuntimeMeta =>
- RUNTIMES.find((r) => r.value === runtime) ?? RUNTIMES[0]
-
-export interface SizeMeta {
- value: WorkerSize
- memory: string
- vcpu: string
- label: string
+ dockerfile: {
+ label: 'Dockerfile',
+ baseImage: 'From ./Dockerfile',
+ entrypoint: 'CMD from Dockerfile',
+ swatchClassName: 'bg-[#2496ED]',
+ },
}
-export const SIZES: SizeMeta[] = [
- { value: '2x1', memory: '2 GB', vcpu: '1 vCPU', label: '2×1 · 2 GB · 1 vCPU' },
- { value: '4x2', memory: '4 GB', vcpu: '2 vCPU', label: '4×2 · 4 GB · 2 vCPU' },
-]
-
-export const getSizeMeta = (size: WorkerSize): SizeMeta =>
- SIZES.find((s) => s.value === size) ?? SIZES[0]
-
-export const formatResources = (size: WorkerSize, instances: number): string => {
- const meta = getSizeMeta(size)
- return `${meta.vcpu} · ${meta.memory} · ${instances} inst`
-}
-
-export const LISTENING_PORT = 8080
-
export interface WorkerStateMeta {
label: string
dotClassName: string
textClassName: string
}
-export const WORKER_STATE_META: Record = {
- deploying: {
- label: 'Deploying',
+const BUILD_STATE_META: Record = {
+ building: {
+ label: 'Building',
dotClassName: 'bg-warning',
textClassName: 'text-foreground-light',
},
active: { label: 'Active', dotClassName: 'bg-brand', textClassName: 'text-foreground-light' },
- draining: {
- label: 'Draining',
- dotClassName: 'bg-warning',
- textClassName: 'text-foreground-light',
- },
- suspended: {
- label: 'Suspended',
- dotClassName: 'bg-foreground-muted',
- textClassName: 'text-foreground-light',
- },
- resuming: {
- label: 'Resuming',
- dotClassName: 'bg-warning',
- textClassName: 'text-foreground-light',
- },
- errored: {
- label: 'Errored',
+ failed: {
+ label: 'Failed',
dotClassName: 'bg-destructive',
textClassName: 'text-foreground-light',
},
- killed: {
- label: 'Killed',
- dotClassName: 'bg-foreground-muted',
- textClassName: 'text-foreground-lighter',
- },
}
-export const WORKER_ERROR_REASON_LABEL: Record = {
- crash: 'The worker crashed with a non-zero exit code',
- unresponsive: 'The worker did not respond on $PORT',
- build: 'The build failed before the worker could start',
- entrypoint: 'The entrypoint could not be found or executed',
- dependency: 'A dependency failed to install during build',
+const DELETING_STATE_META: WorkerStateMeta = {
+ label: 'Deleting',
+ dotClassName: 'bg-foreground-muted',
+ textClassName: 'text-foreground-lighter',
}
+
+export const getWorkerStateMeta = (worker: Worker): WorkerStateMeta =>
+ worker.isDeleting ? DELETING_STATE_META : BUILD_STATE_META[worker.buildState]
diff --git a/apps/studio/components/interfaces/Workers/Workers.types.ts b/apps/studio/components/interfaces/Workers/Workers.types.ts
index 32db6541b7f..17a43de2564 100644
--- a/apps/studio/components/interfaces/Workers/Workers.types.ts
+++ b/apps/studio/components/interfaces/Workers/Workers.types.ts
@@ -1,53 +1,24 @@
-export type WorkerRuntime = 'node' | 'deno' | 'bun' | 'python' | 'dockerfile'
-
-export type WorkerSize = '2x1' | '4x2'
+export type WorkerBuildState = 'building' | 'active' | 'failed'
export type WorkerAccess = 'public' | 'private'
-// A health-check failure is `errored` with errorReason 'unresponsive', not its own state,
-// which is how infra reports it.
-export type WorkerState =
- | 'deploying'
- | 'active'
- | 'draining'
- | 'suspended'
- | 'resuming'
- | 'errored'
- | 'killed'
-
-export type WorkerErrorReason = 'crash' | 'unresponsive' | 'build' | 'entrypoint' | 'dependency'
-
-export type WorkerLifecycleLevel = 'info' | 'error'
-
-export interface WorkerLifecycleEvent {
- id: string
- at: string
- level: WorkerLifecycleLevel
- message: string
- reason?: WorkerErrorReason
+export interface WorkerInstanceTally {
+ declared: number
+ live: number
+ ready: number
+ stale: number
}
export interface Worker {
- id: string
name: string
- runtime: WorkerRuntime
- size: WorkerSize
+ buildState: WorkerBuildState
+ isDeleting: boolean
+ runtime?: string
+ size: string
access: WorkerAccess
- instances: number
- region: string
- state: WorkerState
- errorReason?: WorkerErrorReason
- createdAt: string
- updatedAt: string
- events: WorkerLifecycleEvent[]
-}
-
-export interface WorkerMetrics {
- requests24h: number
- errors24h: number
- errorRate: number
- avgLatencyMs: number
- p99LatencyMs: number
- cpuPercent: number
- memoryPercent: number
+ declaredInstances: number
+ instances?: WorkerInstanceTally
+ imageVersion?: string
+ stateReason?: string
+ instancesError?: string
}
diff --git a/apps/studio/components/interfaces/Workers/Workers.utils.test.ts b/apps/studio/components/interfaces/Workers/Workers.utils.test.ts
index f15e3a7c94e..1419224cc38 100644
--- a/apps/studio/components/interfaces/Workers/Workers.utils.test.ts
+++ b/apps/studio/components/interfaces/Workers/Workers.utils.test.ts
@@ -1,19 +1,15 @@
import { describe, expect, it } from 'vitest'
import type { Worker } from './Workers.types'
-import { filterWorkers, getPage } from './Workers.utils'
+import { filterWorkers, formatResources, formatRuntime, formatSize, getPage } from './Workers.utils'
const worker = (overrides: Partial & Pick): Worker => ({
- id: overrides.name,
+ buildState: 'active',
+ isDeleting: false,
runtime: 'node',
- size: '2x1',
+ size: '2gb-1vcpu',
access: 'public',
- instances: 1,
- region: 'us-west-1',
- state: 'active',
- createdAt: '2026-08-10T09:00:00.000Z',
- updatedAt: '2026-08-10T09:00:00.000Z',
- events: [],
+ declaredInstances: 1,
...overrides,
})
@@ -21,9 +17,9 @@ const NO_FILTERS = { search: '', state: 'all', access: 'all' } as const
describe('filterWorkers', () => {
const workers = [
- worker({ name: 'embed', state: 'active', access: 'public' }),
- worker({ name: 'resize-images', state: 'suspended', access: 'private' }),
- worker({ name: 'embed-batch', state: 'errored', access: 'public' }),
+ worker({ name: 'embed', buildState: 'active', access: 'public' }),
+ worker({ name: 'resize-images', buildState: 'building', access: 'private' }),
+ worker({ name: 'embed-batch', buildState: 'failed', access: 'public' }),
]
it('returns every worker when no filters are set', () => {
@@ -39,8 +35,8 @@ describe('filterWorkers', () => {
expect(filterWorkers(workers, { ...NO_FILTERS, search: ' resize ' })).toHaveLength(1)
})
- it('filters by state and by access', () => {
- expect(filterWorkers(workers, { ...NO_FILTERS, state: 'errored' }).map((w) => w.name)).toEqual([
+ it('filters by build state and by access', () => {
+ expect(filterWorkers(workers, { ...NO_FILTERS, state: 'failed' }).map((w) => w.name)).toEqual([
'embed-batch',
])
expect(filterWorkers(workers, { ...NO_FILTERS, access: 'private' }).map((w) => w.name)).toEqual(
@@ -83,3 +79,37 @@ describe('getPage', () => {
})
})
})
+
+describe('formatSize', () => {
+ it('splits the memory and vCPU parts the API reports', () => {
+ expect(formatSize('2gb-1vcpu')).toBe('2 GB · 1 vCPU')
+ expect(formatSize('4gb-2vcpu')).toBe('4 GB · 2 vCPU')
+ })
+
+ it('falls back to the raw value for a shape it does not recognize', () => {
+ expect(formatSize('8gb-4vcpu-gpu')).toBe('8gb-4vcpu-gpu')
+ expect(formatSize('')).toBe('')
+ })
+})
+
+describe('formatRuntime', () => {
+ it('labels a known runtime', () => {
+ expect(formatRuntime('python')).toBe('Python 3.14')
+ })
+
+ it('shows the raw value for a runtime it does not know', () => {
+ expect(formatRuntime('rust')).toBe('rust')
+ })
+
+ it('reports unknown when the API omits the runtime', () => {
+ expect(formatRuntime(undefined)).toBe('Unknown')
+ })
+})
+
+describe('formatResources', () => {
+ it('combines size and declared instance count', () => {
+ expect(formatResources(worker({ name: 'embed', declaredInstances: 3 }))).toBe(
+ '2 GB · 1 vCPU · 3 inst'
+ )
+ })
+})
diff --git a/apps/studio/components/interfaces/Workers/Workers.utils.ts b/apps/studio/components/interfaces/Workers/Workers.utils.ts
index 494ade860c1..773afac4e23 100644
--- a/apps/studio/components/interfaces/Workers/Workers.utils.ts
+++ b/apps/studio/components/interfaces/Workers/Workers.utils.ts
@@ -1,8 +1,9 @@
-import type { Worker, WorkerAccess, WorkerState } from './Workers.types'
+import { RUNTIMES, type RuntimeMeta } from './Workers.constants'
+import type { Worker, WorkerAccess, WorkerBuildState } from './Workers.types'
export interface WorkerFilters {
search: string
- state: WorkerState | 'all'
+ state: WorkerBuildState | 'all'
access: WorkerAccess | 'all'
}
@@ -10,7 +11,7 @@ export const filterWorkers = (workers: Worker[], filters: WorkerFilters): Worker
const search = filters.search.trim().toLowerCase()
return workers.filter((worker) => {
const matchesSearch = worker.name.toLowerCase().includes(search)
- const matchesState = filters.state === 'all' || worker.state === filters.state
+ const matchesState = filters.state === 'all' || worker.buildState === filters.state
const matchesAccess = filters.access === 'all' || worker.access === filters.access
return matchesSearch && matchesState && matchesAccess
})
@@ -35,3 +36,19 @@ export const getPage = (items: T[], requestedPage: number, pageSize: number):
startIndex,
}
}
+
+export const getRuntimeMeta = (runtime: string | undefined): RuntimeMeta | undefined =>
+ runtime === undefined ? undefined : RUNTIMES[runtime]
+
+export const formatRuntime = (runtime: string | undefined): string =>
+ getRuntimeMeta(runtime)?.label ?? runtime ?? 'Unknown'
+
+// The API reports size as e.g. "2gb-1vcpu"; render the parts when they parse, the raw value if not.
+export const formatSize = (size: string): string => {
+ const match = size.match(/^(\d+)gb-(\d+)vcpu$/)
+ if (!match) return size
+ return `${match[1]} GB · ${match[2]} vCPU`
+}
+
+export const formatResources = (worker: Worker): string =>
+ `${formatSize(worker.size)} · ${worker.declaredInstances} inst`
diff --git a/apps/studio/components/interfaces/Workers/WorkersList.tsx b/apps/studio/components/interfaces/Workers/WorkersList.tsx
index 6674dac9b08..0d25530670c 100644
--- a/apps/studio/components/interfaces/Workers/WorkersList.tsx
+++ b/apps/studio/components/interfaces/Workers/WorkersList.tsx
@@ -21,9 +21,9 @@ import {
} from 'ui'
import { RuntimeBadge } from './RuntimeBadge'
-import { formatResources, WORKERS_REGION_SHORT } from './Workers.constants'
-import type { Worker, WorkerAccess, WorkerState } from './Workers.types'
-import { filterWorkers, getPage } from './Workers.utils'
+import { WORKERS_REGION_SHORT } from './Workers.constants'
+import type { Worker, WorkerAccess, WorkerBuildState } from './Workers.types'
+import { filterWorkers, formatResources, getPage } from './Workers.utils'
import { WorkerStatePill } from './WorkerStatePill'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
@@ -32,12 +32,11 @@ interface WorkersListProps {
workers: Worker[]
}
-const STATE_FILTERS: { value: WorkerState | 'all'; label: string }[] = [
+const STATE_FILTERS: { value: WorkerBuildState | 'all'; label: string }[] = [
{ value: 'all', label: 'All states' },
{ value: 'active', label: 'Active' },
- { value: 'deploying', label: 'Deploying' },
- { value: 'suspended', label: 'Suspended' },
- { value: 'errored', label: 'Errored' },
+ { value: 'building', label: 'Building' },
+ { value: 'failed', label: 'Failed' },
]
const ACCESS_FILTERS: { value: WorkerAccess | 'all'; label: string }[] = [
@@ -48,7 +47,7 @@ const ACCESS_FILTERS: { value: WorkerAccess | 'all'; label: string }[] = [
const PAGE_SIZE = 10
-const parseStateFilter = (value: string): WorkerState | 'all' =>
+const parseStateFilter = (value: string): WorkerBuildState | 'all' =>
STATE_FILTERS.find((option) => option.value === value)?.value ?? 'all'
const parseAccessFilter = (value: string): WorkerAccess | 'all' =>
@@ -57,7 +56,7 @@ const parseAccessFilter = (value: string): WorkerAccess | 'all' =>
export const WorkersList = ({ projectRef, workers }: WorkersListProps) => {
const router = useRouter()
const [search, setSearch] = useState('')
- const [stateFilter, setStateFilter] = useState('all')
+ const [stateFilter, setStateFilter] = useState('all')
const [accessFilter, setAccessFilter] = useState('all')
const [page, setPage] = useState(1)
@@ -151,7 +150,7 @@ export const WorkersList = ({ projectRef, workers }: WorkersListProps) => {
)}
{paged.map((worker) => (
router.push(workerUrl(worker.name))}
>
@@ -161,7 +160,7 @@ export const WorkersList = ({ projectRef, workers }: WorkersListProps) => {
-
+
@@ -177,7 +176,7 @@ export const WorkersList = ({ projectRef, workers }: WorkersListProps) => {
{WORKERS_REGION_SHORT}
- {formatResources(worker.size, worker.instances)}
+ {formatResources(worker)}
))}
diff --git a/apps/studio/data/workers/worker-query.ts b/apps/studio/data/workers/worker-query.ts
index f3225e2c67c..ea2f4257724 100644
--- a/apps/studio/data/workers/worker-query.ts
+++ b/apps/studio/data/workers/worker-query.ts
@@ -1,18 +1,25 @@
import { queryOptions } from '@tanstack/react-query'
import { workersKeys } from './keys'
-import { mockWorkers } from './workers.mock'
+import { parseWorker } from './workers.utils'
+import { get, handleError } from '@/data/fetchers'
import { IS_PLATFORM } from '@/lib/constants'
import type { ResponseError } from '@/types'
export type WorkerVariables = { projectRef?: string; name?: string }
export type WorkerError = ResponseError
-// Mirrors GET /v2/projects/{ref}/workers/{name}, seeded for the same reason as the list query.
-async function getWorker({ projectRef, name }: WorkerVariables) {
+async function getWorker({ projectRef, name }: WorkerVariables, signal?: AbortSignal) {
if (!projectRef) throw new Error('projectRef is required')
if (!name) throw new Error('name is required')
- return mockWorkers().find((worker) => worker.name === name) ?? null
+
+ const { data, error } = await get('/v2/projects/{ref}/workers/{name}', {
+ params: { path: { ref: projectRef, name } },
+ signal,
+ })
+
+ if (error) handleError(error)
+ return parseWorker(data.data)
}
export type WorkerData = Awaited>
@@ -20,6 +27,6 @@ export type WorkerData = Awaited>
export const workerQueryOptions = ({ projectRef, name }: WorkerVariables) =>
queryOptions({
queryKey: workersKeys.detail(projectRef, name),
- queryFn: () => getWorker({ projectRef, name }),
+ queryFn: ({ signal }) => getWorker({ projectRef, name }, signal),
enabled: IS_PLATFORM && typeof projectRef !== 'undefined' && typeof name !== 'undefined',
})
diff --git a/apps/studio/data/workers/workers-query.ts b/apps/studio/data/workers/workers-query.ts
index 7f463a18bb4..1b593f3f59d 100644
--- a/apps/studio/data/workers/workers-query.ts
+++ b/apps/studio/data/workers/workers-query.ts
@@ -1,18 +1,24 @@
import { queryOptions } from '@tanstack/react-query'
import { workersKeys } from './keys'
-import { mockWorkers } from './workers.mock'
+import { parseWorker } from './workers.utils'
+import { get, handleError } from '@/data/fetchers'
import { IS_PLATFORM } from '@/lib/constants'
import type { ResponseError } from '@/types'
export type WorkersVariables = { projectRef?: string }
export type WorkersError = ResponseError
-// GET /v2/projects/{ref}/workers exists but is restricted to allowlisted projects, and its response
-// carries neither lifecycle events nor metrics. Seeded data until both are available (FUNC-774).
-async function getWorkers({ projectRef }: WorkersVariables) {
+async function getWorkers({ projectRef }: WorkersVariables, signal?: AbortSignal) {
if (!projectRef) throw new Error('projectRef is required')
- return mockWorkers()
+
+ const { data, error } = await get('/v2/projects/{ref}/workers', {
+ params: { path: { ref: projectRef } },
+ signal,
+ })
+
+ if (error) handleError(error)
+ return data.data.map(parseWorker)
}
export type WorkersData = Awaited>
@@ -20,6 +26,6 @@ export type WorkersData = Awaited>
export const workersQueryOptions = ({ projectRef }: WorkersVariables) =>
queryOptions({
queryKey: workersKeys.list(projectRef),
- queryFn: () => getWorkers({ projectRef }),
+ queryFn: ({ signal }) => getWorkers({ projectRef }, signal),
enabled: IS_PLATFORM && typeof projectRef !== 'undefined',
})
diff --git a/apps/studio/data/workers/workers.mock.ts b/apps/studio/data/workers/workers.mock.ts
deleted file mode 100644
index ff7ec79d7cd..00000000000
--- a/apps/studio/data/workers/workers.mock.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { WORKERS_REGION } from '@/components/interfaces/Workers/Workers.constants'
-import type { Worker } from '@/components/interfaces/Workers/Workers.types'
-
-// Timestamps derive from a fixed base rather than Date.now() so SSR and client render identically.
-const SEED_BASE = new Date('2026-08-10T09:00:00.000Z').getTime()
-const seedTime = (minutesAgo: number) => new Date(SEED_BASE - minutesAgo * 60_000).toISOString()
-
-export function mockWorkers(): Worker[] {
- return [
- {
- id: 'wk-embed',
- name: 'embed',
- runtime: 'python',
- size: '4x2',
- access: 'public',
- instances: 2,
- region: WORKERS_REGION,
- state: 'active',
- createdAt: seedTime(60 * 26),
- updatedAt: seedTime(42),
- events: [
- { id: 'ev-embed-1', at: seedTime(60 * 26), level: 'info', message: 'Deploy started' },
- {
- id: 'ev-embed-2',
- at: seedTime(60 * 26 - 1),
- level: 'info',
- message: 'Built image python:3.14-slim',
- },
- {
- id: 'ev-embed-3',
- at: seedTime(60 * 26 - 1),
- level: 'info',
- message: 'Worker active on 2 instances',
- },
- ],
- },
- {
- id: 'wk-resize-images',
- name: 'resize-images',
- runtime: 'node',
- size: '2x1',
- access: 'private',
- instances: 1,
- region: WORKERS_REGION,
- state: 'active',
- createdAt: seedTime(60 * 8),
- updatedAt: seedTime(15),
- events: [
- { id: 'ev-resize-1', at: seedTime(60 * 8), level: 'info', message: 'Deploy started' },
- {
- id: 'ev-resize-2',
- at: seedTime(60 * 8 - 1),
- level: 'info',
- message: 'Worker active on 1 instance',
- },
- ],
- },
- {
- id: 'wk-nightly-report',
- name: 'nightly-report',
- runtime: 'deno',
- size: '2x1',
- access: 'private',
- instances: 1,
- region: WORKERS_REGION,
- state: 'suspended',
- createdAt: seedTime(60 * 72),
- updatedAt: seedTime(60 * 5),
- events: [
- {
- id: 'ev-nightly-1',
- at: seedTime(60 * 5),
- level: 'info',
- message: 'Suspended after idle timeout',
- },
- ],
- },
- ]
-}
diff --git a/apps/studio/data/workers/workers.utils.ts b/apps/studio/data/workers/workers.utils.ts
new file mode 100644
index 00000000000..10c11caa5e1
--- /dev/null
+++ b/apps/studio/data/workers/workers.utils.ts
@@ -0,0 +1,24 @@
+import type { components } from 'api-types'
+
+import type { Worker, WorkerBuildState } from '@/components/interfaces/Workers/Workers.types'
+
+type WorkerDatum = components['schemas']['V2WorkerResponse']['data']
+
+const BUILD_STATES: WorkerBuildState[] = ['building', 'active', 'failed']
+
+const parseBuildState = (value: string): WorkerBuildState =>
+ BUILD_STATES.find((state) => state === value) ?? 'failed'
+
+export const parseWorker = ({ id, attributes }: WorkerDatum): Worker => ({
+ name: id,
+ buildState: parseBuildState(attributes.build_state),
+ isDeleting: attributes.deleting ?? false,
+ runtime: attributes.spec.runtime,
+ size: attributes.spec.size,
+ access: attributes.spec.exposure === 'public' ? 'public' : 'private',
+ declaredInstances: attributes.spec.instances,
+ instances: attributes.instances,
+ imageVersion: attributes.image_version,
+ stateReason: attributes.state_reason,
+ instancesError: attributes.instances_error,
+})