From f3bead6f7e0f840fe2037dd4deff2d6323a608d1 Mon Sep 17 00:00:00 2001
From: Paul Yuknewicz <paulyuk@microsoft.com>
Date: Mon, 29 Jul 2024 13:01:37 -0700
Subject: [PATCH 1/7] [rebase release-1.4 branch] Reworked javascript workflow
 examples to use a webserver

Signed-off-by: Paul Yuknewicz <paulyuk@microsoft.com>
---
 workflows/javascript/sdk/README.md            |  46 +++--
 .../sdk/dapr-AppWithDaprServer.yaml           |  10 +
 .../sdk/dapr-AppWithExpressServer.yaml        |  10 +
 workflows/javascript/sdk/dapr.yaml            |   2 +-
 .../{workflowApp.ts => app.ts}                |   0
 .../sdk/order-processor/appWithDaprServer.ts  |  86 +++++++++
 .../order-processor/appWithExpressServer.ts   |  76 ++++++++
 workflows/javascript/sdk/package-lock.json    | 180 +++++++++++++++++-
 workflows/javascript/sdk/package.json         |   8 +-
 9 files changed, 395 insertions(+), 23 deletions(-)
 create mode 100644 workflows/javascript/sdk/dapr-AppWithDaprServer.yaml
 create mode 100644 workflows/javascript/sdk/dapr-AppWithExpressServer.yaml
 rename workflows/javascript/sdk/order-processor/{workflowApp.ts => app.ts} (100%)
 create mode 100644 workflows/javascript/sdk/order-processor/appWithDaprServer.ts
 create mode 100644 workflows/javascript/sdk/order-processor/appWithExpressServer.ts

diff --git a/workflows/javascript/sdk/README.md b/workflows/javascript/sdk/README.md
index e96ea4762..b29ebbb6c 100644
--- a/workflows/javascript/sdk/README.md
+++ b/workflows/javascript/sdk/README.md
@@ -2,9 +2,11 @@
 
 In this quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
 
-This quickstart includes one project:
+This quickstart includes 3 entry points demonstrating different ways to host a workflow:
 
-- JavaScript console app `order-processor` 
+1. JavaScript console app `order-processor` 
+2. Express app `order-process-with-express-server`
+3. Express app via Dapr Server `order-process-with-dapr-server`
 
 The quickstart contains 1 workflow to simulate purchasing items from a store, and 5 unique activities within the workflow. These 5 activities are as follows:
 
@@ -16,7 +18,7 @@ The quickstart contains 1 workflow to simulate purchasing items from a store, an
 
 ### Run the order processor workflow with multi-app-run
 
-1. Open a new terminal window and navigate to `order-processor` directory: 
+1. Open a new terminal window and install the dependencies: 
 
 <!-- STEP
 name: build order-process app
