import type * as listRepos from '#lib/lexicons/dev/knotless/listRepos.ts'; import type * as searchCode from '#lib/lexicons/dev/knotless/searchCode.ts'; import type * as searchOwners from '#lib/lexicons/dev/knotless/searchOwners.ts'; import type { Answer } from '#lib/data.ts'; import type { PageLoad } from './$types'; import { xrpc } from '#lib/data.ts'; /** What a code search found, or why it found nothing. */ export type CodeFound = | { kind: 'found'; answer: Answer } | { kind: 'off' } | { kind: 'bad'; message: string } | { kind: 'missing'; repo: string } | { kind: 'none' }; /** `repo:owner/name` in a query, which searches that repository's code alone. */ const REPO = /(?:^|\s)repo:(\S+?)\/(\S+)(?=\s|$)/; /** * What the results page shows of `q`: its code, with `type=code`, else * the repositories that match it, or every one, newest first, those about * `topic` alone when one is named, and the owners whose handles hold it. * Code is searched in one repository when `q` names it as * `repo:owner/name`. None of either, without an index. Each kind is * counted, for a search, by asking for the other kind too. */ export const load: PageLoad = async (event) => { const q = (event.url.searchParams.get('q') ?? '').trim(); const topic = (event.url.searchParams.get('topic') ?? '').trim() || undefined; const type = event.url.searchParams.get('type') === 'code' ? 'code' : 'repositories'; // The page is cached as the repositories are. const quiet = { ...event, setHeaders: () => {} }; // Each kind's count beside it, for a search: the other kind asked too. // Repositories are asked for with the words alone, not a code search's `lang:`. const words = type === 'code' ? q.replace(/(^|\s)-?lang:("[^"]*"|\S+)/g, ' ').trim() : q; const listed = (e: typeof event) => words || type === 'repositories' ? xrpc(e, 'dev.knotless.listRepos', { q: words, topic }, true) : null; if (type === 'code') { const cursor = event.url.searchParams.get('cursor') ?? undefined; const [found, repos] = await Promise.all([ code(event, q, undefined, cursor), REPO.test(q) ? null : listed(quiet) ]); const counts = { code: found.kind === 'found' ? found.answer.fileCount : undefined, ...reposCounted(repos) }; return { query: q, topic, type, code: found, counts, repos: [], owners: [], indexed: true }; } const [found, owners, files] = await Promise.all([ listed(event), q ? xrpc(quiet, 'dev.knotless.searchOwners', { q }, true) : null, q && !topic ? code(quiet, q, 1) : null ]); // Each with its owner, as the list shows it; whose fork it is, it does not. const repos = found?.repos.flatMap(({ forkOf: _, owner, ...r }) => (owner ? [{ ...r, owner }] : [])) ?? []; const none: CodeFound = { kind: 'none' }; const counts = { code: files?.kind === 'found' ? files.answer.fileCount : undefined, ...(q ? reposCounted(found) : {}) }; return { query: q, topic, type, code: none, counts, repos, owners: owners?.owners ?? [], indexed: found !== null }; }; /** How many repositories a page of them counts, `more` when there are others after it. */ function reposCounted(found: Answer | null) { return found ? { repos: found.repos.length, more: found.cursor !== undefined } : {}; } /** * The code `q` finds, after the files `cursor` says the pages before * held: asked here rather than by `xrpc`, as a query Zoekt * cannot read, or a server that searches no code, is said on the page * rather than failing it. */ async function code( event: Parameters[0], q: string, limit?: number, cursor?: string ): Promise { const named = REPO.exec(q); const rest = named ? q.replace(named[0], ' ').trim() : q; if (!rest) return { kind: 'none' }; const params = new URLSearchParams({ q: rest }); if (named) params.set('repo', `at://${named[1]}/dev.knotless.repo/${named[2]}`); if (limit) params.set('limit', String(limit)); if (cursor) params.set('cursor', cursor); const res = await event.fetch(`/xrpc/dev.knotless.searchCode?${params}`, { cache: 'no-cache' }); if (res.ok) return { kind: 'found', answer: await res.json() }; if (res.status === 501) return { kind: 'off' }; // The repository `repo:` names is no one's. if (res.status === 404 && named) return { kind: 'missing', repo: `${named[1]}/${named[2]}` }; const failure: { message?: string } | null = await res.json().catch(() => null); return { kind: 'bad', message: failure?.message ?? res.statusText }; }