libmodulor
Version:
A TypeScript library to create platform-agnostic applications
135 lines (134 loc) • 4.5 kB
JavaScript
import { Metadata, Server, } from '@grpc/grpc-js';
import { ucTransportType, } from '../../../uc/index.js';
import { defaultStreamOnClose, } from '../../../utils/index.js';
export function buildHandler(appManifest, ucd, fileMetadataManager, serverRequestHandler, ucManager) {
const transportType = ucTransportType(ucd.ext);
switch (transportType) {
case 'standard': {
return buildStandardHandler(appManifest, ucd, fileMetadataManager, serverRequestHandler, ucManager);
}
case 'stream': {
return buildStreamHandler(appManifest, ucd, fileMetadataManager, serverRequestHandler, ucManager);
}
default:
transportType;
throw new Error();
}
}
export function buildStandardHandler(appManifest, ucd, fileMetadataManager, serverRequestHandler, ucManager) {
return async (call, callback) => {
const metadata = new Metadata();
const { body, rawErr, status } = await serverRequestHandler.exec({
appManifest,
req: toReq(fileMetadataManager, call),
res: toRes(metadata),
ucd,
ucManager,
});
if (rawErr) {
rawErr.code = status;
return callback(rawErr);
}
callback(null, body ? body : {}, metadata);
};
}
export function buildStreamHandler(appManifest, ucd, fileMetadataManager, serverRequestHandler, ucManager) {
return async (call) => {
let streamedOnce = false;
const execOpts = {
stream: {
onClose: defaultStreamOnClose(streamedOnce),
onData: async (output) => {
streamedOnce = true;
if (!output) {
return;
}
call.write(output);
},
onDone: async () => {
call.end();
},
},
};
call.on('close', async () => {
await execOpts?.stream?.onClose();
});
const metadata = new Metadata();
await serverRequestHandler.exec({
appManifest,
execOpts,
req: toReq(fileMetadataManager, call),
res: toRes(metadata),
ucd,
ucManager,
});
};
}
export function init() {
const server = new Server();
return server;
}
export function listen(server, creds, entrypointsBuilder, logger) {
const { grpc } = entrypointsBuilder.exec();
server.bindAsync(grpc, creds, (err, _port) => {
if (err) {
logger.error(err);
return;
}
logger.info(`Listening on ${grpc}`);
});
}
export async function stop(server, settingsManager) {
const mode = settingsManager.get()('server_stop_mode');
return new Promise((resolve, reject) => {
switch (mode) {
case 'aggressive':
server.forceShutdown();
resolve();
break;
case 'patient':
server.tryShutdown((err) => {
if (err) {
return reject(err);
}
resolve();
});
break;
default:
mode;
}
});
}
export function toReq(fileMetadataManager, call) {
return {
bodyFromRequest: async () => readBodyFromRequest(fileMetadataManager, call.request),
bodyRaw: call.request,
metadata: async (name) => call.metadata.get(name).toString(),
// TODO : Figure out a way to set this from something else (call does not hold it)
secure: false,
url: `${call.getHost()}${call.getPath()}`,
};
}
export function toRes(metadata) {
return {
setMetadata: async (name, value) => metadata.set(name, value),
};
}
async function readBodyFromRequest(fileMetadataManager, request) {
const input = {};
for (const [k, v] of Object.entries(request)) {
if (v instanceof Buffer) {
let name = 'tmp-1'; // Fine if not unique because never saved as is
const tmpFile = new File([v], name);
const { ext, mimeType } = await fileMetadataManager.info(tmpFile);
name = ext ? `${name}.${ext}` : name;
input[k] = new File([v], name, {
type: mimeType,
});
}
else {
input[k] = v;
}
}
return input;
}