UNPKG

convex

Version:

Client for the Convex Cloud

181 lines (169 loc) 5.48 kB
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>; }; } }