From 03357fd0deed0e6c78c50fa3a35ec01e1d39bc91 Mon Sep 17 00:00:00 2001
From: Adam Plumer <caerus.karu@gmail.com>
Date: Sun, 20 May 2018 19:42:52 +1200
Subject: [PATCH] feat(grpc-engine): introduce package

---
 modules/grpc-engine/BUILD.bazel           | 73 +++++++++++++++++++++++
 modules/grpc-engine/README.md             |  7 +++
 modules/grpc-engine/index.ts              |  8 +++
 modules/grpc-engine/package.json          | 28 +++++++++
 modules/grpc-engine/public_api.ts         |  8 +++
 modules/grpc-engine/spec/index.spec.ts    | 43 +++++++++++++
 modules/grpc-engine/src/grpc-engine.proto | 24 ++++++++
 modules/grpc-engine/src/main.ts           | 59 ++++++++++++++++++
 package.json                              |  5 +-
 9 files changed, 254 insertions(+), 1 deletion(-)
 create mode 100644 modules/grpc-engine/BUILD.bazel
 create mode 100644 modules/grpc-engine/README.md
 create mode 100644 modules/grpc-engine/index.ts
 create mode 100644 modules/grpc-engine/package.json
 create mode 100644 modules/grpc-engine/public_api.ts
 create mode 100644 modules/grpc-engine/spec/index.spec.ts
 create mode 100644 modules/grpc-engine/src/grpc-engine.proto
 create mode 100644 modules/grpc-engine/src/main.ts