@@ -28,27 +30,33 @@ npm install
 npm run build
 ```
 
-<!-- END_STEP -->
-2. Run the console app with Dapr: 
+2. Run the app 
+
+- Entry point 1 : JavaScript console app
 
-<!-- STEP
-name: Run order-processor service
-expected_stdout_lines:
-  - '== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully'
-  - 'there are now 90 item1 in stock'
-  - 'processed successfully!'
-expected_stderr_lines:
-output_match_mode: substring
-background: true
-sleep: 15
-timeout_seconds: 120
--->
-    
 ```bash
 dapr run -f .
 ```
 
-<!-- END_STEP -->
+- Entry point 2 : Express app
+
+```bash
+dapr run -f dapr-AppWithExpressServer.yaml
+```
+```bash
+curl --request POST \
+  --url http://localhost:3500/v1.0/invoke/workflowApp/method/start-workflow
+```
+
+- Entry point 3 : Express app via Dapr Server
+
+```bash
+dapr run -f dapr-AppWithDaprServer.yaml
+```
+```bash
+curl --request POST \
+  --url http://localhost:3500/v1.0/invoke/workflowApp/method/start-workflow
+```
 
 3. Expected output
 
diff --git a/workflows/javascript/sdk/dapr-AppWithDaprServer.yaml b/workflows/javascript/sdk/dapr-AppWithDaprServer.yaml
new file mode 100644
index 000000000..9e588d877
--- /dev/null
+++ b/workflows/javascript/sdk/dapr-AppWithDaprServer.yaml
@@ -0,0 +1,10 @@
+version: 1
+common:
+  resourcesPath: ../../components
+apps:
+  - appID: workflowApp
+    appDirPath: ./order-processor/
+    appPort: 3000
+    daprHTTPPort: 3500
+    daprGRPCPort: 50001
+    command: ["npm", "run", "start:order-process-with-dapr-server"]
diff --git a/workflows/javascript/sdk/dapr-AppWithExpressServer.yaml b/workflows/javascript/sdk/dapr-AppWithExpressServer.yaml
new file mode 100644
index 000000000..c1e4c3424
--- /dev/null
+++ b/workflows/javascript/sdk/dapr-AppWithExpressServer.yaml
@@ -0,0 +1,10 @@
+version: 1
+common:
+  resourcesPath: ../../components
+apps:
+  - appID: workflowApp
+    appDirPath: ./order-processor/
+    appPort: 3000
+    daprHTTPPort: 3500
+    daprGRPCPort: 50001
+    command: ["npm", "run", "start:order-process-with-express-server"]
diff --git a/workflows/javascript/sdk/dapr.yaml b/workflows/javascript/sdk/dapr.yaml
index 40e07493c..0a0c10619 100644
--- a/workflows/javascript/sdk/dapr.yaml
+++ b/workflows/javascript/sdk/dapr.yaml
@@ -4,4 +4,4 @@ common:
 apps:
   - appID: workflowApp
     appDirPath: ./order-processor/
-    command: ["npm", "run", "start:dapr:order-process"]
+    command: ["npm", "run", "start:order-process"]
diff --git a/workflows/javascript/sdk/order-processor/workflowApp.ts b/workflows/javascript/sdk/order-processor/app.ts
similarity index 100%
rename from workflows/javascript/sdk/order-processor/workflowApp.ts
rename to workflows/javascript/sdk/order-processor/app.ts
diff --git a/workflows/javascript/sdk/order-processor/appWithDaprServer.ts b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts
new file mode 100644
index 000000000..c0b0a7bda
--- /dev/null
+++ b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts
@@ -0,0 +1,86 @@
+import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr";
+import { InventoryItem, OrderPayload } from "./model";
+import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
+import { DaprServer, CommunicationProtocolEnum } from "@dapr/dapr";
+import express from "express";
+
+const daprHost = "127.0.0.1"; // Dapr Sidecar Host
+const daprPort = process.env.DAPR_HTTP_PORT || "3500"; // Dapr Sidecar Port of this Example Server
+
+const app = express();
+
+const daprServer = new DaprServer({
+  serverHost:  "127.0.0.1", // App Host
+  serverPort: process.env.APP_PORT || "3000", // App Port
+  serverHttp: app,
+  clientOptions: {
+    daprHost,
+    daprPort
+  }
+});
+
+const daprClient = new DaprClient();
+const workflowClient = new DaprWorkflowClient();
+const workflowWorker = new WorkflowRuntime();
+
+app.post("/start-workflow", async (req, res) => {
+
+  const storeName = "statestore";
+  const inventory = new InventoryItem("item1", 100, 100);
+  const key = inventory.itemName;
+
+  await daprClient.state.delete(storeName, key);
+  await daprClient.state.save(storeName, [
+    {
+      key: key,
+      value: inventory,
+    }
+  ]);
+
+  const order = new OrderPayload("item1", 100, 10);
+
+  // Schedule a new orchestration
+  try {
+    const id = await workflowClient.scheduleNewWorkflow(orderProcessingWorkflow, order);
+    console.log(`Orchestration scheduled with ID: ${id}`);
+
+    // Wait for orchestration completion
+    const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30);
+
+    var orchestrationResult = `Orchestration completed! Result: ${state?.serializedOutput}`;
+    console.log(orchestrationResult);
+  } catch (error) {
+    console.error("Error scheduling or waiting for orchestration:", error);
+    throw error;
+  }
+
+  res.send(orchestrationResult);
+});
+
+async function start() {
+  workflowWorker
+    .registerWorkflow(orderProcessingWorkflow)
+    .registerActivity(notifyActivity)
+    .registerActivity(reserveInventoryActivity)
+    .registerActivity(requestApprovalActivity)
+    .registerActivity(processPaymentActivity)
+    .registerActivity(updateInventoryActivity);
+
+  // Wrap the worker startup in a try-catch block to handle any errors during startup
+  try {
+    await workflowWorker.start();
+    console.log("Workflow runtime started successfully");
+  } catch (error) {
+    console.error("Error starting workflow runtime:", error);
+  }
+
+  // Initialize subscriptions before the server starts, the Dapr sidecar uses it.
+  // This will also initialize the app server itself (removing the need for `app.listen` to be called).
+  await daprServer.start();
+};
+
+start().catch((e) => {
+  workflowWorker.stop();
+  console.error(e);
+  process.exit(1);
+});
\ No newline at end of file
diff --git a/workflows/javascript/sdk/order-processor/appWithExpressServer.ts b/workflows/javascript/sdk/order-processor/appWithExpressServer.ts
new file mode 100644
index 000000000..fe69dd638
--- /dev/null
+++ b/workflows/javascript/sdk/order-processor/appWithExpressServer.ts
@@ -0,0 +1,76 @@
+import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr";
+import { InventoryItem, OrderPayload } from "./model";
+import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
+import express from "express";
+
+const app = express();
+
+const daprClient = new DaprClient();
+const workflowClient = new DaprWorkflowClient();
+const workflowWorker = new WorkflowRuntime();
+
+app.post("/start-workflow", async (req, res) => {
+
+  const storeName = "statestore";
+  const inventory = new InventoryItem("item1", 100, 100);
+  const key = inventory.itemName;
+
+  await daprClient.state.save(storeName, [
+    {
+      key: key,
+      value: inventory,
+    }
+  ]);
+
+  const order = new OrderPayload("item1", 100, 10);
+
+  // Schedule a new orchestration
+  try {
+    const id = await workflowClient.scheduleNewWorkflow(orderProcessingWorkflow, order);
+    console.log(`Orchestration scheduled with ID: ${id}`);
+
+    // Wait for orchestration completion
+    const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30);
+
+    var orchestrationResult = `Orchestration completed! Result: ${state?.serializedOutput}`;
+    console.log(orchestrationResult);
+  } catch (error) {
+    console.error("Error scheduling or waiting for orchestration:", error);
+    throw error;
+  }
+
+  res.send(orchestrationResult);
+});
+
+async function start() {
+  workflowWorker
+    .registerWorkflow(orderProcessingWorkflow)
+    .registerActivity(notifyActivity)
+    .registerActivity(reserveInventoryActivity)
+    .registerActivity(requestApprovalActivity)
+    .registerActivity(processPaymentActivity)
+    .registerActivity(updateInventoryActivity);
+
+  // Wrap the worker startup in a try-catch block to handle any errors during startup
+  try {
+    await workflowWorker.start();
+    console.log("Workflow runtime started successfully");
+  } catch (error) {
+    console.error("Error starting workflow runtime:", error);
+  }
+};
+
+const server = app.listen(process.env.APP_PORT || 3000, () => {
+  console.log(`Example app listening on port APP_PORT or 3000`);
+})
+
+process.on('SIGTERM', () => {
+  workflowWorker.stop();
+  server.close();
+})
+
+start().catch((e) => {
+  workflowWorker.stop();
+  console.error(e);
+  process.exit(1);
+});
\ No newline at end of file
diff --git a/workflows/javascript/sdk/package-lock.json b/workflows/javascript/sdk/package-lock.json
index 50eb154c9..b1378e89a 100644
--- a/workflows/javascript/sdk/package-lock.json
+++ b/workflows/javascript/sdk/package-lock.json
@@ -10,9 +10,11 @@
       "license": "ISC",
       "dependencies": {
         "@dapr/dapr": "^3.3.1",
-        "@types/node": "^18.16.3"
+        "@types/node": "^18.16.3",
+        "express": "^4.19.2"
       },
       "devDependencies": {
+        "@types/express": "^4.17.21",
         "@types/readline-sync": "^1.4.8",
         "ts-node": "^10.9.1",
         "typescript": "^5.0.4"
@@ -286,11 +288,66 @@
       "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
       "dev": true
     },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "dev": true,
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "4.19.5",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
+      "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
     "node_modules/@types/google-protobuf": {
       "version": "3.15.12",
       "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz",
       "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ=="
     },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "dev": true
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "dev": true
+    },
     "node_modules/@types/node": {
       "version": "18.16.3",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
@@ -305,12 +362,45 @@
         "form-data": "^4.0.0"
       }
     },
+    "node_modules/@types/qs": {
+      "version": "6.9.15",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
+      "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
+      "dev": true
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true
+    },
     "node_modules/@types/readline-sync": {
       "version": "1.4.8",
       "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz",
       "integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==",
       "dev": true
     },
+    "node_modules/@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "dev": true,
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.7",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+      "dev": true,
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
     "node_modules/accepts": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -1677,11 +1767,66 @@
       "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
       "dev": true
     },
+    "@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/express": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+      "dev": true,
+      "requires": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.19.5",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
+      "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
     "@types/google-protobuf": {
       "version": "3.15.12",
       "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz",
       "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ=="
     },
+    "@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "dev": true
+    },
+    "@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "dev": true
+    },
     "@types/node": {
       "version": "18.16.3",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz",
@@ -1696,12 +1841,45 @@
         "form-data": "^4.0.0"
       }
     },
+    "@types/qs": {
+      "version": "6.9.15",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
+      "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
+      "dev": true
+    },
+    "@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true
+    },
     "@types/readline-sync": {
       "version": "1.4.8",
       "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz",
       "integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==",
       "dev": true
     },
+    "@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "dev": true,
+      "requires": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "@types/serve-static": {
+      "version": "1.15.7",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+      "dev": true,
+      "requires": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
     "accepts": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
diff --git a/workflows/javascript/sdk/package.json b/workflows/javascript/sdk/package.json
index 2f147c6a4..519c4ed10 100644
--- a/workflows/javascript/sdk/package.json
+++ b/workflows/javascript/sdk/package.json
@@ -5,18 +5,22 @@
   "private": "true",
   "scripts": {
     "build": "npx tsc --outDir ./dist/",
-    "start:order-process": "npm run build && node dist/workflowApp.js",
+    "start:order-process": "npm run build && node dist/app.js",
+    "start:order-process-with-dapr-server": "npm run build && node dist/appWithDaprServer.js",
+    "start:order-process-with-express-server": "npm run build && node dist/appWithExpressServer.js",
     "start:dapr:order-process": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ../../components npm run start:order-process"
   },
   "author": "",
   "license": "ISC",
   "devDependencies": {
+    "@types/express": "^4.17.21",
     "@types/readline-sync": "^1.4.8",
     "ts-node": "^10.9.1",
     "typescript": "^5.0.4"
   },
   "dependencies": {
     "@dapr/dapr": "^3.3.1",
-    "@types/node": "^18.16.3"
+    "@types/node": "^18.16.3",
+    "express": "^4.19.2"
   }
 }

From d0d174bc7e4aa0fca256e999afd7aa0e7c7d22b3 Mon Sep 17 00:00:00 2001
From: Oliver Tomlinson <oliverjamestomlinson@gmail.com>
Date: Fri, 21 Jun 2024 14:29:55 +0100
Subject: [PATCH 2/7] fix validation tests

---
 workflows/javascript/sdk/README.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/workflows/javascript/sdk/README.md b/workflows/javascript/sdk/README.md
index b29ebbb6c..9c6bcc431 100644
--- a/workflows/javascript/sdk/README.md
+++ b/workflows/javascript/sdk/README.md
@@ -30,9 +30,22 @@ npm install
 npm run build
 ```
 
