diff --git a/workflows/javascript/sdk/README.md b/workflows/javascript/sdk/README.md index e96ea4762..829a4bc9d 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: -2. Run the console app with Dapr: +2. Run the app +- Entry point 1 : JavaScript console app - + ```bash dapr run -f . ``` - +- 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 80% rename from workflows/javascript/sdk/order-processor/workflowApp.ts rename to workflows/javascript/sdk/order-processor/app.ts index fbae65b2b..b95982382 100644 --- a/workflows/javascript/sdk/order-processor/workflowApp.ts +++ b/workflows/javascript/sdk/order-processor/app.ts @@ -1,13 +1,23 @@ -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"; +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 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); @@ -52,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); diff --git a/workflows/javascript/sdk/order-processor/appWithDaprServer.ts b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts new file mode 100644 index 000000000..76706b93d --- /dev/null +++ b/workflows/javascript/sdk/order-processor/appWithDaprServer.ts @@ -0,0 +1,87 @@ +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 = 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(); + +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 + } +}); + +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" } }