convex
Version:
Client for the Convex Cloud
75 lines (65 loc) • 2.54 kB
text/typescript
import { ActionRequest, ActionResponse, RequestId } from "./protocol.js";
import { jsonToConvex } from "../../values/index.js";
import { createError, logToConsole } from "../logging.js";
type ActionStatus = {
message: ActionRequest;
onResult: (result: any) => void;
onFailure: (reason: any) => void;
};
export class ActionManager {
private inflightActions: Map<RequestId, ActionStatus>;
constructor() {
this.inflightActions = new Map();
}
request(message: ActionRequest): Promise<any> {
const result = new Promise((resolve, reject) => {
this.inflightActions.set(message.requestId, {
message,
onResult: resolve,
onFailure: reject,
});
});
return result;
}
/**
* Update the state after receiving a action response.
*/
onResponse(response: ActionResponse) {
const actionInfo = this.inflightActions.get(response.requestId);
if (actionInfo === undefined) {
// Got a response of a message that we don't know about. That shouldn't
// really happen unless we get duplicate messages or something.
return;
}
this.inflightActions.delete(response.requestId);
const udfPath = actionInfo.message.udfPath;
for (const line of response.logLines) {
logToConsole("info", "action", udfPath, line);
}
if (response.success) {
actionInfo.onResult(jsonToConvex(response.result));
} else {
logToConsole("error", "action", udfPath, response.result);
actionInfo.onFailure(createError("action", udfPath, response.result));
}
}
hasInflightActions(): boolean {
return this.inflightActions.size > 0;
}
restart() {
// Unlike mutations, actions are not idempotent. When we reconnect to the
// backend, we don't know if it is safe to resend in-flight actions, so we
// cancel them and consider them failed.
// TODO(presley): If we make the server remember it has started executing a
// function, we can resend here and remove browser to backend connectivity as
// a source of transient errors. For example, if a function has never reached
// the server, then we can safely execute it. If a function was executed
// successfully but response didn't reach the client. Server can return
// success on reconnect.
for (const [actionId, actionInfo] of this.inflightActions) {
this.inflightActions.delete(actionId);
const udfPath = actionInfo.message.udfPath;
actionInfo.onFailure(createError("action", udfPath, "Transient error"));
}
}
}