+<!-- END_STEP -->
 2. Run the app 
 
 - Entry point 1 : JavaScript console app
+<!-- STEP
+name: Run order-processor service
+expected_stdout_lines:
+  - '== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully'
+  - 'there are now 90 item1 in stock'
+  - 'processed successfully!'
+expected_stderr_lines:
+output_match_mode: substring
+background: true
+sleep: 15
+timeout_seconds: 120
+-->
 
 ```bash
 dapr run -f .
@@ -152,3 +165,4 @@ When you ran `dapr run --app-id activity-sequence-workflow --app-protocol grpc -
 8. The `notifyActivity` workflow activity sends a notification saying that order `0c332155-1e02-453a-a333-28cfc7777642` has completed and processed.
 9. The workflow terminates as completed and processed.
 
+<!-- END_STEP -->
\ No newline at end of file

From 25e354ae9fb75390fcf41d2c2f6e2ad63cfed093 Mon Sep 17 00:00:00 2001
From: Oliver Tomlinson <oliverjamestomlinson@gmail.com>
Date: Fri, 21 Jun 2024 14:49:48 +0100
Subject: [PATCH 3/7] fix validation again

---
 workflows/javascript/sdk/README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/workflows/javascript/sdk/README.md b/workflows/javascript/sdk/README.md
