telefunc
Version:
Remote functions. Instead of API.
142 lines (141 loc) • 5.25 kB
JavaScript
export { runTelefunc };
import { assert, objectAssign, isProduction } from '../utils.js';
import { loadTelefuncFiles } from './runTelefunc/loadTelefuncFiles.js';
import { parseHttpRequest } from './runTelefunc/parseHttpRequest.js';
// import { getEtag } from './runTelefunc/getEtag.js'
import { executeTelefunction } from './runTelefunc/executeTelefunction.js';
import { serializeTelefunctionResult } from './runTelefunc/serializeTelefunctionResult.js';
import { handleError } from './runTelefunc/handleError.js';
import { callBugListeners } from './runTelefunc/onBug.js';
import { applyShield } from './runTelefunc/applyShield.js';
import { findTelefunction } from './runTelefunc/findTelefunction.js';
import { getServerConfig } from './serverConfig.js';
// HTTP Response for:
// - `throw Abort()`
const abortedRequestStatusCode = 403; // "Forbidden"
// HTTP Response for:
// - User's telefunction threw an error that isn't `Abort()` (i.e. the telefunction has a bug).
// - The `.telefunc.js` file exports a non-function value.
// - The Telefunc code threw an error (i.e. Telefunc has a bug).
const serverError = {
statusCode: 500, // "Internal Server Error"
body: 'Internal Server Error',
contentType: 'text/plain',
etag: null,
};
// HTTP Response for:
// - Some non-telefunc client makes a malformed HTTP request.
// - The telefunction couldn't be found.
const invalidRequest = {
statusCode: 400, // "Bad Request"
body: 'Invalid Telefunc Request',
contentType: 'text/plain',
etag: null,
};
async function runTelefunc(runContext) {
try {
return await runTelefunc_(runContext);
}
catch (err) {
callBugListeners(err);
handleError(err);
return {
err,
...serverError,
};
}
}
async function runTelefunc_(httpRequest) {
const runContext = {};
{
// TODO: remove? Since `serverConfig` is global I don't think we need to set it to `runContext`, see for example https://github.com/brillout/telefunc/commit/5e3367d2d463b72e805e75ddfc68ef7f177a35c0
const serverConfig = getServerConfig();
objectAssign(runContext, {
httpRequest,
serverConfig: {
disableNamingConvention: serverConfig.disableNamingConvention,
telefuncUrl: serverConfig.telefuncUrl,
log: serverConfig.log,
},
appRootDir: serverConfig.root,
telefuncFilesManuallyProvidedByUser: serverConfig.telefuncFiles,
});
}
{
const logInvalidRequests = !isProduction(); /* || process.env.DEBUG.includes('telefunc') */
objectAssign(runContext, { logInvalidRequests });
}
objectAssign(runContext, {
providedContext: httpRequest.context || null,
});
{
const parsed = parseHttpRequest(runContext);
if (parsed.isMalformed) {
return invalidRequest;
}
const { telefunctionKey, telefunctionArgs, telefuncFilePath, telefunctionName } = parsed;
objectAssign(runContext, {
telefunctionKey,
telefunctionArgs,
telefuncFilePath,
telefunctionName,
});
}
{
const { telefuncFilesLoaded, telefuncFilesAll } = await loadTelefuncFiles(runContext);
assert(telefuncFilesLoaded, 'No `.telefunc.js` file found');
objectAssign(runContext, { telefuncFilesLoaded, telefuncFilesAll });
}
{
const telefunction = await findTelefunction(runContext);
if (!telefunction) {
return invalidRequest;
}
objectAssign(runContext, { telefunction });
}
{
const { isValidRequest } = applyShield(runContext);
objectAssign(runContext, { isValidRequest });
if (!isValidRequest) {
objectAssign(runContext, {
telefunctionAborted: true,
telefunctionReturn: undefined,
});
const httpResponseBody = serializeTelefunctionResult(runContext);
return {
statusCode: abortedRequestStatusCode,
body: httpResponseBody,
contentType: 'text/plain',
etag: null,
};
}
}
{
assert(runContext.isValidRequest);
const { telefunctionReturn, telefunctionAborted, telefunctionHasErrored, telefunctionError } = await executeTelefunction(runContext);
objectAssign(runContext, {
telefunctionReturn,
telefunctionHasErrored,
telefunctionAborted,
telefunctionError,
});
}
if (runContext.telefunctionHasErrored) {
throw runContext.telefunctionError;
}
{
const httpResponseBody = serializeTelefunctionResult(runContext);
objectAssign(runContext, { httpResponseBody });
}
// {
// const httpResponseEtag = await getEtag(runContext)
// objectAssign(runContext, { httpResponseEtag })
// }
return {
statusCode: runContext.telefunctionAborted ? abortedRequestStatusCode : 200,
body: runContext.httpResponseBody,
contentType: 'text/plain',
// etag: runContext.httpResponseEtag,
etag: null,
};
}