Skip to content

src/http.ts

63 lines · 1.8 KB · TypeScript
Raw
import { type OriginQuery, type OriginRequestResult } from './types.ts'

export async function originFetch(input: {
	path: string
	method?: string
	query?: OriginQuery
	body?: unknown
	bearerToken: string
}): Promise<OriginRequestResult> {
	const url = new URL(resolveOriginPath(input.path), 'https://api.cursor.com')
	if (input.query) {
		for (const [key, value] of Object.entries(input.query)) {
			if (value === undefined || value === null) continue
			url.searchParams.set(key, String(value))
		}
	}
	const method = (input.method ?? 'GET').toUpperCase()
	const headers: Record<string, string> = {
		Authorization: `Bearer ${input.bearerToken}`,
		Accept: 'application/json',
	}
	let body: string | undefined
	if (input.body !== undefined && method !== 'GET' && method !== 'HEAD') {
		headers['Content-Type'] = 'application/json'
		body = JSON.stringify(input.body)
	}
	const response = await fetch(url.toString(), { method, headers, body })
	const text = await response.text()
	let parsed: unknown = text
	if (text) {
		try {
			parsed = JSON.parse(text)
		} catch {
			parsed = text
		}
	} else {
		parsed = null
	}
	return {
		ok: response.ok,
		status: response.status,
		body: parsed,
		rateLimit: {
			limit: response.headers.get('x-ratelimit-limit'),
			remaining: response.headers.get('x-ratelimit-remaining'),
			used: response.headers.get('x-ratelimit-used'),
			reset: response.headers.get('x-ratelimit-reset'),
		},
	}
}

function resolveOriginPath(path: string): string {
	const trimmed = path.trim()
	if (!trimmed) {
		throw new Error('Origin request path is required.')
	}
	if (/^https?:\/\//i.test(trimmed)) {
		throw new Error('Pass an Origin API path, not an absolute URL.')
	}
	if (trimmed.startsWith('/v1/origin/')) return trimmed
	const suffix = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
	return `/v1/origin${suffix}`
}