index 9c6bcc431..8c6001d3e 100644
--- a/workflows/javascript/sdk/README.md
+++ b/workflows/javascript/sdk/README.md
@@ -50,12 +50,14 @@ timeout_seconds: 120
 ```bash
 dapr run -f .
 ```
+<!-- END_STEP -->
 
 - Entry point 2 : Express app
 
 ```bash
 dapr run -f dapr-AppWithExpressServer.yaml
 ```
+
 ```bash
 curl --request POST \
   --url http://localhost:3500/v1.0/invoke/workflowApp/method/start-workflow
@@ -165,4 +167,3 @@ When you ran `dapr run --app-id activity-sequence-workflow --app-protocol grpc -
 8. The `notifyActivity` workflow activity sends a notification saying that order `0c332155-1e02-453a-a333-28cfc7777642` has completed and processed.
 9. The workflow terminates as completed and processed.
 
-<!-- END_STEP -->
\ No newline at end of file

From e808c68b40b01d4cb39d69e76d4a971264ae6042 Mon Sep 17 00:00:00 2001
From: Oliver Tomlinson <oliverjamestomlinson@gmail.com>
Date: Fri, 21 Jun 2024 15:19:06 +0100
Subject: [PATCH 4/7] attempt to fix test again

---
 workflows/javascript/sdk/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/workflows/javascript/sdk/README.md b/workflows/javascript/sdk/README.md