diff --git a/modules/grpc-engine/BUILD.bazel b/modules/grpc-engine/BUILD.bazel
new file mode 100644
index 0000000000..fcf0e217a2
--- /dev/null
+++ b/modules/grpc-engine/BUILD.bazel
@@ -0,0 +1,73 @@
+load("//tools:defaults.bzl", "ng_module", "ng_package", "ng_test_library", "jasmine_node_test")
+
+package(default_visibility = ["//visibility:public"])
+
+#load("@build_bazel_rules_typescript//:defs.bzl", "ts_proto_library")
+
+#proto_library(
+#    name = "engine_proto",
+#    srcs = ["grpc-engine.proto"],
+#)
+#
+#ts_proto_library(
+#    name = "engine_ts_proto",
+#    deps = [":engine_proto"],
+#)
+
+filegroup(
+  name = "temp_proto",
+  srcs = ["src/grpc-engine.proto"]
+)
+
+ng_module(
+    name = "grpc-engine",
+    srcs = glob([
+        "*.ts",
+        "src/**/*.ts",
+    ]),
+    module_name = "@nguniversal/grpc-engine",
+    deps = [
+        "//modules/common/engine",
+        "@ngudeps//grpc"
+        # ":engine_ts_proto"
+    ],
+)
+
+ng_package(
+    name = "npm_package",
+    srcs = [
+        ":package.json",
+        ":grpc-engine.proto",
+    ],
+    entry_point = "modules/grpc-engine/index.js",
+    readme_md = ":README.md",
+    tags = ["release"],
+    deps = [
+        ":grpc-engine",
+        "@ngudeps//grpc",
+    ],
+)
+
+ng_test_library(
+    name = "unit_test_lib",
+    srcs = glob([
+        "spec/**/*.spec.ts",
+    ]),
+    deps = [
+        ":grpc-engine",
+        "@ngudeps//grpc",
+        "@ngudeps//domino",
+        "@ngudeps//xhr2",
+        "@ngudeps//zone.js",
+        "@angular//packages/platform-browser",
+        "@angular//packages/platform-server",
+    ],
+)
+
+jasmine_node_test(
+    name = "unit_test",
+    srcs = [
+      ":unit_test_lib",
+      ":grpc-engine.proto",
+    ],
+)
diff --git a/modules/grpc-engine/README.md b/modules/grpc-engine/README.md
new file mode 100644
index 0000000000..caf5b43b0b
--- /dev/null
+++ b/modules/grpc-engine/README.md
@@ -0,0 +1,7 @@
+# Angular gRPC Engine
+
+This is a gRPC Engine for running Angular applications on the server for server side rendering
+
+## Usage
+
+To be added
\ No newline at end of file
diff --git a/modules/grpc-engine/index.ts b/modules/grpc-engine/index.ts
new file mode 100644
index 0000000000..45965af3d3
--- /dev/null
+++ b/modules/grpc-engine/index.ts
@@ -0,0 +1,8 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+export * from './public_api';
diff --git a/modules/grpc-engine/package.json b/modules/grpc-engine/package.json
new file mode 100644
index 0000000000..e45c41b049
--- /dev/null
+++ b/modules/grpc-engine/package.json
@@ -0,0 +1,28 @@
+{
+  "name": "@nguniversal/grpc-engine",
+  "version": "0.0.0-PLACEHOLDER",
+  "description": "gRPC engine for running server Angular applications",
+  "license": "MIT",
+  "keywords": [
+    "grpc",
+    "ssr",
+    "universal"
+  ],
+  "peerDependencies": {
+    "@angular/common": "NG_VERSION",
+    "@angular/core": "NG_VERSION",
+    "@angular/platform-server": "NG_VERSION",
+    "grpc": "^1.11.3"
+  },
+  "ng-update": {
+    "packageGroup": "NG_UPDATE_PACKAGE_GROUP"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/angular/universal"
+  },
+  "bugs": {
+    "url": "https://github.com/angular/universal/issues"
+  },
+  "homepage": "https://github.com/angular/universal"
+}
diff --git a/modules/grpc-engine/public_api.ts b/modules/grpc-engine/public_api.ts
new file mode 100644
index 0000000000..ed3a321552
--- /dev/null
+++ b/modules/grpc-engine/public_api.ts
@@ -0,0 +1,8 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+export { startGRPCEngine } from './src/main';
diff --git a/modules/grpc-engine/spec/index.spec.ts b/modules/grpc-engine/spec/index.spec.ts
new file mode 100644
index 0000000000..d210311194
--- /dev/null
+++ b/modules/grpc-engine/spec/index.spec.ts
@@ -0,0 +1,43 @@
+import { ServerModule } from '@angular/platform-server';
+import { NgModule, Component } from '@angular/core';
+import 'zone.js';
+
+import { BrowserModule } from '@angular/platform-browser';
+import { startGRPCEngine } from '@nguniversal/grpc-engine';
+import {load, credentials} from 'grpc';
+
+function createClient() {
+  const engineProto = load('../grpc-engine.proto').GRPCEngine;
+  const client = engineProto.SSR('localhost:9090', credentials.createInsecure());
+  return client;
+}
+
+export function makeTestingModule(template: string, component?: any): any {
+  @Component({
+    selector: 'root',
+    template: template
+  })
+  class MockComponent {}
+  @NgModule({
+    imports: [ServerModule, BrowserModule.withServerTransition({appId: 'mock'})],
+    declarations: [component || MockComponent],
+    bootstrap: [component || MockComponent]
+  })
+  class MockServerModule {}
+  return MockServerModule;
+}
+
+describe('test runner', () => {
+  it('should render a basic template', async (done) => {
+    const template = `some template: ${new Date()}`;
+    const appModule = makeTestingModule(template);
+    const server = await startGRPCEngine(appModule);
+    const client = createClient();
+
+    client.render({id: 1, document: '<root></root>'}, async(_err: any, response: any) => {
+      expect(response.html).toContain(template);
+      await server.close();
+      done();
+    });
+  });
+});
diff --git a/modules/grpc-engine/src/grpc-engine.proto b/modules/grpc-engine/src/grpc-engine.proto
new file mode 100644
index 0000000000..1a461aa8a1
--- /dev/null
+++ b/modules/grpc-engine/src/grpc-engine.proto
@@ -0,0 +1,24 @@
+package grpcengine;
+syntax = "proto3";
+
+
+service SSR {
+  rpc render(RenderOptions) returns (RenderResponse) {}
+}
+
+message RenderOptions {
+  int32 id = 1;
+  string document = 2;
+  string documentFilename = 3;
+}
+
+message RenderResponse {
+  int32 id = 1;
+  string html = 2;
+  optional Error error = 3;
+}
+
+message Error {
+  string message = 1;
+  string stack = 2;
+}
diff --git a/modules/grpc-engine/src/main.ts b/modules/grpc-engine/src/main.ts
new file mode 100644
index 0000000000..eb09a606e5
--- /dev/null
+++ b/modules/grpc-engine/src/main.ts
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {
+  ɵCommonEngine as CommonEngine,
+  ɵRenderOptions as RenderOptions,
+} from '@nguniversal/common/engine';
+import {NgModuleFactory, Type} from '@angular/core';
+import * as grpc from 'grpc';
+
+export interface GRPCEngineServer {
+  close: () => void;
+}
+
+export interface GRPCEngineRenderOptions extends RenderOptions {
+  id: number;
+}
+
+export interface GRPCEngineResponse {
+  id: number;
+  html: string;
+}
+
+export function startGRPCEngine(
+  moduleOrFactory: Type<{}> | NgModuleFactory<{}>,
+  host = 'localhost',
+  port = 9090
+): Promise<GRPCEngineServer> {
+  // needs to be a directory up so it lines up with deployment
+  const protoDescriptor = grpc.load('./grpc-engine.proto');
+  return new Promise((resolve, _reject) => {
+    const engine = new CommonEngine(moduleOrFactory);
+
+    const server = new grpc.Server();
+    server.addProtoService(protoDescriptor.GRPCEngine.service, {
+      render: async (call: any, callback: any) => {
+        const renderOptions = call.request as GRPCEngineRenderOptions;
+        try {
+          const html = await engine.render(renderOptions);
+          callback(null, {id: renderOptions.id, html});
+        } catch (error) {
+          callback(null, {id: renderOptions.id, error});
+        }
+      }
+    });
+    // TODO(Toxicable): how to take credentials as input?
+    server.bind(`${host}:${port}`, grpc.ServerCredentials.createInsecure());
+    server.start();
+
+    resolve({
+      close: () => new Promise((res, _rej) => server.tryShutdown(() => res()))
+    });
+  });
+}
+
diff --git a/package.json b/package.json
index cc35469f02..bc0eea80ce 100644
--- a/package.json
+++ b/package.json
@@ -128,6 +128,9 @@
     "zone.js": "^0.8.26"
   },
   "dependencies": {
-    "yarn": "^1.10.1"
+    "yarn": "^1.10.1",
+    "@types/ws": "^5.1.1",
+    "grpc": "^1.12.2",
+    "ws": "^5.1.1"
   }
 }