convex
Version:
Client for the Convex Cloud
328 lines (327 loc) • 10 kB
JavaScript
;
import {
getFunctionName
} from "../server/api.js";
import { parseArgs, validateDeploymentUrl } from "../common/index.js";
import { version } from "../index.js";
import {
ConvexError,
convexToJson,
jsonToConvex
} from "../values/index.js";
import { logToConsole } from "./logging.js";
export const STATUS_CODE_OK = 200;
export const STATUS_CODE_BAD_REQUEST = 400;
export const STATUS_CODE_UDF_FAILED = 560;
let specifiedFetch = void 0;
export function setFetch(f) {
specifiedFetch = f;
}
export class ConvexHttpClient {
/**
* Create a new {@link ConvexHttpClient}.
*
* @param address - The url of your Convex deployment, often provided
* by an environment variable. E.g. `https://small-mouse-123.convex.cloud`.
* @param skipConvexDeploymentUrlCheck - Skip validating that the Convex deployment URL looks like
* `https://happy-animal-123.convex.cloud` or localhost. This can be useful if running a self-hosted
* Convex backend that uses a different URL.
*/
constructor(address, skipConvexDeploymentUrlCheck) {
if (skipConvexDeploymentUrlCheck !== true) {
validateDeploymentUrl(address);
}
this.address = `${address}/api`;
this.debug = true;
}
/**
* Obtain the {@link ConvexHttpClient}'s URL to its backend.
*
* @returns The URL to the Convex backend, including the client's API version.
*/
backendUrl() {
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) {
this.clearAuth();
this.auth = value;
}
/**
* @internal
*/
setAdminAuth(token, actingAsIdentity) {
this.clearAuth();
if (actingAsIdentity !== void 0) {
const bytes = new TextEncoder().encode(JSON.stringify(actingAsIdentity));
const actingAsIdentityEncoded = btoa(String.fromCodePoint(...bytes));
this.adminAuth = `${token}:${actingAsIdentityEncoded}`;
} else {
this.adminAuth = token;
}
}
/**
* Clear the current authentication token if set.
*/
clearAuth() {
this.auth = void 0;
this.adminAuth = void 0;
}
/**
* Sets whether the result log lines should be printed on the console or not.
*
* @internal
*/
setDebug(debug) {
this.debug = debug;
}
/**
* Used to customize the fetch behavior in some runtimes.
*
* @internal
*/
setFetchOptions(fetchOptions) {
this.fetchOptions = fetchOptions;
}
/**
* Execute a Convex query function.
*
* @param name - The name of the query.
* @param args - The arguments object for the query. If this is omitted,
* the arguments will be `{}`.
* @returns A promise of the query's result.
*/
async query(query, ...args) {
const queryArgs = parseArgs(args[0]);
const name = getFunctionName(query);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: [convexToJson(queryArgs)]
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${version}`
};
if (this.adminAuth) {
headers["Authorization"] = `Convex ${this.adminAuth}`;
} else if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const localFetch = specifiedFetch || fetch;
const response = await localFetch(`${this.address}/query`, {
...this.fetchOptions,
body,
method: "POST",
headers,
credentials: "include"
});
if (!response.ok && response.status !== STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
if (this.debug) {
for (const line of respJSON.logLines ?? []) {
logToConsole("info", "query", name, line);
}
}
switch (respJSON.status) {
case "success":
return jsonToConvex(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new ConvexError(respJSON.errorMessage)
);
}
throw new Error(respJSON.errorMessage);
default:
throw new Error(`Invalid response: ${JSON.stringify(respJSON)}`);
}
}
/**
* Execute a Convex mutation function.
*
* @param name - The name of the mutation.
* @param args - The arguments object for the mutation. If this is omitted,
* the arguments will be `{}`.
* @returns A promise of the mutation's result.
*/
async mutation(mutation, ...args) {
const mutationArgs = parseArgs(args[0]);
const name = getFunctionName(mutation);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: [convexToJson(mutationArgs)]
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${version}`
};
if (this.adminAuth) {
headers["Authorization"] = `Convex ${this.adminAuth}`;
} else if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const localFetch = specifiedFetch || fetch;
const response = await localFetch(`${this.address}/mutation`, {
...this.fetchOptions,
body,
method: "POST",
headers,
credentials: "include"
});
if (!response.ok && response.status !== STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
if (this.debug) {
for (const line of respJSON.logLines ?? []) {
logToConsole("info", "mutation", name, line);
}
}
switch (respJSON.status) {
case "success":
return jsonToConvex(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new ConvexError(respJSON.errorMessage)
);
}
throw new Error(respJSON.errorMessage);
default:
throw new Error(`Invalid response: ${JSON.stringify(respJSON)}`);
}
}
/**
* Execute a Convex action function.
*
* @param name - The name of the action.
* @param args - The arguments object for the action. If this is omitted,
* the arguments will be `{}`.
* @returns A promise of the action's result.
*/
async action(action, ...args) {
const actionArgs = parseArgs(args[0]);
const name = getFunctionName(action);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: [convexToJson(actionArgs)]
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${version}`
};
if (this.adminAuth) {
headers["Authorization"] = `Convex ${this.adminAuth}`;
} else if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const localFetch = specifiedFetch || fetch;
const response = await localFetch(`${this.address}/action`, {
...this.fetchOptions,
body,
method: "POST",
headers,
credentials: "include"
});
if (!response.ok && response.status !== STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
if (this.debug) {
for (const line of respJSON.logLines ?? []) {
logToConsole("info", "action", name, line);
}
}
switch (respJSON.status) {
case "success":
return jsonToConvex(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new ConvexError(respJSON.errorMessage)
);
}
throw new Error(respJSON.errorMessage);
default:
throw new Error(`Invalid response: ${JSON.stringify(respJSON)}`);
}
}
/**
* Execute a Convex function of an unknown type.
*
* @param name - The name of the function.
* @param args - The arguments object for the function. If this is omitted,
* the arguments will be `{}`.
* @returns A promise of the function's result.
*
* @internal
*/
async function(anyFunction, ...args) {
const functionArgs = parseArgs(args[0]);
const name = typeof anyFunction === "string" ? anyFunction : getFunctionName(anyFunction);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: convexToJson(functionArgs)
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${version}`
};
if (this.adminAuth) {
headers["Authorization"] = `Convex ${this.adminAuth}`;
} else if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const localFetch = specifiedFetch || fetch;
const response = await localFetch(`${this.address}/function`, {
...this.fetchOptions,
body,
method: "POST",
headers,
credentials: "include"
});
if (!response.ok && response.status !== STATUS_CODE_UDF_FAILED) {
throw new Error(await response.text());
}
const respJSON = await response.json();
if (this.debug) {
for (const line of respJSON.logLines ?? []) {
logToConsole("info", "any", name, line);
}
}
switch (respJSON.status) {
case "success":
return jsonToConvex(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new ConvexError(respJSON.errorMessage)
);
}
throw new Error(respJSON.errorMessage);
default:
throw new Error(`Invalid response: ${JSON.stringify(respJSON)}`);
}
}
}
function forwardErrorData(errorData, error) {
error.data = jsonToConvex(errorData);
return error;
}
//# sourceMappingURL=http_client.js.map