index 8c6001d3e..829a4bc9d 100644
--- a/workflows/javascript/sdk/README.md
+++ b/workflows/javascript/sdk/README.md
@@ -37,7 +37,7 @@ npm run build
 <!-- STEP
 name: Run order-processor service
 expected_stdout_lines:
-  - '== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully'
+  - '== APP - workflowApp == Payment of 100 for 10 item1 processed successfully'
   - 'there are now 90 item1 in stock'
   - 'processed successfully!'
 expected_stderr_lines:

From 00eab7116863e55640a43eb68dc6982aad767d04 Mon Sep 17 00:00:00 2001
From: Paul Yuknewicz <paulyuk@microsoft.com>
Date: Mon, 29 Jul 2024 14:07:18 -0700
Subject: [PATCH 5/7] Using environment variable for DAPR_HOST

Signed-off-by: Paul Yuknewicz <paulyuk@microsoft.com>
---
 workflows/javascript/sdk/order-processor/appWithDaprServer.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/workflows/javascript/sdk/order-processor/appWithDaprServer.ts b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts
index c0b0a7bda..76706b93d 100644
--- a/workflows/javascript/sdk/order-processor/appWithDaprServer.ts
+++ b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts
@@ -4,7 +4,7 @@ import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, reques
 import { DaprServer, CommunicationProtocolEnum } from "@dapr/dapr";
 import express from "express";
 
