Skip to content
← Public packages

@kody/integrations-sh

Client for the public integrations.sh registry: search, detect, cached surface, and live discover.

src/client.ts

68 lines · 1.6 KB · TypeScript
import {
	BoundedBodyTooLargeError,
	readBoundedBody,
} from './bounded-body.ts'
import { getErrorMessage } from './error-message.ts'

const DEFAULT_MAX_BODY_BYTES = 500_000

export type RegistryFetchResult =
	| { outcome: 'success'; status: number; json: unknown }
	| { outcome: 'not-found'; status: 404 }
	| { outcome: 'failure'; status: number | null; message: string }

export async function fetchRegistryJson(
	url: string,
	timeoutMs: number,
	maxBytes = DEFAULT_MAX_BODY_BYTES,
): Promise<RegistryFetchResult> {
	let response: Response
	try {
		response = await fetch(url, {
			headers: { Accept: 'application/json' },
			redirect: 'follow',
			signal: AbortSignal.timeout(timeoutMs),
		})
	} catch (cause) {
		return { outcome: 'failure', status: null, message: getErrorMessage(cause) }
	}

	if (response.status === 404) {
		return { outcome: 'not-found', status: 404 }
	}

	let body: string
	try {
		body = await readBoundedBody(response, maxBytes)
	} catch (cause) {
		if (cause instanceof BoundedBodyTooLargeError) {
			return { outcome: 'failure', status: response.status, message: cause.message }
		}
		return {
			outcome: 'failure',
			status: response.status,
			message: getErrorMessage(cause),
		}
	}

	let parsed: unknown
	try {
		parsed = JSON.parse(body)
	} catch (cause) {
		return {
			outcome: 'failure',
			status: response.status,
			message: `invalid JSON (${getErrorMessage(cause)})`,
		}
	}

	if (!response.ok) {
		return {
			outcome: 'failure',
			status: response.status,
			message: `HTTP ${response.status} for ${url}`,
		}
	}

	return { outcome: 'success', status: response.status, json: parsed }
}