hts/internal/vercel/src/client.ts

136 lines
3.6 KiB
TypeScript

import { Err, FetchError, Result } from "@aigxion/error";
import { z } from "zod";
type VercelErrorResponse = {
error: string;
message: string;
};
const projectSchema = z.object({
id: z.string(),
name: z.string(),
});
export type Project = z.infer<typeof projectSchema>;
const environmentVariable = z.object({});
export type EnvironmentVariable = z.infer<typeof environmentVariable>;
export class Vercel {
private readonly baseUrl: string;
private readonly token: string;
private readonly teamId: string | null;
constructor(opts: { accessToken: string; baseUrl?: string; teamId?: string }) {
this.baseUrl = opts.baseUrl ?? "https://api.vercel.com";
this.token = opts.accessToken;
this.teamId = opts.teamId ?? null;
}
private async fetch<TResult>(req: {
method: "GET" | "POST" | "PUT" | "DELETE";
path: string[];
parameters?: Record<string, unknown>;
opts?: { cache?: RequestCache; revalidate?: number };
body?: unknown;
}): Promise<Result<TResult, FetchError>> {
try {
const url = new URL(req.path.join("/"), this.baseUrl);
if (req.parameters) {
for (const [key, value] of Object.entries(req.parameters)) {
if (typeof value === "undefined" || value === null) {
continue;
}
url.searchParams.set(key, value.toString());
}
}
if (this.teamId) {
url.searchParams.set("teamId", this.teamId);
}
const res = await fetch(url, {
method: req.method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.token}`,
},
body: req.body ? JSON.stringify(req.body) : undefined,
cache: req.opts?.cache,
// @ts-ignore
next: {
revalidate: req.opts?.revalidate,
},
});
if (!res.ok) {
const error = (await res.json()) as VercelErrorResponse;
console.error(error);
return Err(
new FetchError(error.message, {
retry: true,
context: {
url: url.toString(),
method: req.method,
},
}),
);
}
const body = await res.json();
// @ts-ignore
return result.success(body);
} catch (e) {
return Err(
new FetchError((e as Error).message, {
retry: true,
}),
);
}
}
public async getProject(projectId: string): Promise<Result<Project, FetchError>> {
return this.fetch({
method: "GET",
path: ["v9", "projects", projectId],
});
}
public async listProjects(): Promise<Result<Project[], FetchError>> {
const res = await this.fetch<{ projects: Project[] }>({
method: "GET",
path: ["v9", "projects"],
});
if (res.err) {
return res;
}
// @ts-ignore
return result.success(res.value.projects);
}
public async upsertEnvironmentVariable(
projectId: string,
environment: string,
key: string,
value: string,
sensitive?: boolean,
): Promise<Result<{ created: { id: string } }, FetchError>> {
return await this.fetch({
method: "POST",
path: ["v10", "projects", projectId, "env"],
parameters: { upsert: true },
body: {
key,
value,
type: sensitive ? (environment === "development" ? "encrypted" : "sensitive") : "plain",
target: [environment],
},
});
}
public async removeEnvironmentVariable(
projectId: string,
envId: string,
): Promise<Result<void, FetchError>> {
return await this.fetch({
method: "DELETE",
path: ["v10", "projects", projectId, "env", envId],
});
}
}