From 044ffad0cb8896f2cd87b3919acb1fd2fa8e2f25 Mon Sep 17 00:00:00 2001
From: Erica Kastner <dkastner@gmail.com>
Date: Thu, 19 Mar 2020 10:35:29 -0500
Subject: [PATCH] Generator for JavaScript/Apollo Client

---
 README.md                                     |    3 +-
 bin/javascript-apollo-petstore.sh             |   32 +
 docs/generators/javascript-apollo.md          |  247 ++++
 .../JavascriptApolloClientCodegen.java        | 1162 +++++++++++++++++
 .../Javascript-Apollo/.babelrc.mustache       |   33 +
 .../Javascript-Apollo/ApiClient.mustache      |  299 +++++
 .../Javascript-Apollo/README.mustache         |  226 ++++
 .../resources/Javascript-Apollo/api.mustache  |   88 ++
 .../Javascript-Apollo/api_doc.mustache        |  112 ++
 .../Javascript-Apollo/api_test.mustache       |   44 +
 .../Javascript-Apollo/enumClass.mustache      |   15 +
 .../Javascript-Apollo/git_push.sh.mustache    |   58 +
 .../Javascript-Apollo/gitignore.mustache      |   33 +
 .../Javascript-Apollo/index.mustache          |   58 +
 .../Javascript-Apollo/licenseInfo.mustache    |   12 +
 .../resources/Javascript-Apollo/mocha.opts    |    1 +
 .../Javascript-Apollo/model.mustache          |    4 +
 .../Javascript-Apollo/model_doc.mustache      |   26 +
 .../Javascript-Apollo/model_test.mustache     |   65 +
 .../Javascript-Apollo/package.mustache        |   29 +
 .../partial_model_enum_class.mustache         |   24 +
 .../partial_model_generic.mustache            |   82 ++
 .../partial_model_inner_enum.mustache         |   15 +
 .../resources/Javascript-Apollo/travis.yml    |    5 +
 .../org.openapitools.codegen.CodegenConfig    |    1 +
 pom.xml                                       |    1 +
 website/i18n/en.json                          |    4 +
 27 files changed, 2678 insertions(+), 1 deletion(-)
 create mode 100755 bin/javascript-apollo-petstore.sh
 create mode 100644 docs/generators/javascript-apollo.md
 create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml

diff --git a/README.md b/README.md
index d5faf19b02b..b293f7ed312 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ OpenAPI Generator allows generation of API client libraries (SDK generation), se
 
 |                                | Languages/Frameworks |
 |-|-|