-const daprHost = "127.0.0.1"; // Dapr Sidecar Host
+const daprHost = process.env.DAPR_HOST ?? "localhost"; // Dapr Sidecar Host
 const daprPort = process.env.DAPR_HTTP_PORT || "3500"; // Dapr Sidecar Port of this Example Server
 
 const app = express();
@@ -13,6 +13,7 @@ const daprServer = new DaprServer({
   serverHost:  "127.0.0.1", // App Host
   serverPort: process.env.APP_PORT || "3000", // App Port
   serverHttp: app,
+  communicationProtocol: CommunicationProtocolEnum.HTTP, // Add this line
   clientOptions: {
     daprHost,
     daprPort

From 4e70fad1591d05f3adf913369ad7fb278b273b47 Mon Sep 17 00:00:00 2001
From: Paul Yuknewicz <paulyuk@microsoft.com>
Date: Mon, 29 Jul 2024 15:07:27 -0700
Subject: [PATCH 6/7] updating constructor with explicit gprc connection

Signed-off-by: Paul Yuknewicz <paulyuk@microsoft.com>
---
 workflows/javascript/sdk/order-processor/app.ts | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/workflows/javascript/sdk/order-processor/app.ts b/workflows/javascript/sdk/order-processor/app.ts
index fbae65b2b..c034c3f7d 100644
--- a/workflows/javascript/sdk/order-processor/app.ts
+++ b/workflows/javascript/sdk/order-processor/app.ts
@@ -1,4 +1,4 @@
-import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr";
+import { DaprWorkflowClient, WorkflowRuntime, DaprClient, CommunicationProtocolEnum } from "@dapr/dapr";
 import { InventoryItem, OrderPayload } from "./model";
 import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
 
@@ -7,7 +7,15 @@ async function start() {
   const workflowClient = new DaprWorkflowClient();
   const workflowWorker = new WorkflowRuntime();
 
-  const daprClient = new DaprClient();
+  const daprHost = process.env.DAPR_HOST ?? "127.0.0.1";
+  const daprPort = process.env.DAPR_GRPC_PORT ?? "50001";
+
+  const daprClient = new DaprClient({
+    daprHost,
+    daprPort,
+    communicationProtocol: CommunicationProtocolEnum.GRPC,
+  });
+
   const storeName = "statestore";
 
   const inventory = new InventoryItem("item1", 100, 100);

From 74e2a59a5795de9f1b193aa52dadeb2d3e9be5c9 Mon Sep 17 00:00:00 2001
From: Paul Yuknewicz <paulyuk@microsoft.com>
Date: Mon, 29 Jul 2024 16:45:24 -0700
Subject: [PATCH 7/7] Fix "cancelled on client" moving workflow.stop out to
 SIGTERM handler

Signed-off-by: Paul Yuknewicz <paulyuk@microsoft.com>
---
 workflows/javascript/sdk/order-processor/app.ts | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/workflows/javascript/sdk/order-processor/app.ts b/workflows/javascript/sdk/order-processor/app.ts
index c034c3f7d..b95982382 100644
--- a/workflows/javascript/sdk/order-processor/app.ts
+++ b/workflows/javascript/sdk/order-processor/app.ts
@@ -2,10 +2,12 @@ import { DaprWorkflowClient, WorkflowRuntime, DaprClient, CommunicationProtocolE
 import { InventoryItem, OrderPayload } from "./model";
 import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
 
+const workflowWorker = new WorkflowRuntime();
+
 async function start() {
   // Update the gRPC client and worker to use a local address and port
   const workflowClient = new DaprWorkflowClient();
-  const workflowWorker = new WorkflowRuntime();
+
 
   const daprHost = process.env.DAPR_HOST ?? "127.0.0.1";
   const daprPort = process.env.DAPR_GRPC_PORT ?? "50001";
@@ -60,10 +62,13 @@ async function start() {
     throw error;
   }
 
-  await workflowWorker.stop();
   await workflowClient.stop();
 }
 
+process.on('SIGTERM', () => {
+  workflowWorker.stop();
+})
+
 start().catch((e) => {
   console.error(e);
   process.exit(1);