UNPKG

@cortical/core

Version:

A RESTful API framework for your apps based on GraphQL type system.

393 lines (386 loc) 15.5 kB
"use strict"; 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