convex
Version:
Client for the Convex Cloud
201 lines (192 loc) • 6.71 kB
text/typescript
import { GenericAPI } from "../../api/index.js";
import { convexToJson, jsonToConvex } from "../../values/index.js";
import { GenericDataModel } from "../data_model.js";
import {
ActionCtx,
HttpEndpointCtx,
MutationCtx,
PublicAction,
PublicHttpEndpoint,
PublicMutation,
PublicQuery,
QueryCtx,
} from "../registration.js";
import { setupActionDatabaseClient } from "./actions_impl.js";
import { setupActionAuth, setupAuth } from "./authentication_impl.js";
import { setupReader, setupWriter } from "./database_impl.js";
import { setupHttpCalls } from "./http_impl.js";
import {
setupActionScheduler,
setupMutationScheduler,
} from "./scheduler_impl.js";
import { setupStorageReader, setupStorageWriter } from "./storage_impl.js";
import { performJsSyscall } from "./syscall.js";
async function invokeMutation<
F extends (
ctx: MutationCtx<GenericDataModel, GenericAPI>,
...args: any
) => any
>(func: F, argsStr: string) {
const args = jsonToConvex(JSON.parse(argsStr));
const mutationCtx = {
db: setupWriter(),
auth: setupAuth(),
storage: setupStorageWriter(),
scheduler: setupMutationScheduler(),
};
const result = await Promise.resolve(func(mutationCtx, ...(args as any)));
return JSON.stringify(convexToJson(result === undefined ? null : result));
}
/**
* Define a mutation in this Convex app's public API.
*
* This function will be allowed to modify your Convex database and will be accessible from the client.
*
* If you're using code generation, use the `mutation` function in
* `convex/_generated/server.d.ts` which is typed for your data model.
*
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
*
* @public
*/
export const mutationGeneric = <
DataModel extends GenericDataModel,
API extends GenericAPI,
Args extends any[],
Output
>(
func: (ctx: MutationCtx<DataModel, API>, ...args: Args) => Output
): PublicMutation<DataModel, API, Args, Output> => {
const m = func as unknown as PublicMutation<DataModel, API, Args, Output>;
// Helpful runtime check that functions are only be registered once
if (m.isRegistered) {
throw new Error("Function registered twice " + func);
}
m.isRegistered = true;
m.isMutation = true;
m.invokeMutation = argsStr => invokeMutation(func as any, argsStr);
return m;
};
async function invokeQuery<
F extends (ctx: QueryCtx<GenericDataModel>, ...args: any) => any
>(func: F, argsStr: string) {
const args = jsonToConvex(JSON.parse(argsStr));
const queryCtx = {
db: setupReader(),
auth: setupAuth(),
storage: setupStorageReader(),
};
const result = await Promise.resolve(func(queryCtx, ...(args as any)));
return JSON.stringify(convexToJson(result === undefined ? null : result));
}
/**
* Define a query in this Convex app's public API.
*
* This function will be allowed to read your Convex database and will be accessible from the client.
*
* If you're using code generation, use the `query` function in
* `convex/_generated/server.d.ts` which is typed for your data model.
*
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
*
* @public
*/
export const queryGeneric = <
DataModel extends GenericDataModel,
Args extends any[],
Output
>(
func: (ctx: QueryCtx<DataModel>, ...args: Args) => Output
): PublicQuery<DataModel, Args, Output> => {
const q = func as unknown as PublicQuery<DataModel, Args, Output>;
// Helpful runtime check that functions are only be registered once
if (q.isRegistered) {
throw new Error("Function registered twice " + func);
}
q.isRegistered = true;
q.isQuery = true;
q.invokeQuery = argsStr => invokeQuery(func as any, argsStr);
return q;
};
async function invokeAction<
API extends GenericAPI,
F extends (ctx: ActionCtx<API>, ...args: any) => any
>(func: F, requestId: string, argsStr: string) {
const args = jsonToConvex(JSON.parse(argsStr));
const databaseClient = setupActionDatabaseClient(requestId);
const ctx = {
runQuery: databaseClient.runQuery,
runMutation: databaseClient.runMutation,
auth: setupActionAuth(requestId),
scheduler: setupActionScheduler(requestId),
};
const result = await Promise.resolve(func(ctx, ...(args as any)));
return JSON.stringify(convexToJson(result === undefined ? null : result));
}
/**
* Define an action in this Convex app's public API.
*
* @param func - The function. It receives a {@link ActionCtx} as its first argument.
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
*
* @public
*/
export const actionGeneric = <
API extends GenericAPI,
Args extends any[],
Output
>(
func: (ctx: ActionCtx<API>, ...args: Args) => Output
): PublicAction<API, Args, Output> => {
const q = func as unknown as PublicAction<API, Args, Output>;
// Helpful runtime check that functions are only be registered once
if (q.isRegistered) {
throw new Error("Function registered twice " + func);
}
q.isRegistered = true;
q.isAction = true;
q.invokeAction = (requestId, argsStr) =>
invokeAction(func as any, requestId, argsStr);
return q;
};
async function invokeHttpEndpoint<
API extends GenericAPI,
F extends (ctx: HttpEndpointCtx<API>, request: Request) => any
>(func: F, argsStr: string) {
const calls = setupHttpCalls();
const ctx = {
...calls,
auth: setupAuth(),
};
const request = performJsSyscall("requestFromConvexJson", {
convexJson: JSON.parse(argsStr),
});
const response: any = await Promise.resolve(func(ctx, request));
return JSON.stringify(
performJsSyscall("convexJsonFromResponse", { response })
);
}
/**
* Define a Convex HTTP endpoint.
*
* @param func - The function. It receives an {@link HttpEndpointCtx} as its first argument, and a `Request` object
* as its second.
* @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
*
* @public
*/
export const httpEndpointGeneric = <API extends GenericAPI>(
func: (ctx: HttpEndpointCtx<API>, request: Request) => Promise<Response>
): PublicHttpEndpoint<API> => {
const q = func as unknown as PublicHttpEndpoint<API>;
// Helpful runtime check that functions are only be registered once
if (q.isRegistered) {
throw new Error("Function registered twice " + func);
}
q.isRegistered = true;
q.isHttp = true;
q.invokeHttpEndpoint = argsStr => invokeHttpEndpoint(func as any, argsStr);
return q;
};