GRPC client using with node js facing issue - node.js

I am facing an issue when I am trying to create a grpc client call using node js. when I use import "google/api/annotations.proto" in proto file I get an below error. if I remove it it works file. May I know what I am missing from my client.js
Error: unresolvable extensions: 'extend google.protobuf.MethodOptions' in .google.api
at Root.resolveAll (src/github.com/workspace/explorer/node_modules/protobufjs/src/root.js:255:15)
at Object.loadSync (/src/github.com/workspace/explorer/node_modules/#grpc/proto-loader/build/src/index.js:224:16)
at Object. (/src/github.com/workspace/explorer/server/grpc/client.js:3:37)
syntax = 'proto3';
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
package chain;
service chain {
rpc GetHeight(HeightRequest) returns(HeightResponse) { option (google.api.http).get = "/api/height/{height}";}
}
message HeightRequest {
string hash = 1;
}
message HeightResponse {
int64 height=1;
}
client.js
var PROTO_PATH = __dirname + '/proto/chain.proto';
var parseArgs = require('minimist');
var grpc = require('#grpc/grpc-js');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
var chain_proto = grpc.loadPackageDefinition(packageDefinition).chain;
function main() {
var argv = parseArgs(process.argv.slice(2), {
string: 'target'
});
var target;
if (argv.target) {
target = argv.target;
} else {
target = 'localhost:9040';
}
var client = new chain_proto.chain(target,
grpc.credentials.createInsecure());
client.GetHeight(function (err, response) {
console.log('height:', response);
});
}
main();

I found the solution to the above error, you need to create a folder inside the project directory googleapis->google->api then need to add an annotation.proto file from grpc-gateway GitHub like mention in this link
Grpc-gateway
Next need to add a path as shown below.
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [
__dirname + '/googleapis',
]
});

Related

Is there a way to generate pb files based on the options same as loadSync method uses?

When using dynamic code generation, we can load .proto files using:
const packageDefinition: PackageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [__dirname],
});
We can set keepCase option to preserve field names, don't change them to camel case. And enums option, so we can use the string represent of enums.
Now, I am using static code generation. Facing above two problems, here is a method implementation of my service:
public async getTopics(call: ServerUnaryCall<GetTopicsRequest>, callback: sendUnaryData<GetTopicsResponse>) {
const obj = call.request.toObject();
const url = `${config.CNODE_API_URL}/topics`;
const params = {
page: obj.page || obj.page + 1,
tab: (getEnumKeyByEnumValue(Tab, obj.tab) || '').toLowerCase(),
mdrender: (getEnumKeyByEnumValue(Mdrender, obj.mdrender) || 'true').toLowerCase(),
limit: obj.limit,
};
try {
const res = await axios.get(url, { params });
const data = res.data;
// console.log(data);
const topicsResponse = new GetTopicsResponse();
data.data.forEach((po, i) => {
const topic = new Topic();
topic.setId(po.id);
topic.setAuthorId(po.author_id);
topic.setTab(Tab[po.tab.toUpperCase() as keyof typeof Tab]);
topic.setContent(po.content);
topic.setTitle(po.title);
topic.setLastReplyAt(po.last_reply_at);
topic.setGood(po.good);
topic.setTop(po.top);
topic.setReplyCount(po.reply_count);
topic.setVisitCount(po.visit_count);
topic.setCreateAt(po.create_at);
const author = new UserBase();
author.setLoginname(po.author.loginname);
author.setAvatarUrl(po.avatar_url);
topic.setAuthor(author);
topicsResponse.addData(topic, i);
});
topicsResponse.setSuccess(data.success);
callback(null, topicsResponse);
} catch (error) {
console.log(error);
const metadata = new Metadata({ idempotentRequest: true });
metadata.set('params', JSON.stringify(params));
metadata.set('url', url);
const ErrGetTopics: ServiceError = { code: status.INTERNAL, name: 'getTopicsError', message: 'call CNode API failed', metadata };
callback(ErrGetTopics, null);
}
}
Here are the problems I am facing:
Can't set default value when using proto3, want to set default value of page argument to 1, NOT 0.
I need to convert enum from number to string represent manually.
The fields from RESTful API response is snake case, but protoc, grpc_tools_node_protoc and grpc_tools_node_protoc_ts plugin generate models which have camel case fields. So I want to keep case.
As you can see, the hydration process is not convenient and boring. I need to call setters to set value for field one by one.

How gRPC-web handles bytes data from server-side streaming?

