Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/fly

src/machines.ts

152 lines · 5.1 KB · TypeScript
import {
	clean,
	machinesGet,
	machinesListForApp,
	machinesListForOrg,
	machinesRequest,
	mutationPreview,
	resolveOrganizationSlug,
	summarizeMachine,
} from './fly-core.ts'
import type { FlyMachineMutationInput, FlyMachinesInput } from './types.ts'

export async function listMachines(params: FlyMachinesInput = {}) {
	const appName = String(params.appName || params.app || '').trim()
	const appId = String(params.appId || '').trim()
	const state = String(params.state || '').trim() || undefined

	if (appName) {
		const machines = await machinesListForApp(appName, { ...params, state })
		const summarized = machines.map((machine: any) => summarizeMachine(machine, { name: appName }))
		return { count: summarized.length, appName, machines: summarized }
	}

	if (appId && !appName) {
		const orgResult = await machinesListForOrg({ ...params, state })
		const machines = orgResult.machines
			.filter((machine: any) => machine?.app?.id === appId || machine?.app_id === appId)
			.map((machine: any) => summarizeMachine(machine))
		return {
			count: machines.length,
			organization: orgResult.organization,
			machines,
		}
	}

	const orgResult = await machinesListForOrg({ ...params, state })
	const machines = orgResult.machines.map((machine: any) => summarizeMachine(machine))
	return {
		count: machines.length,
		organization: orgResult.organization,
		orgSlug: orgResult.organization.slug,
		machines,
	}
}

export async function getMachine(params: FlyMachinesInput = {}) {
	const appName = String(params.appName || params.app || '').trim()
	const machineId = String(params.machineId || params.id || '').trim()
	if (!appName) throw new Error('appName is required.')
	if (!machineId) throw new Error('machineId is required.')
	const machine = await machinesGet(appName, machineId, params)
	return {
		machine: summarizeMachine(machine, { name: appName }),
		raw: machine,
	}
}

function mutationTarget(params: FlyMachineMutationInput) {
	const appName = clean(params.appName || params.app)
	const machineId = clean(params.machineId || params.id)
	if (!appName) throw new Error('appName is required.')
	if (!machineId) throw new Error('machineId is required.')
	return { appName, machineId }
}

function machinePath(appName: string, machineId: string, suffix = '') {
	return (
		'/apps/' +
		encodeURIComponent(appName) +
		'/machines/' +
		encodeURIComponent(machineId) +
		suffix
	)
}

/**
 * Start a Fly Machine. Pass `dryRun: true` to preview without contacting Fly.
 */
export async function startMachine(params: FlyMachineMutationInput = {}) {
	const { appName, machineId } = mutationTarget(params)
	const path = machinePath(appName, machineId, '/start')
	const preview = mutationPreview(params, {
		action: 'start machine ' + machineId + ' on ' + appName,
		method: 'POST',
		path,
	})
	if (preview) return preview
	const result = await machinesRequest(path, { ...params, method: 'POST' })
	return { ok: true, action: 'start', appName, machineId, result }
}

/**
 * Stop a Fly Machine. Pass `dryRun: true` to preview without contacting Fly.
 */
export async function stopMachine(params: FlyMachineMutationInput = {}) {
	const { appName, machineId } = mutationTarget(params)
	const path = machinePath(appName, machineId, '/stop')
	const body: Record<string, unknown> = {}
	if (params.timeout != null) body.timeout = params.timeout
	if (clean(params.signal)) body.signal = clean(params.signal)
	const hasBody = Object.keys(body).length > 0
	const preview = mutationPreview(params, {
		action: 'stop machine ' + machineId + ' on ' + appName,
		method: 'POST',
		path,
		body: hasBody ? body : undefined,
	})
	if (preview) return preview
	const result = await machinesRequest(path, {
		...params,
		method: 'POST',
		body: hasBody ? body : undefined,
	})
	return { ok: true, action: 'stop', appName, machineId, result }
}

/**
 * Destroy a Fly Machine. Requires `confirm: true`. Pass `dryRun: true` to preview.
 * Never call this from a smoke test or scheduled job.
 */
export async function destroyMachine(params: FlyMachineMutationInput = {}) {
	const { appName, machineId } = mutationTarget(params)
	const path = machinePath(appName, machineId)
	const query = params.force === true ? { force: true } : undefined
	const preview = mutationPreview(params, {
		action: 'destroy machine ' + machineId + ' on ' + appName,
		method: 'DELETE',
		path,
		query,
		requireConfirm: true,
	})
	if (preview) return preview
	const result = await machinesRequest(path, { ...params, method: 'DELETE', query })
	return { ok: true, action: 'destroy', appName, machineId, result }
}

/**
 * List Fly machines via Machines REST (org-wide or by `appName`), optionally filtered by `state`.
 * @returns Machine count and summarized machine records.
 * @example
 * import machines from 'kody:@kody/fly/machines'
 * const result = await machines({ organizationSlug: 'my-org', state: 'started' })
 * // => { count: 5, machines: [...] }
 */
export default async function machines(params: FlyMachinesInput & FlyMachineMutationInput = {}) {
	if (params.machineId || params.id) {
		return await getMachine(params)
	}
	return await listMachines(params)
}

export { resolveOrganizationSlug }