|
1 | 1 | import { env } from './env.js'; |
2 | | -import { listRepositoriesResponseSchema, searchResponseSchema, fileSourceResponseSchema, searchCommitsResponseSchema } from './schemas.js'; |
3 | | -import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse, ServiceError, SearchCommitsRequest, SearchCommitsResponse } from './types.js'; |
4 | | -import { isServiceError } from './utils.js'; |
| 2 | +import { listReposResponseSchema, searchResponseSchema, fileSourceResponseSchema, listCommitsResponseSchema } from './schemas.js'; |
| 3 | +import { FileSourceRequest, ListReposQueryParams, SearchRequest, ListCommitsQueryParamsSchema } from './types.js'; |
| 4 | +import { isServiceError, ServiceErrorException } from './utils.js'; |
| 5 | +import { z } from 'zod'; |
5 | 6 |
|
6 | | -export const search = async (request: SearchRequest): Promise<SearchResponse | ServiceError> => { |
7 | | - const result = await fetch(`${env.SOURCEBOT_HOST}/api/search`, { |
| 7 | +const parseResponse = async <T extends z.ZodTypeAny>( |
| 8 | + response: Response, |
| 9 | + schema: T |
| 10 | +): Promise<z.infer<T>> => { |
| 11 | + const text = await response.text(); |
| 12 | + |
| 13 | + let json: unknown; |
| 14 | + try { |
| 15 | + json = JSON.parse(text); |
| 16 | + } catch { |
| 17 | + throw new Error(`Invalid JSON response: ${text}`); |
| 18 | + } |
| 19 | + |
| 20 | + // Check if the response is already a service error from the API |
| 21 | + if (isServiceError(json)) { |
| 22 | + throw new ServiceErrorException(json); |
| 23 | + } |
| 24 | + |
| 25 | + const parsed = schema.safeParse(json); |
| 26 | + if (!parsed.success) { |
| 27 | + throw new Error(`Failed to parse response: ${parsed.error.message}`); |
| 28 | + } |
| 29 | + |
| 30 | + return parsed.data; |
| 31 | +}; |
| 32 | + |
| 33 | +export const search = async (request: SearchRequest) => { |
| 34 | + const response = await fetch(`${env.SOURCEBOT_HOST}/api/search`, { |
8 | 35 | method: 'POST', |
9 | 36 | headers: { |
10 | 37 | 'Content-Type': 'application/json', |
11 | 38 | ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) |
12 | 39 | }, |
13 | 40 | body: JSON.stringify(request) |
14 | | - }).then(response => response.json()); |
| 41 | + }); |
15 | 42 |
|
16 | | - if (isServiceError(result)) { |
17 | | - return result; |
18 | | - } |
19 | | - |
20 | | - return searchResponseSchema.parse(result); |
| 43 | + return parseResponse(response, searchResponseSchema); |
21 | 44 | } |
22 | 45 |
|
23 | | -export const listRepos = async (): Promise<ListRepositoriesResponse | ServiceError> => { |
24 | | - const result = await fetch(`${env.SOURCEBOT_HOST}/api/repos`, { |
| 46 | +export const listRepos = async (queryParams: ListReposQueryParams = {}) => { |
| 47 | + const url = new URL(`${env.SOURCEBOT_HOST}/api/repos`); |
| 48 | + |
| 49 | + for (const [key, value] of Object.entries(queryParams)) { |
| 50 | + if (value) { |
| 51 | + url.searchParams.set(key, value.toString()); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + const response = await fetch(url, { |
25 | 56 | method: 'GET', |
26 | 57 | headers: { |
27 | 58 | 'Content-Type': 'application/json', |
28 | 59 | ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) |
29 | 60 | }, |
30 | | - }).then(response => response.json()); |
31 | | - |
32 | | - if (isServiceError(result)) { |
33 | | - return result; |
34 | | - } |
| 61 | + }); |
35 | 62 |
|
36 | | - return listRepositoriesResponseSchema.parse(result); |
| 63 | + const repos = await parseResponse(response, listReposResponseSchema); |
| 64 | + const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10); |
| 65 | + return { repos, totalCount }; |
37 | 66 | } |
38 | 67 |
|
39 | | -export const getFileSource = async (request: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => { |
40 | | - const result = await fetch(`${env.SOURCEBOT_HOST}/api/source`, { |
41 | | - method: 'POST', |
| 68 | +export const getFileSource = async (request: FileSourceRequest) => { |
| 69 | + const url = new URL(`${env.SOURCEBOT_HOST}/api/source`); |
| 70 | + for (const [key, value] of Object.entries(request)) { |
| 71 | + if (value) { |
| 72 | + url.searchParams.set(key, value.toString()); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + const response = await fetch(url, { |
| 77 | + method: 'GET', |
42 | 78 | headers: { |
43 | | - 'Content-Type': 'application/json', |
44 | 79 | ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) |
45 | 80 | }, |
46 | | - body: JSON.stringify(request) |
47 | | - }).then(response => response.json()); |
48 | | - |
49 | | - if (isServiceError(result)) { |
50 | | - return result; |
51 | | - } |
| 81 | + }); |
52 | 82 |
|
53 | | - return fileSourceResponseSchema.parse(result); |
| 83 | + return parseResponse(response, fileSourceResponseSchema); |
54 | 84 | } |
55 | 85 |
|
56 | | -export const searchCommits = async (request: SearchCommitsRequest): Promise<SearchCommitsResponse | ServiceError> => { |
57 | | - const result = await fetch(`${env.SOURCEBOT_HOST}/api/commits`, { |
58 | | - method: 'POST', |
| 86 | +export const listCommits = async (queryParams: ListCommitsQueryParamsSchema) => { |
| 87 | + const url = new URL(`${env.SOURCEBOT_HOST}/api/commits`); |
| 88 | + for (const [key, value] of Object.entries(queryParams)) { |
| 89 | + if (value) { |
| 90 | + url.searchParams.set(key, value.toString()); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + const response = await fetch(url, { |
| 95 | + method: 'GET', |
59 | 96 | headers: { |
60 | | - 'Content-Type': 'application/json', |
61 | 97 | 'X-Org-Domain': '~', |
62 | 98 | ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) |
63 | 99 | }, |
64 | | - body: JSON.stringify(request) |
65 | | - }).then(response => response.json()); |
66 | | - |
67 | | - if (isServiceError(result)) { |
68 | | - return result; |
69 | | - } |
| 100 | + }); |
70 | 101 |
|
71 | | - return searchCommitsResponseSchema.parse(result); |
| 102 | + const commits = await parseResponse(response, listCommitsResponseSchema); |
| 103 | + const totalCount = parseInt(response.headers.get('X-Total-Count') ?? '0', 10); |
| 104 | + return { commits, totalCount }; |
72 | 105 | } |
0 commit comments