@steedos/moleculer-apollo-server 3.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,50 @@
1
+ /*
2
+ * moleculer-apollo-server
3
+ *
4
+ * Apollo Server for Moleculer API Gateway.
5
+ *
6
+ * Based on "apollo-server-micro"
7
+ *
8
+ * https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-micro/
9
+ *
10
+ *
11
+ * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer-apollo-server)
12
+ * MIT Licensed
13
+ */
14
+
15
+ "use strict";
16
+
17
+ const core = require("apollo-server-core");
18
+ const { GraphQLUpload } = require("graphql-upload");
19
+ const { ApolloServer } = require("./src/ApolloServer");
20
+ const ApolloService = require("./src/service");
21
+ const gql = require("./src/gql");
22
+
23
+ module.exports = {
24
+ // Core
25
+ GraphQLExtension: core.GraphQLExtension,
26
+ gql: core.gql,
27
+ ApolloError: core.ApolloError,
28
+ toApolloError: core.toApolloError,
29
+ SyntaxError: core.SyntaxError,
30
+ ValidationError: core.ValidationError,
31
+ AuthenticationError: core.AuthenticationError,
32
+ ForbiddenError: core.ForbiddenError,
33
+ UserInputError: core.UserInputError,
34
+ defaultPlaygroundOptions: core.defaultPlaygroundOptions,
35
+
36
+ // GraphQL tools
37
+ ...require("graphql-tools"),
38
+
39
+ // GraphQL Upload
40
+ GraphQLUpload,
41
+
42
+ // Apollo Server
43
+ ApolloServer,
44
+
45
+ // Apollo Moleculer Service
46
+ ApolloService,
47
+
48
+ // Moleculer gql formatter
49
+ moleculerGql: gql,
50
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@steedos/moleculer-apollo-server",
3
+ "private": false,
4
+ "version": "3.0.0-beta.7",
5
+ "description": "Apollo GraphQL server for Moleculer API Gateway",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "dev": "nodemon examples/index.js",
9
+ "ci": "jest --watch",
10
+ "test": "jest --coverage",
11
+ "ci:integration": "jest \"**/integration/**spec.js\" --watch",
12
+ "lint": "eslint --ext=.js src test",
13
+ "lint:fix": "eslint --fix --ext=.js src test",
14
+ "deps": "npm-check -u",
15
+ "postdeps": "npm test",
16
+ "coverall": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
17
+ },
18
+ "keywords": [
19
+ "graphql",
20
+ "apollo-server",
21
+ "apollo",
22
+ "moleculer",
23
+ "microservice",
24
+ "gateway"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/moleculerjs/moleculer-apollo-server.git"
29
+ },
30
+ "author": "MoleculerJS",
31
+ "license": "MIT",
32
+ "jest": {
33
+ "coverageDirectory": "../coverage",
34
+ "testEnvironment": "node",
35
+ "rootDir": "./src",
36
+ "roots": [
37
+ "../test"
38
+ ],
39
+ "coveragePathIgnorePatterns": [
40
+ "/node_modules/",
41
+ "/test/services/"
42
+ ]
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "engines": {
48
+ "node": ">= 10.x.x"
49
+ },
50
+ "dependencies": {
51
+ "@apollographql/graphql-playground-html": "^1.6.29",
52
+ "@hapi/accept": "^3.2.4",
53
+ "@types/graphql-upload": "^8.0.11",
54
+ "apollo-server-core": "^2.22.2",
55
+ "dataloader": "^2.1.0",
56
+ "graphql-subscriptions": "^1.2.1",
57
+ "graphql-tools": "^9.0.11",
58
+ "graphql-upload": "^11.0.0",
59
+ "lodash": "^4.17.21",
60
+ "object-hash": "^2.2.0"
61
+ },
62
+ "gitHead": "b68a33d2fa33b380aa7ec401b63315886f7de861"
63
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ const { ApolloServerBase } = require("apollo-server-core");
4
+ const { processRequest } = require("graphql-upload");
5
+ const { renderPlaygroundPage } = require("@apollographql/graphql-playground-html");
6
+ const accept = require("@hapi/accept");
7
+ const moleculerApollo = require("./moleculerApollo");
8
+
9
+ async function send(req, res, statusCode, data, responseType = "application/json") {
10
+ res.statusCode = statusCode;
11
+
12
+ const ctx = res.$ctx;
13
+ if (!ctx.meta.$responseType) {
14
+ ctx.meta.$responseType = responseType;
15
+ }
16
+
17
+ const route = res.$route;
18
+ if (route.onAfterCall) {
19
+ data = await route.onAfterCall.call(this, ctx, route, req, res, data);
20
+ }
21
+
22
+ const service = res.$service;
23
+ service.sendResponse(req, res, data);
24
+ }
25
+
26
+ class ApolloServer extends ApolloServerBase {
27
+ // Extract Apollo Server options from the request.
28
+ createGraphQLServerOptions(req, res) {
29
+ return super.graphQLServerOptions({ req, res });
30
+ }
31
+
32
+ // Prepares and returns an async function that can be used to handle
33
+ // GraphQL requests.
34
+ createHandler({ path, disableHealthCheck, onHealthCheck } = {}) {
35
+ const promiseWillStart = this.willStart();
36
+
37
+ return async (req, res) => {
38
+ this.graphqlPath = path || "/graphql";
39
+
40
+ await promiseWillStart;
41
+
42
+ // If file uploads are detected, prepare them for easier handling with
43
+ // the help of `graphql-upload`.
44
+ if (this.uploadsConfig) {
45
+ const contentType = req.headers["content-type"];
46
+ if (contentType && contentType.startsWith("multipart/form-data")) {
47
+ req.filePayload = await processRequest(req, res, this.uploadsConfig);
48
+ }
49
+ }
50
+
51
+ // If health checking is enabled, trigger the `onHealthCheck`
52
+ // function when the health check URL is requested.
53
+ if (!disableHealthCheck && req.url === "/.well-known/apollo/server-health")
54
+ return await this.handleHealthCheck({ req, res, onHealthCheck });
55
+
56
+ // If the `playgroundOptions` are set, register a `graphql-playground` instance
57
+ // (not available in production) that is then used to handle all
58
+ // incoming GraphQL requests.
59
+ if (this.playgroundOptions && req.method === "GET") {
60
+ const { mediaTypes } = accept.parseAll(req.headers);
61
+ const prefersHTML =
62
+ mediaTypes.find(x => x === "text/html" || x === "application/json") ===
63
+ "text/html";
64
+
65
+ if (prefersHTML) {
66
+ const middlewareOptions = Object.assign(
67
+ {
68
+ endpoint: this.graphqlPath,
69
+ subscriptionEndpoint: this.subscriptionsPath,
70
+ },
71
+ this.playgroundOptions
72
+ );
73
+ return send(
74
+ req,
75
+ res,
76
+ 200,
77
+ renderPlaygroundPage(middlewareOptions),
78
+ "text/html"
79
+ );
80
+ }
81
+ }
82
+
83
+ // Handle incoming GraphQL requests using Apollo Server.
84
+ const graphqlHandler = moleculerApollo(() => this.createGraphQLServerOptions(req, res));
85
+ const responseData = await graphqlHandler(req, res);
86
+ return send(req, res, 200, responseData);
87
+ };
88
+ }
89
+
90
+ // This integration supports file uploads.
91
+ supportsUploads() {
92
+ return true;
93
+ }
94
+
95
+ // This integration supports subscriptions.
96
+ supportsSubscriptions() {
97
+ return true;
98
+ }
99
+
100
+ async handleHealthCheck({ req, res, onHealthCheck }) {
101
+ onHealthCheck = onHealthCheck || (() => undefined);
102
+ try {
103
+ const result = await onHealthCheck(req);
104
+ return send(req, res, 200, { status: "pass", result }, "application/health+json");
105
+ } catch (error) {
106
+ const result = error instanceof Error ? error.toString() : error;
107
+ return send(req, res, 503, { status: "fail", result }, "application/health+json");
108
+ }
109
+ }
110
+ }
111
+ module.exports = {
112
+ ApolloServer,
113
+ };
package/src/gql.js ADDED
@@ -0,0 +1,22 @@
1
+ const { zip } = require("lodash");
2
+
3
+ /**
4
+ * @function gql Format graphql strings for usage in moleculer-apollo-server
5
+ * @param {TemplateStringsArray} typeString - Template string array for formatting
6
+ * @param {...string} placeholders - Placeholder expressions
7
+ */
8
+ const gql = (typeString, ...placeholders) => {
9
+ // combine template string array and placeholders into a single string
10
+ const zipped = zip(typeString, placeholders);
11
+ const combinedString = zipped.reduce(
12
+ (prev, [next, placeholder]) => `${prev}${next}${placeholder || ""}`,
13
+ "",
14
+ );
15
+ const re = /type\s+(Query|Mutation|Subscription)\s+{(.*?)}/s;
16
+
17
+ const result = re.exec(combinedString);
18
+ // eliminate Query/Mutation/Subscription wrapper if present as moleculer-apollo-server will stitch them together
19
+ return Array.isArray(result) ? result[2] : combinedString;
20
+ };
21
+
22
+ module.exports = gql;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const { runHttpQuery, convertNodeHttpToRequest } = require("apollo-server-core");
4
+ const url = require("url");
5
+
6
+ // Utility function used to set multiple headers on a response object.
7
+ function setHeaders(res, headers) {
8
+ Object.keys(headers).forEach(header => res.setHeader(header, headers[header]));
9
+ }
10
+
11
+ module.exports = function graphqlMoleculer(options) {
12
+ if (!options) {
13
+ throw new Error("Apollo Server requires options.");
14
+ }
15
+
16
+ if (arguments.length > 1) {
17
+ throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
18
+ }
19
+
20
+ return async function graphqlHandler(req, res) {
21
+ let query;
22
+ try {
23
+ if (req.method === "POST") {
24
+ query = req.filePayload || req.body;
25
+ } else {
26
+ query = url.parse(req.url, true).query;
27
+ }
28
+ } catch (error) {
29
+ // Do nothing; `query` stays `undefined`
30
+ }
31
+
32
+ try {
33
+ const { graphqlResponse, responseInit } = await runHttpQuery([req, res], {
34
+ method: req.method,
35
+ options,
36
+ query,
37
+ request: convertNodeHttpToRequest(req),
38
+ });
39
+
40
+ setHeaders(res, responseInit.headers);
41
+
42
+ return graphqlResponse;
43
+ } catch (error) {
44
+ if ("HttpQueryError" === error.name && error.headers) {
45
+ setHeaders(res, error.headers);
46
+ }
47
+
48
+ if (!error.statusCode) {
49
+ error.statusCode = 500;
50
+ }
51
+
52
+ res.statusCode = error.statusCode || error.code || 500;
53
+ res.end(error.message);
54
+
55
+ return undefined;
56
+ }
57
+ };
58
+ };