← Public packages
@kody/integrations-sh
Client for the public integrations.sh registry: search, detect, cached surface, and live discover.
src/discover.ts
118 lines · 3.3 KB · TypeScriptimport { fetchRegistryJson } from './client.ts'
import { normalizeProviderDomain } from './domain.ts'
import {
hasUsableRegistryData,
mapSurfaceDocument,
type SurfaceDocument,
} from './types.ts'
import { buildDiscoverUrl, buildSurfaceUrl } from './urls.ts'
const SURFACE_FETCH_TIMEOUT_MS = 15_000
const LIVE_DISCOVER_FETCH_TIMEOUT_MS = 75_000
const CACHED_DISCOVERY_STALE_AFTER_MS = 30 * 24 * 60 * 60 * 1000
export type DiscoverInput = {
/** Bare provider hostname (for example linear.app). */
domain: string
/** Skip the cache and always call live discover. */
live?: boolean
}
export type DiscoverOutput = SurfaceDocument & {
source: string
provenance: 'cached' | 'live'
liveDiscoveryError: string | null
}
function isCachedDiscoveryFresh(document: SurfaceDocument): boolean {
if (document.discoveredAt == null) {
return false
}
const discoveredAt = Date.parse(document.discoveredAt)
if (Number.isNaN(discoveredAt)) {
return false
}
return Date.now() - discoveredAt <= CACHED_DISCOVERY_STALE_AFTER_MS
}
/**
* Load integrations.sh discovery for a provider domain.
* Uses a fresh cached surface when available, otherwise live discover.
*
* @param input - Bare provider hostname, optional live=true
* @returns Untrusted surfaces, credentials, and provenance
*
* @example
* import discover from 'kody:@<username>/integrations-sh/discover'
* const found = await discover({ domain: 'github.com' })
*/
export default async function discover(
input: DiscoverInput,
): Promise<DiscoverOutput> {
const domain = normalizeProviderDomain(input.domain)
const surfaceUrl = buildSurfaceUrl(domain)
const discoverUrl = buildDiscoverUrl(domain)
const cached = input.live
? null
: await fetchRegistryJson(surfaceUrl, SURFACE_FETCH_TIMEOUT_MS)
const cachedDocument =
cached?.outcome === 'success'
? mapSurfaceDocument(cached.json)
: null
const usableCached =
cachedDocument != null && hasUsableRegistryData(cachedDocument)
? cachedDocument
: null
if (usableCached != null && isCachedDiscoveryFresh(usableCached)) {
return {
...usableCached,
source: surfaceUrl,
provenance: 'cached',
liveDiscoveryError: null,
}
}
const live = await fetchRegistryJson(
discoverUrl,
LIVE_DISCOVER_FETCH_TIMEOUT_MS,
)
if (live.outcome === 'success') {
const liveDocument = mapSurfaceDocument(live.json)
if (liveDocument != null) {
return {
...liveDocument,
source: discoverUrl,
provenance: 'live',
liveDiscoveryError: null,
}
}
}
const liveFailureMessage =
live.outcome === 'not-found'
? `HTTP 404 for ${discoverUrl}`
: live.outcome === 'failure'
? live.message
: 'unexpected response shape'
if (usableCached != null) {
return {
...usableCached,
source: surfaceUrl,
provenance: 'cached',
liveDiscoveryError: `live discovery failed (${liveFailureMessage}); returning cached registry data discovered at ${usableCached.discoveredAt ?? 'an unknown time'}`,
}
}
if (live.outcome === 'not-found' && cached?.outcome === 'not-found') {
throw new Error(
`Domain "${domain}" is not in the integrations.sh registry. Try search to find the canonical provider domain.`,
)
}
throw new Error(
`integrations.sh discover failed: ${liveFailureMessage}. No usable cached surface data is available for "${domain}".`,
)
}