I want to transmit a sample video file from backend grpc service to the browser using grpc-web, I did some tweaks based on official hello world tutorial. Btw, nothing changed in the envoy configuration. The video file is split into 17 chunks, I can receive 17 messages in browser, however there is nothing inside, what should do so I can get the data?
protobuf definition:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (stream HelloReply);
}
message HelloRequest {}
message HelloReply {
bytes message = 1;
}
server.js:
var PROTO_PATH = __dirname + '/helloworld.proto';
var grpc = require('grpc');
var fs = require('fs');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
var helloworld = protoDescriptor.helloworld;
function doSayHello(call) {
let count = 0;
let videoDataStream = fs.createReadStream('./sample.mp4');
videoDataStream.on('data',function(chunk){
console.log(chunk);
console.log(++count);
call.write({videoStream: chunk});
// call.write(chunk);
}).on('end',function(){
call.end();
});
}
function getServer() {
var server = new grpc.Server();
server.addService(helloworld.Greeter.service, {
sayHello: doSayHello,
});
return server;
}
if (require.main === module) {
var server = getServer();
server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure());
server.start();
}
exports.getServer = getServer;
client.js:
const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
var client = new GreeterClient('http://localhost:8080');
var request = new HelloRequest();
client.sayHello(request).on('data', function(chunk){
//console.log(chunk.getMessage());
console.log(chunk);
});
Anyway, in case there is problem with proxy, below is my envoy.yaml:
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: greeter_service
max_grpc_timeout: 0s
cors:
allow_origin_string_match:
- prefix: "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
- name: envoy.grpc_web
- name: envoy.cors
- name: envoy.router
clusters:
- name: greeter_service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]
the bytes logged on server side:
and below console output in browser:
Have you tried testing with a gRPC (rather than gRPC Web) client to eliminate the possibility that the proxy is the problem?
I'm not super familiar with the Node.JS implementation but...
Should it not be server.addProtoService(...)?
Also the message streamed by the server is:
message HelloReply {
bytes message = 1;
}
But you:
call.write({videoStream: chunk});
Should it not be:
call.write({message: chunk});

How to correctly handle callbacks between Node.js gRPC client & server

I have a simple Express/ gRPC project that's supposed to print a hard coded JSON object to the browser & console. The problem is that the object isn't returned to the client in time to receive it from the gRPC server.
My project is actually a modification of this Codelabs project. I've only implemented the listBooks method though, and have changed Go to Express. I was able to successfully get a response from the server in the Codelabs project.
At first I thought about avoiding callbacks, and trying promises (promises, promisfy...) instead. But I haven't had success. Also, from this answer, I now know that Node gRPC isn't implemented to have sync:
"Node gRPC does not have synchronous calls." - murgatroid99
With that said, the output is showing that the data isn't being received. What do I need to do to have the client wait for the data to be available to be received?
products.proto
syntax = "proto3";
package products;
service ProductService {
rpc ListProduct (Empty) returns (ProductList) {}
}
message Empty {}
message Product {
int32 id = 1;
string name = 2;
string price = 3;
}
message ProductList {
repeated Product products = 1;
}
server.js
var PROTO_PATH = __dirname + '/products.proto';
var grpc = require('grpc');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var productsProto = grpc.loadPackageDefinition(packageDefinition).products;
var products = [{
id: 123,
name: 'apple',
price: '$2'
}];
function listProduct(call, callback){
console.log(products);
callback(null, products);
}
function main(){
var server = new grpc.Server();
server.addService(productsProto.ProductService.service,
{ListProduct: listProduct}
);
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
console.log("server started");
server.start();
}
main();
client.js
var PROTO_PATH = __dirname + '/products.proto';
var grpc = require('grpc');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var products = grpc.loadPackageDefinition(packageDefinition).products;
var client = new products.ProductService('localhost:50051', grpc.credentials.createInsecure());
function printResponse(error, response){
if(error){
console.log('Error: ', error);
}
else{
console.log(response);
}
}
function listProducts() {
client.listProduct({}, function(error, products){
printResponse(error, products);
});
}
exports.listProducts = listProducts;
client-server.js
const express = require('express');
const app = express();
var client = require('./client');
app.get('/', (req, res) => {
console.log('Hello World!');
client.listProducts();
res.send('Hello World!');
});
app.listen(3000, () =>
console.log('Example app listening on port 3000!'),
);
Actual Result
The gRPC server obviously already has the object, but I print it to console just for fun. The client prints its callback result to the console, but is only printing an empty object.
server.js Output
[ { id: 123, name: 'apple', price: '$2' } ]
client.js Output
Hello World!
{ products: [] }
To Reproduce
Open 2 terminals, cd to project
Run node server.js in one terminal
Run node client-server.js in the other terminal
Open a browser to localhost:3000
The problem here has nothing to do with synchronous vs asynchronous actions.
The object you are trying to send from your server request handler does not match the response message type you declared in your products.proto file. The response message type ProductList is a message with a single field products that is a repeated list of ProductObjects. So to match that the object you send should be an object with a products field that contains an array of objects structured like ProductObject messages. The code you have is almost there. You have the array, so your server handler code should look like this:
function listProduct(call, callback){
console.log(products);
callback(null, {products: products});
}
The output you already have hints towards this. The object your client receives is {products: []}, which is the structure of the objects your server should be sending.

