convex
Version:
Client for the Convex Cloud
422 lines (421 loc) • 14.4 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var http_client_exports = {};
__export(http_client_exports, {
ConvexHttpClient: () => ConvexHttpClient,
STATUS_CODE_BAD_REQUEST: () => STATUS_CODE_BAD_REQUEST,
STATUS_CODE_OK: () => STATUS_CODE_OK,
STATUS_CODE_UDF_FAILED: () => STATUS_CODE_UDF_FAILED,
setFetch: () => setFetch
});
module.exports = __toCommonJS(http_client_exports);
var import_api = require("../server/api.js");
var import_common = require("../common/index.js");
var import__ = require("../index.js");
var import_values = require("../values/index.js");
var import_logging = require("./logging.js");
const STATUS_CODE_OK = 200;
const STATUS_CODE_BAD_REQUEST = 400;
const STATUS_CODE_UDF_FAILED = 560;
let specifiedFetch = void 0;
function setFetch(f) {
specifiedFetch = f;
}
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) {
__publicField(this, "address");
__publicField(this, "auth");
__publicField(this, "adminAuth");
__publicField(this, "encodedTsPromise");
__publicField(this, "debug");
__publicField(this, "fetchOptions");
if (skipConvexDeploymentUrlCheck !== true) {
(0, import_common.validateDeploymentUrl)(address);
}
this.address = address;
this.debug = true;
}
/**
* Obtain the {@link ConvexHttpClient}'s URL to its backend.
* @deprecated Use url, which returns the url without /api at the end.
*
* @returns The URL to the Convex backend, including the client's API version.
*/
backendUrl() {
return `${this.address}/api`;
}
/**
* Return the address for this client, useful for creating a new client.
*
* Not guaranteed to match the address with which this client was constructed:
* it may be canonicalized.
*/
get url() {
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;
}
/**
* This API is experimental: it may change or disappear.
*
* Execute a Convex query function at the same timestamp as every other
* consistent query execution run by this HTTP client.
*
* This doesn't make sense for long-lived ConvexHttpClients as Convex
* backends can read a limited amount into the past: beyond 30 seconds
* in the past may not be available.
*
* Create a new client to use a consistent time.
*
* @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.
*
* @deprecated This API is experimental: it may change or disappear.
*/
async consistentQuery(query, ...args) {
const queryArgs = (0, import_common.parseArgs)(args[0]);
const timestampPromise = this.getTimestamp();
return await this.queryInner(query, queryArgs, { timestampPromise });
}
async getTimestamp() {
if (this.encodedTsPromise) {
return this.encodedTsPromise;
}
return this.encodedTsPromise = this.getTimestampInner();
}
async getTimestampInner() {
const localFetch = specifiedFetch || fetch;
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${import__.version}`
};
const response = await localFetch(`${this.address}/api/query_ts`, {
...this.fetchOptions,
method: "POST",
headers,
credentials: "include"
});
if (!response.ok) {
throw new Error(await response.text());
}
const { ts } = await response.json();
return ts;
}
/**
* 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 = (0, import_common.parseArgs)(args[0]);
return await this.queryInner(query, queryArgs, {});
}
async queryInner(query, queryArgs, options) {
const name = (0, import_api.getFunctionName)(query);
const args = [(0, import_values.convexToJson)(queryArgs)];
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${import__.version}`
};
if (this.adminAuth) {
headers["Authorization"] = `Convex ${this.adminAuth}`;
} else if (this.auth) {
headers["Authorization"] = `Bearer ${this.auth}`;
}
const localFetch = specifiedFetch || fetch;
const timestamp = options.timestampPromise ? await options.timestampPromise : void 0;
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args,
...timestamp ? { ts: timestamp } : {}
});
const endpoint = timestamp ? `${this.address}/api/query_at_ts` : `${this.address}/api/query`;
const response = await localFetch(endpoint, {
...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 ?? []) {
(0, import_logging.logToConsole)("info", "query", name, line);
}
}
switch (respJSON.status) {
case "success":
return (0, import_values.jsonToConvex)(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new import_values.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 = (0, import_common.parseArgs)(args[0]);
const name = (0, import_api.getFunctionName)(mutation);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: [(0, import_values.convexToJson)(mutationArgs)]
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${import__.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}/api/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 ?? []) {
(0, import_logging.logToConsole)("info", "mutation", name, line);
}
}
switch (respJSON.status) {
case "success":
return (0, import_values.jsonToConvex)(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new import_values.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 = (0, import_common.parseArgs)(args[0]);
const name = (0, import_api.getFunctionName)(action);
const body = JSON.stringify({
path: name,
format: "convex_encoded_json",
args: [(0, import_values.convexToJson)(actionArgs)]
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${import__.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}/api/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 ?? []) {
(0, import_logging.logToConsole)("info", "action", name, line);
}
}
switch (respJSON.status) {
case "success":
return (0, import_values.jsonToConvex)(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new import_values.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, componentPath, ...args) {
const functionArgs = (0, import_common.parseArgs)(args[0]);
const name = typeof anyFunction === "string" ? anyFunction : (0, import_api.getFunctionName)(anyFunction);
const body = JSON.stringify({
componentPath,
path: name,
format: "convex_encoded_json",
args: (0, import_values.convexToJson)(functionArgs)
});
const headers = {
"Content-Type": "application/json",
"Convex-Client": `npm-${import__.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}/api/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 ?? []) {
(0, import_logging.logToConsole)("info", "any", name, line);
}
}
switch (respJSON.status) {
case "success":
return (0, import_values.jsonToConvex)(respJSON.value);
case "error":
if (respJSON.errorData !== void 0) {
throw forwardErrorData(
respJSON.errorData,
new import_values.ConvexError(respJSON.errorMessage)
);
}
throw new Error(respJSON.errorMessage);
default:
throw new Error(`Invalid response: ${JSON.stringify(respJSON)}`);
}
}
}
function forwardErrorData(errorData, error) {
error.data = (0, import_values.jsonToConvex)(errorData);
return error;
}
//# sourceMappingURL=http_client.js.map
;