convex
Version:
Client for the Convex Cloud
181 lines (169 loc) • 5.48 kB
text/typescript
import {
convexToJson,
jsonToConvex,
version,
STATUS_CODE_UDF_FAILED,
} from "@convex-dev/common";
import { ClientConfiguration } from "./client_config.js";
import { createError, logToConsole } from "./logging.js";
/** Isomorphic `fetch` for Node.js and browser usage. */
const hasFetch =
typeof window !== "undefined" && typeof window.fetch !== "undefined";
type WindowFetch = typeof window.fetch;
const fetch: WindowFetch = hasFetch
? window.fetch
: (...args) =>
import("node-fetch").then(({ default: fetch }) =>
(fetch as unknown as WindowFetch)(...args)
);
// TODO Typedoc doesn't generate documentation for the comment below perhaps
// because it's a callable interface.
/**
* An interface to execute a Convex query function on the server.
*
* @public
*/
export interface Query<F extends (...args: any[]) => Promise<any>> {
/**
* Execute the query on the server, returning a `Promise` of the return value.
*
* @param args - Arguments for the query.
* @returns The result of the query.
*/
(...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>;
}
// TODO Typedoc doesn't generate documentation for the comment below perhaps
// because it's a callable interface.
/**
* An interface to execute a Convex mutation function on the server.
*
* @public
*/
export interface Mutation<F extends (...args: any[]) => Promise<any>> {
/**
* Execute the mutation on the server, returning a `Promise` of its return value.
*
* @param args - Arguments for the mutation.
* @returns The return value of the server-side function call.
*/
(...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>;
}
/**
* A Convex client that runs queries and mutations over HTTP.
*
* This is appropriate for server-side code (like Netlify Lambdas) or non-reactive
* webapps.
*
* If you're building a React app, consider using
* {@link react.ConvexReactClient} instead.
*
*
* @public
*/
export class ConvexHttpClient {
private readonly address: string;
private auth?: string;
constructor(clientConfig: ClientConfiguration) {
this.address = `${clientConfig.address}/api/${version}`;
}
/**
* Obtain the {@link ConvexHttpClient}'s URL to its backend.
*
* @returns The URL to the Convex backend, including the client's API version.
*/
backendUrl(): string {
return this.address;
}
/**
* Set the authentication token to be used for subsequent queries and mutations.
*
* Should be called whenever the token changes (i.e. due to expiration and refresh).
*
* @param value - JWT-encoded OpenID Connect identity token.
*/
setAuth(value: string) {
this.auth = value;
}
/**
* Clear the current authentication token if set.
*/
clearAuth() {
this.auth = undefined;
}
/**
* Construct a new {@link Query}.
*
* @param name - The name of the query function.
* @returns The {@link Query} object with that name.
*/
query<F extends (...args: any[]) => Promise<any>>(name: string): Query<F> {
return async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
// Interpret the arguments as a Convex array, and then serialize
// it to JSON.
const argsJSON = JSON.stringify(convexToJson(args));
const argsComponent = encodeURIComponent(argsJSON);
const url = `${this.address}/udf?path=${name}&args=${argsComponent}`;
const headers: Record<string, string> = this.auth
? { Authorization: `Bearer ${this.auth}` }
: {};
const response = await fetch(url, {
credentials: "include",
headers: headers,
});
if (!response.ok && response.status != STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
const value = jsonToConvex(respJSON.value);
for (const line of respJSON.logs) {
logToConsole("info", "query", name, line);
}
if (!respJSON.success) {
throw createError("query", name, value as string);
}
return value as ReturnType<F>;
};
}
/**
* Construct a new {@link Mutation}.
*
* @param name - The name of the mutation function.
* @returns The {@link Mutation} object with that name.
*/
mutation<F extends (...args: any[]) => Promise<any>>(
name: string
): Mutation<F> {
return async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
// Interpret the arguments as a Convex array and then serialize to JSON.
const body = JSON.stringify({
path: name,
args: convexToJson(args),
tokens: [],
});
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const response = await fetch(`${this.address}/udf`, {
body,
method: "POST",
headers: headers,
credentials: "include",
});
if (!response.ok && response.status != STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
const value = jsonToConvex(respJSON.value);
for (const line of respJSON.logs) {
logToConsole("info", "mutation", name, line);
}
if (!respJSON.success) {
throw createError("mutation", name, value as string);
}
return value as ReturnType<F>;
};
}
}