← Public packages
@kody/openapi
Bind an OpenAPI spec and call selected operations with saved integration or secret names.
src/bind.ts
99 lines · 3.3 KB · TypeScriptimport { 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 {
assertBindingName,
assertOpenApiBindingWithinSizeLimit,
normalizeApiBaseUrl,
normalizeSelection,
resolveOpenApiSelection,
toOpenApiBindingSummary,
type OpenApiBinding,
type OpenApiBindingSelection,
type OpenApiBindingSummary,
} from './selection.ts'
import { loadBindings, saveBindings } from './storage.ts'
export type BindInput = {
/** Binding key used later in call({ name }). */
name: string
/** HTTPS URL of the OpenAPI 3.x document. */
specUrl: string
/** HTTPS API origin operations are pinned to. Spec servers never override this host. */
apiBaseUrl: string
/** Credential names only — never raw tokens. */
auth: OpenApiBoundAuth
/** At least one of operationIds, tags, or pathPrefixes. Max 100 operations. */
selection: OpenApiBindingSelection
/** When false (default), DELETE operations are excluded even if selected. */
includeDestructive?: boolean
description?: string | null
/** Skip the spec fetch when you already have the document text. */
specText?: string
}
export type BindOutput = {
binding: OpenApiBindingSummary
operationSlugs: Array<string>
warnings: Array<string>
}
/**
* Create or replace a named OpenAPI binding in this package's storage.
* Use after community_fork of @kody/openapi, when you want reusable
* call({ name, operation }) instead of a one-off client.
*
* @param input - Spec URL, api base, auth names, and a narrow selection
* @returns Binding summary, selected slugs, and parse/selection warnings
*
* @example
* import bind from 'kody:@<username>/openapi/bind'
* await bind({
* name: 'acme',
* specUrl: 'https://api.example.com/openapi.json',
* apiBaseUrl: 'https://api.example.com',
* auth: { kind: 'integration', provider: 'acme' },
* selection: { pathPrefixes: ['/widgets'] },
* })
*/
export default async function bind(input: BindInput): Promise<BindOutput> {
const name = assertBindingName(input.name)
const specUrl = assertHttpsUrl(input.specUrl, 'specUrl').toString()
const apiBaseUrl = normalizeApiBaseUrl(
assertHttpsUrl(input.apiBaseUrl, 'apiBaseUrl').toString(),
)
const auth = assertOpenApiBoundAuth(input.auth)
const selection = normalizeSelection(input.selection)
const includeDestructive = input.includeDestructive === true
const specText =
input.specText ?? (await fetchOpenApiSpecText({ specUrl }))
const parsed = parseOpenApiSpec(specText)
const resolved = resolveOpenApiSelection({
operations: parsed.operations,
selection,
includeDestructive,
})
const binding: OpenApiBinding = {
name,
specUrl,
apiBaseUrl,
description: input.description ?? null,
auth,
selection,
includeDestructive,
specTitle: parsed.title,
specVersion: parsed.version,
operations: resolved.operations,
updatedAt: new Date().toISOString(),
}
assertOpenApiBindingWithinSizeLimit(binding)
const bindings = await loadBindings()
bindings[name] = binding
await saveBindings(bindings)
return {
binding: toOpenApiBindingSummary(binding),
operationSlugs: binding.operations.map((operation) => operation.slug),
warnings: [...parsed.warnings, ...resolved.warnings],
}
}