Cannot import google's proto with #grpc/proto-loader

I have the following proto:
syntax = "proto3";
import "google/rpc/status.proto";
message Response {
google.rpc.Status status = 1;
}
message Request {
Type name = 1;
}
service Service {
rpc SomeMethod (Request) returns (Response);
}
And I am writing a client in node:
const path = require('path');
const grpc = require('grpc');
const protoLoader = require('#grpc/proto-loader');
const protoFiles = require('google-proto-files');
const PROTO_PATH = path.join(__dirname, '/proto/myproto.proto');
const packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [protoFiles('rpc')],
},
);
const proto = grpc.loadPackageDefinition(packageDefinition);
const client = new proto.Service('localhost:1111', grpc.credentials.createInsecure());
When I run the client, I get the following error: TypeError: proto.Service is not a constructor. I found it's related to the import of status.proto. What is the right way of importing google protos using proto-loader? The server is in Java.
Olga, you cannot use the absolute path in the PROTO_PATH if you are using includeDirs. Apparently you need to put both path, i.e. path to myproto.proto AND path to the google-proto-files into includeDirs and use just file name as PROTO_PATH then it works just fine. See here:
https://github.com/grpc/grpc-node/issues/470
Here is modified code that works. Please note that I also had to replace "Type" with "int32" in the myproto.proto.
const path = require('path');
const grpc = require('grpc');
const protoLoader = require('#grpc/proto-loader');
const protoFiles = require('google-proto-files');
const PROTO_PATH = 'myproto.proto';
const packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['node_modules/google-proto-files', 'proto']
},
);
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const client = new protoDescriptor.Service('localhost:1111', grpc.credentials.createInsecure());
Hope it helps. :)
The problem here is that the path that protoFiles('rpc') returns doesn't work with the import line in your .proto file. That import line means that #grpc/proto-loader is looking for an include directory that contains google/rpc/status.proto, but protoFiles('rpc') returns a directory that directly contains status.proto. So, you have to change one or both of those things so that the relative directories match up properly.

Using Babel to Get ApolloClient to ES5 CommonJS Module Format for Node Environment