-**API clients**                  | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Dart (1.x, 2.x)**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs)
+**API clients**                  | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Dart (1.x, 2.x)**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types, Apollo GraphQL DataStore), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs)
 **Server stubs**                 | **Ada**, **C#** (ASP.NET Core, NancyFx), **C++** (Pistache, Restbed, Qt5 QHTTPEngine), **Erlang**, **F#** (Giraffe), **Go** (net/http, Gin), **Haskell** (Servant), **Java** (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, Jersey, RestEasy, Play Framework, [PKMST](https://github.com/ProKarma-Inc/pkmst-getting-started-examples), [Vert.x](https://vertx.io/)), **Kotlin** (Spring Boot, Ktor, Vertx), **PHP** (Laravel, Lumen, Slim, Silex, [Symfony](https://symfony.com/), [Zend Expressive](https://github.com/zendframework/zend-expressive)), **Python** (Flask), **NodeJS**, **Ruby** (Sinatra, Rails5), **Rust** (rust-server), **Scala** ([Finch](https://github.com/finagle/finch), [Lagom](https://github.com/lagom/lagom), [Play](https://www.playframework.com/), Scalatra)
 **API documentation generators** | **HTML**, **Confluence Wiki**, **Asciidoc**
 **Configuration files**          | [**Apache2**](https://httpd.apache.org/)
@@ -802,6 +802,7 @@ Here is a list of template creators:
    * Java (Rest-assured): @viclovsky
    * Java (Java 11 Native HTTP client): @bbdouglas
    * Javascript/NodeJS: @jfiala
+   * Javascript (Apollo DataSource): @erithmetic
    * Javascript (Closure-annotated Angular) @achew22
    * Javascript (Flow types) @jaypea
    * JMeter: @davidkiss
diff --git a/bin/javascript-apollo-petstore.sh b/bin/javascript-apollo-petstore.sh
new file mode 100755
index 00000000000..13a20dea570
--- /dev/null
+++ b/bin/javascript-apollo-petstore.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+while [ -h "$SCRIPT" ] ; do
+  ls=`ls -ld "$SCRIPT"`
+  link=`expr "$ls" : '.*-> \(.*\)$'`
+  if expr "$link" : '/.*' > /dev/null; then
+    SCRIPT="$link"
+  else
+    SCRIPT=`dirname "$SCRIPT"`/"$link"
+  fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+  APP_DIR=`dirname "$SCRIPT"`/..
+  APP_DIR=`cd "${APP_DIR}"; pwd`
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+  mvn -B clean package
+fi
+
+# if you've executed sbt assembly previously it will use that instead.
+export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="generate -t modules/openapi-generator/src/main/resources/Javascript-Apollo -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g javascript-apollo -o samples/client/petstore/javascript-apollo --additional-properties appName=PetstoreClient $@"
+
+java $JAVA_OPTS -jar $executable $ags
diff --git a/docs/generators/javascript-apollo.md b/docs/generators/javascript-apollo.md
new file mode 100644
index 00000000000..3d012ca6441
--- /dev/null
+++ b/docs/generators/javascript-apollo.md
@@ -0,0 +1,247 @@
+---
+title: Config Options for javascript-apollo
+sidebar_label: javascript-apollo
+---
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
+|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
+|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
+|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
+|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
+|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
+|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
+|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false|
+|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
+|snapshot|When setting this property to true, the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm| |false|
+|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
+|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+|array|Array|
+|list|Array|
+|map|Object|
+
+
+## LANGUAGE PRIMITIVES
+
+<ul class="column-ul">
+<li>Array</li>
+<li>Blob</li>
+<li>Date</li>
+<li>File</li>
+<li>Object</li>
+<li>boolean</li>
+<li>number</li>
+<li>string</li>
+</ul>
+
+## RESERVED WORDS
+
+<ul class="column-ul">
+<li>abstract</li>
+<li>arguments</li>
+<li>array</li>
+<li>boolean</li>
+<li>break</li>
+<li>byte</li>
+<li>case</li>
+<li>catch</li>
+<li>char</li>
+<li>class</li>
+<li>const</li>
+<li>continue</li>
+<li>date</li>
+<li>debugger</li>
+<li>default</li>
+<li>delete</li>
+<li>do</li>
+<li>double</li>
+<li>else</li>
+<li>enum</li>
+<li>eval</li>
+<li>export</li>
+<li>extends</li>
+<li>false</li>
+<li>final</li>
+<li>finally</li>
+<li>float</li>
+<li>for</li>
+<li>formparams</li>
+<li>function</li>
+<li>goto</li>
+<li>hasownproperty</li>
+<li>headerparams</li>
+<li>if</li>
+<li>implements</li>
+<li>import</li>
+<li>in</li>
+<li>infinity</li>
+<li>instanceof</li>
+<li>int</li>
+<li>interface</li>
+<li>isfinite</li>
+<li>isnan</li>
+<li>isprototypeof</li>
+<li>let</li>
+<li>long</li>
+<li>math</li>
+<li>nan</li>
+<li>native</li>
+<li>new</li>
+<li>null</li>
+<li>number</li>
+<li>object</li>
+<li>package</li>
+<li>private</li>
+<li>protected</li>
+<li>prototype</li>
+<li>public</li>
+<li>queryparameters</li>
+<li>requestoptions</li>
+<li>return</li>
+<li>short</li>
+<li>static</li>
+<li>string</li>
+<li>super</li>
+<li>switch</li>
+<li>synchronized</li>
+<li>this</li>
+<li>throw</li>
+<li>throws</li>
+<li>tostring</li>
+<li>transient</li>
+<li>true</li>
+<li>try</li>
+<li>typeof</li>
+<li>undefined</li>
+<li>useformdata</li>
+<li>valueof</li>
+<li>var</li>
+<li>varlocaldeferred</li>
+<li>varlocalpath</li>
+<li>void</li>
+<li>volatile</li>
+<li>while</li>
+<li>with</li>
+<li>yield</li>
+</ul>
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✓|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✗|ToolingExtension
+
+### Data Type Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Custom|✗|OAS2,OAS3
+|Int32|✓|OAS2,OAS3
+|Int64|✓|OAS2,OAS3
+|Float|✓|OAS2,OAS3
+|Double|✓|OAS2,OAS3
+|Decimal|✓|ToolingExtension
+|String|✓|OAS2,OAS3
+|Byte|✓|OAS2,OAS3
+|Binary|✓|OAS2,OAS3
+|Boolean|✓|OAS2,OAS3
+|Date|✓|OAS2,OAS3
+|DateTime|✓|OAS2,OAS3
+|Password|✓|OAS2,OAS3
+|File|✓|OAS2
+|Array|✓|OAS2,OAS3
+|Maps|✓|ToolingExtension
+|CollectionFormat|✓|OAS2
+|CollectionFormatMulti|✓|OAS2
+|Enum|✓|OAS2,OAS3
+|ArrayOfEnum|✓|ToolingExtension
+|ArrayOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
+|ArrayOfCollectionOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfEnum|✓|ToolingExtension
+|MapOfEnum|✓|ToolingExtension
+|MapOfModel|✓|ToolingExtension
+|MapOfCollectionOfPrimitives|✓|ToolingExtension
+|MapOfCollectionOfModel|✓|ToolingExtension
+|MapOfCollectionOfEnum|✓|ToolingExtension
+
+### Documentation Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Readme|✓|ToolingExtension
+|Model|✓|ToolingExtension
+|Api|✓|ToolingExtension
+
+### Global Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Host|✓|OAS2,OAS3
+|BasePath|✓|OAS2,OAS3
+|Info|✓|OAS2,OAS3
+|Schemes|✗|OAS2,OAS3
+|PartialSchemes|✓|OAS2,OAS3
+|Consumes|✓|OAS2
+|Produces|✓|OAS2
+|ExternalDocumentation|✓|OAS2,OAS3
+|Examples|✓|OAS2,OAS3
+|XMLStructureDefinitions|✗|OAS2,OAS3
+|MultiServer|✗|OAS3
+|ParameterizedServer|✗|OAS3
+|ParameterStyling|✗|OAS3
+|Callbacks|✗|OAS3
+|LinkObjects|✗|OAS3
+
+### Parameter Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Path|✓|OAS2,OAS3
+|Query|✓|OAS2,OAS3
+|Header|✓|OAS2,OAS3
+|Body|✓|OAS2
+|FormUnencoded|✓|OAS2
+|FormMultipart|✓|OAS2
+|Cookie|✓|OAS3
+
+### Schema Support Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Simple|✓|OAS2,OAS3
+|Composite|✓|OAS2,OAS3
+|Polymorphism|✓|OAS2,OAS3
+|Union|✗|OAS3
+
+### Security Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasicAuth|✗|OAS2,OAS3
+|ApiKey|✗|OAS2,OAS3
+|OpenIDConnect|✗|OAS3
+|BearerToken|✗|OAS3
+|OAuth2_Implicit|✗|OAS2,OAS3
+|OAuth2_Password|✗|OAS2,OAS3
+|OAuth2_ClientCredentials|✗|OAS2,OAS3
+|OAuth2_AuthorizationCode|✗|OAS2,OAS3
+
+### Wire Format Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|JSON|✓|OAS2,OAS3
+|XML|✓|OAS2,OAS3
+|PROTOBUF|✗|ToolingExtension
+|Custom|✗|OAS2,OAS3
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java
new file mode 100644
index 00000000000..c02bb74c73c
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ * Copyright 2018 SmartBear Software
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openapitools.codegen.languages;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Schema;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.features.DocumentationFeature;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+import static org.openapitools.codegen.utils.OnceLogger.once;
+import static org.openapitools.codegen.utils.StringUtils.*;
+
+public class JavascriptApolloClientCodegen extends DefaultCodegen implements CodegenConfig {
+    @SuppressWarnings("hiding")
+    private static final Logger LOGGER = LoggerFactory.getLogger(JavascriptApolloClientCodegen.class);
+
+    public static final String PROJECT_NAME = "projectName";
+    public static final String MODULE_NAME = "moduleName";
+    public static final String PROJECT_DESCRIPTION = "projectDescription";
+    public static final String PROJECT_VERSION = "projectVersion";
+    public static final String USE_INHERITANCE = "useInheritance";
+    public static final String EMIT_JS_DOC = "emitJSDoc";
+    public static final String NPM_REPOSITORY = "npmRepository";
+
+    final String[][] JAVASCRIPT_SUPPORTING_FILES = new String[][]{
+            new String[]{"package.mustache", "package.json"},
+            // new String[]{"index.mustache", "src/index.js", },
+            // new String[]{"ApiClient.mustache", "src/ApiClient.js"},
+            new String[]{"git_push.sh.mustache", "git_push.sh"},
+            new String[]{"README.mustache", "README.md"},
+            new String[]{"mocha.opts", "mocha.opts"},
+            new String[]{"travis.yml", ".travis.yml"}
+    };
+
+    final String[][] JAVASCRIPT_ES6_SUPPORTING_FILES = new String[][]{
+            new String[]{"package.mustache", "package.json"},
+            // new String[]{"index.mustache", "src/index.js"},
+            // new String[]{"ApiClient.mustache", "src/ApiClient.js"},
+            new String[]{"git_push.sh.mustache", "git_push.sh"},
+            new String[]{"README.mustache", "README.md"},
+            new String[]{"mocha.opts", "mocha.opts"},
+            new String[]{"travis.yml", ".travis.yml"},
+            new String[]{".babelrc.mustache", ".babelrc"}
+    };
+
+    protected String projectName;
+    protected String moduleName;
+    protected String projectDescription;
+    protected String projectVersion;
+    protected String licenseName;
+
+    protected String invokerPackage;
+    protected String sourceFolder = "src";
+    protected boolean emitJSDoc = true;
+    protected String apiDocPath = "docs/";
+    protected String modelDocPath = "docs/";
+    protected String apiTestPath = "api/";
+    protected String modelTestPath = "model/";
+    protected String npmRepository = null;
+    private String modelPropertyNaming = "camelCase";
+
+    public JavascriptApolloClientCodegen() {
+        super();
+
+        modifyFeatureSet(features -> features.includeDocumentationFeatures(DocumentationFeature.Readme));
+
+        outputFolder = "generated-code/js";
+        modelTemplateFiles.put("model.mustache", ".js");
+        modelTestTemplateFiles.put("model_test.mustache", ".js");
+        apiTemplateFiles.put("api.mustache", ".js");
+        apiTestTemplateFiles.put("api_test.mustache", ".js");
+        // subfolder Javascript/es6
+        embeddedTemplateDir = templateDir = "Javascript-Apollo";
+        apiPackage = "api";
+        modelPackage = "model";
+        modelDocTemplateFiles.put("model_doc.mustache", ".md");
+        apiDocTemplateFiles.put("api_doc.mustache", ".md");
+
+        // default HIDE_GENERATION_TIMESTAMP to true
+        hideGenerationTimestamp = Boolean.TRUE;
+
+        // reference: http://www.w3schools.com/js/js_reserved.asp
+        setReservedWordsLowerCase(
+                Arrays.asList(
+                        "abstract", "arguments", "boolean", "break", "byte",
+                        "case", "catch", "char", "class", "const",
+                        "continue", "debugger", "default", "delete", "do",
+                        "double", "else", "enum", "eval", "export",
+                        "extends", "false", "final", "finally", "float",
+                        "for", "function", "goto", "if", "implements",
+                        "import", "in", "instanceof", "int", "interface",
+                        "let", "long", "native", "new", "null",
+                        "package", "private", "protected", "public", "return",
+                        "short", "static", "super", "switch", "synchronized",
+                        "this", "throw", "throws", "transient", "true",
+                        "try", "typeof", "var", "void", "volatile",
+                        "while", "with", "yield",
+                        "Array", "Date", "eval", "function", "hasOwnProperty",
+                        "Infinity", "isFinite", "isNaN", "isPrototypeOf",
+                        "Math", "NaN", "Number", "Object",
+                        "prototype", "String", "toString", "undefined", "valueOf")
+        );
+
+        languageSpecificPrimitives = new HashSet<String>(
+                Arrays.asList("String", "Boolean", "Number", "Array", "Object", "Date", "File", "Blob")
+        );
+        defaultIncludes = new HashSet<String>(languageSpecificPrimitives);
+
+        instantiationTypes.put("array", "Array");
+        instantiationTypes.put("list", "Array");
+        instantiationTypes.put("map", "Object");
+        typeMapping.clear();
+        typeMapping.put("array", "Array");
+        typeMapping.put("map", "Object");
+        typeMapping.put("List", "Array");
+        typeMapping.put("boolean", "Boolean");
+        typeMapping.put("string", "String");
+        typeMapping.put("int", "Number");
+        typeMapping.put("float", "Number");
+        typeMapping.put("number", "Number");
+        typeMapping.put("BigDecimal", "Number");
+        typeMapping.put("DateTime", "Date");
+        typeMapping.put("date", "Date");
+        typeMapping.put("long", "Number");
+        typeMapping.put("short", "Number");
+        typeMapping.put("char", "String");
+        typeMapping.put("double", "Number");
+        typeMapping.put("object", "Object");
+        typeMapping.put("integer", "Number");
+        typeMapping.put("ByteArray", "Blob");
+        typeMapping.put("binary", "File");
+        typeMapping.put("file", "File");
+        typeMapping.put("UUID", "String");
+        typeMapping.put("URI", "String");
+
+        importMapping.clear();
+
+        cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue("src"));
+        cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC));
+        cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
+        cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
+        cliOptions.add(new CliOption(PROJECT_NAME,
+                "name of the project (Default: generated from info.title or \"openapi-js-client\")"));
+        cliOptions.add(new CliOption(MODULE_NAME,
+                "module name for AMD, Node or globals (Default: generated from <projectName>)"));
+        cliOptions.add(new CliOption(PROJECT_DESCRIPTION,
+                "description of the project (Default: using info.description or \"Client library of <projectName>\")"));
+        cliOptions.add(new CliOption(PROJECT_VERSION,
+                "version of the project (Default: using info.version or \"1.0.0\")"));
+        cliOptions.add(new CliOption(CodegenConstants.LICENSE_NAME,
+                "name of the license the project uses (Default: using info.license.name)"));
+        cliOptions.add(new CliOption(EMIT_JS_DOC,
+                "generate JSDoc comments")
+                .defaultValue(Boolean.TRUE.toString()));
+        cliOptions.add(new CliOption(USE_INHERITANCE,
+                "use JavaScript prototype chains & delegation for inheritance")
+                .defaultValue(Boolean.TRUE.toString()));
+        cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC)
+                .defaultValue(Boolean.TRUE.toString()));
+        cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase"));
+        cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json"));
+    }
+
+    @Override
+    public CodegenType getTag() {
+        return CodegenType.CLIENT;
+    }
+
+    @Override
+    public String getName() {
+        return "javascript-apollo";
+    }
+
+    @Override
+    public String getHelp() {
+        return "Generates a JavaScript client library using Apollo RESTDatasource.";
+    }
+
+    @Override
+    public void processOpts() {
+        super.processOpts();
+
+        if (StringUtils.isEmpty(System.getenv("JS_POST_PROCESS_FILE"))) {
+            LOGGER.info("Environment variable JS_POST_PROCESS_FILE not defined so the JS code may not be properly formatted. To define it, try 'export JS_POST_PROCESS_FILE=\"/usr/local/bin/js-beautify -r -f\"' (Linux/Mac)");
+            LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
+        }
+
+        if (additionalProperties.containsKey(PROJECT_NAME)) {
+            setProjectName(((String) additionalProperties.get(PROJECT_NAME)));
+        }
+        if (additionalProperties.containsKey(MODULE_NAME)) {
+            setModuleName(((String) additionalProperties.get(MODULE_NAME)));
+        }
+        if (additionalProperties.containsKey(PROJECT_DESCRIPTION)) {
+            setProjectDescription(((String) additionalProperties.get(PROJECT_DESCRIPTION)));
+        }
+        if (additionalProperties.containsKey(PROJECT_VERSION)) {
+            setProjectVersion(((String) additionalProperties.get(PROJECT_VERSION)));
+        }
+        if (additionalProperties.containsKey(CodegenConstants.LICENSE_NAME)) {
+            setLicenseName(((String) additionalProperties.get(CodegenConstants.LICENSE_NAME)));
+        }
+        if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
+            setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
+        }
+        if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
+            setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE));
+        }
+        if (additionalProperties.containsKey(USE_INHERITANCE)) {
+            setUseInheritance(convertPropertyToBooleanAndWriteBack(USE_INHERITANCE));
+        } else {
+            supportsInheritance = true;
+            supportsMixins = true;
+        }
+        if (additionalProperties.containsKey(EMIT_JS_DOC)) {
+            setEmitJSDoc(convertPropertyToBooleanAndWriteBack(EMIT_JS_DOC));
+        }
+        if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
+            setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
+        }
+        if (additionalProperties.containsKey(NPM_REPOSITORY)) {
+            setNpmRepository(((String) additionalProperties.get(NPM_REPOSITORY)));
+        }
+    }
+
+    @Override
+    public void preprocessOpenAPI(OpenAPI openAPI) {
+        super.preprocessOpenAPI(openAPI);
+
+        if (openAPI.getInfo() != null) {
+            Info info = openAPI.getInfo();
+            if (StringUtils.isBlank(projectName) && info.getTitle() != null) {
+                // when projectName is not specified, generate it from info.title
+                projectName = sanitizeName(dashize(info.getTitle()));
+            }
+            if (StringUtils.isBlank(projectVersion)) {
+                // when projectVersion is not specified, use info.version
+                projectVersion = escapeUnsafeCharacters(escapeQuotationMark(info.getVersion()));
+            }
+            if (projectDescription == null) {
+                // when projectDescription is not specified, use info.description
+                if (StringUtils.isEmpty(info.getDescription())) {
+                    projectDescription = "JS API client generated by OpenAPI Generator";
+                } else {
+                    projectDescription = sanitizeName(info.getDescription());
+                }
+            }
+
+            // when licenceName is not specified, use info.license
+            if (additionalProperties.get(CodegenConstants.LICENSE_NAME) == null && info.getLicense() != null) {
+                License license = info.getLicense();
+                licenseName = license.getName();
+            }
+        }
+
+        // default values
+        if (StringUtils.isBlank(projectName)) {
+            projectName = "openapi-js-client";
+        }
+        if (StringUtils.isBlank(moduleName)) {
+            moduleName = camelize(underscore(projectName));
+        }
+        if (StringUtils.isBlank(projectVersion)) {
+            projectVersion = "1.0.0";
+        }
+        if (projectDescription == null) {
+            projectDescription = "Client library of " + projectName;
+        }
+        if (StringUtils.isBlank(licenseName)) {
+            licenseName = "Unlicense";
+        }
+
+        additionalProperties.put(PROJECT_NAME, projectName);
+        additionalProperties.put(MODULE_NAME, moduleName);
+        additionalProperties.put(PROJECT_DESCRIPTION, escapeText(projectDescription));
+        additionalProperties.put(PROJECT_VERSION, projectVersion);
+        additionalProperties.put(CodegenConstants.LICENSE_NAME, licenseName);
+        additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage);
+        additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
+        additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage);
+        additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder);
+        additionalProperties.put(USE_INHERITANCE, supportsInheritance);
+        additionalProperties.put(EMIT_JS_DOC, emitJSDoc);
+        additionalProperties.put(NPM_REPOSITORY, npmRepository);
+
+        // make api and model doc path available in mustache template
+        additionalProperties.put("apiDocPath", apiDocPath);
+        additionalProperties.put("modelDocPath", modelDocPath);
+
+        String[][] supportingTemplateFiles = JAVASCRIPT_SUPPORTING_FILES;
+
+        for (String[] supportingTemplateFile : supportingTemplateFiles) {
+            supportingFiles.add(new SupportingFile(supportingTemplateFile[0], "", supportingTemplateFile[1]));
+        }
+
+        supportingFiles.add(new SupportingFile("index.mustache", createPath(sourceFolder, invokerPackage), "index.js"));
+        supportingFiles.add(new SupportingFile("ApiClient.mustache", createPath(sourceFolder, invokerPackage), "ApiClient.js"));
+
+    }
+
+    @Override
+    public String escapeReservedWord(String name) {
+        if (this.reservedWordsMappings().containsKey(name)) {
+            return this.reservedWordsMappings().get(name);
+        }
+        return "_" + name;
+    }
+
+    /**
+     * Concatenates an array of path segments into a path string.
+     *
+     * @param segments The path segments to concatenate. A segment may contain either of the file separator characters '\' or '/'.
+     *                 A segment is ignored if it is <code>null</code>, empty or &quot;.&quot;.
+     * @return A path string using the correct platform-specific file separator character.
+     */
+    private String createPath(String... segments) {
+        StringBuilder buf = new StringBuilder();
+        for (String segment : segments) {
+            if (!StringUtils.isEmpty(segment) && !segment.equals(".")) {
+                if (buf.length() != 0)
+                    buf.append(File.separatorChar);
+                buf.append(segment);
+            }
+        }
+        for (int i = 0; i < buf.length(); i++) {
+            char c = buf.charAt(i);
+            if ((c == '/' || c == '\\') && c != File.separatorChar)
+                buf.setCharAt(i, File.separatorChar);
+        }
+        return buf.toString();
+    }
+
+    @Override
+    public String apiTestFileFolder() {
+        return createPath(outputFolder, "test", invokerPackage, apiTestPath);
+    }
+
+    @Override
+    public String modelTestFileFolder() {
+        return createPath(outputFolder, "test", invokerPackage, modelTestPath);
+    }
+
+    @Override
+    public String apiFileFolder() {
+        return createPath(outputFolder, sourceFolder, invokerPackage, apiPackage());
+    }
+
+    @Override
+    public String modelFileFolder() {
+        return createPath(outputFolder, sourceFolder, invokerPackage, modelPackage());
+    }
+
+    public String getInvokerPackage() {
+        return invokerPackage;
+    }
+
+    public void setInvokerPackage(String invokerPackage) {
+        this.invokerPackage = invokerPackage;
+    }
+
+    public void setSourceFolder(String sourceFolder) {
+        this.sourceFolder = sourceFolder;
+    }
+
+    public void setProjectName(String projectName) {
+        this.projectName = projectName;
+    }
+
+    public void setModuleName(String moduleName) {
+        this.moduleName = moduleName;
+    }
+
+    public void setProjectDescription(String projectDescription) {
+        this.projectDescription = projectDescription;
+    }
+
+    public void setProjectVersion(String projectVersion) {
+        this.projectVersion = projectVersion;
+    }
+
+    public void setLicenseName(String licenseName) {
+        this.licenseName = licenseName;
+    }
+
+    public void setNpmRepository(String npmRepository) {
+        this.npmRepository = npmRepository;
+    }
+
+    public void setUseInheritance(boolean useInheritance) {
+        this.supportsInheritance = useInheritance;
+        this.supportsMixins = useInheritance;
+    }
+
+    public void setEmitJSDoc(boolean emitJSDoc) {
+        this.emitJSDoc = emitJSDoc;
+    }
+
+    @Override
+    public String apiDocFileFolder() {
+        return createPath(outputFolder, apiDocPath);
+    }
+
+    @Override
+    public String modelDocFileFolder() {
+        return createPath(outputFolder, modelDocPath);
+    }
+
+    @Override
+    public String toApiDocFilename(String name) {
+        return toApiName(name);
+    }
+
+    @Override
+    public String toModelDocFilename(String name) {
+        return toModelName(name);
+    }
+
+    @Override
+    public String toApiTestFilename(String name) {
+        return toApiName(name) + ".spec";
+    }
+
+    @Override
+    public String toModelTestFilename(String name) {
+        return toModelName(name) + ".spec";
+    }
+
+    public String getModelPropertyNaming() {
+        return this.modelPropertyNaming;
+    }
+
+    private String getNameUsingModelPropertyNaming(String name) {
+        switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
+            case original:
+                return name;
+            case camelCase:
+                return camelize(name, true);
+            case PascalCase:
+                return camelize(name);
+            case snake_case:
+                return underscore(name);
+            default:
+                throw new IllegalArgumentException("Invalid model property naming '" +
+                        name + "'. Must be 'original', 'camelCase', " +
+                        "'PascalCase' or 'snake_case'");
+        }
+    }
+
+    @Override
+    public String toVarName(String name) {
+        // sanitize name
+        name = sanitizeName(name);  // FIXME parameter should not be assigned. Also declare it as "final"
+
+        if ("_".equals(name)) {
+            name = "_u";
+        }
+
+        // if it's all uppper case, do nothing
+        if (name.matches("^[A-Z_]*$")) {
+            return name;
+        }
+
+        // camelize (lower first character) the variable name
+        // pet_id => petId
+        name = getNameUsingModelPropertyNaming(name);
+
+        // for reserved word or word starting with number, append _
+        if (isReservedWord(name) || name.matches("^\\d.*")) {
+            name = escapeReservedWord(name);
+        }
+
+        return name;
+    }
+
+    @Override
+    public String toParamName(String name) {
+        // should be the same as variable name
+        return toVarName(name);
+    }
+
+    @Override
+    public String toModelName(String name) {
+        name = sanitizeName(name);  // FIXME parameter should not be assigned. Also declare it as "final"
+
+        if (!StringUtils.isEmpty(modelNamePrefix)) {
+            name = modelNamePrefix + "_" + name;
+        }
+
+        if (!StringUtils.isEmpty(modelNameSuffix)) {
+            name = name + "_" + modelNameSuffix;
+        }
+
+        // camelize the model name
+        // phone_number => PhoneNumber
+        name = camelize(name);
+
+        // model name cannot use reserved keyword, e.g. return
+        if (isReservedWord(name)) {
+            String modelName = "Model" + name;
+            LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName);
+            return modelName;
+        }
+
+        // model name starts with number
+        if (name.matches("^\\d.*")) {
+            String modelName = "Model" + name; // e.g. 200Response => Model200Response (after camelize)
+            LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
+            return modelName;
+        }
+
+        return name;
+    }
+
+    @Override
+    public String toModelFilename(String name) {
+        // should be the same as the model name
+        return toModelName(name);
+    }
+
+    @Override
+    public String toModelImport(String name) {
+        return name;
+    }
+
+    @Override
+    public String toApiImport(String name) {
+        return toApiName(name);
+    }
+
+    @Override
+    public String getTypeDeclaration(Schema p) {
+        if (ModelUtils.isArraySchema(p)) {
+            ArraySchema ap = (ArraySchema) p;
+            Schema inner = ap.getItems();
+            return "[" + getTypeDeclaration(inner) + "]";
+        } else if (ModelUtils.isMapSchema(p)) {
+            Schema inner = ModelUtils.getAdditionalProperties(p);
+            return "{String: " + getTypeDeclaration(inner) + "}";
+        }
+        return super.getTypeDeclaration(p);
+    }
+
+    @Override
+    public String toDefaultValue(Schema p) {
+        if (ModelUtils.isBooleanSchema(p)) {
+            if (p.getDefault() != null) {
+                return p.getDefault().toString();
+            }
+        } else if (ModelUtils.isDateSchema(p)) {
+            // TODO
+        } else if (ModelUtils.isDateTimeSchema(p)) {
+            // TODO
+        } else if (ModelUtils.isNumberSchema(p)) {
+            if (p.getDefault() != null) {
+                return p.getDefault().toString();
+            }
+        } else if (ModelUtils.isIntegerSchema(p)) {
+            if (p.getDefault() != null) {
+                return p.getDefault().toString();
+            }
+        } else if (ModelUtils.isStringSchema(p)) {
+            if (p.getDefault() != null) {
+                return "'" + p.getDefault() + "'";
+            }
+        }
+
+        return null;
+    }
+
+    public void setModelPropertyNaming(String naming) {
+        if ("original".equals(naming) || "camelCase".equals(naming) ||
+                "PascalCase".equals(naming) || "snake_case".equals(naming)) {
+            this.modelPropertyNaming = naming;
+        } else {
+            throw new IllegalArgumentException("Invalid model property naming '" +
+                    naming + "'. Must be 'original', 'camelCase', " +
+                    "'PascalCase' or 'snake_case'");
+        }
+    }
+
+    @Override
+    public String toDefaultValueWithParam(String name, Schema p) {
+        String type = normalizeType(getTypeDeclaration(p));
+        if (!StringUtils.isEmpty(p.get$ref())) {
+            return " = " + type + ".constructFromObject(data['" + name + "']);";
+        } else {
+            return " = ApiClient.convertToType(data['" + name + "'], " + type + ");";
+        }
+    }
+
+    @Override
+    public void setParameterExampleValue(CodegenParameter p) {
+        String example;
+
+        if (p.defaultValue == null) {
+            example = p.example;
+        } else {
+            example = p.defaultValue;
+        }
+
+        String type = p.baseType;
+        if (type == null) {
+            type = p.dataType;
+        }
+
+        if (Boolean.TRUE.equals(p.isInteger)) {
+            if (example == null) {
+                example = "56";
+            }
+        } else if (Boolean.TRUE.equals(p.isLong)) {
+            if (example == null) {
+                example = "789";
+            }
+        } else if (Boolean.TRUE.equals(p.isDouble)
+                || Boolean.TRUE.equals(p.isFloat)
+                || Boolean.TRUE.equals(p.isNumber)) {
+            if (example == null) {
+                example = "3.4";
+            }
+        } else if (Boolean.TRUE.equals(p.isBoolean)) {
+            if (example == null) {
+                example = "true";
+            }
+        } else if (Boolean.TRUE.equals(p.isFile) || Boolean.TRUE.equals(p.isBinary)) {
+            if (example == null) {
+                example = "/path/to/file";
+            }
+            example = "\"" + escapeText(example) + "\"";
+        } else if (Boolean.TRUE.equals(p.isDate)) {
+            if (example == null) {
+                example = "2013-10-20";
+            }
+            example = "new Date(\"" + escapeText(example) + "\")";
+        } else if (Boolean.TRUE.equals(p.isDateTime)) {
+            if (example == null) {
+                example = "2013-10-20T19:20:30+01:00";
+            }
+            example = "new Date(\"" + escapeText(example) + "\")";
+        } else if (Boolean.TRUE.equals(p.isString)) {
+            if (example == null) {
+                example = p.paramName + "_example";
+            }
+            example = "\"" + escapeText(example) + "\"";
+
+        } else if (!languageSpecificPrimitives.contains(type)) {
+            // type is a model class, e.g. User
+            example = "new " + moduleName + "." + type + "()";
+        }
+
+        // container
+        if (Boolean.TRUE.equals(p.isListContainer)) {
+            example = setPropertyExampleValue(p.items);
+            example = "[" + example + "]";
+        } else if (Boolean.TRUE.equals(p.isMapContainer)) {
+            example = setPropertyExampleValue(p.items);
+            example = "{key: " + example + "}";
+        } else if (example == null) {
+            example = "null";
+        }
+
+        p.example = example;
+    }
+
+    protected String setPropertyExampleValue(CodegenProperty p) {
+        String example;
+
+        if (p == null) {
+            return "null";
+        }
+
+        if (p.defaultValue == null) {
+            example = p.example;
+        } else {
+            example = p.defaultValue;
+        }
+
+        String type = p.baseType;
+        if (type == null) {
+            type = p.dataType;
+        }
+
+        if (Boolean.TRUE.equals(p.isInteger)) {
+            if (example == null) {
+                example = "56";
+            }
+        } else if (Boolean.TRUE.equals(p.isLong)) {
+            if (example == null) {
+                example = "789";
+            }
+        } else if (Boolean.TRUE.equals(p.isDouble)
+                || Boolean.TRUE.equals(p.isFloat)
+                || Boolean.TRUE.equals(p.isNumber)) {
+            if (example == null) {
+                example = "3.4";
+            }
+        } else if (Boolean.TRUE.equals(p.isBoolean)) {
+            if (example == null) {
+                example = "true";
+            }
+        } else if (Boolean.TRUE.equals(p.isFile) || Boolean.TRUE.equals(p.isBinary)) {
+            if (example == null) {
+                example = "/path/to/file";
+            }
+            example = "\"" + escapeText(example) + "\"";
+        } else if (Boolean.TRUE.equals(p.isDate)) {
+            if (example == null) {
+                example = "2013-10-20";
+            }
+            example = "new Date(\"" + escapeText(example) + "\")";
+        } else if (Boolean.TRUE.equals(p.isDateTime)) {
+            if (example == null) {
+                example = "2013-10-20T19:20:30+01:00";
+            }
+            example = "new Date(\"" + escapeText(example) + "\")";
+        } else if (Boolean.TRUE.equals(p.isString)) {
+            if (example == null) {
+                example = p.name + "_example";
+            }
+            example = "\"" + escapeText(example) + "\"";
+
+        } else if (!languageSpecificPrimitives.contains(type)) {
+            // type is a model class, e.g. User
+            example = "new " + moduleName + "." + type + "()";
+        }
+
+        return example;
+    }
+
+    /**
+     * Normalize type by wrapping primitive types with single quotes.
+     *
+     * @param type Primitive type
+     * @return Normalized type
+     */
+    private String normalizeType(String type) {
+        return type.replaceAll("\\b(Boolean|Integer|Number|String|Date|Blob)\\b", "'$1'");
+    }
+
+    @Override
+    public String getSchemaType(Schema p) {
+        String openAPIType = super.getSchemaType(p);
+        String type = null;
+        if (typeMapping.containsKey(openAPIType)) {
+            type = typeMapping.get(openAPIType);
+            if (!needToImport(type)) {
+                return type;
+            }
+        } else {
+            type = openAPIType;
+        }
+        if (null == type) {
+            LOGGER.error("No Type defined for Schema " + p);
+        }
+        return toModelName(type);
+    }
+
+    @Override
+    public String toOperationId(String operationId) {
+        // throw exception if method name is empty
+        if (StringUtils.isEmpty(operationId)) {
+            throw new RuntimeException("Empty method/operation name (operationId) not allowed");
+        }
+
+        operationId = camelize(sanitizeName(operationId), true);
+
+        // method name cannot use reserved keyword, e.g. return
+        if (isReservedWord(operationId)) {
+            String newOperationId = camelize("call_" + operationId, true);
+            LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
+            return newOperationId;
+        }
+
+        // operationId starts with a number
+        if (operationId.matches("^\\d.*")) {
+            String newOperationId = camelize("call_" + operationId, true);
+            LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + newOperationId);
+            return newOperationId;
+        }
+
+        return operationId;
+    }
+
+    @Override
+    public CodegenModel fromModel(String name, Schema model) {
+
+        // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming.
+        once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix.");
+
+        Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
+        CodegenModel codegenModel = super.fromModel(name, model);
+
+        if (allDefinitions != null && codegenModel != null && codegenModel.parent != null && codegenModel.hasEnums) {
+            final Schema parentModel = allDefinitions.get(codegenModel.parentSchema);
+            final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel);
+            codegenModel = JavascriptApolloClientCodegen.reconcileInlineEnums(codegenModel, parentCodegenModel);
+        }
+        if (ModelUtils.isArraySchema(model)) {
+            ArraySchema am = (ArraySchema) model;
+            if (codegenModel != null && am.getItems() != null) {
+                String itemType = getSchemaType(am.getItems());
+                codegenModel.vendorExtensions.put("x-isArray", true); // TODO: 5.0 Remove
+                codegenModel.vendorExtensions.put("x-is-array", true);
+                codegenModel.vendorExtensions.put("x-itemType", itemType); // TODO: 5.0 Remove
+                codegenModel.vendorExtensions.put("x-item-type", itemType);
+            }
+        } else if (ModelUtils.isMapSchema(model)) {
+            if (codegenModel != null && ModelUtils.getAdditionalProperties(model) != null) {
+                String itemType = getSchemaType(ModelUtils.getAdditionalProperties(model));
+                codegenModel.vendorExtensions.put("x-isMap", true); // TODO: 5.0 Remove
+                codegenModel.vendorExtensions.put("x-is-map", true);
+                codegenModel.vendorExtensions.put("x-itemType", itemType); // TODO: 5.0 Remove
+                codegenModel.vendorExtensions.put("x-item-type", itemType);
+            } else {
+                String type = model.getType();
+                if (codegenModel != null && isPrimitiveType(type)) {
+                    codegenModel.vendorExtensions.put("x-isPrimitive", true); // TODO: 5.0 Remove
+                    codegenModel.vendorExtensions.put("x-is-primitive", true);
+                }
+            }
+        }
+
+        return codegenModel;
+    }
+
+    /*
+    private String sanitizePath(String p) {
+        //prefer replace a ', instead of a fuLL URL encode for readability
+        return p.replaceAll("'", "%27");
+    }*/
+
+    private String trimBrackets(String s) {
+        if (s != null) {
+            int beginIdx = s.charAt(0) == '[' ? 1 : 0;
+            int endIdx = s.length();
+            if (s.charAt(endIdx - 1) == ']')
+                endIdx--;
+            return s.substring(beginIdx, endIdx);
+        }
+        return null;
+    }
+
+    private String getJSDocType(CodegenModel cm, CodegenProperty cp) {
+        if (Boolean.TRUE.equals(cp.isContainer)) {
+            if (cp.containerType.equals("array"))
+                return "Array.<" + cp.items + ">";
+            else if (cp.containerType.equals("map"))
+                return "Object.<String, " + cp.items + ">";
+        }
+        String dataType = trimBrackets(cp.datatypeWithEnum);
+        if (cp.isEnum) {
+            dataType = cm.classname + '.' + dataType;
+        }
+        return dataType;
+    }
+
+    private String getJSDocType(CodegenParameter cp) {
+        String dataType = trimBrackets(cp.dataType);
+        if (Boolean.TRUE.equals(cp.isListContainer)) {
+            return "Array.<" + dataType + ">";
+        } else if (Boolean.TRUE.equals(cp.isMapContainer)) {
+            return "Object.<String, " + dataType + ">";
+        }
+        return dataType;
+    }
+
+    private String getJSDocType(CodegenOperation co) {
+        String returnType = trimBrackets(co.returnType);
+        if (returnType != null) {
+            if (Boolean.TRUE.equals(co.isListContainer)) {
+                return "Array.<" + returnType + ">";
+            } else if (Boolean.TRUE.equals(co.isMapContainer)) {
+                return "Object.<String, " + returnType + ">";
+            }
+        }
+        return returnType;
+    }
+
+    private boolean isPrimitiveType(String type) {
+        final String[] primitives = {"number", "integer", "string", "boolean", "null"};
+        return Arrays.asList(primitives).contains(type);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
+        // Generate and store argument list string of each operation into
+        // vendor-extension: x-codegen-argList.
+        Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
+
+        // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming.
+        once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix.");
+
+        if (operations != null) {
+            List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
+            for (CodegenOperation operation : ops) {
+                List<String> argList = new ArrayList<>();
+                boolean hasOptionalParams = false;
+                for (CodegenParameter p : operation.allParams) {
+                    if (p.required) {
+                        argList.add(p.paramName);
+                    } else {
+                        hasOptionalParams = true;
+                    }
+                }
+
+                if (operation.servers != null && !operation.servers.isEmpty()) {
+                    // add optional parameter for servers (e.g. index)
+                    hasOptionalParams = true;
+                }
+
+                if (hasOptionalParams) {
+                    argList.add("opts");
+                }
+
+                String joinedArgList = StringUtils.join(argList, ", ");
+                operation.vendorExtensions.put("x-codegen-argList", joinedArgList); // TODO: 5.0 Remove
+                operation.vendorExtensions.put("x-codegen-arg-list", joinedArgList);
+                operation.vendorExtensions.put("x-codegen-hasOptionalParams", hasOptionalParams); // TODO: 5.0 Remove
+                operation.vendorExtensions.put("x-codegen-has-optional-params", hasOptionalParams);
+
+                // Store JSDoc type specification into vendor-extension: x-jsdoc-type.
+                for (CodegenParameter cp : operation.allParams) {
+                    String jsdocType = getJSDocType(cp);
+                    cp.vendorExtensions.put("x-jsdoc-type", jsdocType);
+                }
+                String jsdocType = getJSDocType(operation);
+                operation.vendorExtensions.put("x-jsdoc-type", jsdocType);
+
+                // Format the return type correctly
+                if (operation.returnType != null) {
+                    operation.vendorExtensions.put("x-return-type", normalizeType(operation.returnType));
+                }
+            }
+        }
+        return objs;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Map<String, Object> postProcessModels(Map<String, Object> objs) {
+        objs = super.postProcessModelsEnum(objs);
+        List<Object> models = (List<Object>) objs.get("models");
+
+        // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming.
+        once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix.");
+
+        for (Object _mo : models) {
+            Map<String, Object> mo = (Map<String, Object>) _mo;
+            CodegenModel cm = (CodegenModel) mo.get("model");
+
+            // Collect each model's required property names in *document order*.
+            // NOTE: can't use 'mandatory' as it is built from ModelImpl.getRequired(), which sorts names
+            // alphabetically and in any case the document order of 'required' and 'properties' can differ.
+            List<CodegenProperty> required = new ArrayList<>();
+            List<CodegenProperty> allRequired = supportsInheritance || supportsMixins ? new ArrayList<>() : required;
+            cm.vendorExtensions.put("x-required", required);
+            cm.vendorExtensions.put("x-all-required", allRequired);
+
+            for (CodegenProperty var : cm.vars) {
+                // Add JSDoc @type value for this property.
+                String jsDocType = getJSDocType(cm, var);
+                var.vendorExtensions.put("x-jsdoc-type", jsDocType);
+
+                if (Boolean.TRUE.equals(var.required)) {
+                    required.add(var);
+                }
+            }
+
+            for (CodegenProperty var : cm.allVars) {
+                // Add JSDoc @type value for this property.
+                String jsDocType = getJSDocType(cm, var);
+                var.vendorExtensions.put("x-jsdoc-type", jsDocType);
+
+                if (Boolean.TRUE.equals(var.required)) {
+                    required.add(var);
+                }
+            }
+
+            if (supportsInheritance || supportsMixins) {
+                for (CodegenProperty var : cm.allVars) {
+                    if (Boolean.TRUE.equals(var.required)) {
+                        allRequired.add(var);
+                    }
+                }
+            }
+
+            // set vendor-extension: x-codegen-hasMoreRequired
+            CodegenProperty lastRequired = null;
+            for (CodegenProperty var : cm.vars) {
+                if (var.required) {
+                    lastRequired = var;
+                }
+            }
+            for (CodegenProperty var : cm.vars) {
+                Optional.ofNullable(lastRequired).ifPresent(_lastRequired -> {
+                    if (var == _lastRequired) {
+                        var.vendorExtensions.put("x-codegen-hasMoreRequired", false); // TODO: 5.0 Remove
+                        var.vendorExtensions.put("x-codegen-has-more-required", false);
+                    } else if (var.required) {
+                        var.vendorExtensions.put("x-codegen-hasMoreRequired", true); // TODO: 5.0 Remove
+                        var.vendorExtensions.put("x-codegen-has-more-required", true);
+                    }
+                });
+            }
+        }
+        return objs;
+    }
+
+    @Override
+    protected boolean needToImport(String type) {
+        return !defaultIncludes.contains(type)
+                && !languageSpecificPrimitives.contains(type);
+    }
+
+    private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, CodegenModel parentCodegenModel) {
+        // This generator uses inline classes to define enums, which breaks when
+        // dealing with models that have subTypes. To clean this up, we will analyze
+        // the parent and child models, look for enums that match, and remove
+        // them from the child models and leave them in the parent.
+        // Because the child models extend the parents, the enums will be available via the parent.
+
+        // Only bother with reconciliation if the parent model has enums.
+        if (parentCodegenModel.hasEnums) {
+
+            // Get the properties for the parent and child models
+            final List<CodegenProperty> parentModelCodegenProperties = parentCodegenModel.vars;
+            List<CodegenProperty> codegenProperties = codegenModel.vars;
+
+            // Iterate over all of the parent model properties
+            boolean removedChildEnum = false;
+            for (CodegenProperty parentModelCodegenPropery : parentModelCodegenProperties) {
+                // Look for enums
+                if (parentModelCodegenPropery.isEnum) {
+                    // Now that we have found an enum in the parent class,
+                    // and search the child class for the same enum.
+                    Iterator<CodegenProperty> iterator = codegenProperties.iterator();
+                    while (iterator.hasNext()) {
+                        CodegenProperty codegenProperty = iterator.next();
+                        if (codegenProperty.isEnum && codegenProperty.equals(parentModelCodegenPropery)) {
+                            // We found an enum in the child class that is
+                            // a duplicate of the one in the parent, so remove it.
+                            iterator.remove();
+                            removedChildEnum = true;
+                        }
+                    }
+                }
+            }
+
+            if (removedChildEnum) {
+                // If we removed an entry from this model's vars, we need to ensure hasMore is updated
+                int count = 0, numVars = codegenProperties.size();
+                for (CodegenProperty codegenProperty : codegenProperties) {
+                    count += 1;
+                    codegenProperty.hasMore = (count < numVars) ? true : false;
+                }
+                codegenModel.vars = codegenProperties;
+            }
+        }
+
+        return codegenModel;
+    }
+
+    @Override
+    public String toEnumName(CodegenProperty property) {
+        return sanitizeName(camelize(property.name)) + "Enum";
+    }
+
+    @Override
+    public String toEnumVarName(String value, String datatype) {
+        if (value.length() == 0) {
+            return "empty";
+        }
+
+        // for symbol, e.g. $, #
+        if (getSymbolName(value) != null) {
+            return (getSymbolName(value)).toUpperCase(Locale.ROOT);
+        }
+
+        return value;
+    }
+
+    @Override
+    public String toEnumValue(String value, String datatype) {
+        if ("Integer".equals(datatype) || "Number".equals(datatype)) {
+            return value;
+        } else {
+            return "\"" + escapeText(value) + "\"";
+        }
+    }
+
+
+    @Override
+    public String escapeQuotationMark(String input) {
+        if (input == null) {
+            return "";
+        }
+        // remove ', " to avoid code injection
+        return input.replace("\"", "").replace("'", "");
+    }
+
+    @Override
+    public String escapeUnsafeCharacters(String input) {
+        if (input == null) {
+            return "";
+        }
+        return input.replace("*/", "*_/").replace("/*", "/_*");
+    }
+
+    @Override
+    public void postProcessFile(File file, String fileType) {
+        if (file == null) {
+            return;
+        }
+
+        String jsPostProcessFile = System.getenv("JS_POST_PROCESS_FILE");
+        if (StringUtils.isEmpty(jsPostProcessFile)) {
+            return; // skip if JS_POST_PROCESS_FILE env variable is not defined
+        }
+
+        // only process files with js extension
+        if ("js".equals(FilenameUtils.getExtension(file.toString()))) {
+            String command = jsPostProcessFile + " " + file.toString();
+            try {
+                Process p = Runtime.getRuntime().exec(command);
+                p.waitFor();
+                int exitValue = p.exitValue();
+                if (exitValue != 0) {
+                    LOGGER.error("Error running the command ({}). Exit code: {}", command, exitValue);
+                }
+                LOGGER.info("Successfully executed: " + command);
+            } catch (Exception e) {
+                LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
+            }
+        }
+    }
+}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache
new file mode 100644
index 00000000000..c73df9d50b4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache
@@ -0,0 +1,33 @@
+{
+  "presets": [
+    "@babel/preset-env"
+  ],
+  "plugins": [
+    "@babel/plugin-syntax-dynamic-import",
+    "@babel/plugin-syntax-import-meta",
+    "@babel/plugin-proposal-class-properties",
+    "@babel/plugin-proposal-json-strings",
+    [
+      "@babel/plugin-proposal-decorators",
+      {
+        "legacy": true
+      }
+    ],
+    "@babel/plugin-proposal-function-sent",
+    "@babel/plugin-proposal-export-namespace-from",
+    "@babel/plugin-proposal-numeric-separator",
+    "@babel/plugin-proposal-throw-expressions",
+    "@babel/plugin-proposal-export-default-from",
+    "@babel/plugin-proposal-logical-assignment-operators",
+    "@babel/plugin-proposal-optional-chaining",
+    [
+      "@babel/plugin-proposal-pipeline-operator",
+      {
+        "proposal": "minimal"
+      }
+    ],
+    "@babel/plugin-proposal-nullish-coalescing-operator",
+    "@babel/plugin-proposal-do-expressions",
+    "@babel/plugin-proposal-function-bind"
+  ]
+}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache
new file mode 100644
index 00000000000..445e1cef2b8
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache
@@ -0,0 +1,299 @@
+{{>licenseInfo}}
+
+import RESTDataSource from 'apollo-datasource-rest';
+
+{{#emitJSDoc}}/**
+* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient
+* @version {{projectVersion}}
+*/
+
+/**
+* Manages low level client-server communications, parameter marshalling, etc. There should not be any need for an
+* application to use this class directly - the *Api and model classes provide the public API for the service.
+* @alias module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient
+* @class
+*/{{/emitJSDoc}}
+export default class ApiClient extends RESTDataSource {
+    constructor() {
+        super()
+        
+        {{#emitJSDoc}}/**
+         * The authentication methods to be included for all API calls.
+         * @type {Array.<String>}
+         */{{/emitJSDoc}}{{=< >=}}
+        this.authentications = {
+<#authMethods>
+<#isBasic>
+<#isBasicBasic>
+            '<name>': {type: 'basic'}<^-last>,</-last>
+</isBasicBasic>
+<#isBasicBearer>
+            '<name>': {type: 'bearer'}<^-last>,</-last><#bearerFormat> // <&.></bearerFormat>
+</isBasicBearer>
+</isBasic>
+<#isApiKey>
+            '<name>': {type: 'apiKey', 'in': <#isKeyInHeader>'header'</isKeyInHeader><^isKeyInHeader>'query'</isKeyInHeader>, name: '<keyParamName>'}<^-last>,</-last>
+</isApiKey>
+<#isOAuth>
+            '<name>': {type: 'oauth2'}<^-last>,</-last>
+</isOAuth>
+</authMethods>
+        }
+    }
+
+    paramToString(param) {
+        if (param == undefined || param == null) {
+            return '';
+        }
+        if (param instanceof Date) {
+            return param.toJSON();
+        }
+
+        return param.toString();
+    }
+
+    parametrizePath(path, pathParams) {
+        return url.replace(/\{([\w-]+)\}/g, (fullMatch, key) => {
+            var value;
+            if (pathParams.hasOwnProperty(key)) {
+                value = this.paramToString(pathParams[key]);
+            } else {
+                value = fullMatch;
+            }
+
+            return encodeURIComponent(value);
+        });
+    }
+
+    isFileParam(param) {
+        // fs.ReadStream in Node.js and Electron (but not in runtime like browserify)
+        if (typeof require === 'function') {
+            let fs;
+            try {
+                fs = require('fs');
+            } catch (err) {}
+            if (fs && fs.ReadStream && param instanceof fs.ReadStream) {
+                return true;
+            }
+        }
+
+        // Buffer in Node.js
+        if (typeof Buffer === 'function' && param instanceof Buffer) {
+            return true;
+        }
+
+        // Blob in browser
+        if (typeof Blob === 'function' && param instanceof Blob) {
+            return true;
+        }
+
+        // File in browser (it seems File object is also instance of Blob, but keep this for safe)
+        if (typeof File === 'function' && param instanceof File) {
+            return true;
+        }
+
+        return false;
+    }
+
+    normalizeParams(params) {
+        var newParams = {};
+        for (var key in params) {
+            if (params.hasOwnProperty(key) && params[key] != undefined && params[key] != null) {
+                var value = params[key];
+                if (this.isFileParam(value) || Array.isArray(value)) {
+                    newParams[key] = value;
+                } else {
+                    newParams[key] = this.paramToString(value);
+                }
+            }
+        }
+
+        return newParams;
+    }
+
+    buildCollectionParam(param, collectionFormat) {
+        if (param == null) {
+            return null;
+        }
+        switch (collectionFormat) {
+            case 'csv':
+                return param.map(this.paramToString).join(',');
+            case 'ssv':
+                return param.map(this.paramToString).join(' ');
+            case 'tsv':
+                return param.map(this.paramToString).join('\t');
+            case 'pipes':
+                return param.map(this.paramToString).join('|');
+            case 'multi':
+                //return the array directly as SuperAgent will handle it as expected
+                return param.map(this.paramToString);
+            default:
+                throw new Error('Unknown collection format: ' + collectionFormat);
+        }
+    }
+
+    applyAuthOptions(fetchOptions, authNames) {
+        fetchOptions.headers = fetchOptions.headers || {};
+
+        authNames.forEach((authName) => {
+            var auth = this.authentications[authName];
+            switch (auth.type) {
+                case 'basic':
+                    if (auth.username || auth.password) {
+                        fetchOptions.headers['Authorization'] = 'Basic ' + base64.encode(auth.username + ":" + auth.password);
+                    }
+
+                    break;
+                case 'bearer':
+                case 'oauth2':
+                    if (auth.accessToken) {
+                        fetchOptions.headers['Authorization'] = 'Bearer ' + auth.accessToken;
+                    }
+
+                    break;
+                case 'apiKey':
+                    if (auth.apiKey) {
+                        var data = {};
+                        if (auth.apiKeyPrefix) {
+                            data[auth.name] = auth.apiKeyPrefix + ' ' + auth.apiKey;
+                        } else {
+                            data[auth.name] = auth.apiKey;
+                        }
+
+                        if (auth['in'] === 'header') {
+                            Object.assign(fetchOptions.headers, data);
+                        } else {
+                            Object.assign(fetchOptions.params, data);
+                        }
+                    }
+
+                    break;
+                default:
+                    throw new Error('Unknown authentication type: ' + auth.type);
+            }
+        });
+    }
+
+    async callApi(path, httpMethod, pathParams,
+        queryParams, headerParams, formParams, bodyParam, authNames,
+        returnType) {
+
+        var parameterizedPath = this.parametrizePath(path, pathParams);
+        var fetchOptions = {
+            headers: headerParams,
+            params: queryParams
+        };
+
+        this.applyAuthOptions(fetchOptions, authNames);
+
+        var body = null;
+        
+        if (bodyParam !== null && bodyParam !== undefined) {
+            body = bodyParam;
+        } else if (formParams !== null && formParams !== undefined) {
+            var _formParams = this.normalizeParams(formParams);
+            for (var key in _formParams) {
+                if (_formParams.hasOwnProperty(key)) {
+                    body[key] = _formParams[key];
+                }
+            }
+        }
+
+        var response;
+        var httpMethodFn = httpMethod.toLowerCase();
+
+        if (httpMethodFn == 'get' || httpMethodFn == 'delete') {
+            response = await this[httpMethodFn](parameterizedPath, fetchOptions);
+        } else {
+            response = await this[httpMethodFn](parameterizedPath, body, fetchOptions)
+        }
+
+        var convertedResponse = ApiClient.convertToType(response, returnType);
+        return convertedResponse;
+    }
+
+    static parseDate(str) {
+        return new Date(str);
+    }
+
+    static convertToType(data, type) {
+        if (data === null || data === undefined)
+            return data
+
+        switch (type) {
+            case 'Boolean':
+                return Boolean(data);
+            case 'Integer':
+                return parseInt(data, 10);
+            case 'Number':
+                return parseFloat(data);
+            case 'String':
+                return String(data);
+            case 'Date':
+                return ApiClient.parseDate(String(data));
+            case 'Blob':
+                return data;
+            default:
+                if (type === Object) {
+                    // generic object, return directly
+                    return data;
+                } else if (typeof type.constructFromObject === 'function') {
+                    // for model type like User and enum class
+                    return type.constructFromObject(data);
+                } else if (Array.isArray(type)) {
+                    // for array type like: ['String']
+                    var itemType = type[0];
+
+                    return data.map((item) => {
+                        return ApiClient.convertToType(item, itemType);
+                    });
+                } else if (typeof type === 'object') {
+                    // for plain object type like: {'String': 'Integer'}
+                    var keyType, valueType;
+                    for (var k in type) {
+                        if (type.hasOwnProperty(k)) {
+                            keyType = k;
+                            valueType = type[k];
+                            break;
+                        }
+                    }
+
+                    var result = {};
+                    for (var k in data) {
+                        if (data.hasOwnProperty(k)) {
+                            var key = ApiClient.convertToType(k, keyType);
+                            var value = ApiClient.convertToType(data[k], valueType);
+                            result[key] = value;
+                        }
+                    }
+
+                    return result;
+                } else {
+                    // for unknown type, return the data directly
+                    return data;
+                }
+        }
+    }
+
+    static constructFromObject(data, obj, itemType) {
+        if (Array.isArray(data)) {
+            for (var i = 0; i < data.length; i++) {
+                if (data.hasOwnProperty(i))
+                    obj[i] = ApiClient.convertToType(data[i], itemType);
+            }
+        } else {
+            for (var k in data) {
+                if (data.hasOwnProperty(k))
+                    obj[k] = ApiClient.convertToType(data[k], itemType);
+            }
+        }
+    };
+}
+
+ApiClient.CollectionFormatEnum = {
+    CSV: ',',
+    SSV: ' ',
+    TSV: '\t',
+    PIPES: '|',
+    MULTI: 'multi'
+};
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache
new file mode 100644
index 00000000000..dd4a6721a2d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache
@@ -0,0 +1,226 @@
+# {{projectName}}
+
+{{moduleName}} - JavaScript client for {{projectName}}
+{{#appDescriptionWithNewLines}}
+{{{appDescriptionWithNewLines}}}
+{{/appDescriptionWithNewLines}}
+This SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: {{appVersion}}
+- Package version: {{projectVersion}}
+{{^hideGenerationTimestamp}}
+- Build date: {{generatedDate}}
+{{/hideGenerationTimestamp}}
+- Build package: {{generatorClass}}
+{{#infoUrl}}
+For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
+{{/infoUrl}}
+
+## Installation
+
+### For [Node.js](https://nodejs.org/)
+
+#### npm
+
+To publish the library as a [npm](https://www.npmjs.com/), please follow the procedure in ["Publishing npm packages"](https://docs.npmjs.com/getting-started/publishing-npm-packages).
+
+Then install it via:
+
+```shell
+npm install {{{projectName}}} --save
+```
+
+Finally, you need to build the module:
+
+```shell
+npm run build
+```
+
+##### Local development
+
+To use the library locally without publishing to a remote npm registry, first install the dependencies by changing into the directory containing `package.json` (and this README). Let's call this `JAVASCRIPT_CLIENT_DIR`. Then run:
+
+```shell
+npm install
+```
+
+Next, [link](https://docs.npmjs.com/cli/link) it globally in npm with the following, also from `JAVASCRIPT_CLIENT_DIR`:
+
+```shell
+npm link
+```
+
+To use the link you just defined in your project, switch to the directory you want to use your {{{projectName}}} from, and run:
+
+```shell
+npm link /path/to/<JAVASCRIPT_CLIENT_DIR>
+```
+
+Finally, you need to build the module:
+
+```shell
+npm run build
+```
+
+#### git
+
+If the library is hosted at a git repository, e.g.https://github.com/{{#gitUserId}}{{.}}{{/gitUserId}}{{^gitUserId}}YOUR_USERNAME{{/gitUserId}}/{{#gitRepoId}}{{.}}{{/gitRepoId}}{{^gitRepoId}}{{projectName}}{{/gitRepoId}}
+then install it via:
+
+```shell
+    npm install {{#gitUserId}}{{.}}{{/gitUserId}}{{^gitUserId}}YOUR_USERNAME{{/gitUserId}}/{{#gitRepoId}}{{.}}{{/gitRepoId}}{{^gitRepoId}}{{projectName}}{{/gitRepoId}} --save
+```
+
+### For browser
+
+The library also works in the browser environment via npm and [browserify](http://browserify.org/). After following
+the above steps with Node.js and installing browserify with `npm install -g browserify`,
+perform the following (assuming *main.js* is your entry file):
+
+```shell
+browserify main.js > bundle.js
+```
+
+Then include *bundle.js* in the HTML pages.
+
+### Webpack Configuration
+
+Using Webpack you may encounter the following error: "Module not found: Error:
+Cannot resolve module", most certainly you should disable AMD loader. Add/merge
+the following section to your webpack config:
+
+```javascript
+module: {
+  rules: [
+    {
+      parser: {
+        amd: false
+      }
+    }
+  ]
+}
+```
+
+## Getting Started
+
+Please follow the [installation](#installation) instruction and execute the following JS code:
+
+```javascript
+var {{{moduleName}}} = require('{{{projectName}}}');
+{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}}
+{{#hasAuthMethods}}
+var defaultClient = {{{moduleName}}}.ApiClient.instance;
+{{#authMethods}}
+{{#isBasic}}
+{{#isBasicBasic}}
+// Configure HTTP basic authorization: {{{name}}}
+var {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.username = 'YOUR USERNAME'
+{{{name}}}.password = 'YOUR PASSWORD'
+{{/isBasicBasic}}
+{{#isBasicBearer}}
+// Configure Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} access token for authorization: {{{name}}}
+var {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.accessToken = "YOUR ACCESS TOKEN"
+{{/isBasicBearer}}
+{{/isBasic}}
+{{#isApiKey}}
+// Configure API key authorization: {{{name}}}
+var {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.apiKey = "YOUR API KEY"
+// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
+//{{{name}}}.apiKeyPrefix['{{{keyParamName}}}'] = "Token"
+{{/isApiKey}}
+{{#isOAuth}}
+// Configure OAuth2 access token for authorization: {{{name}}}
+var {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.accessToken = "YOUR ACCESS TOKEN"
+{{/isOAuth}}
+{{/authMethods}}
+{{/hasAuthMethods}}
+
+var api = new {{{moduleName}}}.{{{classname}}}()
+{{#hasParams}}
+{{#requiredParams}}
+var {{{paramName}}} = {{{example}}}; // {{=< >=}}{<&dataType>}<={{ }}=> {{{description}}}
+{{/requiredParams}}
+{{#optionalParams}}
+{{#-first}}
+var opts = {
+{{/-first}}
+  '{{{paramName}}}': {{{example}}}{{^-last}},{{/-last}} // {{=< >=}}{<&dataType>}<={{ }}=> {{{description}}}
+{{#-last}}
+};
+{{/-last}}
+{{/optionalParams}}
+{{/hasParams}}
+{{#usePromises}}
+api.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}).then(function({{#returnType}}data{{/returnType}}) {
+  {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}}
+}, function(error) {
+  console.error(error);
+});
+
+{{/usePromises}}
+{{^usePromises}}
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}}
+  }
+};
+api.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}{{#hasParams}}, {{/hasParams}}callback);
+{{/usePromises}}
+{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}}
+```
+
+## Documentation for API Endpoints
+
+All URIs are relative to *{{basePath}}*
+
+Class | Method | HTTP request | Description
+------------ | ------------- | ------------- | -------------
+{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{moduleName}}.{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
+{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
+
+## Documentation for Models
+
+{{#models}}{{#model}} - [{{moduleName}}.{{classname}}]({{modelDocPath}}{{classname}}.md)
+{{/model}}{{/models}}
+
+## Documentation for Authorization
+
+{{^authMethods}}
+All endpoints do not require authorization.
+{{/authMethods}}
+{{#authMethods}}
+{{#last}} Authentication schemes defined for the API:{{/last}}
+
+### {{name}}
+
+{{#isApiKey}}
+
+- **Type**: API key
+- **API key parameter name**: {{keyParamName}}
+- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
+{{/isApiKey}}
+{{#isBasic}}
+{{#isBasicBasic}}
+- **Type**: HTTP basic authentication
+{{/isBasicBasic}}
+{{#isBasicBearer}}
+- **Type**: Bearer authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}
+{{/isBasicBearer}}
+{{/isBasic}}
+{{#isOAuth}}
+
+- **Type**: OAuth
+- **Flow**: {{flow}}
+- **Authorization URL**: {{authorizationUrl}}
+- **Scopes**: {{^scopes}}N/A{{/scopes}}
+{{#scopes}}  - {{scope}}: {{description}}
+{{/scopes}}
+{{/isOAuth}}
+
+{{/authMethods}}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache
new file mode 100644
index 00000000000..be5f9f1d358
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache
@@ -0,0 +1,88 @@
+{{>licenseInfo}}
+
+{{=< >=}}
+
+import ApiClient from "../ApiClient";
+<#imports>import <&import> from '../<#modelPackage><&modelPackage>/</modelPackage><import>';
+</imports>
+
+<#emitJSDoc>/**
+* <baseName> service.
+* @module <#invokerPackage><&invokerPackage>/</invokerPackage><#apiPackage><&apiPackage>/</apiPackage><classname>
+* @version <&projectVersion>
+*/</emitJSDoc>
+export default class <&classname> extends ApiClient {
+
+    <#emitJSDoc>/**
+    * Constructs a new <&classname>. <#description>
+    * <description></description>
+    * @alias module:<#invokerPackage><&invokerPackage>/</invokerPackage><#apiPackage><&apiPackage>/</apiPackage><classname>
+    * @class
+    */</emitJSDoc>
+    constructor() {
+      super();
+      this.baseURL = <#servers.0>basePath</servers.0><^servers.0>null</servers.0>;
+    }
+
+<#operations><#operation><#emitJSDoc>
+    /**<#summary>
+     * <&summary></summary><#notes>
+     * <&notes></notes><#allParams><#required>
+     * @param {<&vendorExtensions.x-jsdoc-type>} <&paramName> <&description></required></allParams><#hasOptionalParams>
+     * @param {Object} opts Optional parameters<#allParams><^required>
+     * @param {<&vendorExtensions.x-jsdoc-type>} opts.<&paramName> <&description><#defaultValue> (default to <&.>)</defaultValue></required></allParams></hasOptionalParams>
+     <=| |=>* @return {Promise|#returnType|<|&vendorExtensions.x-jsdoc-type|>|/returnType|}|=< >=|
+     */
+</emitJSDoc>    async <operationId>(<vendorExtensions.x-codegen-arg-list>) {
+      <#vendorExtensions.x-codegen-has-optional-params>
+      opts = opts || {};
+      </vendorExtensions.x-codegen-has-optional-params>
+      let postBody = <#bodyParam><#required><paramName></required><^required>opts['<paramName>']</required></bodyParam><^bodyParam>null</bodyParam>;
+<#allParams>
+<#required>
+      // verify the required parameter '<paramName>' is set
+      if (<paramName> === undefined || <paramName> === null) {
+        throw new Error("Missing the required parameter '<paramName>' when calling <operationId>");
+      }
+</required>
+</allParams>
+
+      let pathParams = {<#pathParams>
+        '<baseName>': <#required><paramName></required><^required>opts['<paramName>']</required><#hasMore>,</hasMore></pathParams>
+      };
+      let queryParams = {<#queryParams>
+        '<baseName>': <#collectionFormat>this.buildCollectionParam(<#required><paramName></required><^required>opts['<paramName>']</required>, '<collectionFormat>')</collectionFormat><^collectionFormat><#required><paramName></required><^required>opts['<paramName>']</required></collectionFormat><#hasMore>,</hasMore></queryParams>
+      };
+      let headerParams = {<#headerParams>
+        '<baseName>': <#required><paramName></required><^required>opts['<paramName>']</required><#hasMore>,</hasMore></headerParams>
+      };
+      let formParams = {<#formParams>
+        '<baseName>': <#collectionFormat>this.buildCollectionParam(<#required><paramName></required><^required>opts['<paramName>']</required>, '<collectionFormat>')</collectionFormat><^collectionFormat><#required><paramName></required><^required>opts['<paramName>']</required></collectionFormat><#hasMore>,</hasMore></formParams>
+      };
+
+      let authNames = [<#authMethods>'<name>'<#hasMore>, </hasMore></authMethods>];
+      let contentTypes = [<#consumes>'<& mediaType>'<#hasMore>, </hasMore></consumes>];
+      let accepts = [<#produces>'<& mediaType>'<#hasMore>, </hasMore></produces>];
+      let returnType = <#vendorExtensions.x-return-type><&vendorExtensions.x-return-type></vendorExtensions.x-return-type><^vendorExtensions.x-return-type>null</vendorExtensions.x-return-type>;
+      <#servers.0>
+      let basePaths = [<#servers>'<url>'<^-last>, </-last></servers>];
+      let basePath = basePaths[0]; // by default use the first one in "servers" defined in OpenAPI
+      if (typeof opts['_base_path_index'] !== 'undefined') {
+        if (opts['_base_path_index']  >= basePaths.length || opts['_base_path_index'] <  0) {
+          throw new Error("Invalid index " + opts['_base_path_index'] + " when selecting the host settings. Must be less than " + basePaths.length);
+        }
+        basePath = basePaths[opts['_base_path_index']];
+      }
+
+      </servers.0>
+
+      return this.callApi(
+        '<&path>', '<httpMethod>',
+        pathParams, queryParams, headerParams, formParams, postBody,
+        authNames, contentTypes, accepts, returnType
+      );
+    }
+</operation></operations>
+
+}
+<={{ }}=>
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache
new file mode 100644
index 00000000000..47f5a32f183
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache
@@ -0,0 +1,112 @@
+# {{moduleName}}.{{classname}}{{#description}}
+
+{{description}}{{/description}}
+
+All URIs are relative to *{{basePath}}*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
+{{/operation}}{{/operations}}
+
+{{#operations}}
+{{#operation}}
+
+## {{operationId}}
+
+> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}})
+
+{{summary}}{{#notes}}
+
+{{notes}}{{/notes}}
+
+### Example
+
+```javascript
+import {{{moduleName}}} from '{{{projectName}}}';
+{{#hasAuthMethods}}
+let defaultClient = {{{moduleName}}}.ApiClient.instance;
+{{#authMethods}}
+{{#isBasic}}
+{{#isBasicBasic}}
+// Configure HTTP basic authorization: {{{name}}}
+let {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.username = 'YOUR USERNAME';
+{{{name}}}.password = 'YOUR PASSWORD';
+{{/isBasicBasic}}
+{{#isBasicBearer}}
+// Configure Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} access token for authorization: {{{name}}}
+let {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.accessToken = "YOUR ACCESS TOKEN"
+{{/isBasicBearer}}
+{{/isBasic}}
+{{#isApiKey}}
+// Configure API key authorization: {{{name}}}
+let {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.apiKey = 'YOUR API KEY';
+// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null)
+//{{{name}}}.apiKeyPrefix = 'Token';
+{{/isApiKey}}
+{{#isOAuth}}
+// Configure OAuth2 access token for authorization: {{{name}}}
+let {{{name}}} = defaultClient.authentications['{{{name}}}'];
+{{{name}}}.accessToken = 'YOUR ACCESS TOKEN';
+{{/isOAuth}}
+{{/authMethods}}
+{{/hasAuthMethods}}
+
+let apiInstance = new {{{moduleName}}}.{{{classname}}}();
+{{#requiredParams}}
+let {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}}
+{{/requiredParams}}
+{{#optionalParams}}
+{{#-first}}
+let opts = {
+{{/-first}}
+  '{{{paramName}}}': {{{example}}}{{^-last}},{{/-last}} // {{{dataType}}} | {{{description}}}
+{{#-last}}
+};
+{{/-last}}
+{{/optionalParams}}
+{{#usePromises}}
+apiInstance.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}).then(({{#returnType}}data{{/returnType}}) => {
+  {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}}
+}, (error) => {
+  console.error(error);
+});
+
+{{/usePromises}}
+{{^usePromises}}
+apiInstance.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}{{#hasParams}}, {{/hasParams}}(error, data, response) => {
+  if (error) {
+    console.error(error);
+  } else {
+    {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}}
+  }
+});
+{{/usePromises}}
+```
+
+### Parameters
+
+{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
+{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}}
+{{/allParams}}
+
+### Return type
+
+{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}}
+
+### Authorization
+
+{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}}
+
+### HTTP request headers
+
+- **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
+- **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}}
+
+{{/operation}}
+{{/operations}}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache
new file mode 100644
index 00000000000..e56b66d47e3
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache
@@ -0,0 +1,44 @@
+{{>licenseInfo}}
+// CommonJS-like environments that support module.exports, like Node.
+factory(require('expect.js'), require(process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'));
+
+'use strict';
+
+var instance;
+
+beforeEach(function() {
+  instance = new {{moduleName}}.{{classname}}();
+});
+
+var getProperty = function(object, getter, property) {
+  // Use getter method if present; otherwise, get the property directly.
+  if (typeof object[getter] === 'function')
+    return object[getter]();
+  else
+    return object[property];
+}
+
+var setProperty = function(object, setter, property, value) {
+  // Use setter method if present; otherwise, set the property directly.
+  if (typeof object[setter] === 'function')
+    object[setter](value);
+  else
+    object[property] = value;
+}
+
+describe('{{classname}}', function() {
+{{#operations}}
+{{#operation}}
+    describe('{{operationId}}', function() {
+      it('should call {{operationId}} successfully', function(done) {
+        //uncomment below and update the code to test {{operationId}}
+        //instance.{{operationId}}(function(error) {
+        //  if (error) throw error;
+        //expect().to.be();
+        //});
+        done();
+      });
+    });
+{{/operation}}
+{{/operations}}
+});
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache
new file mode 100644
index 00000000000..635476d2c2c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache
@@ -0,0 +1,15 @@
+{{#emitJSDoc}}/**
+* Allowed values for the <code>{{baseName}}</code> property.
+* @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%>
+* @readonly
+*/{{/emitJSDoc}}
+export default {{datatypeWithEnum}} = {
+{{#allowableValues}}{{#enumVars}}
+    {{#emitJSDoc}}/**
+     * value: {{{value}}}
+     * @const
+     */{{/emitJSDoc}}
+    "{{name}}": {{{value}}}{{^-last}},
+    {{/-last}}
+{{/enumVars}}{{/allowableValues}}
+};
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache
new file mode 100644
index 00000000000..8b3f689c912
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache
@@ -0,0 +1,58 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+    git_host="{{{gitHost}}}"
+    echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+    git_user_id="{{{gitUserId}}}"
+    echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+    git_repo_id="{{{gitRepoId}}}"
+    echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+    release_note="{{{releaseNote}}}"
+    echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=`git remote`
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+    if [ "$GIT_TOKEN" = "" ]; then
+        echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+    else
+        git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git
+    fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
+
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache
new file mode 100644
index 00000000000..e920c16718d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache
@@ -0,0 +1,33 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+node_modules
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache
new file mode 100644
index 00000000000..36b8a2032d1
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache
@@ -0,0 +1,58 @@
+{{>licenseInfo}}
+
+import ApiClient from './ApiClient';
+{{#models}}import {{#model}}{{classFilename}}{{/model}} from './{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{importPath}}';
+{{/models}}{{#apiInfo}}{{#apis}}import {{importPath}} from './{{#apiPackage}}{{apiPackage}}/{{/apiPackage}}{{importPath}}';
+{{/apis}}{{/apiInfo}}
+
+{{#emitJSDoc}}/**{{#projectDescription}}
+* {{projectDescription}}.<br>{{/projectDescription}}
+* The <code>index</code> module provides access to constructors for all the classes which comprise the public API.
+* <p>
+* An AMD (recommended!) or CommonJS application will generally do something equivalent to the following:
+* <pre>
+* var {{moduleName}} = require('{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'); // See note below*.
+* var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
+* var yyyModel = new {{moduleName}}.Yyy(); // Construct a model instance.
+* yyyModel.someProperty = 'someValue';
+* ...
+* var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
+* ...
+* </pre>
+* <em>*NOTE: For a top-level AMD script, use require(['{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'], function(){...})
+* and put the application logic within the callback function.</em>
+* </p>
+* <p>
+* A non-AMD browser application (discouraged) might do something like this:
+* <pre>
+* var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
+* var yyy = new {{moduleName}}.Yyy(); // Construct a model instance.
+* yyyModel.someProperty = 'someValue';
+* ...
+* var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
+* ...
+* </pre>
+* </p>
+* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index
+* @version {{projectVersion}}
+*/{{/emitJSDoc}}
+export {
+    {{=< >=}}
+    <#emitJSDoc>/**
+     * The ApiClient constructor.
+     * @property {module:<#invokerPackage><invokerPackage>/</invokerPackage>ApiClient}
+     */</emitJSDoc>
+    ApiClient<#models>,
+
+    <#emitJSDoc>/**
+     * The <importPath> model constructor.
+     * @property {module:<#invokerPackage><invokerPackage>/</invokerPackage><#modelPackage><modelPackage>/</modelPackage><importPath>}
+     */</emitJSDoc>
+    <importPath></models><#apiInfo><#apis>,
+
+    <#emitJSDoc>/**
+    * The <importPath> service constructor.
+    * @property {module:<#invokerPackage><invokerPackage>/</invokerPackage><#apiPackage><apiPackage>/</apiPackage><importPath>}
+    */</emitJSDoc>
+    <importPath></apis></apiInfo>
+};<={{ }}=>
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache
new file mode 100644
index 00000000000..40cac6d4a3c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache
@@ -0,0 +1,12 @@
+/**
+ * {{{appName}}}
+ * {{{appDescription}}}
+ *
+ * {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}}
+ * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ *
+ */
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts b/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts
new file mode 100644
index 00000000000..907011807d6
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts
@@ -0,0 +1 @@
+--timeout 10000
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache
new file mode 100644
index 00000000000..3ddf83730f5
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache
@@ -0,0 +1,4 @@
+{{>licenseInfo}}
+import ApiClient from '../ApiClient';
+{{#imports}}import {{import}} from './{{import}}';
+{{/imports}}{{#models}}{{#model}}{{#isEnum}}{{>partial_model_enum_class}}{{/isEnum}}{{^isEnum}}{{>partial_model_generic}}{{/isEnum}}{{/model}}{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache
new file mode 100644
index 00000000000..04ab6f949ac
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache
@@ -0,0 +1,26 @@
+{{#models}}{{#model}}{{#isEnum}}# {{moduleName}}.{{classname}}
+
+## Enum
+
+{{#allowableValues}}{{#enumVars}}
+* `{{name}}` (value: `{{{value}}}`)
+{{/enumVars}}{{/allowableValues}}
+{{/isEnum}}{{^isEnum}}# {{moduleName}}.{{classname}}
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}}
+{{/vars}}
+{{#vars}}{{#isEnum}}
+
+
+## Enum: {{datatypeWithEnum}}
+
+{{#allowableValues}}{{#enumVars}}
+* `{{name}}` (value: `{{{value}}}`)
+{{/enumVars}}{{/allowableValues}}
+
+{{/isEnum}}{{/vars}}
+{{/isEnum}}{{/model}}{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache
new file mode 100644
index 00000000000..9c80d3291b5
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache
@@ -0,0 +1,65 @@
+{{>licenseInfo}}
+(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD.
+    define(['expect.js', process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    // CommonJS-like environments that support module.exports, like Node.
+    factory(require('expect.js'), require(process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'));
+  } else {
+    // Browser globals (root is window)
+    factory(root.expect, root.{{moduleName}});
+  }
+}(this, function(expect, {{moduleName}}) {
+  'use strict';
+
+  var instance;
+
+  beforeEach(function() {
+{{#models}}
+{{#model}}
+{{^isEnum}}
+    instance = new {{moduleName}}.{{classname}}();
+{{/isEnum}}
+{{/model}}
+{{/models}}
+  });
+
+  var getProperty = function(object, getter, property) {
+    // Use getter method if present; otherwise, get the property directly.
+    if (typeof object[getter] === 'function')
+      return object[getter]();
+    else
+      return object[property];
+  }
+
+  var setProperty = function(object, setter, property, value) {
+    // Use setter method if present; otherwise, set the property directly.
+    if (typeof object[setter] === 'function')
+      object[setter](value);
+    else
+      object[property] = value;
+  }
+
+  describe('{{classname}}', function() {
+    it('should create an instance of {{classname}}', function() {
+      // uncomment below and update the code to test {{classname}}
+      //var instane = new {{moduleName}}.{{classname}}();
+      //expect(instance).to.be.a({{moduleName}}.{{classname}});
+    });
+
+{{#models}}
+{{#model}}
+{{#vars}}
+    it('should have the property {{name}} (base name: "{{baseName}}")', function() {
+      // uncomment below and update the code to test the property {{name}}
+      //var instane = new {{moduleName}}.{{classname}}();
+      //expect(instance).to.be();
+    });
+
+{{/vars}}
+{{/model}}
+{{/models}}
+  });
+
+}));
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache
new file mode 100644
index 00000000000..e6d3c752295
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache
@@ -0,0 +1,29 @@
+{
+  "name": "{{{projectName}}}",
+  "version": "{{{projectVersion}}}",
+  "description": "{{{projectDescription}}}",
+  "license": "{{licenseName}}",
+  "main": "src/index.js",
+  "scripts": {
+    "test": "mocha --require @babel/register --recursive"
+  },
+  "browser": {
+    "fs": false
+  },
+{{#npmRepository}}
+  "publishConfig":{
+    "registry":"{{npmRepository}}"
+  },
+{{/npmRepository}}
+  "dependencies": {
+    "apollo-datasource-rest": "^0.7.0"
+  },
+  "devDependencies": {
+    "expect.js": "^0.3.1",
+    "mocha": "^5.2.0",
+    "sinon": "^7.2.0"
+  },
+  "files": [
+    "src"
+  ]
+}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache
new file mode 100644
index 00000000000..e88ddd9cfb5
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache
@@ -0,0 +1,24 @@
+{{#emitJSDoc}}/**
+* Enum class {{classname}}.
+* @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%>
+* @readonly
+*/{{/emitJSDoc}}
+export default class {{classname}} {
+    {{#allowableValues}}{{#enumVars}}
+        {{#emitJSDoc}}/**
+         * value: {{{value}}}
+         * @const
+         */{{/emitJSDoc}}
+        "{{name}}" = {{{value}}};
+
+    {{/enumVars}}{{/allowableValues}}
+
+    {{#emitJSDoc}}/**
+    * Returns a <code>{{classname}}</code> enum value from a Javascript object name.
+    * @param {Object} data The plain JavaScript object containing the name of the enum value.
+    * @return {{=< >=}}{module:<#invokerPackage><invokerPackage>/</invokerPackage><#modelPackage><modelPackage>/</modelPackage><classname>}<={{ }}=> The enum <code>{{classname}}</code> value.
+    */{{/emitJSDoc}}
+    static constructFromObject(object) {
+        return object;
+    }
+}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache
new file mode 100644
index 00000000000..c16b43ec4d4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache
@@ -0,0 +1,82 @@
+
+{{#models}}{{#model}}{{#emitJSDoc}}/**
+ * The {{classname}} model module.
+ * @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}}
+ * @version {{projectVersion}}
+ */{{/emitJSDoc}}
+class {{classname}} {{#parent}}{{^parentModel}}{{#vendorExtensions.x-is-array}}extends Array {{/vendorExtensions.x-is-array}}{{/parentModel}}{{/parent}}{
+    {{#vars}}
+    {{#emitJSDoc}}/**
+     * @member {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{baseName}}
+     * @type {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=>{{#defaultValue}}
+     * @default {{{defaultValue}}}{{/defaultValue}}
+     */{{/emitJSDoc}}
+    {{baseName}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}};
+    {{/vars}}
+
+    {{#useInheritance}}{{#interfaceModels}}{{#allVars}}{{#emitJSDoc}}/**
+     * @member {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{baseName}}
+     * @type {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=>
+     */{{/emitJSDoc}}
+    #{{baseName}};
+    {{/allVars}}{{/interfaceModels}}{{/useInheritance}}
+
+    {{#emitJSDoc}}/**
+     * Constructs a new <code>{{classname}}</code>.{{#description}}
+     * {{description}}{{/description}}
+     * @alias module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}}{{#useInheritance}}{{#parent}}
+     * @extends {{#parentModel}}module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}}{{/parentModel}}{{^parentModel}}{{#vendorExtensions.x-is-array}}Array{{/vendorExtensions.x-is-array}}{{#vendorExtensions.x-is-map}}Object{{/vendorExtensions.x-is-map}}{{/parentModel}}{{/parent}}{{#interfaces}}
+     * @implements module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{.}}{{/interfaces}}{{/useInheritance}}{{#vendorExtensions.x-all-required}}
+     * @param {{name}} {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{#description}}{{{description}}}{{/description}}{{/vendorExtensions.x-all-required}}
+     */{{/emitJSDoc}}
+    constructor({{#vendorExtensions.x-all-required}}{{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-all-required}}) { {{#parent}}{{^parentModel}}{{#vendorExtensions.x-is-array}}
+        super();
+        {{/vendorExtensions.x-is-array}}{{/parentModel}}{{/parent}}{{#useInheritance}}
+        {{#interfaceModels}}{{classname}}.initialize(this{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}});{{/interfaceModels}}{{/useInheritance}}
+        {{classname}}.initialize(this{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}});
+    }
+
+    {{#emitJSDoc}}/**
+     * Initializes the fields of this object.
+     * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
+     * Only for internal use.
+     */{{/emitJSDoc}}
+    static initialize(obj{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}}) { {{#vars}}{{#required}}
+        obj['{{baseName}}'] = {{name}};{{/required}}{{/vars}}
+    }
+
+    {{#emitJSDoc}}/**
+     * Constructs a <code>{{classname}}</code> from a plain JavaScript object, optionally creating a new instance.
+     * Copies all relevant properties from <code>data</code> to <code>obj</code> if supplied or a new instance if not.
+     * @param {Object} data The plain JavaScript object bearing properties of interest.
+     * @param {{=< >=}}{module:<#invokerPackage><invokerPackage>/</invokerPackage><#modelPackage><modelPackage>/</modelPackage><classname>}<={{ }}=> obj Optional instance to populate.
+     * @return {{=< >=}}{module:<#invokerPackage><invokerPackage>/</invokerPackage><#modelPackage><modelPackage>/</modelPackage><classname>}<={{ }}=> The populated <code>{{classname}}</code> instance.
+     */{{/emitJSDoc}}
+    static constructFromObject(data, obj) {
+        if (data){{! TODO: support polymorphism: discriminator property on data determines class to instantiate.}} {
+            obj = obj || new {{classname}}();{{#parent}}{{^parentModel}}
+
+            ApiClient.constructFromObject(data, obj, '{{vendorExtensions.x-item-type}}');
+            {{/parentModel}}{{/parent}}{{#useInheritance}}{{#parentModel}}
+            {{classname}}.constructFromObject(data, obj);{{/parentModel}}{{#interfaces}}
+            {{.}}.constructFromObject(data, obj);{{/interfaces}}{{/useInheritance}}
+
+            {{#vars}}
+            if (data.hasOwnProperty('{{baseName}}')) {
+                obj['{{baseName}}']{{{defaultValueWithParam}}}
+            }
+            {{/vars}}
+        }
+        return obj;
+    }
+    {{/model}}
+}
+
+{{#vars}}{{#isEnum}}{{^isContainer}}
+{{>partial_model_inner_enum}}
+{{/isContainer}}{{/isEnum}}{{#items.isEnum}}{{#items}}{{^isContainer}}
+{{>partial_model_inner_enum}}
+{{/isContainer}}{{/items}}{{/items.isEnum}}{{/vars}}
+
+export default {{classname}};
+{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache
new file mode 100644
index 00000000000..17cea4407d5
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache
@@ -0,0 +1,15 @@
+{{#emitJSDoc}}/**
+ * Allowed values for the <code>{{baseName}}</code> property.
+ * @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%>
+ * @readonly
+ */{{/emitJSDoc}}
+{{classname}}['{{datatypeWithEnum}}'] = {
+{{#allowableValues}}{{#enumVars}}
+    {{#emitJSDoc}}/**
+     * value: {{{value}}}
+     * @const
+     */{{/emitJSDoc}}
+    "{{name}}": {{{value}}}{{^-last}},
+    {{/-last}}
+{{/enumVars}}{{/allowableValues}}
+};
diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml b/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml
new file mode 100644
index 00000000000..0968f7a4333
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+cache: npm
+node_js:
+  - "6"
+  - "6.1"
diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
index 89cbc16b896..43071312ef7 100644
--- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
+++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -60,6 +60,7 @@ org.openapitools.codegen.languages.JavaResteasyServerCodegen
 org.openapitools.codegen.languages.JavaResteasyEapServerCodegen
 org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen
 org.openapitools.codegen.languages.JavascriptClientCodegen
+org.openapitools.codegen.languages.JavascriptApolloClientCodegen
 org.openapitools.codegen.languages.JavascriptFlowtypedClientCodegen
 org.openapitools.codegen.languages.JavascriptClosureAngularClientCodegen
 org.openapitools.codegen.languages.JMeterClientCodegen
diff --git a/pom.xml b/pom.xml
index eb094907ada..826e0b3a685 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1239,6 +1239,7 @@
                 <module>samples/server/petstore/php-slim</module>
                 <module>samples/server/petstore/php-slim4</module>
                 <module>samples/client/petstore/javascript</module>
+                <module>samples/client/petstore/javascript-apollo</module>
                 <module>samples/client/petstore/javascript-es6</module>
                 <module>samples/openapi3/client/petstore/javascript-es6</module>
                 <module>samples/client/petstore/javascript-promise</module>
diff --git a/website/i18n/en.json b/website/i18n/en.json
index 890f1a9d449..89b5ad427b6 100644
--- a/website/i18n/en.json
+++ b/website/i18n/en.json
@@ -271,6 +271,10 @@
         "title": "Config Options for javascript",
         "sidebar_label": "javascript"
       },
+      "generators/javascript-apollo": {
+        "title": "Config Options for javascript-apollo",
+        "sidebar_label": "javascript-apollo"
+      },
       "generators/jaxrs-cxf-cdi": {
         "title": "Config Options for jaxrs-cxf-cdi",
         "sidebar_label": "jaxrs-cxf-cdi"
-- 
GitLab