/** * Scaffolded Sentry OpenAPI client (dependency-free ESM). * * Source: openapi_client_scaffold against * https://raw.githubusercontent.com/getsentry/sentry-api-schema/main/openapi-derefed.json * Slugs: listorganizations, listorganizationprojects, listorganizationissues * Auth: bearerSecret sentryAuthToken * * Host defaults to sentry.io. Pass `host` for us.sentry.io, de.sentry.io, * or a self-hosted instance. Secret placeholders only resolve for approved * hosts. */ import { authorizationHeader, getSentryFetch, resolveIntegrationName, type SentryAuthSelection } from './auth.ts' import { sentryApiBaseUrl } from './host.ts' export type QueryValue = string | number | boolean | null | undefined export type QueryInput = Record function authHeaders(auth: SentryAuthSelection = {}): Record { if (resolveIntegrationName(auth)) return {} return { Authorization: authorizationHeader(auth) } } export function buildUrl( pathTemplate: string, params: Record = {}, host?: unknown, ): string { return sentryApiBaseUrl(host) + pathTemplate.replace(/\{([^}]+)\}/g, (_match, name: string) => { const value = params[name] if (value === undefined || value === null) { throw new Error(`Missing required path parameter: ${name}`) } return encodeURIComponent(String(value)) }) } export function appendQuery(url: string, query: QueryInput = {}): string { const search = new URLSearchParams() for (const [key, value] of Object.entries(query)) { if (value === undefined || value === null || value === '') continue if (Array.isArray(value)) { for (const item of value) { if (item === undefined || item === null || item === '') continue search.append(key, String(item)) } continue } search.append(key, String(value)) } const qs = search.toString() return qs ? `${url}?${qs}` : url } function mergeHeaders( userHeaders: Record | undefined, authHeaders: Record, ): Record { const merged: Record = { ...(userHeaders ?? {}) } for (const [key, value] of Object.entries(authHeaders)) { const lower = key.toLowerCase() for (const existing of Object.keys(merged)) { if (existing.toLowerCase() === lower) delete merged[existing] } merged[key] = value } return merged } export type ScaffoldInput = SentryAuthSelection & { params?: Record query?: QueryInput headers?: Record body?: unknown host?: string } function requestAuth(input: SentryAuthSelection = {}): SentryAuthSelection { return { secretName: input.secretName, integration: input.integration } } async function sentryFetch(url: string, init: RequestInit, auth: SentryAuthSelection = {}): Promise { const fetchFn = await getSentryFetch(auth) return fetchFn(url, init) } /** GET /api/0/organizations/ — List Your Organizations */ export async function listorganizations(input: ScaffoldInput = {}): Promise { const params = input.params ?? {} const url = appendQuery(buildUrl('/api/0/organizations/', params, input.host), input.query) const auth = requestAuth(input) return sentryFetch(url, { method: 'GET', headers: mergeHeaders(input.headers, authHeaders(auth)) }, auth) } /** GET /api/0/organizations/{organization_id_or_slug}/projects/ */ export async function listorganizationprojects(input: ScaffoldInput = {}): Promise { const params = input.params ?? {} if (params.organization_id_or_slug === undefined || params.organization_id_or_slug === null) { throw new Error('Missing required path parameter: organization_id_or_slug') } const url = appendQuery( buildUrl('/api/0/organizations/{organization_id_or_slug}/projects/', params, input.host), input.query, ) const auth = requestAuth(input) return sentryFetch(url, { method: 'GET', headers: mergeHeaders(input.headers, authHeaders(auth)) }, auth) } /** GET /api/0/organizations/{organization_id_or_slug}/issues/ */ export async function listorganizationissues(input: ScaffoldInput = {}): Promise { const params = input.params ?? {} if (params.organization_id_or_slug === undefined || params.organization_id_or_slug === null) { throw new Error('Missing required path parameter: organization_id_or_slug') } const url = appendQuery( buildUrl('/api/0/organizations/{organization_id_or_slug}/issues/', params, input.host), input.query, ) const auth = requestAuth(input) return sentryFetch(url, { method: 'GET', headers: mergeHeaders(input.headers, authHeaders(auth)) }, auth) } /** * Minimal fetch for endpoints absent from the public OpenAPI inventory * (e.g. /api/0/issues/{id}/events/latest/). Path is relative to /api/0. */ export async function rawSentryRequest( path: string, options: { query?: QueryInput; init?: RequestInit; host?: string } & SentryAuthSelection = {}, ): Promise { const normalizedPath = path.startsWith('/') ? path : '/' + path const url = appendQuery(`${sentryApiBaseUrl(options.host)}/api/0${normalizedPath}`, options.query) const auth = requestAuth(options) const headers = mergeHeaders( { accept: 'application/json', ...(options.init?.headers as Record | undefined), }, authHeaders(auth), ) return sentryFetch(url, { ...options.init, headers }, auth) }