Using Babel to Get ApolloClient to ES5 CommonJS Module Format
Im trying to use Babel to get the apollo-client module to work as ES5 in a non-browser, node environment. I've gone through step below which always give me the same result. Im trying to figure out if that result is right result for a node environment. When I import the babel processed documents into my project and call a method that should be exported, im getting, cannot find module. For context, the project is a fusetools.com demo. Fusetools does not support ES2015 Promises so the idea is that with the babel es2015 preset, it should work. I'm mostly chasing this down to learn something but it would be great if I could get it to work. Any comments on an easier way to do this, now that I understand it better, would be greatly appreciated. The project where I babeled the code can be found here. The fusetools project where i used the transformed code is here.
The error I get is :
LOG: Error: JavaScript error in MainView.ux line 9: Name: Fuse.Scripting.Error
Error message: require(): module not found: js/apollo-client/ApolloClient.js
File name: MainView.ux
Line number: 9
Source line: var ApolloClient = require('js/apollo-client/ApolloClient.js');
This is the code im trying to reach:
```
"use strict";
var networkInterface_1 = require('./transport/networkInterface');
var isUndefined = require('lodash.isundefined');
var assign = require('lodash.assign');
var isString = require('lodash.isstring');
var store_1 = require('./store');
var QueryManager_1 = require('./core/QueryManager');
var storeUtils_1 = require('./data/storeUtils');
var fragments_1 = require('./fragments');
var getFromAST_1 = require('./queries/getFromAST');
var DEFAULT_REDUX_ROOT_KEY = 'apollo';
function defaultReduxRootSelector(state) {
return state[DEFAULT_REDUX_ROOT_KEY];
}
var ApolloClient = function () {
function ApolloClient(_a) {
var _this = this;
var _b = _a === void 0 ? {} : _a,
networkInterface = _b.networkInterface,
reduxRootKey = _b.reduxRootKey,
reduxRootSelector = _b.reduxRootSelector,
initialState = _b.initialState,
dataIdFromObject = _b.dataIdFromObject,
resultTransformer = _b.resultTransformer,
resultComparator = _b.resultComparator,
_c = _b.ssrMode,
ssrMode = _c === void 0 ? false : _c,
_d = _b.ssrForceFetchDelay,
ssrForceFetchDelay = _d === void 0 ? 0 : _d,
_e = _b.mutationBehaviorReducers,
mutationBehaviorReducers = _e === void 0 ? {} : _e,
_f = _b.addTypename,
addTypename = _f === void 0 ? true : _f,
queryTransformer = _b.queryTransformer;
this.middleware = function () {
return function (store) {
_this.setStore(store);
return function (next) {
return function (action) {
var returnValue = next(action);
_this.queryManager.broadcastNewStore(store.getState());
return returnValue;
};
};
};
};
if (reduxRootKey && reduxRootSelector) {
throw new Error('Both "reduxRootKey" and "reduxRootSelector" are configured, but only one of two is allowed.');
}
if (reduxRootKey) {
console.warn('"reduxRootKey" option is deprecated and might be removed in the upcoming versions, ' + 'please use the "reduxRootSelector" instead.');
this.reduxRootKey = reduxRootKey;
}
if (queryTransformer) {
throw new Error('queryTransformer option no longer supported in Apollo Client 0.5. ' + 'Instead, there is a new "addTypename" option, which is on by default.');
}
if (!reduxRootSelector && reduxRootKey) {
this.reduxRootSelector = function (state) {
return state[reduxRootKey];
};
} else if (isString(reduxRootSelector)) {
this.reduxRootKey = reduxRootSelector;
this.reduxRootSelector = function (state) {
return state[reduxRootSelector];
};
} else if (typeof reduxRootSelector === 'function') {
this.reduxRootSelector = reduxRootSelector;
} else {
this.reduxRootSelector = null;
}
this.initialState = initialState ? initialState : {};
this.networkInterface = networkInterface ? networkInterface : networkInterface_1.createNetworkInterface({ uri: '/graphql' });
this.addTypename = addTypename;
this.resultTransformer = resultTransformer;
this.resultComparator = resultComparator;
this.shouldForceFetch = !(ssrMode || ssrForceFetchDelay > 0);
this.dataId = dataIdFromObject;
this.fieldWithArgs = storeUtils_1.storeKeyNameFromFieldNameAndArgs;
if (ssrForceFetchDelay) {
setTimeout(function () {
return _this.shouldForceFetch = true;
}, ssrForceFetchDelay);
}
this.reducerConfig = {
dataIdFromObject: dataIdFromObject,
mutationBehaviorReducers: mutationBehaviorReducers
};
this.watchQuery = this.watchQuery.bind(this);
this.query = this.query.bind(this);
this.mutate = this.mutate.bind(this);
this.setStore = this.setStore.bind(this);
this.resetStore = this.resetStore.bind(this);
}
ApolloClient.prototype.watchQuery = function (options) {
this.initStore();
if (!this.shouldForceFetch && options.forceFetch) {
options = assign({}, options, {
forceFetch: false
});
}
fragments_1.createFragment(options.query);
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
query: fullDocument
});
delete realOptions.fragments;
return this.queryManager.watchQuery(realOptions);
};
;
ApolloClient.prototype.query = function (options) {
this.initStore();
if (!this.shouldForceFetch && options.forceFetch) {
options = assign({}, options, {
forceFetch: false
});
}
fragments_1.createFragment(options.query);
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
query: fullDocument
});
delete realOptions.fragments;
return this.queryManager.query(realOptions);
};
;
ApolloClient.prototype.mutate = function (options) {
this.initStore();
var fullDocument = getFromAST_1.addFragmentsToDocument(options.mutation, options.fragments);
var realOptions = Object.assign({}, options, {
mutation: fullDocument
});
delete realOptions.fragments;
return this.queryManager.mutate(realOptions);
};
;
ApolloClient.prototype.subscribe = function (options) {
this.initStore();
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
document: fullDocument
});
delete realOptions.fragments;
delete realOptions.query;
return this.queryManager.startGraphQLSubscription(realOptions);
};
ApolloClient.prototype.reducer = function () {
return store_1.createApolloReducer(this.reducerConfig);
};
ApolloClient.prototype.initStore = function () {
if (this.store) {
return;
}
if (this.reduxRootSelector) {
throw new Error('Cannot initialize the store because "reduxRootSelector" or "reduxRootKey" is provided. ' + 'They should only be used when the store is created outside of the client. ' + 'This may lead to unexpected results when querying the store internally. ' + "Please remove that option from ApolloClient constructor.");
}
this.setStore(store_1.createApolloStore({
reduxRootKey: DEFAULT_REDUX_ROOT_KEY,
initialState: this.initialState,
config: this.reducerConfig
}));
this.reduxRootKey = DEFAULT_REDUX_ROOT_KEY;
};
;
ApolloClient.prototype.resetStore = function () {
this.queryManager.resetStore();
};
;
ApolloClient.prototype.setStore = function (store) {
var reduxRootSelector;
if (this.reduxRootSelector) {
reduxRootSelector = this.reduxRootSelector;
} else {
reduxRootSelector = defaultReduxRootSelector;
this.reduxRootKey = DEFAULT_REDUX_ROOT_KEY;
}
if (isUndefined(reduxRootSelector(store.getState()))) {
throw new Error('Existing store does not use apolloReducer. Please make sure the store ' + 'is properly configured and "reduxRootSelector" is correctly specified.');
}
this.store = store;
this.queryManager = new QueryManager_1.QueryManager({
networkInterface: this.networkInterface,
reduxRootSelector: reduxRootSelector,
store: store,
addTypename: this.addTypename,
resultTransformer: this.resultTransformer,
resultComparator: this.resultComparator,
reducerConfig: this.reducerConfig
});
};
;
return ApolloClient;
}();
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = ApolloClient;
//# sourceMappingURL=ApolloClient.js.map
```
Any and all comments I might learn from are appreciated. Thank you.
One way to do this would be to use webpack like this:
const webpack = require('webpack');
const path = require('path');
module.exports = {
// watch: true,
entry: {
ApolloClient: './config/ApolloClient.js',
createNetworkInterface: './config/createNetworkInterface.js',
Redux: './config/Redux.js',
},
output: {
path: path.join(__dirname, 'build/Libs'),
filename: '[name].js',
library: '[name]',
libraryTarget: 'commonjs',
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
],
};
Then in config directory you could have:
/* ApolloClient.js */
import { ApolloClient } from 'apollo-client';
export default ApolloClient;
and
/* createNetworkInterface.js */
import { createNetworkInterface } from 'apollo-client/transport/networkInterface';
export default createNetworkInterface;
plus if you want to have Redux as well:
/* Redux.js */
import * as Redux from 'redux';
export default Redux;
However I was not able to get gql done this way and had to use bolav's fusepm.
Which you would use exactly as bolav has mention, first install it globally:
npm install -G fusepm and then fusepm npm graphql-tag
Once you have all these in place you can require them as follow:
var Redux = require('build/Libs/Redux');
var ApolloClient = require('build/Libs/ApolloClient');
var createNetworkInterface = require('build/Libs/createNetworkInterface');
var gql = require('fusejs_lib/graphql-tag_graphql-tag.umd');
This way still could use some TLC but for now, it works and get's the job done:
var networkInterface = createNetworkInterface.createNetworkInterface({
uri: 'http://localhost:8000/graphql',
});
var client = new ApolloClient.ApolloClient({
networkInterface,
});
client.query({
query: gql`
query {
allPosts {
edges {
node {
id
headline
summary(length: 80)
body
createdAt
updatedAt
personByAuthorId {
firstName
lastName
}
}
}
}
}
`,
})
.then(data => data.data.allPosts.edges.forEach(node => pages.add(createPage(node))))
.catch(error => console.log(error));
Also if you like I've setup a whole project along with server that might be of an interest to you: fuseR
I made fusepm, which has a mode to convert npm modules to run them under FuseTools. It's still has a lot of bugs, but at least I managed to come longer than you:
fuse create app apolloc
cd apolloc
npm install apollo-client
fusepm npm apollo-client
And then in your javascript:
<JavaScript>
var ApolloClient = require('fusejs_lib/apollo-client.js');
</JavaScript>
fusepm uses Babel, with some custom plugins.

Resources