369 lines
8.4 KiB
TypeScript
369 lines
8.4 KiB
TypeScript
import {
|
|
ApiEventLog,
|
|
ApiEventLogSchema,
|
|
CompleteTaskBodyInput,
|
|
ConnectionAuthSchema,
|
|
CreateRunBody,
|
|
CreateRunResponseBodySchema,
|
|
LogLevel,
|
|
Logger,
|
|
RegisterSourceEvent,
|
|
RegisterSourceEventSchema,
|
|
RegisterTriggerBody,
|
|
RunTaskBodyInput,
|
|
SendEvent,
|
|
SendEventOptions,
|
|
ServerTaskSchema,
|
|
TriggerSource,
|
|
TriggerSourceSchema,
|
|
UpdateTriggerSourceBody,
|
|
} from "@trigger.dev/internal";
|
|
import { z } from "zod";
|
|
|
|
export type ApiClientOptions = {
|
|
apiKey?: string;
|
|
apiUrl?: string;
|
|
logLevel?: LogLevel;
|
|
};
|
|
|
|
export type EndpointRecord = {
|
|
id: string;
|
|
name: string;
|
|
url: string;
|
|
};
|
|
|
|
export type HttpSourceRecord = {
|
|
id: string;
|
|
key: string;
|
|
managed: boolean;
|
|
url: string;
|
|
status: "PENDING" | "ACTIVE" | "INACTIVE";
|
|
secret?: string;
|
|
data?: any;
|
|
};
|
|
|
|
export type RunRecord = {
|
|
id: string;
|
|
jobId: string;
|
|
callbackUrl: string;
|
|
event: ApiEventLog;
|
|
};
|
|
|
|
export class ApiClient {
|
|
#apiUrl: string;
|
|
#options: ApiClientOptions;
|
|
#logger: Logger;
|
|
|
|
constructor(options: ApiClientOptions) {
|
|
this.#options = options;
|
|
|
|
this.#apiUrl =
|
|
this.#options.apiUrl ??
|
|
process.env.TRIGGER_API_URL ??
|
|
"https://api.trigger.dev";
|
|
this.#logger = new Logger("trigger.dev", this.#options.logLevel);
|
|
}
|
|
|
|
async registerEndpoint(options: {
|
|
url: string;
|
|
name: string;
|
|
}): Promise<EndpointRecord> {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("Registering endpoint", {
|
|
url: options.url,
|
|
name: options.name,
|
|
});
|
|
|
|
const response = await fetch(`${this.#apiUrl}/api/v3/endpoints`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
url: options.url,
|
|
name: options.name,
|
|
}),
|
|
});
|
|
|
|
if (response.status >= 400 && response.status < 500) {
|
|
const body = await response.json();
|
|
|
|
throw new Error(body.error);
|
|
}
|
|
|
|
if (response.status !== 200) {
|
|
throw new Error(
|
|
`Failed to register entry point, got status code ${response.status}`
|
|
);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
async createRun(params: CreateRunBody) {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("Creating run", {
|
|
params,
|
|
});
|
|
|
|
return await zodfetch(
|
|
CreateRunResponseBodySchema,
|
|
`${this.#apiUrl}/api/v3/runs`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(params),
|
|
}
|
|
);
|
|
}
|
|
|
|
async runTask(runId: string, task: RunTaskBodyInput) {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("Running Task", {
|
|
task,
|
|
});
|
|
|
|
return await zodfetch(
|
|
ServerTaskSchema,
|
|
`${this.#apiUrl}/api/v3/runs/${runId}/tasks`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Idempotency-Key": task.idempotencyKey,
|
|
},
|
|
body: JSON.stringify(task),
|
|
}
|
|
);
|
|
}
|
|
|
|
async completeTask(runId: string, id: string, task: CompleteTaskBodyInput) {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("Complete Task", {
|
|
task,
|
|
});
|
|
|
|
return await zodfetch(
|
|
ServerTaskSchema,
|
|
`${this.#apiUrl}/api/v3/runs/${runId}/tasks/${id}/complete`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(task),
|
|
}
|
|
);
|
|
}
|
|
|
|
async sendEvent(event: SendEvent, options: SendEventOptions = {}) {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("Sending event", {
|
|
event,
|
|
});
|
|
|
|
return await zodfetch(ApiEventLogSchema, `${this.#apiUrl}/api/v3/events`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({ event, options }),
|
|
});
|
|
}
|
|
|
|
async updateSource(
|
|
client: string,
|
|
key: string,
|
|
source: UpdateTriggerSourceBody
|
|
): Promise<TriggerSource> {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("activating http source", {
|
|
source,
|
|
});
|
|
|
|
const response = await zodfetch(
|
|
TriggerSourceSchema,
|
|
`${this.#apiUrl}/api/v3/${client}/sources/${key}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(source),
|
|
}
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
async registerTrigger(
|
|
client: string,
|
|
id: string,
|
|
payload: RegisterTriggerBody
|
|
): Promise<RegisterSourceEvent> {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("registering trigger", {
|
|
id,
|
|
payload,
|
|
});
|
|
|
|
const response = await zodfetch(
|
|
RegisterSourceEventSchema,
|
|
`${this.#apiUrl}/api/v3/${client}/triggers/${id}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
async getAuth(client: string, id: string) {
|
|
const apiKey = await this.#apiKey();
|
|
|
|
this.#logger.debug("getting auth", {
|
|
id,
|
|
});
|
|
|
|
const response = await zodfetch(
|
|
ConnectionAuthSchema,
|
|
`${this.#apiUrl}/api/v3/${client}/auth/${id}`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "application/json",
|
|
Authorization: `Bearer ${apiKey}`,
|
|
},
|
|
},
|
|
{
|
|
optional: true,
|
|
}
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
async #apiKey() {
|
|
const apiKey = getApiKey(this.#options.apiKey);
|
|
|
|
if (apiKey.status === "invalid") {
|
|
throw new Error("Invalid API key");
|
|
|
|
// const chalk = (await import("chalk")).default;
|
|
// const terminalLink = (await import("terminal-link")).default;
|
|
|
|
// throw new Error(
|
|
// `${chalk.red("Trigger.dev error")}: Invalid API key ("${chalk.italic(
|
|
// apiKey.apiKey
|
|
// )}"), please set the TRIGGER_API_KEY environment variable or pass the apiKey option to a valid value. ${terminalLink(
|
|
// "Get your API key here",
|
|
// "https://app.trigger.dev",
|
|
// {
|
|
// fallback(text, url) {
|
|
// return `${text} 👉 ${url}`;
|
|
// },
|
|
// }
|
|
// )}`
|
|
// );
|
|
} else if (apiKey.status === "missing") {
|
|
throw new Error("Missing API key");
|
|
// const chalk = (await import("chalk")).default;
|
|
// const terminalLink = (await import("terminal-link")).default;
|
|
|
|
// throw new Error(
|
|
// `${chalk.red(
|
|
// "Trigger.dev error"
|
|
// )}: Missing an API key, please set the TRIGGER_API_KEY environment variable or pass the apiKey option to the Trigger constructor. ${terminalLink(
|
|
// "Get your API key here",
|
|
// "https://app.trigger.dev",
|
|
// {
|
|
// fallback(text, url) {
|
|
// return `${text} 👉 ${url}`;
|
|
// },
|
|
// }
|
|
// )}`
|
|
// );
|
|
}
|
|
|
|
return apiKey.apiKey;
|
|
}
|
|
}
|
|
|
|
function getApiKey(key?: string) {
|
|
const apiKey = key ?? process.env.TRIGGER_API_KEY;
|
|
|
|
if (!apiKey) {
|
|
return { status: "missing" as const };
|
|
}
|
|
|
|
// Validate the api_key format (should be tr_{env}_XXXXX)
|
|
const isValid = apiKey.match(/^tr_[a-z]+_[a-zA-Z0-9]+$/);
|
|
|
|
if (!isValid) {
|
|
return { status: "invalid" as const, apiKey };
|
|
}
|
|
|
|
return { status: "valid" as const, apiKey };
|
|
}
|
|
|
|
async function zodfetch<
|
|
TResponseBody extends any,
|
|
TOptional extends boolean = false
|
|
>(
|
|
schema: z.Schema<TResponseBody>,
|
|
url: string,
|
|
requestInit?: RequestInit,
|
|
options?: {
|
|
errorMessage?: string;
|
|
optional?: TOptional;
|
|
}
|
|
): Promise<TOptional extends true ? TResponseBody | undefined : TResponseBody> {
|
|
const response = await fetch(url, requestInit);
|
|
|
|
if (
|
|
(!requestInit || requestInit.method === "GET") &&
|
|
response.status === 404 &&
|
|
options?.optional
|
|
) {
|
|
// @ts-ignore
|
|
return;
|
|
}
|
|
|
|
if (response.status >= 400 && response.status < 500) {
|
|
const body = await response.json();
|
|
|
|
throw new Error(body.error);
|
|
}
|
|
|
|
if (response.status !== 200) {
|
|
throw new Error(
|
|
options?.errorMessage ??
|
|
`Failed to fetch ${url}, got status code ${response.status}`
|
|
);
|
|
}
|
|
|
|
const jsonBody = await response.json();
|
|
|
|
return schema.parse(jsonBody);
|
|
}
|