Skip to content
← Public packages

@kody/api-research

Research third-party APIs: registry search, provider discovery, OpenAPI summarize, and client scaffold.

src/scaffold-client.ts

83 lines · 3.0 KB · TypeScript
import { assertOpenApiBoundAuth, type OpenApiBoundAuth } from './auth.ts'
import { fetchOpenApiSpecText } from './fetch-spec-lib.ts'
import { assertHttpsUrl } from './https.ts'
import { parseOpenApiSpec } from './parse-spec-lib.ts'
import {
	scaffoldOpenApiClient,
	type OpenApiClientScaffold,
} from './scaffold-client-lib.ts'

export type ScaffoldClientInput = {
	/** HTTPS URL of an OpenAPI 3.x document (JSON or YAML). */
	specUrl: string
	/** Operation slugs from summarize-spec (stable snake_case ids). */
	operationIds: Array<string>
	/** How the generated client authenticates. Names only — never credential values. */
	auth: OpenApiBoundAuth
	/** Absolute https API base URL used by the generated client. */
	apiBaseUrl: string
	/** Optional label included only in generated source comments. */
	providerLabel?: string
}

export type ScaffoldClientOutput = OpenApiClientScaffold & {
	usageNotes: string
}

function buildUsageNotes(authKind: string): string {
	const integrationNote =
		authKind === 'integration'
			? ' For integration auth, complete the connect flow for that provider first.'
			: ''
	return [
		'Paste moduleSource into execute as an ephemeral module, or into a package client.ts.',
		'Hosts and secrets still require approval in the account security UI; the OpenAPI spec never widens host approval.',
		`Auth kind for this scaffold: ${authKind}.${integrationNote}`,
	].join(' ')
}

/**
 * Fetch an OpenAPI spec and scaffold a small dependency-free ESM client.
 * Use after summarize-spec to turn chosen operation slugs into package source.
 *
 * @param input - Spec URL, selected slugs, auth names, and API base URL
 * @returns Generated module source plus selected operations
 *
 * @example
 * import scaffoldClient from 'kody:@kody/api-research/scaffold-client'
 * const client = await scaffoldClient({
 *   specUrl: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
 *   operationIds: ['users_get_authenticated'],
 *   auth: { kind: 'integration', provider: 'github' },
 *   apiBaseUrl: 'https://api.github.com',
 * })
 */
export default async function scaffoldClient(
	input: ScaffoldClientInput,
): Promise<ScaffoldClientOutput> {
	assertHttpsUrl(input.specUrl, 'OpenAPI spec URL')
	assertHttpsUrl(input.apiBaseUrl, 'API base URL')
	const auth = assertOpenApiBoundAuth(input.auth)
	if (!Array.isArray(input.operationIds) || input.operationIds.length === 0) {
		throw new Error('operationIds must include at least one slug')
	}
	if (input.operationIds.length > 50) {
		throw new Error('operationIds is limited to 50 slugs')
	}

	const rawText = await fetchOpenApiSpecText({ specUrl: input.specUrl })
	const parsed = parseOpenApiSpec(rawText)
	const scaffold = scaffoldOpenApiClient({
		spec: parsed,
		operationSlugs: input.operationIds,
		auth,
		apiBaseUrl: input.apiBaseUrl,
		providerLabel: input.providerLabel,
	})
	return {
		...scaffold,
		usageNotes: buildUsageNotes(auth.kind),
	}
}

export { scaffoldOpenApiClient } from './scaffold-client-lib.ts'