@cortical/core
Version:
A RESTful API framework for your apps based on GraphQL type system.
393 lines (386 loc) • 15.5 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
const Express = require("express");
const http = require("http");
const body_parser_1 = require("body-parser");
const cors = require("cors");
const accepts = require("accepts");
const graphql_playground_html_1 = require("@apollographql/graphql-playground-html");
const apollo_server_express_1 = require("apollo-server-express");
const subscriptions_transport_ws_1 = require("subscriptions-transport-ws");
const getSchema_1 = require("../api/getSchema");
const url_1 = require("../api/url");
const death_1 = require("../hooks/death");
const init_1 = require("../hooks/init");
const middlewares_1 = require("../hooks/middlewares");
const listening_1 = require("../hooks/listening");
const gatewayRequest_1 = require("../hooks/gatewayRequest");
const gatewayConnect_1 = require("../hooks/gatewayConnect");
const serverLaunchInfo_1 = require("../hooks/serverLaunchInfo");
const logger_1 = require("@spine/logger");
const config_1 = require("../config");
const __1 = require("..");
const context_1 = require("../api/context");
// Create our express based server.
exports.api = Object.assign(Express(), {});
const methods = [
'use',
'all',
'get',
'post',
'put',
'delete',
'patch',
'options',
'head',
'checkout',
'connect',
'copy',
'lock',
'merge',
'mkactivity',
'mkcol',
'move',
'm-search',
'notify',
'propfind',
'proppatch',
'purge',
'report',
'search',
'subscribe',
'trace',
'unlock',
'unsubscribe',
];
function applyReq(req) {
if (!('context' in req)) {
Object.defineProperty(req, 'context', {
enumerable: true,
configurable: true,
get() {
throw new Error(`Context is not ready`);
},
});
}
return req;
}
function applyRes(res) {
if (typeof res.sendResult === 'undefined') {
Object.defineProperty(res, 'sendResult', {
writable: true,
configurable: true,
value: function sendResult(result) {
res.json(__1.formatResponse(res.req.context, result));
return res;
},
});
}
if (typeof res.sendData === 'undefined') {
Object.defineProperty(res, 'sendData', {
writable: true,
configurable: true,
value: function sendData(data) {
res.sendResult({ data });
return res;
},
});
}
if (typeof res.sendErrors === 'undefined') {
Object.defineProperty(res, 'sendErrors', {
writable: true,
configurable: true,
value: function sendErrors(errors) {
res.sendResult({ errors });
return res;
},
});
}
return res;
}
function applyNext(req, res, next) {
return (error) => {
if (error == null) {
return next();
}
handleExpressError(error, req, res, next);
};
}
function handleExpressError(error, req, res, next) {
res.json(__1.formatResponse(contextReady ? req.context : undefined, { errors: [error], data: undefined }));
}
function mapHandler(handler) {
if (typeof handler === 'function') {
const length = handler.length;
const newHandler = (...args) => __awaiter(this, void 0, void 0, function* () {
let parsedArgs;
if (args[0] instanceof Error) {
const [error, req, res, next] = args;
const newReq = applyReq(req);
const newRes = applyRes(res);
const newNext = applyNext(newReq, newRes, next);
parsedArgs = [error, newReq, newRes, newNext];
}
else {
const [req, res, next] = args;
const newReq = applyReq(req);
const newRes = applyRes(res);
const newNext = applyNext(newReq, newRes, next);
parsedArgs = [newReq, newRes, newNext];
}
try {
return yield handler(...parsedArgs);
}
catch (error) {
const next = parsedArgs[args.length - 1];
return yield next(error);
}
});
Object.defineProperty(newHandler, 'length', {
enumerable: false,
writable: false,
configurable: true,
value: length,
});
return newHandler;
}
return handler;
}
methods.forEach(method => {
const originalMethod = exports.api[method];
Object.assign(exports.api, {
// tslint:disable-next-line function-name
[method](...args) {
const newArgs = args.slice(0);
for (const index in newArgs) {
const handler = newArgs[index];
if (Array.isArray(handler)) {
for (const subindex in handler) {
handler[subindex] = mapHandler(handler[subindex]);
}
}
else {
newArgs[index] = mapHandler(handler);
}
}
return originalMethod.apply(this, newArgs);
},
});
});
exports.log = logger_1.logger('server');
exports.httpServer = http.createServer(exports.api);
middlewares_1.default.addAction('body.json', ({ api }) => __awaiter(this, void 0, void 0, function* () {
api.use(config_1.config.server.path, body_parser_1.json(config_1.config.server.json));
}), { priority: 5 });
middlewares_1.default.addAction('cors', ({ api }) => __awaiter(this, void 0, void 0, function* () {
api.use(config_1.config.server.path, cors(config_1.config.server.cors));
}), { priority: 5 });
let contextReady = false;
middlewares_1.default.addAction('context', () => {
exports.api.use(config_1.config.server.path, (req, res, next) => __awaiter(this, void 0, void 0, function* () {
yield gatewayRequest_1.default.do({ req });
const context = yield context_1.createContextFromExpressRequest(req);
Object.defineProperty(req, 'context', {
enumerable: true,
writable: false,
configurable: true,
value: context,
});
contextReady = true;
next();
}));
}, { priority: 8 });
middlewares_1.default.addAction('errors', ({ api }) => __awaiter(this, void 0, void 0, function* () {
api.use(config_1.config.server.path, handleExpressError);
}), { priority: 1 });
middlewares_1.default.addAction('graphql', ({ api }) => __awaiter(this, void 0, void 0, function* () {
api.post(url_1.basePath(config_1.config.server.graphqlPath), (req, res, next) => __awaiter(this, void 0, void 0, function* () {
try {
const context = req.context;
const apolloServer = new apollo_server_express_1.ApolloServer({
context,
schema: context.schema,
debug: false,
formatResponse(response, options) {
return __1.formatResponse(context, response);
},
formatError: (error) => {
return __1.formatErrors(context, Array.isArray(error) ? error : [error]);
},
});
require('apollo-server-express/dist/expressApollo').graphqlExpress(() => apolloServer.createGraphQLServerOptions(req, res))(req, res, next);
}
catch (error) {
next(error);
}
}));
}), { priority: 50 });
const _a = config_1.config.server.playground, { disable: disablePlayground, path: playgroundPath } = _a, playgroundOptions = __rest(_a, ["disable", "path"]);
const playgroundHook = middlewares_1.default.addAction('playground', ({ api }) => __awaiter(this, void 0, void 0, function* () {
if (process.env.NODE_ENV === 'development') {
const reload = require('reload')(api);
death_1.default.addAction('reload', () => {
if (typeof reload.wss !== 'undefined') {
reload.wss.close();
}
});
}
api.get(url_1.basePath(playgroundPath), (req, res, next) => {
if (process.env.NODE_ENV === 'development') {
const reloadInjection = '<script src="/reload/reload.js"></script>';
const write = res.write.bind(res);
Object.assign(res, {
write(body) {
return write(body.replace(/(<\/body>)/, ` ${reloadInjection}\n$1`));
}
});
}
// perform more expensive content-type check only if necessary
// XXX We could potentially move this logic into the GuiOptions lambda,
// but I don't think it needs any overriding
const accept = accepts(req);
const types = accept.types();
const prefersHTML = types.find((x) => x === 'text/html' || x === 'application/json') === 'text/html';
if (prefersHTML) {
const playgroundRenderPageOptions = Object.assign({ endpoint: url_1.basePath(config_1.config.server.graphqlPath) }, (!config_1.config.server.ws.disable && {
subscriptionEndpoint: url_1.baseWSPath(config_1.config.server.ws.graphqlPath),
}), playgroundOptions);
res.setHeader('Content-Type', 'text/html');
const playground = graphql_playground_html_1.renderPlaygroundPage(playgroundRenderPageOptions);
res.write(playground);
res.end();
return;
}
});
}), { priority: 60 });
if (disablePlayground) {
playgroundHook.disable();
}
init_1.default.addAction('middlewares', () => __awaiter(this, void 0, void 0, function* () {
yield middlewares_1.default.do({ api: exports.api });
}));
init_1.default.addAction('serverStartingLog', () => __awaiter(this, void 0, void 0, function* () {
if (process.env.SERVER_RESTART !== 'true') {
exports.log.verbose('Launching...');
}
}), { before: 'server' });
serverLaunchInfo_1.default.addFilter('websocket', (items) => {
return [
...items,
process.stdout.isTTY
? `WebSocket/Subscriptions: ${config_1.config.server.ws.disable ? 'Disabled' : 'Enabled'}`
: `🔴 WebSocket/Subscriptions: ${config_1.config.server.ws.disable ? '❌ Disabled' : '✅ Enabled'}`,
];
});
listening_1.default.addAction('log', () => __awaiter(this, void 0, void 0, function* () {
if (process.env.SERVER_RESTART === 'true') {
exports.log.info(`Up to date!`);
}
else {
if (process.stdout.isTTY) {
exports.log.info(`Listening on Port ${config_1.config.server.listenPort} and Address ${config_1.config.server.listenAddress}`);
exports.log.info(`Available in the browser at ${url_1.siteUrl()}`);
exports.log.info(serverLaunchInfo_1.default.filter([], {}).join('\n '));
}
else {
console.log(`
You are all set!
Now listening on Port ${config_1.config.server.listenPort} and Address ${config_1.config.server.listenAddress}
Available in the browser at ${url_1.siteUrl()}
${serverLaunchInfo_1.default.filter([], {}).join('\n ')}
Press Ctrl-C to stop.
`);
}
}
if (process.env.NODE_ENV === 'development' && process.env.SERVER_RESTART === 'true') {
const { notify } = yield Promise.resolve().then(() => require('../internal/utils/notify'));
notify('✔️ Server is running with latest changes');
}
}));
init_1.default.addAction('subscription', () => {
const subscribeServer = new subscriptions_transport_ws_1.SubscriptionServer({
execute: __1.executeWithContext,
subscribe: __1.subscribeWithContext,
schema: getSchema_1.getSchema(),
onConnect(connectionParams, webSocket, context) {
return __awaiter(this, void 0, void 0, function* () {
const operationContext = {};
Object.assign(webSocket, { operationContext });
yield gatewayConnect_1.default.do({ connectionParams, webSocket, context });
return true;
});
},
onOperation(message, params, webSocket) {
return __awaiter(this, void 0, void 0, function* () {
const { id: opId } = message;
if (opId == null) {
throw new Error('No operation id set');
}
const context = yield context_1.createContextFromWebSocketTransport(webSocket, message, params);
webSocket.operationContext[opId] = context;
return Object.assign({}, params, { context });
});
},
onOperationComplete(webSocket, opId) {
return __awaiter(this, void 0, void 0, function* () {
const { operationContext } = webSocket;
if (typeof operationContext === 'undefined') {
return;
}
const context = operationContext[opId];
if (context != null && !context.completed) {
context.end(null, 'unsubscribed');
}
});
},
}, {
server: exports.httpServer,
path: url_1.basePath(config_1.config.server.ws.graphqlPath),
});
death_1.default.addAction('subscription', () => {
subscribeServer.close();
}, 100);
});
init_1.default.addAction('server', () => __awaiter(this, void 0, void 0, function* () {
yield new Promise((resolve, reject) => {
// Create an http listener for our express app.
exports.httpServer.listen(config_1.config.server.listenPort, config_1.config.server.listenAddress, () => {
// Execute listening hook
listening_1.default.do({ httpServer: exports.httpServer, api: exports.api })
.then(resolve)
.catch(reject);
});
exports.httpServer.on('error', reject);
});
death_1.default.addAction('server', () => __awaiter(this, void 0, void 0, function* () {
yield new Promise((resolve, reject) => {
exports.httpServer.close((error) => {
if (error !== undefined) {
reject(error);
}
else {
resolve();
}
});
});
}));
}), { priority: 50 });
exports.default = exports.api;
//# sourceMappingURL=index.js.map