From d1bd43e101bce300f5330f8e0d9b058dad1599f1 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Thu, 21 Dec 2023 10:22:27 -0600 Subject: [PATCH 01/18] init support for workflow more updates update durabletask-js version init workflow support Signed-off-by: kaibocai --- package-lock.json | 571 ++++++++++++++++++ package.json | 8 +- src/workflow/client/WorkflowClient.ts | 180 ++++++ src/workflow/client/WorkflowFailureDetails.ts | 46 ++ src/workflow/client/WorkflowState.ts | 108 ++++ src/workflow/examples/activity-sequence.ts | 67 ++ src/workflow/examples/fanout-fanin.ts | 94 +++ .../internal/ApiTokenClientInterceptor.ts | 28 + src/workflow/internal/index.ts | 20 + .../runtime/WorkflowActivityContext.ts | 42 ++ src/workflow/runtime/WorkflowContext.ts | 152 +++++ src/workflow/runtime/WorkflowRuntime.ts | 110 ++++ src/workflow/runtime/WorkflowRuntimeStatus.ts | 42 ++ src/workflow/types/Activity.type.ts | 16 + src/workflow/types/InputOutput.type.ts | 15 + src/workflow/types/Workflow.type.ts | 18 + tsconfig.json | 2 +- 17 files changed, 1516 insertions(+), 3 deletions(-) create mode 100644 src/workflow/client/WorkflowClient.ts create mode 100644 src/workflow/client/WorkflowFailureDetails.ts create mode 100644 src/workflow/client/WorkflowState.ts create mode 100644 src/workflow/examples/activity-sequence.ts create mode 100644 src/workflow/examples/fanout-fanin.ts create mode 100644 src/workflow/internal/ApiTokenClientInterceptor.ts create mode 100644 src/workflow/internal/index.ts create mode 100644 src/workflow/runtime/WorkflowActivityContext.ts create mode 100644 src/workflow/runtime/WorkflowContext.ts create mode 100644 src/workflow/runtime/WorkflowRuntime.ts create mode 100644 src/workflow/runtime/WorkflowRuntimeStatus.ts create mode 100644 src/workflow/types/Activity.type.ts create mode 100644 src/workflow/types/InputOutput.type.ts create mode 100644 src/workflow/types/Workflow.type.ts diff --git a/package-lock.json b/package-lock.json index 7666e0b4..d3b592ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,9 +17,11 @@ "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", + "kaibocai-durabletask-js": "^0.0.5-beta.3", "node-fetch": "^2.6.7" }, "devDependencies": { + "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/jest": "^27.0.1", @@ -38,6 +40,7 @@ "prettier": "^2.4.0", "pretty-quick": "^3.1.3", "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", "typescript": "^4.5.5" } }, @@ -703,6 +706,28 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1341,6 +1366,216 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@swc/core": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.101.tgz", + "integrity": "sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.101", + "@swc/core-darwin-x64": "1.3.101", + "@swc/core-linux-arm-gnueabihf": "1.3.101", + "@swc/core-linux-arm64-gnu": "1.3.101", + "@swc/core-linux-arm64-musl": "1.3.101", + "@swc/core-linux-x64-gnu": "1.3.101", + "@swc/core-linux-x64-musl": "1.3.101", + "@swc/core-win32-arm64-msvc": "1.3.101", + "@swc/core-win32-ia32-msvc": "1.3.101", + "@swc/core-win32-x64-msvc": "1.3.101" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.101.tgz", + "integrity": "sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.101.tgz", + "integrity": "sha512-B085j8XOx73Fg15KsHvzYWG262bRweGr3JooO1aW5ec5pYbz5Ew9VS5JKYS03w2UBSxf2maWdbPz2UFAxg0whw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.101.tgz", + "integrity": "sha512-9xLKRb6zSzRGPqdz52Hy5GuB1lSjmLqa0lST6MTFads3apmx4Vgs8Y5NuGhx/h2I8QM4jXdLbpqQlifpzTlSSw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.101.tgz", + "integrity": "sha512-oE+r1lo7g/vs96Weh2R5l971dt+ZLuhaUX+n3BfDdPxNHfObXgKMjO7E+QS5RbGjv/AwiPCxQmbdCp/xN5ICJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.101.tgz", + "integrity": "sha512-OGjYG3H4BMOTnJWJyBIovCez6KiHF30zMIu4+lGJTCrxRI2fAjGLml3PEXj8tC3FMcud7U2WUn6TdG0/te2k6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.101.tgz", + "integrity": "sha512-/kBMcoF12PRO/lwa8Z7w4YyiKDcXQEiLvM+S3G9EvkoKYGgkkz4Q6PSNhF5rwg/E3+Hq5/9D2R+6nrkF287ihg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.101.tgz", + "integrity": "sha512-kDN8lm4Eew0u1p+h1l3JzoeGgZPQ05qDE0czngnjmfpsH2sOZxVj1hdiCwS5lArpy7ktaLu5JdRnx70MkUzhXw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.101.tgz", + "integrity": "sha512-9Wn8TTLWwJKw63K/S+jjrZb9yoJfJwCE2RV5vPCCWmlMf3U1AXj5XuWOLUX+Rp2sGKau7wZKsvywhheWm+qndQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.101.tgz", + "integrity": "sha512-onO5KvICRVlu2xmr4//V2je9O2XgS1SGKpbX206KmmjcJhXN5EYLSxW9qgg+kgV5mip+sKTHTAu7IkzkAtElYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.101.tgz", + "integrity": "sha512-T3GeJtNQV00YmiVw/88/nxJ/H43CJvFnpvBHCVn17xbahiVUOPOduh3rc9LgAkKiNt/aV8vU3OJR+6PhfMR7UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1350,6 +1585,30 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", @@ -1982,6 +2241,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2511,6 +2776,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2681,6 +2952,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -4903,6 +5183,15 @@ "node": ">=6" } }, + "node_modules/kaibocai-durabletask-js": { + "version": "0.0.5-beta.3", + "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.5-beta.3.tgz", + "integrity": "sha512-Z9RCV8MxfHQfIVLcIDVoVeMMUuER6xrmYMtKGf+q+vvNCcK+qC1UUsw3a1A4hsCtwAkuCQSuqDBD3/ABtIxdrQ==", + "dependencies": { + "@grpc/grpc-js": "^1.8.14", + "google-protobuf": "^3.21.2" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -6721,6 +7010,58 @@ } } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -6906,6 +7247,12 @@ "node": ">= 0.4.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -7142,6 +7489,15 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7662,6 +8018,27 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -8168,12 +8545,138 @@ "@sinonjs/commons": "^1.7.0" } }, + "@swc/core": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.101.tgz", + "integrity": "sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.3.101", + "@swc/core-darwin-x64": "1.3.101", + "@swc/core-linux-arm-gnueabihf": "1.3.101", + "@swc/core-linux-arm64-gnu": "1.3.101", + "@swc/core-linux-arm64-musl": "1.3.101", + "@swc/core-linux-x64-gnu": "1.3.101", + "@swc/core-linux-x64-musl": "1.3.101", + "@swc/core-win32-arm64-msvc": "1.3.101", + "@swc/core-win32-ia32-msvc": "1.3.101", + "@swc/core-win32-x64-msvc": "1.3.101", + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.101.tgz", + "integrity": "sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.101.tgz", + "integrity": "sha512-B085j8XOx73Fg15KsHvzYWG262bRweGr3JooO1aW5ec5pYbz5Ew9VS5JKYS03w2UBSxf2maWdbPz2UFAxg0whw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.101.tgz", + "integrity": "sha512-9xLKRb6zSzRGPqdz52Hy5GuB1lSjmLqa0lST6MTFads3apmx4Vgs8Y5NuGhx/h2I8QM4jXdLbpqQlifpzTlSSw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.101.tgz", + "integrity": "sha512-oE+r1lo7g/vs96Weh2R5l971dt+ZLuhaUX+n3BfDdPxNHfObXgKMjO7E+QS5RbGjv/AwiPCxQmbdCp/xN5ICJA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.101.tgz", + "integrity": "sha512-OGjYG3H4BMOTnJWJyBIovCez6KiHF30zMIu4+lGJTCrxRI2fAjGLml3PEXj8tC3FMcud7U2WUn6TdG0/te2k6g==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.101.tgz", + "integrity": "sha512-/kBMcoF12PRO/lwa8Z7w4YyiKDcXQEiLvM+S3G9EvkoKYGgkkz4Q6PSNhF5rwg/E3+Hq5/9D2R+6nrkF287ihg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.101.tgz", + "integrity": "sha512-kDN8lm4Eew0u1p+h1l3JzoeGgZPQ05qDE0czngnjmfpsH2sOZxVj1hdiCwS5lArpy7ktaLu5JdRnx70MkUzhXw==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.101.tgz", + "integrity": "sha512-9Wn8TTLWwJKw63K/S+jjrZb9yoJfJwCE2RV5vPCCWmlMf3U1AXj5XuWOLUX+Rp2sGKau7wZKsvywhheWm+qndQ==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.101.tgz", + "integrity": "sha512-onO5KvICRVlu2xmr4//V2je9O2XgS1SGKpbX206KmmjcJhXN5EYLSxW9qgg+kgV5mip+sKTHTAu7IkzkAtElYA==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.101.tgz", + "integrity": "sha512-T3GeJtNQV00YmiVw/88/nxJ/H43CJvFnpvBHCVn17xbahiVUOPOduh3rc9LgAkKiNt/aV8vU3OJR+6PhfMR7UQ==", + "dev": true, + "optional": true + }, + "@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "@types/babel__core": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", @@ -8660,6 +9163,12 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -9047,6 +9556,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9171,6 +9686,12 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -10873,6 +11394,15 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "kaibocai-durabletask-js": { + "version": "0.0.5-beta.3", + "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.5-beta.3.tgz", + "integrity": "sha512-Z9RCV8MxfHQfIVLcIDVoVeMMUuER6xrmYMtKGf+q+vvNCcK+qC1UUsw3a1A4hsCtwAkuCQSuqDBD3/ABtIxdrQ==", + "requires": { + "@grpc/grpc-js": "^1.8.14", + "google-protobuf": "^3.21.2" + } + }, "keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -12201,6 +12731,35 @@ "yargs-parser": "20.x" } }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true + } + } + }, "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -12325,6 +12884,12 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -12508,6 +13073,12 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e7167ffe..0dd727d8 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "build": "npm install && npm run lint && npm run pretty && ./scripts/build.sh", "start:dev": "npm run build && nodemon --ext \".ts,.js\" --watch \"./src\" --exec \"npm run build\"", "pretty": "prettier --list-different \"**/*.{ts,tsx,js,jsx,json,md}\"", - "pretty-fix": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"" + "pretty-fix": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", + "example": "ts-node --swc" }, "keywords": [], "author": "Xavier Geerinck", @@ -44,6 +45,7 @@ "dependencies": { "@grpc/grpc-js": "^1.9.3", "@js-temporal/polyfill": "^0.3.0", + "kaibocai-durabletask-js": "^0.0.5-beta.3", "@types/google-protobuf": "^3.15.5", "@types/node-fetch": "^2.6.2", "body-parser": "^1.19.0", @@ -53,6 +55,7 @@ "node-fetch": "^2.6.7" }, "devDependencies": { + "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/jest": "^27.0.1", @@ -71,7 +74,8 @@ "prettier": "^2.4.0", "pretty-quick": "^3.1.3", "ts-jest": "^27.0.5", - "typescript": "^4.5.5" + "typescript": "^4.5.5", + "ts-node": "^10.9.1" }, "repository": { "type": "git", diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/WorkflowClient.ts new file mode 100644 index 00000000..e4671092 --- /dev/null +++ b/src/workflow/client/WorkflowClient.ts @@ -0,0 +1,180 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { TaskHubGrpcClient } from "kaibocai-durabletask-js"; +import * as grpc from "@grpc/grpc-js"; +import { WorkflowState } from "./WorkflowState"; +import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; +import { TWorkflow } from "../types/Workflow.type"; +import { getFunctionName } from "../internal"; + +export default class WorkflowClient { + private readonly _innerClient: TaskHubGrpcClient; + + /** + * Initializes a new instance of the DaprWorkflowClient. + * @param {string | undefined} hostAddress - The address of the Dapr runtime hosting the workflow services. + * @param {grpc.ChannelOptions | undefined} options - Additional options for configuring the gRPC channel. + */ + constructor(hostAddress?: string, options?: grpc.ChannelOptions) { + this._innerClient = this._buildInnerClient(hostAddress, options); + } + + _buildInnerClient(hostAddress = "127.0.0.1:50001", options: grpc.ChannelOptions = {}): TaskHubGrpcClient { + const innerOptions = { + ...options, + interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], + }; + return new TaskHubGrpcClient(hostAddress, innerOptions); + } + + /** + * Schedules a new workflow using the DurableTask client. + * + * @param {TWorkflow | string} workflow - The Workflow or the name of the workflow to be scheduled. + * @return {Promise} A Promise resolving to the unique ID of the scheduled workflow instance. + */ + public async scheduleNewWorkflow( + workflow: TWorkflow | string, + input?: any, + instanceId?: string, + startAt?: Date, + ): Promise { + if (typeof workflow === "string") { + return await this._innerClient.scheduleNewOrchestration(workflow, input, instanceId, startAt); + } + return await this._innerClient.scheduleNewOrchestration(getFunctionName(workflow), input, instanceId, startAt); + } + + /** + * Terminates the workflow associated with the provided instance id. + * + * @param {string} workflowInstanceId - Workflow instance id to terminate. + * @param {any} output - The optional output to set for the terminated workflow instance. + */ + public async terminateWorkflow(workflowInstanceId: string, output: any) { + await this._innerClient.terminateOrchestration(workflowInstanceId, output); + } + + /** + * Fetches workflow instance metadata from the configured durable store. + * + * @param {string} workflowInstanceId - The unique identifier of the workflow instance to fetch. + * @param {boolean} getInputsAndOutputs - Indicates whether to fetch the workflow instance's + * inputs, outputs, and custom status (true) or omit them (false). + * @returns {Promise} A Promise that resolves to a metadata record describing + * the workflow instance and its execution status, or undefined + * if the instance is not found. + */ + public async getWorkflowState( + workflowInstanceId: string, + getInputsAndOutputs: boolean, + ): Promise { + const state = await this._innerClient.getOrchestrationState(workflowInstanceId, getInputsAndOutputs); + if (state !== undefined) { + return new WorkflowState(state); + } + } + + /** + * Waits for a workflow to start running and returns a {@link WorkflowState} object + * containing metadata about the started instance, and optionally, its input, output, + * and custom status payloads. + * + * A "started" workflow instance refers to any instance not in the Pending state. + * + * If a workflow instance is already running when this method is called, it returns immediately. + * + * @param {string} workflowInstanceId - The unique identifier of the workflow instance to wait for. + * @param {boolean} fetchPayloads - Indicates whether to fetch the workflow instance's + * inputs, outputs (true) or omit them (false). + * @param {number} timeout - The amount of time, in seconds, to wait for the workflow instance to start. + * @returns {Promise} A Promise that resolves to the workflow instance metadata + * or undefined if no such instance is found. + */ + public async waitForWorkflowStart( + workflowInstanceId: string, + fetchPayloads: boolean, + timeout: number, + ): Promise { + const state = await this._innerClient.waitForOrchestrationStart(workflowInstanceId, fetchPayloads, timeout); + if (state !== undefined) { + return new WorkflowState(state); + } + } + + /** + * Waits for a workflow to complete running and returns a {@link WorkflowState} object + * containing metadata about the completed instance, and optionally, its input, output, + * and custom status payloads. + * + * A "completed" workflow instance refers to any instance in one of the terminal states. + * For example, the Completed, Failed, or Terminated states. + * + * If a workflow instance is already running when this method is called, it returns immediately. + * + * @param {string} workflowInstanceId - The unique identifier of the workflow instance to wait for. + * @param {boolean} fetchPayloads - Indicates whether to fetch the workflow instance's + * inputs, outputs (true) or omit them (false). + * @param {number} timeout - The amount of time, in seconds, to wait for the workflow instance to start. + * @returns {Promise} A Promise that resolves to the workflow instance metadata + * or undefined if no such instance is found. + */ + public async waitForWorkflowCompletion( + workflowInstanceId: string, + fetchPayloads = true, + timeout: number, + ): Promise { + const state = await this._innerClient.waitForOrchestrationCompletion(workflowInstanceId, fetchPayloads, timeout); + if (state != undefined) { + return new WorkflowState(state); + } + } + + /** + * Sends an event notification message to an awaiting workflow instance. + * + * This method triggers the specified event in a running workflow instance, + * allowing the workflow to respond to the event if it has defined event handlers. + * + * @param {string} workflowInstanceId - The unique identifier of the workflow instance that will handle the event. + * @param {string} eventName - The name of the event. Event names are case-insensitive. + * @param {any} [eventPayload] - An optional serializable data payload to include with the event. + */ + public async raiseEvent(workflowInstanceId: string, eventName: string, eventPayload?: any) { + this._innerClient.raiseOrchestrationEvent(workflowInstanceId, eventName, eventPayload); + } + + /** + * Purges the workflow instance state from the workflow state store. + * + * This method removes the persisted state associated with a workflow instance from the state store. + * + * @param {string} workflowInstanceId - The unique identifier of the workflow instance to purge. + * @return {Promise} A Promise that resolves to true if the workflow state was found and purged successfully, otherwise false. + */ + public async purgeWorkflow(workflowInstanceId: string): Promise { + const purgeResult = await this._innerClient.purgeOrchestration(workflowInstanceId); + if (purgeResult !== undefined) { + return purgeResult.deletedInstanceCount > 0; + } + return false; + } + + /** + * Closes the inner DurableTask client and shutdown the GRPC channel. + */ + public async stop() { + await this._innerClient.stop(); + } +} diff --git a/src/workflow/client/WorkflowFailureDetails.ts b/src/workflow/client/WorkflowFailureDetails.ts new file mode 100644 index 00000000..c86da7d9 --- /dev/null +++ b/src/workflow/client/WorkflowFailureDetails.ts @@ -0,0 +1,46 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { FailureDetails } from "kaibocai-durabletask-js/task/failure-details"; + +export class WorkflowFailureDetails { + private readonly failureDetails: FailureDetails; + + constructor(failureDetails: FailureDetails) { + this.failureDetails = failureDetails; + } + + /** + * Gets the error type, which is the namespace-qualified exception type name. + * @return {string} The error type. + */ + public getErrorType(): string { + return this.failureDetails.errorType; + } + + /** + * Gets the error message. + * @return {string} The error message. + */ + public getErrorMessage(): string { + return this.failureDetails.message; + } + + /** + * Gets the stack trace. + * @return {string | undefined} The stack trace, or undefined if not available. + */ + public getStackTrace(): string | undefined { + return this.failureDetails.stackTrace; + } +} diff --git a/src/workflow/client/WorkflowState.ts b/src/workflow/client/WorkflowState.ts new file mode 100644 index 00000000..738f4ef6 --- /dev/null +++ b/src/workflow/client/WorkflowState.ts @@ -0,0 +1,108 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { OrchestrationState } from "kaibocai-durabletask-js/orchestration/orchestration-state"; +import { WorkflowFailureDetails } from "./WorkflowFailureDetails"; +import { WorkflowRuntimeStatus, fromOrchestrationStatus } from "../runtime/WorkflowRuntimeStatus"; + +/** + * Represents the state of a workflow instance. + */ +export class WorkflowState { + private readonly _orchestrationState: OrchestrationState; + private readonly _workflowFailureDetails?: WorkflowFailureDetails; + + /** + * Creates an instance of WorkflowState. + * @param {OrchestrationState} orchestrationState - The state of the orchestration. + * @throws {Error} Throws an error if orchestrationState is null. + */ + constructor(orchestrationState: OrchestrationState) { + if (!orchestrationState) { + throw new Error("OrchestrationMetadata cannot be null"); + } + + this._orchestrationState = orchestrationState; + + const failureDetails = orchestrationState.failureDetails; + if (failureDetails) { + this._workflowFailureDetails = new WorkflowFailureDetails(failureDetails); + } + } + + /** + * Gets the name of the workflow. + * @returns {string} The name of the workflow. + */ + public get name(): string { + return this._orchestrationState.name; + } + + /** + * Gets the unique ID of the workflow instance. + * @returns {string} The unique ID of the workflow instance. + */ + public get instanceId(): string { + return this._orchestrationState.instanceId; + } + + /** + * Gets the current runtime status of the workflow instance. + * @returns {WorkflowRuntimeStatus} The current runtime status. + */ + public get runtimeStatus(): WorkflowRuntimeStatus { + return fromOrchestrationStatus(this._orchestrationState.runtimeStatus); + } + + /** + * Gets the workflow instance's creation time in UTC. + * @returns {Date} The workflow instance's creation time in UTC. + */ + public get createdAt(): Date { + return this._orchestrationState.createdAt; + } + + /** + * Gets the workflow instance's last updated time in UTC. + * @returns {Date} The workflow instance's last updated time in UTC. + */ + public get lastUpdatedAt(): Date { + return this._orchestrationState.lastUpdatedAt; + } + + /** + * Gets the workflow instance's serialized input, if any, as a string value. + * @returns {string | undefined} The workflow instance's serialized input or undefined. + */ + public get serializedInput(): string | undefined { + return this._orchestrationState.serializedInput; + } + + /** + * Gets the workflow instance's serialized output, if any, as a string value. + * @returns {string | undefined} The workflow instance's serialized output or undefined. + */ + public get serializedOutput(): string | undefined { + return this._orchestrationState.serializedOutput; + } + + /** + * Gets the failure details, if any, for the failed workflow instance. + * This method returns data only if the workflow is in the FAILED state and + * only if this instance metadata was fetched with the option to include output data. + * @returns {WorkflowFailureDetails | undefined} The failure details of the failed workflow instance or undefined. + */ + public get workflowFailureDetails(): WorkflowFailureDetails | undefined { + return this._workflowFailureDetails; + } +} diff --git a/src/workflow/examples/activity-sequence.ts b/src/workflow/examples/activity-sequence.ts new file mode 100644 index 00000000..aa06fe7c --- /dev/null +++ b/src/workflow/examples/activity-sequence.ts @@ -0,0 +1,67 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import WorkflowClient from "../client/WorkflowClient"; +import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; +import WorkflowContext from "../runtime/WorkflowContext"; +import WorkflowRuntime from "../runtime/WorkflowRuntime"; +import { TWorkflow } from "../types/Workflow.type"; + +(async () => { + const grpcEndpoint = "localhost:4001"; + const workflowClient = new WorkflowClient(grpcEndpoint); + const workflowRuntime = new WorkflowRuntime(grpcEndpoint); + + const hello = async (_: WorkflowActivityContext, name: string) => { + return `Hello ${name}!`; + }; + + const sequence: TWorkflow = async function* (ctx: WorkflowContext): any { + const cities: string[] = []; + + const result1 = yield ctx.callActivity(hello, "Tokyo"); + cities.push(result1); + const result2 = yield ctx.callActivity(hello, "Seattle"); // Correct the spelling of "Seattle" + cities.push(result2); + const result3 = yield ctx.callActivity(hello, "London"); + cities.push(result3); + + return cities; + }; + + workflowRuntime.registerWorkflow(sequence).registerActivity(hello); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Workflow runtime started successfully"); + } catch (error) { + console.error("Error starting workflow runtime:", error); + } + + // Schedule a new orchestration + try { + const id = await workflowClient.scheduleNewWorkflow(sequence); + console.log(`Orchestration scheduled with ID: ${id}`); + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/src/workflow/examples/fanout-fanin.ts b/src/workflow/examples/fanout-fanin.ts new file mode 100644 index 00000000..941ff488 --- /dev/null +++ b/src/workflow/examples/fanout-fanin.ts @@ -0,0 +1,94 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { Task } from "kaibocai-durabletask-js/task/task"; +import WorkflowClient from "../client/WorkflowClient"; +import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; +import WorkflowContext from "../runtime/WorkflowContext"; +import WorkflowRuntime from "../runtime/WorkflowRuntime"; +import { TWorkflow } from "../types/Workflow.type"; + +// Wrap the entire code in an immediately-invoked async function +(async () => { + // Update the gRPC client and worker to use a local address and port + const grpcServerAddress = "localhost:4001"; + const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + + function getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + async function getWorkItemsActivity(_: WorkflowActivityContext): Promise { + const count: number = getRandomInt(2, 10); + console.log(`generating ${count} work items...`); + + const workItems: string[] = Array.from({ length: count }, (_, i) => `work item ${i}`); + return workItems; + } + + function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async function processWorkItemActivity(context: WorkflowActivityContext, item: string): Promise { + console.log(`processing work item: ${item}`); + + // Simulate some work that takes a variable amount of time + const sleepTime = Math.random() * 5000; + await sleep(sleepTime); + + // Return a result for the given work item, which is also a random number in this case + return Math.floor(Math.random() * 11); + } + + const workflow: TWorkflow = async function* (ctx: WorkflowContext): any { + const tasks: Task[] = []; + const workItems = yield ctx.callActivity(getWorkItemsActivity); + for (const workItem of workItems) { + tasks.push(ctx.callActivity(processWorkItemActivity, workItem)); + } + const results: number[] = yield ctx.whenAll(tasks); + const sum: number = results.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + return sum; + }; + + workflowRuntime.registerWorkflow(workflow); + workflowRuntime.registerActivity(getWorkItemsActivity); + workflowRuntime.registerActivity(processWorkItemActivity); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Worker started successfully"); + } catch (error) { + console.error("Error starting worker:", error); + } + + // Schedule a new orchestration + try { + const id = await workflowClient.scheduleNewWorkflow(workflow); + console.log(`Orchestration scheduled with ID: ${id}`); + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + // stop worker and client + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/src/workflow/internal/ApiTokenClientInterceptor.ts b/src/workflow/internal/ApiTokenClientInterceptor.ts new file mode 100644 index 00000000..727eb096 --- /dev/null +++ b/src/workflow/internal/ApiTokenClientInterceptor.ts @@ -0,0 +1,28 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import * as grpc from "@grpc/grpc-js"; +import { Settings } from "../../utils/Settings.util"; + +export function generateInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall { + return (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata, listener, next) => { + if (metadata.get("dapr-api-token").length == 0) { + metadata.add("dapr-api-token", Settings.getDefaultApiToken() as grpc.MetadataValue); + } + next(metadata, listener); + }, + }); + }; +} diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts new file mode 100644 index 00000000..77c8b400 --- /dev/null +++ b/src/workflow/internal/index.ts @@ -0,0 +1,20 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { TInput, TOutput} from "../types/InputOutput.type"; +import { TWorkflowActivity } from "../types/Activity.type"; +import { TWorkflow } from "../types/Workflow.type"; + +export function getFunctionName(fn: TWorkflow | TWorkflowActivity): string { + return fn.name || fn.toString().match(/function\s*([^(]*)\(/)![1]; +} diff --git a/src/workflow/runtime/WorkflowActivityContext.ts b/src/workflow/runtime/WorkflowActivityContext.ts new file mode 100644 index 00000000..5e01dead --- /dev/null +++ b/src/workflow/runtime/WorkflowActivityContext.ts @@ -0,0 +1,42 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { ActivityContext } from "kaibocai-durabletask-js"; + +export default class WorkflowActivityContext { + private readonly _innerContext: ActivityContext; + constructor(innerContext: ActivityContext) { + if (!innerContext) { + throw new Error("ActivityContext cannot be undefined"); + } + this._innerContext = innerContext; + } + + /** + * Gets the unique identifier of the workflow instance associated with the current context. + * + * @returns {string} The unique identifier (orchestrationId) of the workflow instance. + */ + public getWorkflowInstanceId(): string { + return this._innerContext.orchestrationId; + } + + /** + * Gets the task ID (activityId) associated with the current workflow activity context. + * + * @returns {number} The task ID (activityId) of the current workflow activity. + */ + public getWorkflowActivityId(): number { + return this._innerContext.taskId; + } +} diff --git a/src/workflow/runtime/WorkflowContext.ts b/src/workflow/runtime/WorkflowContext.ts new file mode 100644 index 00000000..47f13477 --- /dev/null +++ b/src/workflow/runtime/WorkflowContext.ts @@ -0,0 +1,152 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { OrchestrationContext } from "kaibocai-durabletask-js"; +import { Task } from "kaibocai-durabletask-js/task/task"; +import { TInput } from "kaibocai-durabletask-js/types/input.type"; +import { TOutput } from "kaibocai-durabletask-js/types/output.type"; +import { TWorkflowActivity } from "../types/Activity.type"; +import { TWorkflow } from "../types/Workflow.type"; +import { getFunctionName } from "../internal"; +import { WhenAllTask } from "kaibocai-durabletask-js/task/when-all-task"; +import { whenAll, whenAny } from "kaibocai-durabletask-js/task"; +import { WhenAnyTask } from "kaibocai-durabletask-js/task/when-any-task"; + +export default class WorkflowContext { + private readonly _innerContext: OrchestrationContext; + constructor(innerContext: OrchestrationContext) { + if (!innerContext) { + throw new Error("ActivityContext cannot be undefined"); + } + this._innerContext = innerContext; + } + + /** + * Gets the unique ID of the current orchestration instance. + * @returns {string} The unique ID of the current orchestration instance + */ + public getWorkflowInstanceId(): string { + return this._innerContext.instanceId; + } + + /** + * Get the current date/time as UTC + * + * @returns {Date} The current timestamp in a way that is safe for use by orchestrator functions + */ + public getCurrentUtcDateTime(): Date { + return this._innerContext.currentUtcDateTime; + } + + /** + * Get the value indicating whether the orchestrator is replaying from history. + * + * This property is useful when there is logic that needs to run only when + * the orchestrator function is _not_ replaying. For example, certain + * types of application logging may become too noisy when duplicated as + * part of orchestrator function replay. The orchestrator code could check + * to see whether the function is being replayed and then issue the log + * statements when this value is `false`. + * + * @returns {boolean} `true` if the orchestrator function is replaying from history; otherwise, `false`. + */ + public isReplaying(): boolean { + return this._innerContext.isReplaying; + } + + /** + * Create a timer task that will fire at a specified time. + * + * @param {Date | number} fireAt The time at which the timer should fire. + * @returns {Task} A Durable Timer task that schedules the timer to wake up the orchestrator + */ + public createTimer(fireAt: Date | number): Task { + return this._innerContext.createTimer(fireAt); + } + + /** + * schedule an activity for execution. + * + * @param {Orchestrator} orchestrator The sub-orchestrator function to call. + * @param {TInput} input The JSON-serializable input value for the sub-orchestrator function. + * @param {string} instanceId The ID to use for the sub-orchestration instance. If not provided, a new GUID will be used. + * + * @returns {Task} A Durable Task that completes when the sub-orchestrator function completes. + */ + public callActivity(activity: TWorkflowActivity | string, input?: TInput): Task { + if (typeof activity === "string") { + return this._innerContext.callActivity(activity, input); + } + return this._innerContext.callActivity(getFunctionName(activity), input); + } + + /** + * Schedule sub-orchestrator function for execution. + * + * @param orchestrator A reference to the orchestrator function call + * @param input The JSON-serializable input value for the orchestrator function. + * @param instanceId A unique ID to use for the sub-orchestration instance. If not provided, a new GUID will be used. + * + * @returns {Task} A Durable Task that completes when the sub-orchestrator function completes. + */ + public callSubWorkflow( + orchestrator: TWorkflow | string, + input?: TInput, + instanceId?: string, + ): Task { + if (typeof orchestrator === "string") { + return this._innerContext.callSubOrchestrator(orchestrator, input, instanceId); + } + return this._innerContext.callSubOrchestrator(getFunctionName(orchestrator), input, instanceId); + } + + /** + * Wait for an event to be raised with the name "name" + * + * @param name The name of the event to wait for + * @returns {Task} A Durable Task that completes when the event is received + */ + public waitForExternalEvent(name: string): Task { + return this._innerContext.waitForExternalEvent(name); + } + + /** + * Continue the orchestration execution as a new instance + * + * @param newInput {any} The new input to use for the new orchestration instance. + * @param saveEvents {boolean} A flag indicating whether to add any unprocessed external events in the new orchestration history. + */ + public continueAsNew(newInput: any, saveEvents: boolean): void { + this._innerContext.continueAsNew(newInput, saveEvents); + } + + /** + * Returns a task that completes when all of the provided tasks complete or when one of the tasks fail + * + * @param tasks the tasks to wait for + * @returns {WhenAllTask} a task that completes when all of the provided tasks complete or when one of the tasks fail + */ + public whenAll(tasks: Task[]): WhenAllTask { + return whenAll(tasks); + } + + /** + * Returns a task that completes when any of the provided tasks complete or fail + * + * @param tasks the tasks to wait for + * @returns {WhenAnyTask} a task that completes when one of the provided tasks complete or when one of the tasks fail + */ + public whenAny(tasks: Task[]): WhenAnyTask { + return whenAny(tasks); + } +} diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts new file mode 100644 index 00000000..3d128f46 --- /dev/null +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -0,0 +1,110 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import * as grpc from "@grpc/grpc-js"; +import { ActivityContext, OrchestrationContext, TaskHubGrpcWorker } from "kaibocai-durabletask-js"; +import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflowActivity } from "../types/Activity.type"; +import { TInput, TOutput } from "../types/InputOutput.type"; +import WorkflowActivityContext from "./WorkflowActivityContext"; +import WorkflowContext from "./WorkflowContext"; +import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; +import { getFunctionName } from "../internal"; + +export default class WorkflowRuntime { + private worker: TaskHubGrpcWorker; + constructor(hostAddress?: string, options?: grpc.ChannelOptions) { + const innerOptions = { + ...options, + interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], + }; + this.worker = new TaskHubGrpcWorker(hostAddress, innerOptions); + } + + /** + * Registers a Workflow implementation for handling orchestrations. + * + * @param {TWorkflow} workflow - The instance of the Workflow class being registered. + */ + public registerWorkflow(workflow: TWorkflow): WorkflowRuntime { + const name = getFunctionName(workflow); + const workflowWrapper = (ctx: OrchestrationContext, input: any): any => { + const workflowContext = new WorkflowContext(ctx); + return workflow(workflowContext, input); + }; + this.worker.addNamedOrchestrator(name, workflowWrapper); + return this; + } + + /** + * Registers a Workflow implementation for handling orchestrations with a given name. + * + * @param {string} name - The name or identifier for the registered Workflow. + * @param {TWorkflow} workflow - The instance of the Workflow class being registered. + */ + public registerWorkflowWithName(name: string, workflow: TWorkflow): WorkflowRuntime { + const workflowWrapper = (ctx: OrchestrationContext, input: any): any => { + const workflowContext = new WorkflowContext(ctx); + return workflow(workflowContext, input); + }; + this.worker.addNamedOrchestrator(name, workflowWrapper); + return this; + } + + /** + * Registers an Activity object. + * + * @param {TWorkflowActivity} fn - The instance of the WorkflowActivity class being registered. + * @returns {WorkflowRuntime} The current instance of WorkflowRuntime. + */ + public registerActivity(fn: TWorkflowActivity): WorkflowRuntime { + const name = getFunctionName(fn); + const activityWrapper = (ctx: ActivityContext, intput: TInput): TOutput => { + const wfActivityContext = new WorkflowActivityContext(ctx); + return fn(wfActivityContext, intput); + }; + this.worker.addNamedActivity(name, activityWrapper); + return this; + } + + /** + * Registers an Activity object with a given name. + * + * @param {string} name - The name or identifier for the registered Activity. + * @param {TWorkflowActivity} fn - The instance of the WorkflowActivity class being registered. + * @returns {WorkflowRuntime} The current instance of WorkflowRuntime. + */ + public registerActivityWithName(name: string, fn: TWorkflowActivity): WorkflowRuntime { + const activityWrapper = (ctx: ActivityContext, intput: TInput): any => { + const wfActivityContext = new WorkflowActivityContext(ctx); + return fn(wfActivityContext, intput); + }; + + this.worker.addNamedActivity(name, activityWrapper); + return this; + } + + /** + * Start the Workflow runtime processing items and block. + */ + public async start() { + await this.worker.start(); + } + + /** + * Stop the worker and wait for any pending work items to complete + */ + public async stop() { + await this.worker.stop(); + } +} diff --git a/src/workflow/runtime/WorkflowRuntimeStatus.ts b/src/workflow/runtime/WorkflowRuntimeStatus.ts new file mode 100644 index 00000000..df10f196 --- /dev/null +++ b/src/workflow/runtime/WorkflowRuntimeStatus.ts @@ -0,0 +1,42 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum"; + +export enum WorkflowRuntimeStatus { + RUNNING = OrchestrationStatus.RUNNING, + COMPLETED = OrchestrationStatus.COMPLETED, + FAILED = OrchestrationStatus.FAILED, + TERMINATED = OrchestrationStatus.TERMINATED, + CONTINUED_AS_NEW = OrchestrationStatus.CONTINUED_AS_NEW, + PENDING = OrchestrationStatus.PENDING, + SUSPENDED = OrchestrationStatus.SUSPENDED, +} + +export function fromOrchestrationStatus(val: OrchestrationStatus): WorkflowRuntimeStatus { + const values = Object.values(WorkflowRuntimeStatus); + const valIdx = values.findIndex((v) => v == (val as number)); + + //Return the entry of the WorkflowRuntimeStatus enum at index + const entries = Object.entries(WorkflowRuntimeStatus); + return entries[valIdx][1] as WorkflowRuntimeStatus; +} + +export function toOrchestrationStatus(val: WorkflowRuntimeStatus): OrchestrationStatus { + const values = Object.values(OrchestrationStatus); + const valIdx = values.findIndex((v) => v == (val as number)); + + //Return the entry of the WorkflowRuntimeStatus enum at index + const entries = Object.entries(OrchestrationStatus); + return entries[valIdx][1] as OrchestrationStatus; +} diff --git a/src/workflow/types/Activity.type.ts b/src/workflow/types/Activity.type.ts new file mode 100644 index 00000000..380852b0 --- /dev/null +++ b/src/workflow/types/Activity.type.ts @@ -0,0 +1,16 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; + +export type TWorkflowActivity = (context: WorkflowActivityContext, input: TInput) => TOutput; diff --git a/src/workflow/types/InputOutput.type.ts b/src/workflow/types/InputOutput.type.ts new file mode 100644 index 00000000..8a38125f --- /dev/null +++ b/src/workflow/types/InputOutput.type.ts @@ -0,0 +1,15 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +export type TInput = any; +export type TOutput = any; diff --git a/src/workflow/types/Workflow.type.ts b/src/workflow/types/Workflow.type.ts new file mode 100644 index 00000000..3805fe61 --- /dev/null +++ b/src/workflow/types/Workflow.type.ts @@ -0,0 +1,18 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import WorkflowContext from "../runtime/WorkflowContext"; +import { Task } from "kaibocai-durabletask-js/task/task"; +import { TOutput } from "./InputOutput.type"; + +export type TWorkflow = (context: WorkflowContext, input: any) => Generator, any, any> | TOutput; diff --git a/tsconfig.json b/tsconfig.json index 4c9ef303..f86af11a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "resolveJsonModule": true, - "target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, "lib": [] /* Specify library files to be included in the compilation. */, // "allowJs": true, /* Allow javascript files to be compiled. */ From 8dd0f6f4c0951d7bf34a28df90ad10f1b0992b42 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Fri, 22 Dec 2023 11:21:01 -0600 Subject: [PATCH 02/18] add human-interaction example Signed-off-by: kaibocai --- package.json | 8 +- src/workflow/examples/human-interaction.ts | 110 +++++++++++++++++++++ src/workflow/internal/index.ts | 2 +- 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/workflow/examples/human-interaction.ts diff --git a/package.json b/package.json index 0dd727d8..c00ec927 100644 --- a/package.json +++ b/package.json @@ -45,13 +45,13 @@ "dependencies": { "@grpc/grpc-js": "^1.9.3", "@js-temporal/polyfill": "^0.3.0", - "kaibocai-durabletask-js": "^0.0.5-beta.3", "@types/google-protobuf": "^3.15.5", "@types/node-fetch": "^2.6.2", "body-parser": "^1.19.0", "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", + "kaibocai-durabletask-js": "^0.0.5-beta.3", "node-fetch": "^2.6.7" }, "devDependencies": { @@ -60,6 +60,7 @@ "@types/express": "^4.17.15", "@types/jest": "^27.0.1", "@types/node": "^16.9.1", + "@types/readline-sync": "^1.4.8", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", @@ -73,9 +74,10 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", - "typescript": "^4.5.5", - "ts-node": "^10.9.1" + "ts-node": "^10.9.1", + "typescript": "^4.5.5" }, "repository": { "type": "git", diff --git a/src/workflow/examples/human-interaction.ts b/src/workflow/examples/human-interaction.ts new file mode 100644 index 00000000..ebd5e4aa --- /dev/null +++ b/src/workflow/examples/human-interaction.ts @@ -0,0 +1,110 @@ +import { Task } from "kaibocai-durabletask-js/task/task"; +import WorkflowClient from "../client/WorkflowClient"; +import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; +import WorkflowContext from "../runtime/WorkflowContext"; +import WorkflowRuntime from "../runtime/WorkflowRuntime"; +import { TWorkflow } from "../types/Workflow.type"; +import * as readlineSync from "readline-sync"; + +// Wrap the entire code in an immediately-invoked async function +(async () => { + class Order { + cost: number; + product: string; + quantity: number; + constructor(cost: number, product: string, quantity: number) { + this.cost = cost; + this.product = product; + this.quantity = quantity; + } + } + + function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // Update the gRPC client and worker to use a local address and port + const grpcServerAddress = "localhost:4001"; + let workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + let workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + + //Activity function that sends an approval request to the manager + const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { + // Simulate some work that takes an amount of time + await sleep(3000); + console.log(`Sending approval request for order: ${order.product}`); + }; + + // Activity function that places an order + const placeOrder = async (_: WorkflowActivityContext, order: Order) => { + console.log(`Placing order: ${order.product}`); + }; + + // Orchestrator function that represents a purchase order workflow + const purchaseOrderWorkflow: TWorkflow = async function* (ctx: WorkflowContext, order: Order): any { + // Orders under $1000 are auto-approved + if (order.cost < 1000) { + return "Auto-approved"; + } + + // Orders of $1000 or more require manager approval + yield ctx.callActivity(sendApprovalRequest, order); + + // Approvals must be received within 24 hours or they will be cancled. + const tasks: Task[] = []; + const approvalEvent = ctx.waitForExternalEvent("approval_received"); + const timeoutEvent = ctx.createTimer(24 * 60 * 60); + tasks.push(approvalEvent); + tasks.push(timeoutEvent); + const winner = ctx.whenAny(tasks); + + if (winner == timeoutEvent) { + return "Cancelled"; + } + + yield ctx.callActivity(placeOrder, order); + const approvalDetails = approvalEvent.getResult(); + return `Approved by ${approvalDetails.approver}`; + }; + + workflowRuntime + .registerWorkflow(purchaseOrderWorkflow) + .registerActivity(sendApprovalRequest) + .registerActivity(placeOrder); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Worker started successfully"); + } catch (error) { + console.error("Error starting worker:", error); + } + + // Schedule a new orchestration + try { + const cost = readlineSync.questionInt("Cost of your order:"); + const approver = readlineSync.question("Approver of your order:"); + const timeout = readlineSync.questionInt("Timeout for your order in seconds:"); + const order = new Order(cost, "MyProduct", 1); + const id = await workflowClient.scheduleNewWorkflow(purchaseOrderWorkflow, order); + console.log(`Orchestration scheduled with ID: ${id}`); + + if (readlineSync.keyInYN("Press [Y] to approve the order... Y/yes, N/no")) { + const approvalEvent = { approver: approver }; + await workflowClient.raiseEvent(id, "approval_received", approvalEvent); + } else { + return "Order rejected"; + } + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, timeout + 2); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + // stop worker and client + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index 77c8b400..a84c36ba 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TInput, TOutput} from "../types/InputOutput.type"; +import { TInput, TOutput } from "../types/InputOutput.type"; import { TWorkflowActivity } from "../types/Activity.type"; import { TWorkflow } from "../types/Workflow.type"; From 3a2649fd9d2083cdebe9f714f6b6b2e9ef397025 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Tue, 2 Jan 2024 12:39:10 -0600 Subject: [PATCH 03/18] add test suits for workflow Signed-off-by: kaibocai --- package.json | 6 +- scripts/test-e2e-workflow.sh | 29 ++ src/workflow/client/WorkflowClient.ts | 4 +- test/components/workflow/redis.yaml | 33 ++ test/e2e/workflow/workflow.test.ts | 380 ++++++++++++++++++ .../workflow/workflowRuntimeStatus.test.ts | 24 ++ 6 files changed, 473 insertions(+), 3 deletions(-) create mode 100755 scripts/test-e2e-workflow.sh create mode 100644 test/components/workflow/redis.yaml create mode 100644 test/e2e/workflow/workflow.test.ts create mode 100644 test/unit/workflow/workflowRuntimeStatus.test.ts diff --git a/package.json b/package.json index c00ec927..dcb5b8cc 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test:load": "jest --runInBand --detectOpenHandles", "test:load:http": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- npm run test:load 'test/load'", "test:e2e": "jest --runInBand --detectOpenHandles", - "test:e2e:all": "npm run test:e2e:http; npm run test:e2e:grpc; npm run test:e2e:common", + "test:e2e:all": "npm run test:e2e:http; npm run test:e2e:grpc; npm run test:e2e:common; npm run test:e2e:workflow", "test:e2e:grpc": "npm run test:e2e:grpc:client && npm run test:e2e:grpc:server && npm run test:e2e:grpc:clientWithApiToken", "test:e2e:grpc:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/*client.test.ts' ]", "test:e2e:grpc:clientWithApiToken": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 DAPR_API_TOKEN=test dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/clientWithApiToken.test.ts' ]", @@ -20,6 +20,9 @@ "test:e2e:common": "npm run test:e2e:common:client && npm run test:e2e:common:server", "test:e2e:common:client": "./scripts/test-e2e-common.sh client", "test:e2e:common:server": "./scripts/test-e2e-common.sh server", + "test:e2e:workflow": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id workflow-test-suite --app-protocol grpc --dapr-grpc-port 4001 --components-path ./test/components/workflow -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/workflow/workflow.test.ts' ]", + "test:e2e:workflow:internal": "jest test/e2e/workflow --runInBand --detectOpenHandles", + "test:e2e:workflow:durabletask": "./scripts/test-e2e-workflow.sh", "test:unit": "jest --runInBand --detectOpenHandles", "test:unit:all": "npm run test:unit:main && npm run test:unit:http && npm run test:unit:grpc && npm run test:unit:common && npm run test:unit:actors && npm run test:unit:logger && npm run test:unit:utils && npm run test:unit:errors", "test:unit:main": "NODE_ENV=test npm run test:unit 'test/unit/main/.*\\.test\\.ts'", @@ -30,6 +33,7 @@ "test:unit:logger": "NODE_ENV=test npm run test:unit 'test/unit/logger/.*\\.test\\.ts'", "test:unit:utils": "NODE_ENV=test npm run test:unit 'test/unit/utils/.*\\.test\\.ts'", "test:unit:errors": "NODE_ENV=test npm run test:unit 'test/unit/errors/.*\\.test\\.ts'", + "test:unit:workflow": "NODE_ENV=test npm run test:unit 'test/unit/workflow/.*\\.test\\.ts'", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "prebuild": "./scripts/prebuild.sh", "build-ci": "npm run prebuild && ./scripts/build.sh", diff --git a/scripts/test-e2e-workflow.sh b/scripts/test-e2e-workflow.sh new file mode 100755 index 00000000..18372c93 --- /dev/null +++ b/scripts/test-e2e-workflow.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Start the sidecar if it is not running yet +if [ ! "$(docker ps -q -f name=durabletask-sidecar)" ]; then + if [ "$(docker ps -aq -f status=exited -f name=durabletask-sidecar)" ]; then + # cleanup + docker rm durabletask-sidecar + fi + + # run your container + echo "Starting Sidecar" + docker run \ + --name durabletask-sidecar -d --rm \ + -p 4001:4001 \ + --env 'DURABLETASK_SIDECAR_LOGLEVEL=Debug' \ + cgillum/durabletask-sidecar:latest start \ + --backend Emulator +fi + +echo "Running workflow E2E tests" +npm run test:e2e:workflow:internal + +# It should fail if the npm run fails +if [ $? -ne 0 ]; then + echo "E2E tests failed" + exit 1 +fi + +echo "Stopping Sidecar" +docker stop durabletask-sidecar \ No newline at end of file diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/WorkflowClient.ts index e4671092..65de18fe 100644 --- a/src/workflow/client/WorkflowClient.ts +++ b/src/workflow/client/WorkflowClient.ts @@ -104,8 +104,8 @@ export default class WorkflowClient { */ public async waitForWorkflowStart( workflowInstanceId: string, - fetchPayloads: boolean, - timeout: number, + fetchPayloads?: boolean, + timeout?: number, ): Promise { const state = await this._innerClient.waitForOrchestrationStart(workflowInstanceId, fetchPayloads, timeout); if (state !== undefined) { diff --git a/test/components/workflow/redis.yaml b/test/components/workflow/redis.yaml new file mode 100644 index 00000000..175c0e7a --- /dev/null +++ b/test/components/workflow/redis.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2022 The Dapr Authors +# 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 +# http://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. +# + +# https://docs.dapr.io/reference/components-reference/supported-bindings/rabbitmq/ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: state-redis + namespace: default +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: enableTLS + value: "false" + - name: failover + value: "false" + - name: actorStateStore + value: "true" \ No newline at end of file diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts new file mode 100644 index 00000000..101f9467 --- /dev/null +++ b/test/e2e/workflow/workflow.test.ts @@ -0,0 +1,380 @@ +import WorkflowClient from "../../../src/workflow/client/WorkflowClient"; +import WorkflowContext from "../../../src/workflow/runtime/WorkflowContext"; +import WorkflowRuntime from "../../../src/workflow/runtime/WorkflowRuntime" +import { TWorkflow } from "../../../src/workflow/types/Workflow.type"; +import { getFunctionName } from "../../../src/workflow/internal"; +import { WorkflowRuntimeStatus } from "../../../src/workflow/runtime/WorkflowRuntimeStatus"; +import WorkflowActivityContext from "../../../src/workflow/runtime/WorkflowActivityContext"; +import { Task } from "kaibocai-durabletask-js/task/task"; + + +describe("Workflow", () => { + const grpcEndpoint = "localhost:4001"; + let workflowClient: WorkflowClient; + let workflowRuntime: WorkflowRuntime; + + beforeAll(async () => { }); + + beforeEach(async () => { + // Start a worker, which will connect to the sidecar in a background thread + workflowClient = new WorkflowClient(grpcEndpoint); + workflowRuntime = new WorkflowRuntime(grpcEndpoint); + }); + + afterEach(async () => { + await workflowRuntime.stop(); + await workflowClient.stop(); + }); + + it("should be able to run an empty orchestration", async () => { + let invoked = false; + const emptyWorkflow: TWorkflow = async (_: WorkflowContext, __: any) => { + invoked = true; + } + workflowRuntime.registerWorkflow(emptyWorkflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(emptyWorkflow); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(invoked); + expect(state); + expect(state?.name).toEqual(getFunctionName(emptyWorkflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + }); + + it("should be able to run an activity sequence", async () => { + const plusOne = async (_: WorkflowActivityContext, input: number) => { + return input + 1; + }; + + const sequence: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { + const numbers = [startVal]; + let current = startVal; + + for (let i = 0; i < 10; i++) { + current = yield ctx.callActivity(plusOne, current); + numbers.push(current); + } + + return numbers; + }; + + workflowRuntime.registerWorkflow(sequence).registerActivity(plusOne); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(sequence, 1); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.name).toEqual(getFunctionName(sequence)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(1)); + expect(state?.serializedOutput).toEqual(JSON.stringify([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + }, 31000); + + it("should be able to run fan-out/fan-in", async () => { + let activityCounter = 0; + + const increment = (ctx: WorkflowActivityContext, _: any) => { + activityCounter++; + }; + + const workflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { + // Fan out to multiple sub-orchestrations + const tasks: Task[] = []; + + for (let i = 0; i < count; i++) { + tasks.push(ctx.callActivity(increment)); + } + + // Wait for all the sub-orchestrations to complete + yield ctx.whenAll(tasks); + }; + + workflowRuntime.registerWorkflow(workflow).registerActivity(increment); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 10); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 10); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(activityCounter).toEqual(10); + }, 31000); + + it("should be able to use the sub-orchestration", async () => { + let activityCounter = 0; + + const increment = (ctx: WorkflowActivityContext, _: any) => { + activityCounter++; + }; + + const childWorkflow: TWorkflow = async function* (ctx: WorkflowContext, activityCount: number): any { + yield ctx.callActivity(increment); + }; + + const parentWorkflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { + // Call sub-orchestration + yield ctx.callSubWorkflow(childWorkflow); + }; + + workflowRuntime + .registerActivity(increment) + .registerWorkflow(childWorkflow) + .registerWorkflow(parentWorkflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(parentWorkflow, 10); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(activityCounter).toEqual(1); + }, 31000); + + it("should be able to use the sub-orchestration", async () => { + let activityCounter = 0; + + const increment = (ctx: WorkflowActivityContext, _: any) => { + activityCounter++; + }; + + const childWorkflow: TWorkflow = async function* (ctx: WorkflowContext, activityCount: number): any { + yield ctx.callActivity(increment); + }; + + const parentWorkflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { + // Call sub-orchestration + yield ctx.callSubWorkflow(childWorkflow); + }; + + workflowRuntime + .registerWorkflow(childWorkflow) + .registerWorkflow(parentWorkflow) + .registerActivity(increment); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(parentWorkflow, 10); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(activityCounter).toEqual(1); + }, 31000); + + it("should allow waiting for multiple external events", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const a = yield ctx.waitForExternalEvent("A"); + const b = yield ctx.waitForExternalEvent("B"); + const c = yield ctx.waitForExternalEvent("C"); + return [a, b, c]; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + workflowClient.raiseEvent(id, "A", "a"); + workflowClient.raiseEvent(id, "B", "b"); + workflowClient.raiseEvent(id, "C", "c"); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedOutput).toEqual(JSON.stringify(["a", "b", "c"])); + }); + + it("should be able to run an single timer", async () => { + const delay = 3; + const singleTimer: TWorkflow = async function* (ctx: WorkflowContext, _: number): any { + yield ctx.createTimer(delay); + }; + + workflowRuntime.registerWorkflow(singleTimer); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(singleTimer); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + const expectedCompletionSecond = state?.createdAt?.getTime()! + delay * 1000; + const actualCompletionSecond = state?.lastUpdatedAt?.getTime(); + + expect(state); + expect(state?.name).toEqual(getFunctionName(singleTimer)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.createdAt).toBeDefined(); + expect(state?.lastUpdatedAt).toBeDefined(); + expect(expectedCompletionSecond).toBeLessThanOrEqual(actualCompletionSecond!); + }, 31000); + + it("should wait for external events with a timeout - true", async () => { + const shouldRaiseEvent = true; + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const approval = ctx.waitForExternalEvent("Approval"); + const timeout = ctx.createTimer(3); + const winner = yield ctx.whenAny([approval, timeout]); + + if (winner == approval) { + return "approved"; + } else { + return "timed out"; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + + if (shouldRaiseEvent) { + workflowClient.raiseEvent(id, "Approval"); + } + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + + if (shouldRaiseEvent) { + expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); + } else { + expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); + } + }, 31000); + + it("should wait for external events with a timeout - false", async () => { + const shouldRaiseEvent = false; + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const approval = ctx.waitForExternalEvent("Approval"); + const timeout = ctx.createTimer(3); + const winner = yield ctx.whenAny([approval, timeout]); + + if (winner == approval) { + return "approved"; + } else { + return "timed out"; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + + if (shouldRaiseEvent) { + workflowClient.raiseEvent(id, "Approval"); + } + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + + if (shouldRaiseEvent) { + expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); + } else { + expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); + } + }, 31000); + + it("should be able to terminate an orchestration", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const res = yield ctx.waitForExternalEvent("my_event"); + return res; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow); + let state = await workflowClient.waitForWorkflowStart(id, undefined, 30); + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.RUNNING); + + await workflowClient.terminateWorkflow(id, "some reason for termination"); + state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.TERMINATED); + expect(state?.serializedOutput).toEqual(JSON.stringify("some reason for termination")); + }, 31000); + + it("should allow to continue as new", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, input: number): any { + if (input < 10) { + ctx.continueAsNew(input + 1, true); + } else { + return input; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 1); + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedOutput).toEqual(JSON.stringify(10)); + }, 31000); + + it("should be able to run an single orchestration without activity", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { + return startVal + 1; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 15); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.name).toEqual(getFunctionName(workflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(15)); + expect(state?.serializedOutput).toEqual(JSON.stringify(16)); + }, 31000); + + it("should be able to purge orchestration by id", async () => { + const plusOne = async (_: WorkflowActivityContext, input: number) => { + return input + 1; + }; + + const workflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { + return yield ctx.callActivity(plusOne, startVal); + }; + + workflowRuntime.registerWorkflow(workflow).registerActivity(plusOne); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 1); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.name).toEqual(getFunctionName(workflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(1)); + expect(state?.serializedOutput).toEqual(JSON.stringify(2)); + + const purgeResult = await workflowClient.purgeWorkflow(id); + expect(purgeResult).toEqual(true); + }, 31000); +}) \ No newline at end of file diff --git a/test/unit/workflow/workflowRuntimeStatus.test.ts b/test/unit/workflow/workflowRuntimeStatus.test.ts new file mode 100644 index 00000000..ff4b644a --- /dev/null +++ b/test/unit/workflow/workflowRuntimeStatus.test.ts @@ -0,0 +1,24 @@ +import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum" +import { WorkflowRuntimeStatus, fromOrchestrationStatus, toOrchestrationStatus } from "../../../src/workflow/runtime/WorkflowRuntimeStatus" + +describe("Workflow Runtime Status", () => { + it("should convert from orchestration status to workflow runtime status", async () => { + expect(fromOrchestrationStatus(OrchestrationStatus.RUNNING)).toEqual(WorkflowRuntimeStatus.RUNNING); + expect(fromOrchestrationStatus(OrchestrationStatus.COMPLETED)).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(fromOrchestrationStatus(OrchestrationStatus.FAILED)).toEqual(WorkflowRuntimeStatus.FAILED); + expect(fromOrchestrationStatus(OrchestrationStatus.TERMINATED)).toEqual(WorkflowRuntimeStatus.TERMINATED); + expect(fromOrchestrationStatus(OrchestrationStatus.CONTINUED_AS_NEW)).toEqual(WorkflowRuntimeStatus.CONTINUED_AS_NEW); + expect(fromOrchestrationStatus(OrchestrationStatus.PENDING)).toEqual(WorkflowRuntimeStatus.PENDING); + expect(fromOrchestrationStatus(OrchestrationStatus.SUSPENDED)).toEqual(WorkflowRuntimeStatus.SUSPENDED); + }) + + it("should convert from workflow runtime status to orchestration status", async () => { + expect(toOrchestrationStatus(WorkflowRuntimeStatus.RUNNING)).toEqual(OrchestrationStatus.RUNNING); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.COMPLETED)).toEqual(OrchestrationStatus.COMPLETED); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.FAILED)).toEqual(OrchestrationStatus.FAILED); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.TERMINATED)).toEqual(OrchestrationStatus.TERMINATED); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.CONTINUED_AS_NEW)).toEqual(OrchestrationStatus.CONTINUED_AS_NEW); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.PENDING)).toEqual(OrchestrationStatus.PENDING); + expect(toOrchestrationStatus(WorkflowRuntimeStatus.SUSPENDED)).toEqual(OrchestrationStatus.SUSPENDED); + }) +}) \ No newline at end of file From 781a620abb7eae80f9dd2517b4c758c4e6c2df2d Mon Sep 17 00:00:00 2001 From: kaibocai Date: Sun, 7 Jan 2024 14:06:59 -0600 Subject: [PATCH 04/18] minor updates improve workflow unit test Signed-off-by: kaibocai --- package-lock.json | 29 + package.json | 2 +- .../types => types/workflow}/Activity.type.ts | 4 +- .../workflow}/InputOutput.type.ts | 2 +- .../types => types/workflow}/Workflow.type.ts | 4 +- src/workflow/client/WorkflowClient.ts | 11 +- src/workflow/client/WorkflowFailureDetails.ts | 2 +- src/workflow/client/WorkflowState.ts | 2 +- src/workflow/examples/activity-sequence.ts | 6 +- src/workflow/examples/fanout-fanin.ts | 4 +- src/workflow/examples/human-interaction.ts | 19 +- .../internal/ApiTokenClientInterceptor.ts | 2 +- src/workflow/internal/index.ts | 8 +- .../runtime/WorkflowActivityContext.ts | 2 +- src/workflow/runtime/WorkflowContext.ts | 6 +- src/workflow/runtime/WorkflowRuntime.ts | 8 +- src/workflow/runtime/WorkflowRuntimeStatus.ts | 2 +- test/e2e/workflow/workflow.test.ts | 729 +++++++++--------- .../workflow/workflowRuntimeStatus.test.ts | 85 +- 19 files changed, 499 insertions(+), 428 deletions(-) rename src/{workflow/types => types/workflow}/Activity.type.ts (84%) rename src/{workflow/types => types/workflow}/InputOutput.type.ts (94%) rename src/{workflow/types => types/workflow}/Workflow.type.ts (87%) diff --git a/package-lock.json b/package-lock.json index d3b592ec..a0fe00e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@types/express": "^4.17.15", "@types/jest": "^27.0.1", "@types/node": "^16.9.1", + "@types/readline-sync": "^1.4.8", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", @@ -39,6 +40,7 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", "ts-node": "^10.9.1", "typescript": "^4.5.5" @@ -1797,6 +1799,12 @@ "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "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/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -6322,6 +6330,15 @@ "node": ">=8.10.0" } }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -8865,6 +8882,12 @@ "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "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/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -12234,6 +12257,12 @@ "picomatch": "^2.2.1" } }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index dcb5b8cc..4f997518 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "test:e2e:common": "npm run test:e2e:common:client && npm run test:e2e:common:server", "test:e2e:common:client": "./scripts/test-e2e-common.sh client", "test:e2e:common:server": "./scripts/test-e2e-common.sh server", - "test:e2e:workflow": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id workflow-test-suite --app-protocol grpc --dapr-grpc-port 4001 --components-path ./test/components/workflow -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/workflow/workflow.test.ts' ]", + "test:e2e:workflow": "npm run prebuild && dapr run --app-id workflow-test-suite --app-protocol grpc --dapr-grpc-port 4001 --components-path ./test/components/workflow -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/workflow/workflow.test.ts' ]", "test:e2e:workflow:internal": "jest test/e2e/workflow --runInBand --detectOpenHandles", "test:e2e:workflow:durabletask": "./scripts/test-e2e-workflow.sh", "test:unit": "jest --runInBand --detectOpenHandles", diff --git a/src/workflow/types/Activity.type.ts b/src/types/workflow/Activity.type.ts similarity index 84% rename from src/workflow/types/Activity.type.ts rename to src/types/workflow/Activity.type.ts index 380852b0..54959925 100644 --- a/src/workflow/types/Activity.type.ts +++ b/src/types/workflow/Activity.type.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -11,6 +11,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; +import WorkflowActivityContext from "../../workflow/runtime/WorkflowActivityContext"; export type TWorkflowActivity = (context: WorkflowActivityContext, input: TInput) => TOutput; diff --git a/src/workflow/types/InputOutput.type.ts b/src/types/workflow/InputOutput.type.ts similarity index 94% rename from src/workflow/types/InputOutput.type.ts rename to src/types/workflow/InputOutput.type.ts index 8a38125f..9d9b1f1c 100644 --- a/src/workflow/types/InputOutput.type.ts +++ b/src/types/workflow/InputOutput.type.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/src/workflow/types/Workflow.type.ts b/src/types/workflow/Workflow.type.ts similarity index 87% rename from src/workflow/types/Workflow.type.ts rename to src/types/workflow/Workflow.type.ts index 3805fe61..ad6489ad 100644 --- a/src/workflow/types/Workflow.type.ts +++ b/src/types/workflow/Workflow.type.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import WorkflowContext from "../runtime/WorkflowContext"; +import WorkflowContext from "../../workflow/runtime/WorkflowContext"; import { Task } from "kaibocai-durabletask-js/task/task"; import { TOutput } from "./InputOutput.type"; diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/WorkflowClient.ts index 65de18fe..9040692d 100644 --- a/src/workflow/client/WorkflowClient.ts +++ b/src/workflow/client/WorkflowClient.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -15,7 +15,7 @@ import { TaskHubGrpcClient } from "kaibocai-durabletask-js"; import * as grpc from "@grpc/grpc-js"; import { WorkflowState } from "./WorkflowState"; import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; -import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; export default class WorkflowClient { @@ -27,10 +27,10 @@ export default class WorkflowClient { * @param {grpc.ChannelOptions | undefined} options - Additional options for configuring the gRPC channel. */ constructor(hostAddress?: string, options?: grpc.ChannelOptions) { - this._innerClient = this._buildInnerClient(hostAddress, options); + this._innerClient = this.buildInnerClient(hostAddress, options); } - _buildInnerClient(hostAddress = "127.0.0.1:50001", options: grpc.ChannelOptions = {}): TaskHubGrpcClient { + private buildInnerClient(hostAddress = "127.0.0.1:50001", options: grpc.ChannelOptions = {}): TaskHubGrpcClient { const innerOptions = { ...options, interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], @@ -42,6 +42,9 @@ export default class WorkflowClient { * Schedules a new workflow using the DurableTask client. * * @param {TWorkflow | string} workflow - The Workflow or the name of the workflow to be scheduled. + * @param {any} [input] - The input to be provided to the scheduled workflow. + * @param {string} [instanceId] - An optional unique identifier for the workflow instance. + * @param {Date} [startAt] - An optional date and time at which the workflow should start. * @return {Promise} A Promise resolving to the unique ID of the scheduled workflow instance. */ public async scheduleNewWorkflow( diff --git a/src/workflow/client/WorkflowFailureDetails.ts b/src/workflow/client/WorkflowFailureDetails.ts index c86da7d9..219f0ebd 100644 --- a/src/workflow/client/WorkflowFailureDetails.ts +++ b/src/workflow/client/WorkflowFailureDetails.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/src/workflow/client/WorkflowState.ts b/src/workflow/client/WorkflowState.ts index 738f4ef6..2e9ee91b 100644 --- a/src/workflow/client/WorkflowState.ts +++ b/src/workflow/client/WorkflowState.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/src/workflow/examples/activity-sequence.ts b/src/workflow/examples/activity-sequence.ts index aa06fe7c..54acb473 100644 --- a/src/workflow/examples/activity-sequence.ts +++ b/src/workflow/examples/activity-sequence.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -15,7 +15,7 @@ import WorkflowClient from "../client/WorkflowClient"; import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; import WorkflowContext from "../runtime/WorkflowContext"; import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; (async () => { const grpcEndpoint = "localhost:4001"; @@ -31,7 +31,7 @@ import { TWorkflow } from "../types/Workflow.type"; const result1 = yield ctx.callActivity(hello, "Tokyo"); cities.push(result1); - const result2 = yield ctx.callActivity(hello, "Seattle"); // Correct the spelling of "Seattle" + const result2 = yield ctx.callActivity(hello, "Seattle"); cities.push(result2); const result3 = yield ctx.callActivity(hello, "London"); cities.push(result3); diff --git a/src/workflow/examples/fanout-fanin.ts b/src/workflow/examples/fanout-fanin.ts index 941ff488..f71067df 100644 --- a/src/workflow/examples/fanout-fanin.ts +++ b/src/workflow/examples/fanout-fanin.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -16,7 +16,7 @@ import WorkflowClient from "../client/WorkflowClient"; import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; import WorkflowContext from "../runtime/WorkflowContext"; import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; // Wrap the entire code in an immediately-invoked async function (async () => { diff --git a/src/workflow/examples/human-interaction.ts b/src/workflow/examples/human-interaction.ts index ebd5e4aa..9cfe8192 100644 --- a/src/workflow/examples/human-interaction.ts +++ b/src/workflow/examples/human-interaction.ts @@ -1,9 +1,22 @@ +/* +Copyright 2024 The Dapr Authors +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 + http://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. +*/ + import { Task } from "kaibocai-durabletask-js/task/task"; import WorkflowClient from "../client/WorkflowClient"; import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; import WorkflowContext from "../runtime/WorkflowContext"; import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; import * as readlineSync from "readline-sync"; // Wrap the entire code in an immediately-invoked async function @@ -25,8 +38,8 @@ import * as readlineSync from "readline-sync"; // Update the gRPC client and worker to use a local address and port const grpcServerAddress = "localhost:4001"; - let workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); - let workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); //Activity function that sends an approval request to the manager const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { diff --git a/src/workflow/internal/ApiTokenClientInterceptor.ts b/src/workflow/internal/ApiTokenClientInterceptor.ts index 727eb096..962847ff 100644 --- a/src/workflow/internal/ApiTokenClientInterceptor.ts +++ b/src/workflow/internal/ApiTokenClientInterceptor.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index a84c36ba..2af98af3 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -11,9 +11,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TInput, TOutput } from "../types/InputOutput.type"; -import { TWorkflowActivity } from "../types/Activity.type"; -import { TWorkflow } from "../types/Workflow.type"; +import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; +import { TWorkflowActivity } from "../../types/workflow/Activity.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; export function getFunctionName(fn: TWorkflow | TWorkflowActivity): string { return fn.name || fn.toString().match(/function\s*([^(]*)\(/)![1]; diff --git a/src/workflow/runtime/WorkflowActivityContext.ts b/src/workflow/runtime/WorkflowActivityContext.ts index 5e01dead..c121cb43 100644 --- a/src/workflow/runtime/WorkflowActivityContext.ts +++ b/src/workflow/runtime/WorkflowActivityContext.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/src/workflow/runtime/WorkflowContext.ts b/src/workflow/runtime/WorkflowContext.ts index 47f13477..3dae60eb 100644 --- a/src/workflow/runtime/WorkflowContext.ts +++ b/src/workflow/runtime/WorkflowContext.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -15,8 +15,8 @@ import { OrchestrationContext } from "kaibocai-durabletask-js"; import { Task } from "kaibocai-durabletask-js/task/task"; import { TInput } from "kaibocai-durabletask-js/types/input.type"; import { TOutput } from "kaibocai-durabletask-js/types/output.type"; -import { TWorkflowActivity } from "../types/Activity.type"; -import { TWorkflow } from "../types/Workflow.type"; +import { TWorkflowActivity } from "../../types/workflow/Activity.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; import { WhenAllTask } from "kaibocai-durabletask-js/task/when-all-task"; import { whenAll, whenAny } from "kaibocai-durabletask-js/task"; diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 3d128f46..d41858d7 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 @@ -13,9 +13,9 @@ limitations under the License. import * as grpc from "@grpc/grpc-js"; import { ActivityContext, OrchestrationContext, TaskHubGrpcWorker } from "kaibocai-durabletask-js"; -import { TWorkflow } from "../types/Workflow.type"; -import { TWorkflowActivity } from "../types/Activity.type"; -import { TInput, TOutput } from "../types/InputOutput.type"; +import { TWorkflow } from "../../types/workflow/Workflow.type"; +import { TWorkflowActivity } from "../../types/workflow/Activity.type"; +import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; import WorkflowActivityContext from "./WorkflowActivityContext"; import WorkflowContext from "./WorkflowContext"; import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; diff --git a/src/workflow/runtime/WorkflowRuntimeStatus.ts b/src/workflow/runtime/WorkflowRuntimeStatus.ts index df10f196..b110df2c 100644 --- a/src/workflow/runtime/WorkflowRuntimeStatus.ts +++ b/src/workflow/runtime/WorkflowRuntimeStatus.ts @@ -1,5 +1,5 @@ /* -Copyright 2022 The Dapr Authors +Copyright 2024 The Dapr Authors 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 diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 101f9467..56f9c216 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -1,380 +1,365 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + import WorkflowClient from "../../../src/workflow/client/WorkflowClient"; import WorkflowContext from "../../../src/workflow/runtime/WorkflowContext"; -import WorkflowRuntime from "../../../src/workflow/runtime/WorkflowRuntime" -import { TWorkflow } from "../../../src/workflow/types/Workflow.type"; +import WorkflowRuntime from "../../../src/workflow/runtime/WorkflowRuntime"; +import { TWorkflow } from "../../../src/types/workflow/Workflow.type"; import { getFunctionName } from "../../../src/workflow/internal"; import { WorkflowRuntimeStatus } from "../../../src/workflow/runtime/WorkflowRuntimeStatus"; import WorkflowActivityContext from "../../../src/workflow/runtime/WorkflowActivityContext"; import { Task } from "kaibocai-durabletask-js/task/task"; - describe("Workflow", () => { - const grpcEndpoint = "localhost:4001"; - let workflowClient: WorkflowClient; - let workflowRuntime: WorkflowRuntime; - - beforeAll(async () => { }); - - beforeEach(async () => { - // Start a worker, which will connect to the sidecar in a background thread - workflowClient = new WorkflowClient(grpcEndpoint); - workflowRuntime = new WorkflowRuntime(grpcEndpoint); - }); - - afterEach(async () => { - await workflowRuntime.stop(); - await workflowClient.stop(); - }); - - it("should be able to run an empty orchestration", async () => { - let invoked = false; - const emptyWorkflow: TWorkflow = async (_: WorkflowContext, __: any) => { - invoked = true; - } - workflowRuntime.registerWorkflow(emptyWorkflow); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(emptyWorkflow); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(invoked); - expect(state); - expect(state?.name).toEqual(getFunctionName(emptyWorkflow)); - expect(state?.instanceId).toEqual(id); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - }); - - it("should be able to run an activity sequence", async () => { - const plusOne = async (_: WorkflowActivityContext, input: number) => { - return input + 1; - }; - - const sequence: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { - const numbers = [startVal]; - let current = startVal; - - for (let i = 0; i < 10; i++) { - current = yield ctx.callActivity(plusOne, current); - numbers.push(current); - } - - return numbers; - }; - - workflowRuntime.registerWorkflow(sequence).registerActivity(plusOne); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(sequence, 1); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.name).toEqual(getFunctionName(sequence)); - expect(state?.instanceId).toEqual(id); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.serializedInput).toEqual(JSON.stringify(1)); - expect(state?.serializedOutput).toEqual(JSON.stringify([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); - }, 31000); - - it("should be able to run fan-out/fan-in", async () => { - let activityCounter = 0; - - const increment = (ctx: WorkflowActivityContext, _: any) => { - activityCounter++; - }; - - const workflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { - // Fan out to multiple sub-orchestrations - const tasks: Task[] = []; - - for (let i = 0; i < count; i++) { - tasks.push(ctx.callActivity(increment)); - } - - // Wait for all the sub-orchestrations to complete - yield ctx.whenAll(tasks); - }; - - workflowRuntime.registerWorkflow(workflow).registerActivity(increment); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(workflow, 10); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 10); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(activityCounter).toEqual(10); - }, 31000); - - it("should be able to use the sub-orchestration", async () => { - let activityCounter = 0; - - const increment = (ctx: WorkflowActivityContext, _: any) => { - activityCounter++; - }; - - const childWorkflow: TWorkflow = async function* (ctx: WorkflowContext, activityCount: number): any { - yield ctx.callActivity(increment); - }; - - const parentWorkflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { - // Call sub-orchestration - yield ctx.callSubWorkflow(childWorkflow); - }; - - workflowRuntime - .registerActivity(increment) - .registerWorkflow(childWorkflow) - .registerWorkflow(parentWorkflow); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(parentWorkflow, 10); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(activityCounter).toEqual(1); - }, 31000); - - it("should be able to use the sub-orchestration", async () => { - let activityCounter = 0; - - const increment = (ctx: WorkflowActivityContext, _: any) => { - activityCounter++; - }; - - const childWorkflow: TWorkflow = async function* (ctx: WorkflowContext, activityCount: number): any { - yield ctx.callActivity(increment); - }; - - const parentWorkflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { - // Call sub-orchestration - yield ctx.callSubWorkflow(childWorkflow); - }; - - workflowRuntime - .registerWorkflow(childWorkflow) - .registerWorkflow(parentWorkflow) - .registerActivity(increment); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(parentWorkflow, 10); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(activityCounter).toEqual(1); - }, 31000); - - it("should allow waiting for multiple external events", async () => { - const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { - const a = yield ctx.waitForExternalEvent("A"); - const b = yield ctx.waitForExternalEvent("B"); - const c = yield ctx.waitForExternalEvent("C"); - return [a, b, c]; - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - // Send events to the client immediately - const id = await workflowClient.scheduleNewWorkflow(workflow); - workflowClient.raiseEvent(id, "A", "a"); - workflowClient.raiseEvent(id, "B", "b"); - workflowClient.raiseEvent(id, "C", "c"); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.serializedOutput).toEqual(JSON.stringify(["a", "b", "c"])); - }); - - it("should be able to run an single timer", async () => { - const delay = 3; - const singleTimer: TWorkflow = async function* (ctx: WorkflowContext, _: number): any { - yield ctx.createTimer(delay); - }; - - workflowRuntime.registerWorkflow(singleTimer); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(singleTimer); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - const expectedCompletionSecond = state?.createdAt?.getTime()! + delay * 1000; - const actualCompletionSecond = state?.lastUpdatedAt?.getTime(); - - expect(state); - expect(state?.name).toEqual(getFunctionName(singleTimer)); - expect(state?.instanceId).toEqual(id); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.createdAt).toBeDefined(); - expect(state?.lastUpdatedAt).toBeDefined(); - expect(expectedCompletionSecond).toBeLessThanOrEqual(actualCompletionSecond!); - }, 31000); - - it("should wait for external events with a timeout - true", async () => { - const shouldRaiseEvent = true; - const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { - const approval = ctx.waitForExternalEvent("Approval"); - const timeout = ctx.createTimer(3); - const winner = yield ctx.whenAny([approval, timeout]); - - if (winner == approval) { - return "approved"; - } else { - return "timed out"; - } - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - // Send events to the client immediately - const id = await workflowClient.scheduleNewWorkflow(workflow); - - if (shouldRaiseEvent) { - workflowClient.raiseEvent(id, "Approval"); - } - - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - - if (shouldRaiseEvent) { - expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); - } else { - expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); - } - }, 31000); - - it("should wait for external events with a timeout - false", async () => { - const shouldRaiseEvent = false; - const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { - const approval = ctx.waitForExternalEvent("Approval"); - const timeout = ctx.createTimer(3); - const winner = yield ctx.whenAny([approval, timeout]); - - if (winner == approval) { - return "approved"; - } else { - return "timed out"; - } - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - // Send events to the client immediately - const id = await workflowClient.scheduleNewWorkflow(workflow); - - if (shouldRaiseEvent) { - workflowClient.raiseEvent(id, "Approval"); - } - - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - - if (shouldRaiseEvent) { - expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); - } else { - expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); - } - }, 31000); - - it("should be able to terminate an orchestration", async () => { - const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { - const res = yield ctx.waitForExternalEvent("my_event"); - return res; - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(workflow); - let state = await workflowClient.waitForWorkflowStart(id, undefined, 30); - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.RUNNING); - - await workflowClient.terminateWorkflow(id, "some reason for termination"); - state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.TERMINATED); - expect(state?.serializedOutput).toEqual(JSON.stringify("some reason for termination")); - }, 31000); - - it("should allow to continue as new", async () => { - const workflow: TWorkflow = async function* (ctx: WorkflowContext, input: number): any { - if (input < 10) { - ctx.continueAsNew(input + 1, true); - } else { - return input; - } - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(workflow, 1); - - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - expect(state); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.serializedOutput).toEqual(JSON.stringify(10)); - }, 31000); - - it("should be able to run an single orchestration without activity", async () => { - const workflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { - return startVal + 1; - }; - - workflowRuntime.registerWorkflow(workflow); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(workflow, 15); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.name).toEqual(getFunctionName(workflow)); - expect(state?.instanceId).toEqual(id); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.serializedInput).toEqual(JSON.stringify(15)); - expect(state?.serializedOutput).toEqual(JSON.stringify(16)); - }, 31000); - - it("should be able to purge orchestration by id", async () => { - const plusOne = async (_: WorkflowActivityContext, input: number) => { - return input + 1; - }; - - const workflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { - return yield ctx.callActivity(plusOne, startVal); - }; - - workflowRuntime.registerWorkflow(workflow).registerActivity(plusOne); - await workflowRuntime.start(); - - const id = await workflowClient.scheduleNewWorkflow(workflow, 1); - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - expect(state); - expect(state?.name).toEqual(getFunctionName(workflow)); - expect(state?.instanceId).toEqual(id); - expect(state?.workflowFailureDetails).toBeUndefined(); - expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(state?.serializedInput).toEqual(JSON.stringify(1)); - expect(state?.serializedOutput).toEqual(JSON.stringify(2)); - - const purgeResult = await workflowClient.purgeWorkflow(id); - expect(purgeResult).toEqual(true); - }, 31000); -}) \ No newline at end of file + const grpcEndpoint = "localhost:4001"; + let workflowClient: WorkflowClient; + let workflowRuntime: WorkflowRuntime; + + beforeEach(async () => { + // Start a worker, which will connect to the sidecar in a background thread + workflowClient = new WorkflowClient(grpcEndpoint); + workflowRuntime = new WorkflowRuntime(grpcEndpoint); + }); + + afterEach(async () => { + await workflowRuntime.stop(); + await workflowClient.stop(); + }); + + it("should be able to run an empty orchestration", async () => { + let invoked = false; + const emptyWorkflow: TWorkflow = async (_: WorkflowContext, __: any) => { + invoked = true; + }; + workflowRuntime.registerWorkflow(emptyWorkflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(emptyWorkflow); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(invoked).toBe(true); + expect(state).toBeDefined(); + expect(state?.name).toEqual(getFunctionName(emptyWorkflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + }, 31000); + + it("should be able to run an activity sequence", async () => { + const plusOne = async (_: WorkflowActivityContext, input: number) => { + return input + 1; + }; + + const sequenceWorkflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { + const numbers = [startVal]; + let current = startVal; + + for (let i = 0; i < 10; i++) { + current = yield ctx.callActivity(plusOne, current); + numbers.push(current); + } + + return numbers; + }; + + workflowRuntime.registerWorkflow(sequenceWorkflow).registerActivity(plusOne); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(sequenceWorkflow, 1); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.name).toEqual(getFunctionName(sequenceWorkflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(1)); + expect(state?.serializedOutput).toEqual(JSON.stringify([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + }, 31000); + + it("should be able to run fan-out/fan-in", async () => { + let activityCounter = 0; + + const incrementActivity = (_: WorkflowActivityContext) => { + activityCounter++; + }; + + const sequenceWorkflow: TWorkflow = async function* (ctx: WorkflowContext, count: number): any { + // Fan out to multiple sub-orchestrations + const tasks: Task[] = []; + + for (let i = 0; i < count; i++) { + tasks.push(ctx.callActivity(incrementActivity)); + } + + // Wait for all the sub-orchestrations to complete + yield ctx.whenAll(tasks); + }; + + workflowRuntime.registerWorkflow(sequenceWorkflow).registerActivity(incrementActivity); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(sequenceWorkflow, 10); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 10); + + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(activityCounter).toEqual(10); + }, 31000); + + it("should be able to use the sub-orchestration", async () => { + let activityCounter = 0; + + const incrementActivity = (_: WorkflowActivityContext) => { + activityCounter++; + }; + + const childWorkflow: TWorkflow = async function* (ctx: WorkflowContext): any { + yield ctx.callActivity(incrementActivity); + }; + + const parentWorkflow: TWorkflow = async function* (ctx: WorkflowContext): any { + // Call sub-orchestration + yield ctx.callSubWorkflow(childWorkflow); + }; + + workflowRuntime + .registerActivity(incrementActivity) + .registerWorkflow(childWorkflow) + .registerWorkflow(parentWorkflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(parentWorkflow, 10); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(activityCounter).toEqual(1); + }, 31000); + + it("should allow waiting for multiple external events", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const a = yield ctx.waitForExternalEvent("A"); + const b = yield ctx.waitForExternalEvent("B"); + const c = yield ctx.waitForExternalEvent("C"); + return [a, b, c]; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + workflowClient.raiseEvent(id, "A", "a"); + workflowClient.raiseEvent(id, "B", "b"); + workflowClient.raiseEvent(id, "C", "c"); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedOutput).toEqual(JSON.stringify(["a", "b", "c"])); + }, 31000); + + it("should be able to run an single timer", async () => { + const delay = 3; + const singleTimerWorkflow: TWorkflow = async function* (ctx: WorkflowContext, _: number): any { + yield ctx.createTimer(delay); + }; + + workflowRuntime.registerWorkflow(singleTimerWorkflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(singleTimerWorkflow); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + let expectedCompletionSecond = state?.createdAt?.getTime() ?? 0; + if (state && state.createdAt !== undefined) { + expectedCompletionSecond += delay * 1000; + } + // const expectedCompletionSecond = state?.createdAt?.getTime()! + delay * 1000; + expect(expectedCompletionSecond).toBeDefined(); + const actualCompletionSecond = state?.lastUpdatedAt?.getTime(); + expect(actualCompletionSecond).toBeDefined(); + + expect(state).toBeDefined(); + expect(state?.name).toEqual(getFunctionName(singleTimerWorkflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.createdAt).toBeDefined(); + expect(state?.lastUpdatedAt).toBeDefined(); + expect(expectedCompletionSecond).toBeLessThanOrEqual(actualCompletionSecond!); + }, 31000); + + it("should wait for external events with a timeout - true", async () => { + const shouldRaiseEvent = true; + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const approval = ctx.waitForExternalEvent("Approval"); + const timeout = ctx.createTimer(3); + const winner = yield ctx.whenAny([approval, timeout]); + + if (winner == approval) { + return "approved"; + } else { + return "timed out"; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + + if (shouldRaiseEvent) { + workflowClient.raiseEvent(id, "Approval"); + } + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + + if (shouldRaiseEvent) { + expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); + } else { + expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); + } + }, 31000); + + it("should wait for external events with a timeout - false", async () => { + const shouldRaiseEvent = false; + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const approval = ctx.waitForExternalEvent("Approval"); + const timeout = ctx.createTimer(3); + const winner = yield ctx.whenAny([approval, timeout]); + + if (winner == approval) { + return "approved"; + } else { + return "timed out"; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + // Send events to the client immediately + const id = await workflowClient.scheduleNewWorkflow(workflow); + + if (shouldRaiseEvent) { + workflowClient.raiseEvent(id, "Approval"); + } + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + + if (shouldRaiseEvent) { + expect(state?.serializedOutput).toEqual(JSON.stringify("approved")); + } else { + expect(state?.serializedOutput).toEqual(JSON.stringify("timed out")); + } + }, 31000); + + it("should be able to terminate an orchestration", async () => { + const workflow: TWorkflow = async function* (ctx: WorkflowContext, _: any): any { + const res = yield ctx.waitForExternalEvent("my_event"); + return res; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow); + let state = await workflowClient.waitForWorkflowStart(id, undefined, 30); + expect(state); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.RUNNING); + + await workflowClient.terminateWorkflow(id, "some reason for termination"); + state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.TERMINATED); + expect(state?.serializedOutput).toEqual(JSON.stringify("some reason for termination")); + }, 31000); + + it("should allow to continue as new", async () => { + const workflow: TWorkflow = async (ctx: WorkflowContext, input: number) => { + if (input < 10) { + ctx.continueAsNew(input + 1, true); + } else { + return input; + } + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 1); + + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + expect(state).toBeDefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedOutput).toEqual(JSON.stringify(10)); + }, 31000); + + it("should be able to run an single orchestration without activity", async () => { + const workflow: TWorkflow = async (_: WorkflowContext, startVal: number) => { + return startVal + 1; + }; + + workflowRuntime.registerWorkflow(workflow); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 15); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.name).toEqual(getFunctionName(workflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(15)); + expect(state?.serializedOutput).toEqual(JSON.stringify(16)); + }, 31000); + + it("should be able to purge orchestration by id", async () => { + const plusOneActivity = async (_: WorkflowActivityContext, input: number) => { + return input + 1; + }; + + const workflow: TWorkflow = async function* (ctx: WorkflowContext, startVal: number): any { + return yield ctx.callActivity(plusOneActivity, startVal); + }; + + workflowRuntime.registerWorkflow(workflow).registerActivity(plusOneActivity); + await workflowRuntime.start(); + + const id = await workflowClient.scheduleNewWorkflow(workflow, 1); + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + expect(state).toBeDefined(); + expect(state?.name).toEqual(getFunctionName(workflow)); + expect(state?.instanceId).toEqual(id); + expect(state?.workflowFailureDetails).toBeUndefined(); + expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); + expect(state?.serializedInput).toEqual(JSON.stringify(1)); + expect(state?.serializedOutput).toEqual(JSON.stringify(2)); + + const purgeResult = await workflowClient.purgeWorkflow(id); + expect(purgeResult).toEqual(true); + }, 31000); +}); diff --git a/test/unit/workflow/workflowRuntimeStatus.test.ts b/test/unit/workflow/workflowRuntimeStatus.test.ts index ff4b644a..317b5ae7 100644 --- a/test/unit/workflow/workflowRuntimeStatus.test.ts +++ b/test/unit/workflow/workflowRuntimeStatus.test.ts @@ -1,24 +1,65 @@ -import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum" -import { WorkflowRuntimeStatus, fromOrchestrationStatus, toOrchestrationStatus } from "../../../src/workflow/runtime/WorkflowRuntimeStatus" +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum"; +import { + WorkflowRuntimeStatus, + fromOrchestrationStatus, + toOrchestrationStatus, +} from "../../../src/workflow/runtime/WorkflowRuntimeStatus"; describe("Workflow Runtime Status", () => { - it("should convert from orchestration status to workflow runtime status", async () => { - expect(fromOrchestrationStatus(OrchestrationStatus.RUNNING)).toEqual(WorkflowRuntimeStatus.RUNNING); - expect(fromOrchestrationStatus(OrchestrationStatus.COMPLETED)).toEqual(WorkflowRuntimeStatus.COMPLETED); - expect(fromOrchestrationStatus(OrchestrationStatus.FAILED)).toEqual(WorkflowRuntimeStatus.FAILED); - expect(fromOrchestrationStatus(OrchestrationStatus.TERMINATED)).toEqual(WorkflowRuntimeStatus.TERMINATED); - expect(fromOrchestrationStatus(OrchestrationStatus.CONTINUED_AS_NEW)).toEqual(WorkflowRuntimeStatus.CONTINUED_AS_NEW); - expect(fromOrchestrationStatus(OrchestrationStatus.PENDING)).toEqual(WorkflowRuntimeStatus.PENDING); - expect(fromOrchestrationStatus(OrchestrationStatus.SUSPENDED)).toEqual(WorkflowRuntimeStatus.SUSPENDED); - }) - - it("should convert from workflow runtime status to orchestration status", async () => { - expect(toOrchestrationStatus(WorkflowRuntimeStatus.RUNNING)).toEqual(OrchestrationStatus.RUNNING); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.COMPLETED)).toEqual(OrchestrationStatus.COMPLETED); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.FAILED)).toEqual(OrchestrationStatus.FAILED); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.TERMINATED)).toEqual(OrchestrationStatus.TERMINATED); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.CONTINUED_AS_NEW)).toEqual(OrchestrationStatus.CONTINUED_AS_NEW); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.PENDING)).toEqual(OrchestrationStatus.PENDING); - expect(toOrchestrationStatus(WorkflowRuntimeStatus.SUSPENDED)).toEqual(OrchestrationStatus.SUSPENDED); - }) -}) \ No newline at end of file + const testCases = [ + { + orchestrationStatus: OrchestrationStatus.RUNNING, + workflowRuntimeStatus: WorkflowRuntimeStatus.RUNNING, + }, + + { + orchestrationStatus: OrchestrationStatus.COMPLETED, + workflowRuntimeStatus: WorkflowRuntimeStatus.COMPLETED, + }, + + { + orchestrationStatus: OrchestrationStatus.FAILED, + workflowRuntimeStatus: WorkflowRuntimeStatus.FAILED, + }, + + { + orchestrationStatus: OrchestrationStatus.TERMINATED, + workflowRuntimeStatus: WorkflowRuntimeStatus.TERMINATED, + }, + + { + orchestrationStatus: OrchestrationStatus.CONTINUED_AS_NEW, + workflowRuntimeStatus: WorkflowRuntimeStatus.CONTINUED_AS_NEW, + }, + + { + orchestrationStatus: OrchestrationStatus.PENDING, + workflowRuntimeStatus: WorkflowRuntimeStatus.PENDING, + }, + + { + orchestrationStatus: OrchestrationStatus.SUSPENDED, + workflowRuntimeStatus: WorkflowRuntimeStatus.SUSPENDED, + }, + ]; + + testCases.forEach((testCase) => { + test("Should be able to convert between orchestration status to workflow runtime status", () => { + expect(fromOrchestrationStatus(testCase.orchestrationStatus)).toEqual(testCase.workflowRuntimeStatus); + expect(toOrchestrationStatus(testCase.workflowRuntimeStatus)).toEqual(testCase.orchestrationStatus); + }); + }); +}); From 873bb69f6d0679aa6debf0f37400071c40de7f93 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Sun, 7 Jan 2024 15:56:40 -0600 Subject: [PATCH 05/18] clean up - update durabletask-js dependency Signed-off-by: kaibocai --- package-lock.json | 14 +++++++------- package.json | 2 +- test/e2e/workflow/workflow.test.ts | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index a0fe00e4..bad24069 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", - "kaibocai-durabletask-js": "^0.0.5-beta.3", + "kaibocai-durabletask-js": "^0.0.6-beta.1", "node-fetch": "^2.6.7" }, "devDependencies": { @@ -5192,9 +5192,9 @@ } }, "node_modules/kaibocai-durabletask-js": { - "version": "0.0.5-beta.3", - "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.5-beta.3.tgz", - "integrity": "sha512-Z9RCV8MxfHQfIVLcIDVoVeMMUuER6xrmYMtKGf+q+vvNCcK+qC1UUsw3a1A4hsCtwAkuCQSuqDBD3/ABtIxdrQ==", + "version": "0.0.6-beta.1", + "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.6-beta.1.tgz", + "integrity": "sha512-OoP/0YUBVInuSJ97cW8MzXfgrTX2WHyw1neESxZp9HKXSCmp1feMOtwNNjQa5HoqgVVS4TV1tH9oFZsXs7SHEA==", "dependencies": { "@grpc/grpc-js": "^1.8.14", "google-protobuf": "^3.21.2" @@ -11418,9 +11418,9 @@ "dev": true }, "kaibocai-durabletask-js": { - "version": "0.0.5-beta.3", - "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.5-beta.3.tgz", - "integrity": "sha512-Z9RCV8MxfHQfIVLcIDVoVeMMUuER6xrmYMtKGf+q+vvNCcK+qC1UUsw3a1A4hsCtwAkuCQSuqDBD3/ABtIxdrQ==", + "version": "0.0.6-beta.1", + "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.6-beta.1.tgz", + "integrity": "sha512-OoP/0YUBVInuSJ97cW8MzXfgrTX2WHyw1neESxZp9HKXSCmp1feMOtwNNjQa5HoqgVVS4TV1tH9oFZsXs7SHEA==", "requires": { "@grpc/grpc-js": "^1.8.14", "google-protobuf": "^3.21.2" diff --git a/package.json b/package.json index 4f997518..1cbfe213 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", - "kaibocai-durabletask-js": "^0.0.5-beta.3", + "kaibocai-durabletask-js": "^0.0.6-beta.1", "node-fetch": "^2.6.7" }, "devDependencies": { diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 56f9c216..453e06a8 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -188,7 +188,6 @@ describe("Workflow", () => { if (state && state.createdAt !== undefined) { expectedCompletionSecond += delay * 1000; } - // const expectedCompletionSecond = state?.createdAt?.getTime()! + delay * 1000; expect(expectedCompletionSecond).toBeDefined(); const actualCompletionSecond = state?.lastUpdatedAt?.getTime(); expect(actualCompletionSecond).toBeDefined(); From 404778ad1d41745f8e9a72e82de20e7a92854418 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 8 Jan 2024 14:29:19 -0600 Subject: [PATCH 06/18] update to microsoft/durabletask-js - move examples Signed-off-by: kaibocai --- examples/workflow/authoring/README.md | 33 ++ .../workflow/authoring/components/redis.yaml | 33 ++ .../{ => authoring}/package-lock.json | 62 ++- examples/workflow/authoring/package.json | 25 + .../authoring/src/activity-sequence.ts | 68 +++ .../workflow/authoring/src/fanout-fanin.ts | 89 ++++ .../authoring/src/human-interaction.ts | 118 +++++ .../workflow/{ => authoring}/tsconfig.json | 0 .../workflow/management/package-lock.json | 465 ++++++++++++++++++ .../workflow/{ => management}/package.json | 4 +- .../workflow/{ => management}/src/index.ts | 0 examples/workflow/management/tsconfig.json | 71 +++ package-lock.json | 38 +- package.json | 2 +- src/index.ts | 25 + src/types/workflow/Workflow.type.ts | 2 +- src/workflow/client/WorkflowClient.ts | 2 +- src/workflow/client/WorkflowFailureDetails.ts | 2 +- src/workflow/client/WorkflowState.ts | 2 +- src/workflow/examples/fanout-fanin.ts | 2 +- src/workflow/examples/human-interaction.ts | 2 +- .../runtime/WorkflowActivityContext.ts | 2 +- src/workflow/runtime/WorkflowContext.ts | 14 +- src/workflow/runtime/WorkflowRuntime.ts | 2 +- src/workflow/runtime/WorkflowRuntimeStatus.ts | 2 +- test/e2e/workflow/workflow.test.ts | 2 +- .../workflow/workflowRuntimeStatus.test.ts | 2 +- 27 files changed, 1022 insertions(+), 47 deletions(-) create mode 100644 examples/workflow/authoring/README.md create mode 100644 examples/workflow/authoring/components/redis.yaml rename examples/workflow/{ => authoring}/package-lock.json (89%) create mode 100644 examples/workflow/authoring/package.json create mode 100644 examples/workflow/authoring/src/activity-sequence.ts create mode 100644 examples/workflow/authoring/src/fanout-fanin.ts create mode 100644 examples/workflow/authoring/src/human-interaction.ts rename examples/workflow/{ => authoring}/tsconfig.json (100%) create mode 100644 examples/workflow/management/package-lock.json rename examples/workflow/{ => management}/package.json (89%) rename examples/workflow/{ => management}/src/index.ts (100%) create mode 100644 examples/workflow/management/tsconfig.json diff --git a/examples/workflow/authoring/README.md b/examples/workflow/authoring/README.md new file mode 100644 index 00000000..4ac79b8c --- /dev/null +++ b/examples/workflow/authoring/README.md @@ -0,0 +1,33 @@ +# Examples - Dapr Workflow + +## Running + +### Activity Sequence example + +```bash +# Install +npm install + +# Run the example +npm run start:dapr:activity-sequence +``` + +### Fan out/Fan in example + +```bash +# Install +npm install + +# Run the example +npm run start:dapr:fanout-fanin +``` + +### Human Interaction in example + +```bash +# Install +npm install + +# Run the example +npm run start:dapr:human-interaction +``` diff --git a/examples/workflow/authoring/components/redis.yaml b/examples/workflow/authoring/components/redis.yaml new file mode 100644 index 00000000..175c0e7a --- /dev/null +++ b/examples/workflow/authoring/components/redis.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2022 The Dapr Authors +# 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 +# http://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. +# + +# https://docs.dapr.io/reference/components-reference/supported-bindings/rabbitmq/ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: state-redis + namespace: default +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: enableTLS + value: "false" + - name: failover + value: "false" + - name: actorStateStore + value: "true" \ No newline at end of file diff --git a/examples/workflow/package-lock.json b/examples/workflow/authoring/package-lock.json similarity index 89% rename from examples/workflow/package-lock.json rename to examples/workflow/authoring/package-lock.json index 60d31df1..1c97be24 100644 --- a/examples/workflow/package-lock.json +++ b/examples/workflow/authoring/package-lock.json @@ -1,15 +1,15 @@ { - "name": "dapr-example-workflow", + "name": "dapr-example-workflow-authoring", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "dapr-example-workflow", + "name": "dapr-example-workflow-authoring", "version": "1.0.0", "license": "ISC", "dependencies": { - "@dapr/dapr": "file:../../build", + "@dapr/dapr": "file:../../../build", "@types/node": "^18.16.3" }, "devDependencies": { @@ -17,9 +17,52 @@ "typescript": "^5.0.4" } }, + "../../../build": { + "name": "@dapr/dapr", + "version": "3.2.0", + "license": "ISC", + "dependencies": { + "@grpc/grpc-js": "^1.9.3", + "@js-temporal/polyfill": "^0.3.0", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", + "@types/google-protobuf": "^3.15.5", + "@types/node-fetch": "^2.6.2", + "body-parser": "^1.19.0", + "express": "^4.18.2", + "google-protobuf": "^3.18.0", + "http-terminator": "^3.2.0", + "node-fetch": "^2.6.7" + }, + "devDependencies": { + "@swc/core": "^1.3.55", + "@types/body-parser": "^1.19.1", + "@types/express": "^4.17.15", + "@types/jest": "^27.0.1", + "@types/node": "^16.9.1", + "@types/readline-sync": "^1.4.8", + "@types/uuid": "^8.3.1", + "@typescript-eslint/eslint-plugin": "^5.1.0", + "@typescript-eslint/parser": "^5.1.0", + "eslint": "^8.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-prettier": "^4.2.1", + "grpc_tools_node_protoc_ts": "^5.3.2", + "husky": "^8.0.1", + "jest": "^27.2.0", + "nodemon": "^2.0.20", + "prettier": "^2.4.0", + "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", + "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.5.5" + } + }, "../../build": { "name": "@dapr/dapr", "version": "3.0.0", + "extraneous": true, "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.3.7", @@ -67,7 +110,7 @@ } }, "node_modules/@dapr/dapr": { - "resolved": "../../build", + "resolved": "../../../build", "link": true }, "node_modules/@jridgewell/resolve-uri": { @@ -255,16 +298,19 @@ } }, "@dapr/dapr": { - "version": "file:../../build", + "version": "file:../../../build", "requires": { - "@grpc/grpc-js": "^1.3.7", + "@grpc/grpc-js": "^1.9.3", "@js-temporal/polyfill": "^0.3.0", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", + "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/google-protobuf": "^3.15.5", "@types/jest": "^27.0.1", "@types/node": "^16.9.1", "@types/node-fetch": "^2.6.2", + "@types/readline-sync": "^1.4.8", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", @@ -276,14 +322,16 @@ "express": "^4.18.2", "google-protobuf": "^3.18.0", "grpc_tools_node_protoc_ts": "^5.3.2", - "http-terminator": "^3.0.4", + "http-terminator": "^3.2.0", "husky": "^8.0.1", "jest": "^27.2.0", "node-fetch": "^2.6.7", "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", "typescript": "^4.5.5" } }, diff --git a/examples/workflow/authoring/package.json b/examples/workflow/authoring/package.json new file mode 100644 index 00000000..499faf25 --- /dev/null +++ b/examples/workflow/authoring/package.json @@ -0,0 +1,25 @@ +{ + "name": "dapr-example-workflow-authoring", + "version": "1.0.0", + "description": "An example utilizing the Dapr JS-SDK to manage workflow", + "private": "true", + "scripts": { + "build": "rimraf ./dist && tsc", + "start:activity-sequence": "npm run build && node dist/activity-sequence.js", + "start:fanout-fanin": "npm run build && node dist/fanout-fanin.js", + "start:human-interaction": "npm run build && node dist/human-interaction.js", + "start:dapr:activity-sequence": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:activity-sequence", + "start:dapr:fanout-fanin": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:fanout-fanin", + "start:dapr:human-interaction": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:human-interaction" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "dependencies": { + "@dapr/dapr": "file:../../../build", + "@types/node": "^18.16.3" + } +} diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts new file mode 100644 index 00000000..e4ee9ac1 --- /dev/null +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -0,0 +1,68 @@ +/* +Copyright 2024 The Dapr Authors +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 + http://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. +*/ + +// import { WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; +import WorkflowClient from "@dapr/dapr/workflow/client/WorkflowClient"; +import WorkflowActivityContext from "@dapr/dapr/workflow/runtime/WorkflowActivityContext"; +import WorkflowContext from "@dapr/dapr/workflow/runtime/WorkflowContext"; +import WorkflowRuntime from "@dapr/dapr/workflow/runtime/WorkflowRuntime"; +import { TWorkflow } from "@dapr/dapr/types/workflow/Workflow.type"; + +(async () => { + const grpcEndpoint = "localhost:4001"; + const workflowClient = new WorkflowClient(grpcEndpoint); + const workflowRuntime = new WorkflowRuntime(grpcEndpoint); + + const hello = async (_: WorkflowActivityContext, name: string) => { + return `Hello ${name}!`; + }; + + const sequence: TWorkflow = async function* (ctx: WorkflowContext): any { + const cities: string[] = []; + + const result1 = yield ctx.callActivity(hello, "Tokyo"); + cities.push(result1); + const result2 = yield ctx.callActivity(hello, "Seattle"); + cities.push(result2); + const result3 = yield ctx.callActivity(hello, "London"); + cities.push(result3); + + return cities; + }; + + workflowRuntime.registerWorkflow(sequence).registerActivity(hello); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Workflow runtime started successfully"); + } catch (error) { + console.error("Error starting workflow runtime:", error); + } + + // Schedule a new orchestration + try { + const id = await workflowClient.scheduleNewWorkflow(sequence); + console.log(`Orchestration scheduled with ID: ${id}`); + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts new file mode 100644 index 00000000..7114dc1b --- /dev/null +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -0,0 +1,89 @@ +/* +Copyright 2024 The Dapr Authors +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 + http://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. +*/ + +import { Task, WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; + +// Wrap the entire code in an immediately-invoked async function +(async () => { + // Update the gRPC client and worker to use a local address and port + const grpcServerAddress = "localhost:4001"; + const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + + function getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + async function getWorkItemsActivity(_: WorkflowActivityContext): Promise { + const count: number = getRandomInt(2, 10); + console.log(`generating ${count} work items...`); + + const workItems: string[] = Array.from({ length: count }, (_, i) => `work item ${i}`); + return workItems; + } + + function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async function processWorkItemActivity(context: WorkflowActivityContext, item: string): Promise { + console.log(`processing work item: ${item}`); + + // Simulate some work that takes a variable amount of time + const sleepTime = Math.random() * 5000; + await sleep(sleepTime); + + // Return a result for the given work item, which is also a random number in this case + return Math.floor(Math.random() * 11); + } + + const workflow: TWorkflow = async function* (ctx: WorkflowContext): any { + const tasks: Task[] = []; + const workItems = yield ctx.callActivity(getWorkItemsActivity); + for (const workItem of workItems) { + tasks.push(ctx.callActivity(processWorkItemActivity, workItem)); + } + const results: number[] = yield ctx.whenAll(tasks); + const sum: number = results.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + return sum; + }; + + workflowRuntime.registerWorkflow(workflow); + workflowRuntime.registerActivity(getWorkItemsActivity); + workflowRuntime.registerActivity(processWorkItemActivity); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Worker started successfully"); + } catch (error) { + console.error("Error starting worker:", error); + } + + // Schedule a new orchestration + try { + const id = await workflowClient.scheduleNewWorkflow(workflow); + console.log(`Orchestration scheduled with ID: ${id}`); + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + // stop worker and client + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts new file mode 100644 index 00000000..7a24ffe1 --- /dev/null +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -0,0 +1,118 @@ +/* +Copyright 2024 The Dapr Authors +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 + http://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. +*/ + +import { Task, WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; +import * as readlineSync from "readline-sync"; + +// Wrap the entire code in an immediately-invoked async function +(async () => { + class Order { + cost: number; + product: string; + quantity: number; + constructor(cost: number, product: string, quantity: number) { + this.cost = cost; + this.product = product; + this.quantity = quantity; + } + } + + function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // Update the gRPC client and worker to use a local address and port + const grpcServerAddress = "localhost:4001"; + const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + + //Activity function that sends an approval request to the manager + const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { + // Simulate some work that takes an amount of time + await sleep(3000); + console.log(`Sending approval request for order: ${order.product}`); + }; + + // Activity function that places an order + const placeOrder = async (_: WorkflowActivityContext, order: Order) => { + console.log(`Placing order: ${order.product}`); + }; + + // Orchestrator function that represents a purchase order workflow + const purchaseOrderWorkflow: TWorkflow = async function* (ctx: WorkflowContext, order: Order): any { + // Orders under $1000 are auto-approved + if (order.cost < 1000) { + return "Auto-approved"; + } + + // Orders of $1000 or more require manager approval + yield ctx.callActivity(sendApprovalRequest, order); + + // Approvals must be received within 24 hours or they will be cancled. + const tasks: Task[] = []; + const approvalEvent = ctx.waitForExternalEvent("approval_received"); + const timeoutEvent = ctx.createTimer(24 * 60 * 60); + tasks.push(approvalEvent); + tasks.push(timeoutEvent); + const winner = ctx.whenAny(tasks); + + if (winner == timeoutEvent) { + return "Cancelled"; + } + + yield ctx.callActivity(placeOrder, order); + const approvalDetails = approvalEvent.getResult(); + return `Approved by ${approvalDetails.approver}`; + }; + + workflowRuntime + .registerWorkflow(purchaseOrderWorkflow) + .registerActivity(sendApprovalRequest) + .registerActivity(placeOrder); + + // Wrap the worker startup in a try-catch block to handle any errors during startup + try { + await workflowRuntime.start(); + console.log("Worker started successfully"); + } catch (error) { + console.error("Error starting worker:", error); + } + + // Schedule a new orchestration + try { + const cost = readlineSync.questionInt("Cost of your order:"); + const approver = readlineSync.question("Approver of your order:"); + const timeout = readlineSync.questionInt("Timeout for your order in seconds:"); + const order = new Order(cost, "MyProduct", 1); + const id = await workflowClient.scheduleNewWorkflow(purchaseOrderWorkflow, order); + console.log(`Orchestration scheduled with ID: ${id}`); + + if (readlineSync.keyInYN("Press [Y] to approve the order... Y/yes, N/no")) { + const approvalEvent = { approver: approver }; + await workflowClient.raiseEvent(id, "approval_received", approvalEvent); + } else { + return "Order rejected"; + } + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, timeout + 2); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); + } catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); + } + + // stop worker and client + await workflowRuntime.stop(); + await workflowClient.stop(); +})(); diff --git a/examples/workflow/tsconfig.json b/examples/workflow/authoring/tsconfig.json similarity index 100% rename from examples/workflow/tsconfig.json rename to examples/workflow/authoring/tsconfig.json diff --git a/examples/workflow/management/package-lock.json b/examples/workflow/management/package-lock.json new file mode 100644 index 00000000..c2a8b93c --- /dev/null +++ b/examples/workflow/management/package-lock.json @@ -0,0 +1,465 @@ +{ + "name": "dapr-example-workflow-management", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "dapr-example-workflow-management", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@dapr/dapr": "file:../../../build", + "@types/node": "^18.16.3" + }, + "devDependencies": { + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + } + }, + "../../../build": { + "name": "@dapr/dapr", + "version": "3.2.0", + "license": "ISC", + "dependencies": { + "@grpc/grpc-js": "^1.9.3", + "@js-temporal/polyfill": "^0.3.0", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", + "@types/google-protobuf": "^3.15.5", + "@types/node-fetch": "^2.6.2", + "body-parser": "^1.19.0", + "express": "^4.18.2", + "google-protobuf": "^3.18.0", + "http-terminator": "^3.2.0", + "node-fetch": "^2.6.7" + }, + "devDependencies": { + "@swc/core": "^1.3.55", + "@types/body-parser": "^1.19.1", + "@types/express": "^4.17.15", + "@types/jest": "^27.0.1", + "@types/node": "^16.9.1", + "@types/readline-sync": "^1.4.8", + "@types/uuid": "^8.3.1", + "@typescript-eslint/eslint-plugin": "^5.1.0", + "@typescript-eslint/parser": "^5.1.0", + "eslint": "^8.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-prettier": "^4.2.1", + "grpc_tools_node_protoc_ts": "^5.3.2", + "husky": "^8.0.1", + "jest": "^27.2.0", + "nodemon": "^2.0.20", + "prettier": "^2.4.0", + "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", + "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.5.5" + } + }, + "../../build": { + "name": "@dapr/dapr", + "version": "3.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@grpc/grpc-js": "^1.3.7", + "@js-temporal/polyfill": "^0.3.0", + "@types/google-protobuf": "^3.15.5", + "@types/node-fetch": "^2.6.2", + "body-parser": "^1.19.0", + "express": "^4.18.2", + "google-protobuf": "^3.18.0", + "http-terminator": "^3.0.4", + "node-fetch": "^2.6.7" + }, + "devDependencies": { + "@types/body-parser": "^1.19.1", + "@types/express": "^4.17.15", + "@types/jest": "^27.0.1", + "@types/node": "^16.9.1", + "@types/uuid": "^8.3.1", + "@typescript-eslint/eslint-plugin": "^5.1.0", + "@typescript-eslint/parser": "^5.1.0", + "eslint": "^8.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-prettier": "^4.2.1", + "grpc_tools_node_protoc_ts": "^5.3.2", + "husky": "^8.0.1", + "jest": "^27.2.0", + "nodemon": "^2.0.20", + "prettier": "^2.4.0", + "pretty-quick": "^3.1.3", + "ts-jest": "^27.0.5", + "typescript": "^4.5.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dapr/dapr": { + "resolved": "../../../build", + "link": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@dapr/dapr": { + "version": "file:../../../build", + "requires": { + "@grpc/grpc-js": "^1.9.3", + "@js-temporal/polyfill": "^0.3.0", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", + "@swc/core": "^1.3.55", + "@types/body-parser": "^1.19.1", + "@types/express": "^4.17.15", + "@types/google-protobuf": "^3.15.5", + "@types/jest": "^27.0.1", + "@types/node": "^16.9.1", + "@types/node-fetch": "^2.6.2", + "@types/readline-sync": "^1.4.8", + "@types/uuid": "^8.3.1", + "@typescript-eslint/eslint-plugin": "^5.1.0", + "@typescript-eslint/parser": "^5.1.0", + "body-parser": "^1.19.0", + "eslint": "^8.1.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-prettier": "^4.2.1", + "express": "^4.18.2", + "google-protobuf": "^3.18.0", + "grpc_tools_node_protoc_ts": "^5.3.2", + "http-terminator": "^3.2.0", + "husky": "^8.0.1", + "jest": "^27.2.0", + "node-fetch": "^2.6.7", + "nodemon": "^2.0.20", + "prettier": "^2.4.0", + "pretty-quick": "^3.1.3", + "readline-sync": "^1.4.10", + "ts-jest": "^27.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.5.5" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/node": { + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/examples/workflow/package.json b/examples/workflow/management/package.json similarity index 89% rename from examples/workflow/package.json rename to examples/workflow/management/package.json index 46fc5e1c..e11854cc 100644 --- a/examples/workflow/package.json +++ b/examples/workflow/management/package.json @@ -1,5 +1,5 @@ { - "name": "dapr-example-workflow", + "name": "dapr-example-workflow-management", "version": "1.0.0", "description": "An example utilizing the Dapr JS-SDK to manage workflow", "main": "dist/index.js", @@ -17,7 +17,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@dapr/dapr": "file:../../build", + "@dapr/dapr": "file:../../../build", "@types/node": "^18.16.3" } } diff --git a/examples/workflow/src/index.ts b/examples/workflow/management/src/index.ts similarity index 100% rename from examples/workflow/src/index.ts rename to examples/workflow/management/src/index.ts diff --git a/examples/workflow/management/tsconfig.json b/examples/workflow/management/tsconfig.json new file mode 100644 index 00000000..4c8d3f94 --- /dev/null +++ b/examples/workflow/management/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist" /* Redirect output structure to the directory. */, + "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/package-lock.json b/package-lock.json index bad24069..fc873984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,13 @@ "dependencies": { "@grpc/grpc-js": "^1.9.3", "@js-temporal/polyfill": "^0.3.0", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", "@types/google-protobuf": "^3.15.5", "@types/node-fetch": "^2.6.2", "body-parser": "^1.19.0", "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", - "kaibocai-durabletask-js": "^0.0.6-beta.1", "node-fetch": "^2.6.7" }, "devDependencies": { @@ -1261,6 +1261,15 @@ "node": ">=12" } }, + "node_modules/@microsoft/durabletask-js": { + "version": "0.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/@microsoft/durabletask-js/-/durabletask-js-0.1.0-alpha.1.tgz", + "integrity": "sha512-wdBCz86FCj2lknLqyjU+J0Auetxxr7vj0SUjPjnjxRaE0VrM4G3WmX65XDlsligoIg2JEe0M89REzaA6IVh4pw==", + "dependencies": { + "@grpc/grpc-js": "^1.8.14", + "google-protobuf": "^3.21.2" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5191,15 +5200,6 @@ "node": ">=6" } }, - "node_modules/kaibocai-durabletask-js": { - "version": "0.0.6-beta.1", - "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.6-beta.1.tgz", - "integrity": "sha512-OoP/0YUBVInuSJ97cW8MzXfgrTX2WHyw1neESxZp9HKXSCmp1feMOtwNNjQa5HoqgVVS4TV1tH9oFZsXs7SHEA==", - "dependencies": { - "@grpc/grpc-js": "^1.8.14", - "google-protobuf": "^3.21.2" - } - }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -8464,6 +8464,15 @@ "tslib": "^2.3.1" } }, + "@microsoft/durabletask-js": { + "version": "0.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/@microsoft/durabletask-js/-/durabletask-js-0.1.0-alpha.1.tgz", + "integrity": "sha512-wdBCz86FCj2lknLqyjU+J0Auetxxr7vj0SUjPjnjxRaE0VrM4G3WmX65XDlsligoIg2JEe0M89REzaA6IVh4pw==", + "requires": { + "@grpc/grpc-js": "^1.8.14", + "google-protobuf": "^3.21.2" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11417,15 +11426,6 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, - "kaibocai-durabletask-js": { - "version": "0.0.6-beta.1", - "resolved": "https://registry.npmjs.org/kaibocai-durabletask-js/-/kaibocai-durabletask-js-0.0.6-beta.1.tgz", - "integrity": "sha512-OoP/0YUBVInuSJ97cW8MzXfgrTX2WHyw1neESxZp9HKXSCmp1feMOtwNNjQa5HoqgVVS4TV1tH9oFZsXs7SHEA==", - "requires": { - "@grpc/grpc-js": "^1.8.14", - "google-protobuf": "^3.21.2" - } - }, "keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", diff --git a/package.json b/package.json index 1cbfe213..db445a7f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "express": "^4.18.2", "google-protobuf": "^3.18.0", "http-terminator": "^3.2.0", - "kaibocai-durabletask-js": "^0.0.6-beta.1", + "@microsoft/durabletask-js": "^0.1.0-alpha.1", "node-fetch": "^2.6.7" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index aaee2a0d..770edeed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,6 +40,20 @@ import StateConcurrencyEnum from "./enum/StateConcurrency.enum"; import StateConsistencyEnum from "./enum/StateConsistency.enum"; import { StateGetBulkOptions } from "./types/state/StateGetBulkOptions.type"; +import WorkflowClient from "./workflow/client/WorkflowClient"; +import WorkflowActivityContext from "./workflow/runtime/WorkflowActivityContext"; +import WorkflowContext from "./workflow/runtime/WorkflowContext"; +import WorkflowRuntime from "./workflow/runtime/WorkflowRuntime"; +import { TWorkflow } from "./types/workflow/Workflow.type"; +import { Task } from "@microsoft/durabletask-js/task/task"; +import { WorkflowFailureDetails } from "./workflow/client/WorkflowFailureDetails"; +import { WorkflowState } from "./workflow/client/WorkflowState"; +import { + WorkflowRuntimeStatus, + fromOrchestrationStatus, + toOrchestrationStatus, +} from "./workflow/runtime/WorkflowRuntimeStatus"; + export { DaprClient, DaprServer, @@ -65,4 +79,15 @@ export { StateConsistencyEnum, PubSubBulkPublishResponse, StateGetBulkOptions, + WorkflowClient, + WorkflowActivityContext, + WorkflowContext, + WorkflowRuntime, + TWorkflow, + Task, + WorkflowFailureDetails, + WorkflowState, + WorkflowRuntimeStatus, + fromOrchestrationStatus, + toOrchestrationStatus, }; diff --git a/src/types/workflow/Workflow.type.ts b/src/types/workflow/Workflow.type.ts index ad6489ad..ef75ba06 100644 --- a/src/types/workflow/Workflow.type.ts +++ b/src/types/workflow/Workflow.type.ts @@ -12,7 +12,7 @@ limitations under the License. */ import WorkflowContext from "../../workflow/runtime/WorkflowContext"; -import { Task } from "kaibocai-durabletask-js/task/task"; +import { Task } from "@microsoft/durabletask-js/task/task"; import { TOutput } from "./InputOutput.type"; export type TWorkflow = (context: WorkflowContext, input: any) => Generator, any, any> | TOutput; diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/WorkflowClient.ts index 9040692d..da485be7 100644 --- a/src/workflow/client/WorkflowClient.ts +++ b/src/workflow/client/WorkflowClient.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TaskHubGrpcClient } from "kaibocai-durabletask-js"; +import { TaskHubGrpcClient } from "@microsoft/durabletask-js"; import * as grpc from "@grpc/grpc-js"; import { WorkflowState } from "./WorkflowState"; import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; diff --git a/src/workflow/client/WorkflowFailureDetails.ts b/src/workflow/client/WorkflowFailureDetails.ts index 219f0ebd..d2903dd6 100644 --- a/src/workflow/client/WorkflowFailureDetails.ts +++ b/src/workflow/client/WorkflowFailureDetails.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { FailureDetails } from "kaibocai-durabletask-js/task/failure-details"; +import { FailureDetails } from "@microsoft/durabletask-js/task/failure-details"; export class WorkflowFailureDetails { private readonly failureDetails: FailureDetails; diff --git a/src/workflow/client/WorkflowState.ts b/src/workflow/client/WorkflowState.ts index 2e9ee91b..a5cbfd03 100644 --- a/src/workflow/client/WorkflowState.ts +++ b/src/workflow/client/WorkflowState.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { OrchestrationState } from "kaibocai-durabletask-js/orchestration/orchestration-state"; +import { OrchestrationState } from "@microsoft/durabletask-js/orchestration/orchestration-state"; import { WorkflowFailureDetails } from "./WorkflowFailureDetails"; import { WorkflowRuntimeStatus, fromOrchestrationStatus } from "../runtime/WorkflowRuntimeStatus"; diff --git a/src/workflow/examples/fanout-fanin.ts b/src/workflow/examples/fanout-fanin.ts index f71067df..45652e69 100644 --- a/src/workflow/examples/fanout-fanin.ts +++ b/src/workflow/examples/fanout-fanin.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Task } from "kaibocai-durabletask-js/task/task"; +import { Task } from "@microsoft/durabletask-js/task/task"; import WorkflowClient from "../client/WorkflowClient"; import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; import WorkflowContext from "../runtime/WorkflowContext"; diff --git a/src/workflow/examples/human-interaction.ts b/src/workflow/examples/human-interaction.ts index 9cfe8192..14be98d2 100644 --- a/src/workflow/examples/human-interaction.ts +++ b/src/workflow/examples/human-interaction.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Task } from "kaibocai-durabletask-js/task/task"; +import { Task } from "@microsoft/durabletask-js/task/task"; import WorkflowClient from "../client/WorkflowClient"; import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; import WorkflowContext from "../runtime/WorkflowContext"; diff --git a/src/workflow/runtime/WorkflowActivityContext.ts b/src/workflow/runtime/WorkflowActivityContext.ts index c121cb43..2e357daf 100644 --- a/src/workflow/runtime/WorkflowActivityContext.ts +++ b/src/workflow/runtime/WorkflowActivityContext.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ActivityContext } from "kaibocai-durabletask-js"; +import { ActivityContext } from "@microsoft/durabletask-js"; export default class WorkflowActivityContext { private readonly _innerContext: ActivityContext; diff --git a/src/workflow/runtime/WorkflowContext.ts b/src/workflow/runtime/WorkflowContext.ts index 3dae60eb..137c16cf 100644 --- a/src/workflow/runtime/WorkflowContext.ts +++ b/src/workflow/runtime/WorkflowContext.ts @@ -11,16 +11,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { OrchestrationContext } from "kaibocai-durabletask-js"; -import { Task } from "kaibocai-durabletask-js/task/task"; -import { TInput } from "kaibocai-durabletask-js/types/input.type"; -import { TOutput } from "kaibocai-durabletask-js/types/output.type"; +import { OrchestrationContext } from "@microsoft/durabletask-js"; +import { Task } from "@microsoft/durabletask-js/task/task"; +import { TInput } from "@microsoft/durabletask-js/types/input.type"; +import { TOutput } from "@microsoft/durabletask-js/types/output.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; -import { WhenAllTask } from "kaibocai-durabletask-js/task/when-all-task"; -import { whenAll, whenAny } from "kaibocai-durabletask-js/task"; -import { WhenAnyTask } from "kaibocai-durabletask-js/task/when-any-task"; +import { WhenAllTask } from "@microsoft/durabletask-js/task/when-all-task"; +import { whenAll, whenAny } from "@microsoft/durabletask-js/task"; +import { WhenAnyTask } from "@microsoft/durabletask-js/task/when-any-task"; export default class WorkflowContext { private readonly _innerContext: OrchestrationContext; diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index d41858d7..21da9f12 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -12,7 +12,7 @@ limitations under the License. */ import * as grpc from "@grpc/grpc-js"; -import { ActivityContext, OrchestrationContext, TaskHubGrpcWorker } from "kaibocai-durabletask-js"; +import { ActivityContext, OrchestrationContext, TaskHubGrpcWorker } from "@microsoft/durabletask-js"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; diff --git a/src/workflow/runtime/WorkflowRuntimeStatus.ts b/src/workflow/runtime/WorkflowRuntimeStatus.ts index b110df2c..67291657 100644 --- a/src/workflow/runtime/WorkflowRuntimeStatus.ts +++ b/src/workflow/runtime/WorkflowRuntimeStatus.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum"; +import { OrchestrationStatus } from "@microsoft/durabletask-js/orchestration/enum/orchestration-status.enum"; export enum WorkflowRuntimeStatus { RUNNING = OrchestrationStatus.RUNNING, diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 453e06a8..89b1ee0b 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -18,7 +18,7 @@ import { TWorkflow } from "../../../src/types/workflow/Workflow.type"; import { getFunctionName } from "../../../src/workflow/internal"; import { WorkflowRuntimeStatus } from "../../../src/workflow/runtime/WorkflowRuntimeStatus"; import WorkflowActivityContext from "../../../src/workflow/runtime/WorkflowActivityContext"; -import { Task } from "kaibocai-durabletask-js/task/task"; +import { Task } from "@microsoft/durabletask-js/task/task"; describe("Workflow", () => { const grpcEndpoint = "localhost:4001"; diff --git a/test/unit/workflow/workflowRuntimeStatus.test.ts b/test/unit/workflow/workflowRuntimeStatus.test.ts index 317b5ae7..eb9359b4 100644 --- a/test/unit/workflow/workflowRuntimeStatus.test.ts +++ b/test/unit/workflow/workflowRuntimeStatus.test.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { OrchestrationStatus } from "kaibocai-durabletask-js/orchestration/enum/orchestration-status.enum"; +import { OrchestrationStatus } from "@microsoft/durabletask-js/orchestration/enum/orchestration-status.enum"; import { WorkflowRuntimeStatus, fromOrchestrationStatus, From d1619a274dba6ad69773ed7f7c9f25336cde30c0 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 8 Jan 2024 14:55:05 -0600 Subject: [PATCH 07/18] minor updates on examples Signed-off-by: kaibocai --- examples/workflow/authoring/package.json | 8 +- .../authoring/src/activity-sequence.ts | 9 +- .../workflow/authoring/src/fanout-fanin.ts | 2 +- .../authoring/src/human-interaction.ts | 2 +- examples/workflow/authoring/tsconfig.json | 2 +- src/workflow/examples/activity-sequence.ts | 67 ---------- src/workflow/examples/fanout-fanin.ts | 94 ------------- src/workflow/examples/human-interaction.ts | 123 ------------------ 8 files changed, 9 insertions(+), 298 deletions(-) delete mode 100644 src/workflow/examples/activity-sequence.ts delete mode 100644 src/workflow/examples/fanout-fanin.ts delete mode 100644 src/workflow/examples/human-interaction.ts diff --git a/examples/workflow/authoring/package.json b/examples/workflow/authoring/package.json index 499faf25..f41e7080 100644 --- a/examples/workflow/authoring/package.json +++ b/examples/workflow/authoring/package.json @@ -4,13 +4,13 @@ "description": "An example utilizing the Dapr JS-SDK to manage workflow", "private": "true", "scripts": { - "build": "rimraf ./dist && tsc", + "build": "npx tsc --outDir ./dist/", "start:activity-sequence": "npm run build && node dist/activity-sequence.js", "start:fanout-fanin": "npm run build && node dist/fanout-fanin.js", "start:human-interaction": "npm run build && node dist/human-interaction.js", - "start:dapr:activity-sequence": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:activity-sequence", - "start:dapr:fanout-fanin": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:fanout-fanin", - "start:dapr:human-interaction": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 4001 --components-path ./components npm run start:human-interaction" + "start:dapr:activity-sequence": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ./components npm run start:activity-sequence", + "start:dapr:fanout-fanin": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ./components npm run start:fanout-fanin", + "start:dapr:human-interaction": "dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ./components npm run start:human-interaction" }, "author": "", "license": "ISC", diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index e4ee9ac1..d1ce5423 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -11,15 +11,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -// import { WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; -import WorkflowClient from "@dapr/dapr/workflow/client/WorkflowClient"; -import WorkflowActivityContext from "@dapr/dapr/workflow/runtime/WorkflowActivityContext"; -import WorkflowContext from "@dapr/dapr/workflow/runtime/WorkflowContext"; -import WorkflowRuntime from "@dapr/dapr/workflow/runtime/WorkflowRuntime"; -import { TWorkflow } from "@dapr/dapr/types/workflow/Workflow.type"; +import { WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; (async () => { - const grpcEndpoint = "localhost:4001"; + const grpcEndpoint = "localhost:50001"; const workflowClient = new WorkflowClient(grpcEndpoint); const workflowRuntime = new WorkflowRuntime(grpcEndpoint); diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index 7114dc1b..d53840e3 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -16,7 +16,7 @@ import { Task, WorkflowClient, WorkflowActivityContext, WorkflowContext, Workflo // Wrap the entire code in an immediately-invoked async function (async () => { // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:4001"; + const grpcServerAddress = "localhost:50001"; const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index 7a24ffe1..700024cd 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -32,7 +32,7 @@ import * as readlineSync from "readline-sync"; } // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:4001"; + const grpcServerAddress = "localhost:50001"; const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); diff --git a/examples/workflow/authoring/tsconfig.json b/examples/workflow/authoring/tsconfig.json index 4c8d3f94..fbbfbe8d 100644 --- a/examples/workflow/authoring/tsconfig.json +++ b/examples/workflow/authoring/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ diff --git a/src/workflow/examples/activity-sequence.ts b/src/workflow/examples/activity-sequence.ts deleted file mode 100644 index 54acb473..00000000 --- a/src/workflow/examples/activity-sequence.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2024 The Dapr Authors -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 - http://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. -*/ - -import WorkflowClient from "../client/WorkflowClient"; -import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; -import WorkflowContext from "../runtime/WorkflowContext"; -import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../../types/workflow/Workflow.type"; - -(async () => { - const grpcEndpoint = "localhost:4001"; - const workflowClient = new WorkflowClient(grpcEndpoint); - const workflowRuntime = new WorkflowRuntime(grpcEndpoint); - - const hello = async (_: WorkflowActivityContext, name: string) => { - return `Hello ${name}!`; - }; - - const sequence: TWorkflow = async function* (ctx: WorkflowContext): any { - const cities: string[] = []; - - const result1 = yield ctx.callActivity(hello, "Tokyo"); - cities.push(result1); - const result2 = yield ctx.callActivity(hello, "Seattle"); - cities.push(result2); - const result3 = yield ctx.callActivity(hello, "London"); - cities.push(result3); - - return cities; - }; - - workflowRuntime.registerWorkflow(sequence).registerActivity(hello); - - // Wrap the worker startup in a try-catch block to handle any errors during startup - try { - await workflowRuntime.start(); - console.log("Workflow runtime started successfully"); - } catch (error) { - console.error("Error starting workflow runtime:", error); - } - - // Schedule a new orchestration - try { - const id = await workflowClient.scheduleNewWorkflow(sequence); - console.log(`Orchestration scheduled with ID: ${id}`); - - // Wait for orchestration completion - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); - } catch (error) { - console.error("Error scheduling or waiting for orchestration:", error); - } - - await workflowRuntime.stop(); - await workflowClient.stop(); -})(); diff --git a/src/workflow/examples/fanout-fanin.ts b/src/workflow/examples/fanout-fanin.ts deleted file mode 100644 index 45652e69..00000000 --- a/src/workflow/examples/fanout-fanin.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2024 The Dapr Authors -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 - http://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. -*/ - -import { Task } from "@microsoft/durabletask-js/task/task"; -import WorkflowClient from "../client/WorkflowClient"; -import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; -import WorkflowContext from "../runtime/WorkflowContext"; -import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../../types/workflow/Workflow.type"; - -// Wrap the entire code in an immediately-invoked async function -(async () => { - // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:4001"; - const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); - const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); - - function getRandomInt(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - - async function getWorkItemsActivity(_: WorkflowActivityContext): Promise { - const count: number = getRandomInt(2, 10); - console.log(`generating ${count} work items...`); - - const workItems: string[] = Array.from({ length: count }, (_, i) => `work item ${i}`); - return workItems; - } - - function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - async function processWorkItemActivity(context: WorkflowActivityContext, item: string): Promise { - console.log(`processing work item: ${item}`); - - // Simulate some work that takes a variable amount of time - const sleepTime = Math.random() * 5000; - await sleep(sleepTime); - - // Return a result for the given work item, which is also a random number in this case - return Math.floor(Math.random() * 11); - } - - const workflow: TWorkflow = async function* (ctx: WorkflowContext): any { - const tasks: Task[] = []; - const workItems = yield ctx.callActivity(getWorkItemsActivity); - for (const workItem of workItems) { - tasks.push(ctx.callActivity(processWorkItemActivity, workItem)); - } - const results: number[] = yield ctx.whenAll(tasks); - const sum: number = results.reduce((accumulator, currentValue) => accumulator + currentValue, 0); - return sum; - }; - - workflowRuntime.registerWorkflow(workflow); - workflowRuntime.registerActivity(getWorkItemsActivity); - workflowRuntime.registerActivity(processWorkItemActivity); - - // Wrap the worker startup in a try-catch block to handle any errors during startup - try { - await workflowRuntime.start(); - console.log("Worker started successfully"); - } catch (error) { - console.error("Error starting worker:", error); - } - - // Schedule a new orchestration - try { - const id = await workflowClient.scheduleNewWorkflow(workflow); - console.log(`Orchestration scheduled with ID: ${id}`); - - // Wait for orchestration completion - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); - - console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); - } catch (error) { - console.error("Error scheduling or waiting for orchestration:", error); - } - - // stop worker and client - await workflowRuntime.stop(); - await workflowClient.stop(); -})(); diff --git a/src/workflow/examples/human-interaction.ts b/src/workflow/examples/human-interaction.ts deleted file mode 100644 index 14be98d2..00000000 --- a/src/workflow/examples/human-interaction.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2024 The Dapr Authors -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 - http://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. -*/ - -import { Task } from "@microsoft/durabletask-js/task/task"; -import WorkflowClient from "../client/WorkflowClient"; -import WorkflowActivityContext from "../runtime/WorkflowActivityContext"; -import WorkflowContext from "../runtime/WorkflowContext"; -import WorkflowRuntime from "../runtime/WorkflowRuntime"; -import { TWorkflow } from "../../types/workflow/Workflow.type"; -import * as readlineSync from "readline-sync"; - -// Wrap the entire code in an immediately-invoked async function -(async () => { - class Order { - cost: number; - product: string; - quantity: number; - constructor(cost: number, product: string, quantity: number) { - this.cost = cost; - this.product = product; - this.quantity = quantity; - } - } - - function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:4001"; - const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); - const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); - - //Activity function that sends an approval request to the manager - const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { - // Simulate some work that takes an amount of time - await sleep(3000); - console.log(`Sending approval request for order: ${order.product}`); - }; - - // Activity function that places an order - const placeOrder = async (_: WorkflowActivityContext, order: Order) => { - console.log(`Placing order: ${order.product}`); - }; - - // Orchestrator function that represents a purchase order workflow - const purchaseOrderWorkflow: TWorkflow = async function* (ctx: WorkflowContext, order: Order): any { - // Orders under $1000 are auto-approved - if (order.cost < 1000) { - return "Auto-approved"; - } - - // Orders of $1000 or more require manager approval - yield ctx.callActivity(sendApprovalRequest, order); - - // Approvals must be received within 24 hours or they will be cancled. - const tasks: Task[] = []; - const approvalEvent = ctx.waitForExternalEvent("approval_received"); - const timeoutEvent = ctx.createTimer(24 * 60 * 60); - tasks.push(approvalEvent); - tasks.push(timeoutEvent); - const winner = ctx.whenAny(tasks); - - if (winner == timeoutEvent) { - return "Cancelled"; - } - - yield ctx.callActivity(placeOrder, order); - const approvalDetails = approvalEvent.getResult(); - return `Approved by ${approvalDetails.approver}`; - }; - - workflowRuntime - .registerWorkflow(purchaseOrderWorkflow) - .registerActivity(sendApprovalRequest) - .registerActivity(placeOrder); - - // Wrap the worker startup in a try-catch block to handle any errors during startup - try { - await workflowRuntime.start(); - console.log("Worker started successfully"); - } catch (error) { - console.error("Error starting worker:", error); - } - - // Schedule a new orchestration - try { - const cost = readlineSync.questionInt("Cost of your order:"); - const approver = readlineSync.question("Approver of your order:"); - const timeout = readlineSync.questionInt("Timeout for your order in seconds:"); - const order = new Order(cost, "MyProduct", 1); - const id = await workflowClient.scheduleNewWorkflow(purchaseOrderWorkflow, order); - console.log(`Orchestration scheduled with ID: ${id}`); - - if (readlineSync.keyInYN("Press [Y] to approve the order... Y/yes, N/no")) { - const approvalEvent = { approver: approver }; - await workflowClient.raiseEvent(id, "approval_received", approvalEvent); - } else { - return "Order rejected"; - } - - // Wait for orchestration completion - const state = await workflowClient.waitForWorkflowCompletion(id, undefined, timeout + 2); - - console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); - } catch (error) { - console.error("Error scheduling or waiting for orchestration:", error); - } - - // stop worker and client - await workflowRuntime.stop(); - await workflowClient.stop(); -})(); From 378dfe2b75f50082c36ce75c631c65a010baf2a7 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Wed, 10 Jan 2024 10:49:30 -0600 Subject: [PATCH 08/18] minor updates on comments Signed-off-by: kaibocai --- src/workflow/runtime/WorkflowRuntime.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 21da9f12..8952a30c 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -48,7 +48,8 @@ export default class WorkflowRuntime { /** * Registers a Workflow implementation for handling orchestrations with a given name. - * + * The name provided need not be same as workflow name. + * * @param {string} name - The name or identifier for the registered Workflow. * @param {TWorkflow} workflow - The instance of the Workflow class being registered. */ @@ -79,6 +80,7 @@ export default class WorkflowRuntime { /** * Registers an Activity object with a given name. + * The name provided need not be same as WorkflowActivity name. * * @param {string} name - The name or identifier for the registered Activity. * @param {TWorkflowActivity} fn - The instance of the WorkflowActivity class being registered. From c3d8d77877d91f1906480554ff571562e2f31113 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Wed, 10 Jan 2024 11:29:33 -0600 Subject: [PATCH 09/18] minor updates Signed-off-by: kaibocai --- examples/workflow/authoring/src/activity-sequence.ts | 4 ++-- examples/workflow/authoring/src/fanout-fanin.ts | 11 +++++++++-- examples/workflow/authoring/src/human-interaction.ts | 11 +++++++++-- src/index.ts | 4 ++-- src/workflow/client/WorkflowClient.ts | 8 ++++++-- src/workflow/runtime/WorkflowContext.ts | 3 +-- src/workflow/runtime/WorkflowRuntime.ts | 2 +- test/e2e/workflow/workflow.test.ts | 6 +++--- 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index d1ce5423..227b69e8 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -11,11 +11,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; +import { DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; (async () => { const grpcEndpoint = "localhost:50001"; - const workflowClient = new WorkflowClient(grpcEndpoint); + const workflowClient = new DaprWorkflowClient(grpcEndpoint); const workflowRuntime = new WorkflowRuntime(grpcEndpoint); const hello = async (_: WorkflowActivityContext, name: string) => { diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index d53840e3..3fac4091 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -11,13 +11,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Task, WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; +import { + Task, + DaprWorkflowClient, + WorkflowActivityContext, + WorkflowContext, + WorkflowRuntime, + TWorkflow, +} from "@dapr/dapr"; // Wrap the entire code in an immediately-invoked async function (async () => { // Update the gRPC client and worker to use a local address and port const grpcServerAddress = "localhost:50001"; - const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowClient: DaprWorkflowClient = new DaprWorkflowClient(grpcServerAddress); const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); function getRandomInt(min: number, max: number): number { diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index 700024cd..1211ac38 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -11,7 +11,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Task, WorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; +import { + Task, + DaprWorkflowClient, + WorkflowActivityContext, + WorkflowContext, + WorkflowRuntime, + TWorkflow, +} from "@dapr/dapr"; import * as readlineSync from "readline-sync"; // Wrap the entire code in an immediately-invoked async function @@ -33,7 +40,7 @@ import * as readlineSync from "readline-sync"; // Update the gRPC client and worker to use a local address and port const grpcServerAddress = "localhost:50001"; - const workflowClient: WorkflowClient = new WorkflowClient(grpcServerAddress); + const workflowClient: DaprWorkflowClient = new DaprWorkflowClient(grpcServerAddress); const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); //Activity function that sends an approval request to the manager diff --git a/src/index.ts b/src/index.ts index 770edeed..80b70c83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,7 +40,7 @@ import StateConcurrencyEnum from "./enum/StateConcurrency.enum"; import StateConsistencyEnum from "./enum/StateConsistency.enum"; import { StateGetBulkOptions } from "./types/state/StateGetBulkOptions.type"; -import WorkflowClient from "./workflow/client/WorkflowClient"; +import DaprWorkflowClient from "./workflow/client/WorkflowClient"; import WorkflowActivityContext from "./workflow/runtime/WorkflowActivityContext"; import WorkflowContext from "./workflow/runtime/WorkflowContext"; import WorkflowRuntime from "./workflow/runtime/WorkflowRuntime"; @@ -79,7 +79,7 @@ export { StateConsistencyEnum, PubSubBulkPublishResponse, StateGetBulkOptions, - WorkflowClient, + DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/WorkflowClient.ts index da485be7..460acebd 100644 --- a/src/workflow/client/WorkflowClient.ts +++ b/src/workflow/client/WorkflowClient.ts @@ -17,8 +17,9 @@ import { WorkflowState } from "./WorkflowState"; import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; +import { Settings } from "../../utils/Settings.util"; -export default class WorkflowClient { +export default class DaprWorkflowClient { private readonly _innerClient: TaskHubGrpcClient; /** @@ -30,11 +31,14 @@ export default class WorkflowClient { this._innerClient = this.buildInnerClient(hostAddress, options); } - private buildInnerClient(hostAddress = "127.0.0.1:50001", options: grpc.ChannelOptions = {}): TaskHubGrpcClient { + private buildInnerClient(hostAddress?: string, options: grpc.ChannelOptions = {}): TaskHubGrpcClient { const innerOptions = { ...options, interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], }; + if (hostAddress === undefined) { + hostAddress = Settings.getDefaultHost() + ":" + Settings.getDefaultGrpcPort(); + } return new TaskHubGrpcClient(hostAddress, innerOptions); } diff --git a/src/workflow/runtime/WorkflowContext.ts b/src/workflow/runtime/WorkflowContext.ts index 137c16cf..8b04febb 100644 --- a/src/workflow/runtime/WorkflowContext.ts +++ b/src/workflow/runtime/WorkflowContext.ts @@ -13,14 +13,13 @@ limitations under the License. import { OrchestrationContext } from "@microsoft/durabletask-js"; import { Task } from "@microsoft/durabletask-js/task/task"; -import { TInput } from "@microsoft/durabletask-js/types/input.type"; -import { TOutput } from "@microsoft/durabletask-js/types/output.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; import { WhenAllTask } from "@microsoft/durabletask-js/task/when-all-task"; import { whenAll, whenAny } from "@microsoft/durabletask-js/task"; import { WhenAnyTask } from "@microsoft/durabletask-js/task/when-any-task"; +import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; export default class WorkflowContext { private readonly _innerContext: OrchestrationContext; diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 8952a30c..967db918 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -49,7 +49,7 @@ export default class WorkflowRuntime { /** * Registers a Workflow implementation for handling orchestrations with a given name. * The name provided need not be same as workflow name. - * + * * @param {string} name - The name or identifier for the registered Workflow. * @param {TWorkflow} workflow - The instance of the Workflow class being registered. */ diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 89b1ee0b..096bc871 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import WorkflowClient from "../../../src/workflow/client/WorkflowClient"; +import DaprWorkflowClient from "../../../src/workflow/client/WorkflowClient"; import WorkflowContext from "../../../src/workflow/runtime/WorkflowContext"; import WorkflowRuntime from "../../../src/workflow/runtime/WorkflowRuntime"; import { TWorkflow } from "../../../src/types/workflow/Workflow.type"; @@ -22,12 +22,12 @@ import { Task } from "@microsoft/durabletask-js/task/task"; describe("Workflow", () => { const grpcEndpoint = "localhost:4001"; - let workflowClient: WorkflowClient; + let workflowClient: DaprWorkflowClient; let workflowRuntime: WorkflowRuntime; beforeEach(async () => { // Start a worker, which will connect to the sidecar in a background thread - workflowClient = new WorkflowClient(grpcEndpoint); + workflowClient = new DaprWorkflowClient(grpcEndpoint); workflowRuntime = new WorkflowRuntime(grpcEndpoint); }); From cc444dbf266410c9f496ebd39d154e0a04e87d64 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Thu, 11 Jan 2024 10:59:00 -0600 Subject: [PATCH 10/18] refactor:update examples - add doc - add unit tests Signed-off-by: kaibocai --- examples/workflow/authoring/package-lock.json | 949 +++++++++++++++++- examples/workflow/authoring/package.json | 3 +- .../authoring/src/activity-sequence.ts | 9 +- .../workflow/authoring/src/fanout-fanin.ts | 9 +- .../authoring/src/human-interaction.ts | 26 +- package-lock.json | 77 +- package.json | 5 +- src/index.ts | 2 +- src/types/workflow/Activity.type.ts | 3 + src/types/workflow/InputOutput.type.ts | 7 + src/types/workflow/Workflow.type.ts | 3 + ...orkflowClient.ts => DaprWorkflowClient.ts} | 32 +- src/workflow/client/WorkflowFailureDetails.ts | 7 + .../internal/ApiTokenClientInterceptor.ts | 28 - src/workflow/internal/index.ts | 33 + .../runtime/WorkflowActivityContext.ts | 4 + src/workflow/runtime/WorkflowContext.ts | 16 +- src/workflow/runtime/WorkflowRuntime.ts | 11 +- src/workflow/runtime/WorkflowRuntimeStatus.ts | 19 +- test/e2e/workflow/workflow.test.ts | 2 +- .../workflow/workflowRuntimeStatus.test.ts | 131 ++- 21 files changed, 1234 insertions(+), 142 deletions(-) rename src/workflow/client/{WorkflowClient.ts => DaprWorkflowClient.ts} (89%) delete mode 100644 src/workflow/internal/ApiTokenClientInterceptor.ts diff --git a/examples/workflow/authoring/package-lock.json b/examples/workflow/authoring/package-lock.json index 1c97be24..0d2be66a 100644 --- a/examples/workflow/authoring/package-lock.json +++ b/examples/workflow/authoring/package-lock.json @@ -10,9 +10,12 @@ "license": "ISC", "dependencies": { "@dapr/dapr": "file:../../../build", - "@types/node": "^18.16.3" + "@types/node": "^18.16.3", + "prompt-sync": "^4.2.0" }, "devDependencies": { + "@inquirer/prompts": "^3.3.0", + "readline-sync": "^1.4.10", "ts-node": "^10.9.1", "typescript": "^5.0.4" } @@ -34,7 +37,6 @@ "node-fetch": "^2.6.7" }, "devDependencies": { - "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/jest": "^27.0.1", @@ -53,7 +55,6 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", - "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", "ts-node": "^10.9.1", "typescript": "^4.5.5" @@ -113,6 +114,188 @@ "resolved": "../../../build", "link": true }, + "node_modules/@inquirer/checkbox": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.5.0.tgz", + "integrity": "sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "figures": "^3.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/confirm": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.15.tgz", + "integrity": "sha512-hj8Q/z7sQXsF0DSpLQZVDhWYGN6KLM/gNjjqGkpKwBzljbQofGjn0ueHADy4HUY+OqDHmXuwk/bY+tZyIuuB0w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-5.1.1.tgz", + "integrity": "sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg==", + "dev": true, + "dependencies": { + "@inquirer/type": "^1.1.5", + "@types/mute-stream": "^0.0.4", + "@types/node": "^20.9.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "cli-spinners": "^2.9.1", + "cli-width": "^4.1.0", + "figures": "^3.2.0", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@inquirer/editor": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-1.2.13.tgz", + "integrity": "sha512-gBxjqt0B9GLN0j6M/tkEcmcIvB2fo9Cw0f5NRqDTkYyB9AaCzj7qvgG0onQ3GVPbMyMbbP4tWYxrBOaOdKpzNA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/expand": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-1.1.14.tgz", + "integrity": "sha512-yS6fJ8jZYAsxdxuw2c8XTFMTvMR1NxZAw3LxDaFnqh7BZ++wTQ6rSp/2gGJhMacdZ85osb+tHxjVgx7F+ilv5g==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2", + "figures": "^3.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/input": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-1.2.14.tgz", + "integrity": "sha512-tISLGpUKXixIQue7jypNEShrdzJoLvEvZOJ4QRsw5XTfrIYfoWFqAjMQLerGs9CzR86yAI89JR6snHmKwnNddw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/password": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-1.1.14.tgz", + "integrity": "sha512-vL2BFxfMo8EvuGuZYlryiyAB3XsgtbxOcFs4H9WI9szAS/VZCAwdVqs8rqEeaAf/GV/eZOghIOYxvD91IsRWSg==", + "dev": true, + "dependencies": { + "@inquirer/input": "^1.2.14", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/prompts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-3.3.0.tgz", + "integrity": "sha512-BBCqdSnhNs+WziSIo4f/RNDu6HAj4R/Q5nMgJb5MNPFX8sJGCvj9BoALdmR0HTWXyDS7TO8euKj6W6vtqCQG7A==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^1.5.0", + "@inquirer/confirm": "^2.0.15", + "@inquirer/core": "^5.1.1", + "@inquirer/editor": "^1.2.13", + "@inquirer/expand": "^1.1.14", + "@inquirer/input": "^1.2.14", + "@inquirer/password": "^1.1.14", + "@inquirer/rawlist": "^1.2.14", + "@inquirer/select": "^1.3.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-1.2.14.tgz", + "integrity": "sha512-xIYmDpYgfz2XGCKubSDLKEvadkIZAKbehHdWF082AyC2I4eHK44RUfXaoOAqnbqItZq4KHXS6jDJ78F2BmQvxg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/select": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.1.tgz", + "integrity": "sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "figures": "^3.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@inquirer/type": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.1.5.tgz", + "integrity": "sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -162,11 +345,26 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -188,12 +386,112 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -209,12 +507,217 @@ "node": ">=0.3.1" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prompt-sync": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", + "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", + "dependencies": { + "strip-ansi": "^5.0.0" + } + }, + "node_modules/prompt-sync/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompt-sync/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -258,6 +761,18 @@ } } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", @@ -271,12 +786,32 @@ "node": ">=12.20" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -303,7 +838,6 @@ "@grpc/grpc-js": "^1.9.3", "@js-temporal/polyfill": "^0.3.0", "@microsoft/durabletask-js": "^0.1.0-alpha.1", - "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/google-protobuf": "^3.15.5", @@ -329,12 +863,162 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", - "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", "ts-node": "^10.9.1", "typescript": "^4.5.5" } }, + "@inquirer/checkbox": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.5.0.tgz", + "integrity": "sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "figures": "^3.2.0" + } + }, + "@inquirer/confirm": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.15.tgz", + "integrity": "sha512-hj8Q/z7sQXsF0DSpLQZVDhWYGN6KLM/gNjjqGkpKwBzljbQofGjn0ueHADy4HUY+OqDHmXuwk/bY+tZyIuuB0w==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + } + }, + "@inquirer/core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-5.1.1.tgz", + "integrity": "sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg==", + "dev": true, + "requires": { + "@inquirer/type": "^1.1.5", + "@types/mute-stream": "^0.0.4", + "@types/node": "^20.9.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "cli-spinners": "^2.9.1", + "cli-width": "^4.1.0", + "figures": "^3.2.0", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "@types/node": { + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + } + } + }, + "@inquirer/editor": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-1.2.13.tgz", + "integrity": "sha512-gBxjqt0B9GLN0j6M/tkEcmcIvB2fo9Cw0f5NRqDTkYyB9AaCzj7qvgG0onQ3GVPbMyMbbP4tWYxrBOaOdKpzNA==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2", + "external-editor": "^3.1.0" + } + }, + "@inquirer/expand": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-1.1.14.tgz", + "integrity": "sha512-yS6fJ8jZYAsxdxuw2c8XTFMTvMR1NxZAw3LxDaFnqh7BZ++wTQ6rSp/2gGJhMacdZ85osb+tHxjVgx7F+ilv5g==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2", + "figures": "^3.2.0" + } + }, + "@inquirer/input": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-1.2.14.tgz", + "integrity": "sha512-tISLGpUKXixIQue7jypNEShrdzJoLvEvZOJ4QRsw5XTfrIYfoWFqAjMQLerGs9CzR86yAI89JR6snHmKwnNddw==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + } + }, + "@inquirer/password": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-1.1.14.tgz", + "integrity": "sha512-vL2BFxfMo8EvuGuZYlryiyAB3XsgtbxOcFs4H9WI9szAS/VZCAwdVqs8rqEeaAf/GV/eZOghIOYxvD91IsRWSg==", + "dev": true, + "requires": { + "@inquirer/input": "^1.2.14", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2" + } + }, + "@inquirer/prompts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-3.3.0.tgz", + "integrity": "sha512-BBCqdSnhNs+WziSIo4f/RNDu6HAj4R/Q5nMgJb5MNPFX8sJGCvj9BoALdmR0HTWXyDS7TO8euKj6W6vtqCQG7A==", + "dev": true, + "requires": { + "@inquirer/checkbox": "^1.5.0", + "@inquirer/confirm": "^2.0.15", + "@inquirer/core": "^5.1.1", + "@inquirer/editor": "^1.2.13", + "@inquirer/expand": "^1.1.14", + "@inquirer/input": "^1.2.14", + "@inquirer/password": "^1.1.14", + "@inquirer/rawlist": "^1.2.14", + "@inquirer/select": "^1.3.1" + } + }, + "@inquirer/rawlist": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-1.2.14.tgz", + "integrity": "sha512-xIYmDpYgfz2XGCKubSDLKEvadkIZAKbehHdWF082AyC2I4eHK44RUfXaoOAqnbqItZq4KHXS6jDJ78F2BmQvxg==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "chalk": "^4.1.2" + } + }, + "@inquirer/select": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.1.tgz", + "integrity": "sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ==", + "dev": true, + "requires": { + "@inquirer/core": "^5.1.1", + "@inquirer/type": "^1.1.5", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "figures": "^3.2.0" + } + }, + "@inquirer/type": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.1.5.tgz", + "integrity": "sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA==", + "dev": true + }, "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -381,11 +1065,26 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "18.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" }, + "@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -398,12 +1097,79 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true + }, + "cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -416,12 +1182,162 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true + }, + "prompt-sync": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", + "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", + "requires": { + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true + }, + "run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -443,18 +1359,41 @@ "yn": "3.1.1" } }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, "typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/examples/workflow/authoring/package.json b/examples/workflow/authoring/package.json index f41e7080..65e36a5d 100644 --- a/examples/workflow/authoring/package.json +++ b/examples/workflow/authoring/package.json @@ -1,7 +1,7 @@ { "name": "dapr-example-workflow-authoring", "version": "1.0.0", - "description": "An example utilizing the Dapr JS-SDK to manage workflow", + "description": "An example utilizing the Dapr JS-SDK to author workflow", "private": "true", "scripts": { "build": "npx tsc --outDir ./dist/", @@ -15,6 +15,7 @@ "author": "", "license": "ISC", "devDependencies": { + "readline-sync": "^1.4.10", "ts-node": "^10.9.1", "typescript": "^5.0.4" }, diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index 227b69e8..4d4cd28c 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -13,7 +13,7 @@ limitations under the License. import { DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; -(async () => { +async function start() { const grpcEndpoint = "localhost:50001"; const workflowClient = new DaprWorkflowClient(grpcEndpoint); const workflowRuntime = new WorkflowRuntime(grpcEndpoint); @@ -60,4 +60,9 @@ import { DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowR await workflowRuntime.stop(); await workflowClient.stop(); -})(); +} + +start().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index 3fac4091..a608d683 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -21,7 +21,7 @@ import { } from "@dapr/dapr"; // Wrap the entire code in an immediately-invoked async function -(async () => { +async function start() { // Update the gRPC client and worker to use a local address and port const grpcServerAddress = "localhost:50001"; const workflowClient: DaprWorkflowClient = new DaprWorkflowClient(grpcServerAddress); @@ -93,4 +93,9 @@ import { // stop worker and client await workflowRuntime.stop(); await workflowClient.stop(); -})(); +} + +start().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index 1211ac38..c533c188 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -22,7 +22,7 @@ import { import * as readlineSync from "readline-sync"; // Wrap the entire code in an immediately-invoked async function -(async () => { +async function start() { class Order { cost: number; product: string; @@ -104,12 +104,8 @@ import * as readlineSync from "readline-sync"; const id = await workflowClient.scheduleNewWorkflow(purchaseOrderWorkflow, order); console.log(`Orchestration scheduled with ID: ${id}`); - if (readlineSync.keyInYN("Press [Y] to approve the order... Y/yes, N/no")) { - const approvalEvent = { approver: approver }; - await workflowClient.raiseEvent(id, "approval_received", approvalEvent); - } else { - return "Order rejected"; - } + //prompt for approval asynchronously + promptForApproval(approver, workflowClient, id); // Wait for orchestration completion const state = await workflowClient.waitForWorkflowCompletion(id, undefined, timeout + 2); @@ -122,4 +118,18 @@ import * as readlineSync from "readline-sync"; // stop worker and client await workflowRuntime.stop(); await workflowClient.stop(); -})(); +} + +async function promptForApproval(approver: string, workflowClient: DaprWorkflowClient, id: string) { + if (readlineSync.keyInYN("Press [Y] to approve the order... Y/yes, N/no")) { + const approvalEvent = { approver: approver }; + await workflowClient.raiseEvent(id, "approval_received", approvalEvent); + } else { + return "Order rejected"; + } +} + +start().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index fc873984..6ad65d63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "node-fetch": "^2.6.7" }, "devDependencies": { - "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/jest": "^27.0.1", @@ -40,7 +39,6 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", - "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", "ts-node": "^10.9.1", "typescript": "^4.5.5" @@ -1383,6 +1381,8 @@ "integrity": "sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A==", "dev": true, "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.1", "@swc/types": "^0.1.5" @@ -1427,6 +1427,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1443,6 +1444,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1459,6 +1461,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1475,6 +1478,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1491,6 +1495,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1507,6 +1512,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1523,6 +1529,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1539,6 +1546,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1555,6 +1563,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1571,6 +1580,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1579,13 +1589,17 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@swc/types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tootallnate/once": { "version": "1.1.2", @@ -6330,15 +6344,6 @@ "node": ">=8.10.0" } }, - "node_modules/readline-sync": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", - "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -8576,6 +8581,8 @@ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.101.tgz", "integrity": "sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A==", "dev": true, + "optional": true, + "peer": true, "requires": { "@swc/core-darwin-arm64": "1.3.101", "@swc/core-darwin-x64": "1.3.101", @@ -8596,82 +8603,96 @@ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.101.tgz", "integrity": "sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-darwin-x64": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.101.tgz", "integrity": "sha512-B085j8XOx73Fg15KsHvzYWG262bRweGr3JooO1aW5ec5pYbz5Ew9VS5JKYS03w2UBSxf2maWdbPz2UFAxg0whw==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-linux-arm-gnueabihf": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.101.tgz", "integrity": "sha512-9xLKRb6zSzRGPqdz52Hy5GuB1lSjmLqa0lST6MTFads3apmx4Vgs8Y5NuGhx/h2I8QM4jXdLbpqQlifpzTlSSw==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-linux-arm64-gnu": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.101.tgz", "integrity": "sha512-oE+r1lo7g/vs96Weh2R5l971dt+ZLuhaUX+n3BfDdPxNHfObXgKMjO7E+QS5RbGjv/AwiPCxQmbdCp/xN5ICJA==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-linux-arm64-musl": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.101.tgz", "integrity": "sha512-OGjYG3H4BMOTnJWJyBIovCez6KiHF30zMIu4+lGJTCrxRI2fAjGLml3PEXj8tC3FMcud7U2WUn6TdG0/te2k6g==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-linux-x64-gnu": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.101.tgz", "integrity": "sha512-/kBMcoF12PRO/lwa8Z7w4YyiKDcXQEiLvM+S3G9EvkoKYGgkkz4Q6PSNhF5rwg/E3+Hq5/9D2R+6nrkF287ihg==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-linux-x64-musl": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.101.tgz", "integrity": "sha512-kDN8lm4Eew0u1p+h1l3JzoeGgZPQ05qDE0czngnjmfpsH2sOZxVj1hdiCwS5lArpy7ktaLu5JdRnx70MkUzhXw==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-win32-arm64-msvc": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.101.tgz", "integrity": "sha512-9Wn8TTLWwJKw63K/S+jjrZb9yoJfJwCE2RV5vPCCWmlMf3U1AXj5XuWOLUX+Rp2sGKau7wZKsvywhheWm+qndQ==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-win32-ia32-msvc": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.101.tgz", "integrity": "sha512-onO5KvICRVlu2xmr4//V2je9O2XgS1SGKpbX206KmmjcJhXN5EYLSxW9qgg+kgV5mip+sKTHTAu7IkzkAtElYA==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/core-win32-x64-msvc": { "version": "1.3.101", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.101.tgz", "integrity": "sha512-T3GeJtNQV00YmiVw/88/nxJ/H43CJvFnpvBHCVn17xbahiVUOPOduh3rc9LgAkKiNt/aV8vU3OJR+6PhfMR7UQ==", "dev": true, - "optional": true + "optional": true, + "peer": true }, "@swc/counter": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@swc/types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tootallnate/once": { "version": "1.1.2", @@ -12257,12 +12278,6 @@ "picomatch": "^2.2.1" } }, - "readline-sync": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", - "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", - "dev": true - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index db445a7f..40e8d414 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,7 @@ "build": "npm install && npm run lint && npm run pretty && ./scripts/build.sh", "start:dev": "npm run build && nodemon --ext \".ts,.js\" --watch \"./src\" --exec \"npm run build\"", "pretty": "prettier --list-different \"**/*.{ts,tsx,js,jsx,json,md}\"", - "pretty-fix": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", - "example": "ts-node --swc" + "pretty-fix": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"" }, "keywords": [], "author": "Xavier Geerinck", @@ -59,7 +58,6 @@ "node-fetch": "^2.6.7" }, "devDependencies": { - "@swc/core": "^1.3.55", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.15", "@types/jest": "^27.0.1", @@ -78,7 +76,6 @@ "nodemon": "^2.0.20", "prettier": "^2.4.0", "pretty-quick": "^3.1.3", - "readline-sync": "^1.4.10", "ts-jest": "^27.0.5", "ts-node": "^10.9.1", "typescript": "^4.5.5" diff --git a/src/index.ts b/src/index.ts index 80b70c83..8a0a929d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,7 +40,7 @@ import StateConcurrencyEnum from "./enum/StateConcurrency.enum"; import StateConsistencyEnum from "./enum/StateConsistency.enum"; import { StateGetBulkOptions } from "./types/state/StateGetBulkOptions.type"; -import DaprWorkflowClient from "./workflow/client/WorkflowClient"; +import DaprWorkflowClient from "./workflow/client/DaprWorkflowClient"; import WorkflowActivityContext from "./workflow/runtime/WorkflowActivityContext"; import WorkflowContext from "./workflow/runtime/WorkflowContext"; import WorkflowRuntime from "./workflow/runtime/WorkflowRuntime"; diff --git a/src/types/workflow/Activity.type.ts b/src/types/workflow/Activity.type.ts index 54959925..37db69b7 100644 --- a/src/types/workflow/Activity.type.ts +++ b/src/types/workflow/Activity.type.ts @@ -13,4 +13,7 @@ limitations under the License. import WorkflowActivityContext from "../../workflow/runtime/WorkflowActivityContext"; +/** + * The type of the activity function. + */ export type TWorkflowActivity = (context: WorkflowActivityContext, input: TInput) => TOutput; diff --git a/src/types/workflow/InputOutput.type.ts b/src/types/workflow/InputOutput.type.ts index 9d9b1f1c..ae1f4d80 100644 --- a/src/types/workflow/InputOutput.type.ts +++ b/src/types/workflow/InputOutput.type.ts @@ -11,5 +11,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +/** + * The type of the input for the workflow activity + */ export type TInput = any; + +/** + * The type of the output for the workflow and workflow activity + */ export type TOutput = any; diff --git a/src/types/workflow/Workflow.type.ts b/src/types/workflow/Workflow.type.ts index ef75ba06..36d2bbc2 100644 --- a/src/types/workflow/Workflow.type.ts +++ b/src/types/workflow/Workflow.type.ts @@ -15,4 +15,7 @@ import WorkflowContext from "../../workflow/runtime/WorkflowContext"; import { Task } from "@microsoft/durabletask-js/task/task"; import { TOutput } from "./InputOutput.type"; +/** + * The type of the workflow. + */ export type TWorkflow = (context: WorkflowContext, input: any) => Generator, any, any> | TOutput; diff --git a/src/workflow/client/WorkflowClient.ts b/src/workflow/client/DaprWorkflowClient.ts similarity index 89% rename from src/workflow/client/WorkflowClient.ts rename to src/workflow/client/DaprWorkflowClient.ts index 460acebd..ad746ff9 100644 --- a/src/workflow/client/WorkflowClient.ts +++ b/src/workflow/client/DaprWorkflowClient.ts @@ -14,11 +14,17 @@ limitations under the License. import { TaskHubGrpcClient } from "@microsoft/durabletask-js"; import * as grpc from "@grpc/grpc-js"; import { WorkflowState } from "./WorkflowState"; -import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; +import { generateApiTokenClientInterceptors } from "../internal/index"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; import { Settings } from "../../utils/Settings.util"; +/** + * Class that defines client operations for managing workflow instances. + * + * Instances of this class can be used to start, query, raise events to, and terminate workflow instances. In most + * cases, methods on this class accept an instance ID as a parameter, which identifies the workflow instance. + */ export default class DaprWorkflowClient { private readonly _innerClient: TaskHubGrpcClient; @@ -34,7 +40,7 @@ export default class DaprWorkflowClient { private buildInnerClient(hostAddress?: string, options: grpc.ChannelOptions = {}): TaskHubGrpcClient { const innerOptions = { ...options, - interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], + interceptors: [generateApiTokenClientInterceptors(), ...(options?.interceptors ?? [])], }; if (hostAddress === undefined) { hostAddress = Settings.getDefaultHost() + ":" + Settings.getDefaultGrpcPort(); @@ -105,16 +111,20 @@ export default class DaprWorkflowClient { * @param {string} workflowInstanceId - The unique identifier of the workflow instance to wait for. * @param {boolean} fetchPayloads - Indicates whether to fetch the workflow instance's * inputs, outputs (true) or omit them (false). - * @param {number} timeout - The amount of time, in seconds, to wait for the workflow instance to start. + * @param {number} timeoutInSeconds - The amount of time, in seconds, to wait for the workflow instance to start. * @returns {Promise} A Promise that resolves to the workflow instance metadata * or undefined if no such instance is found. */ public async waitForWorkflowStart( workflowInstanceId: string, - fetchPayloads?: boolean, - timeout?: number, + fetchPayloads = true, + timeoutInSeconds = 60, ): Promise { - const state = await this._innerClient.waitForOrchestrationStart(workflowInstanceId, fetchPayloads, timeout); + const state = await this._innerClient.waitForOrchestrationStart( + workflowInstanceId, + fetchPayloads, + timeoutInSeconds, + ); if (state !== undefined) { return new WorkflowState(state); } @@ -133,16 +143,20 @@ export default class DaprWorkflowClient { * @param {string} workflowInstanceId - The unique identifier of the workflow instance to wait for. * @param {boolean} fetchPayloads - Indicates whether to fetch the workflow instance's * inputs, outputs (true) or omit them (false). - * @param {number} timeout - The amount of time, in seconds, to wait for the workflow instance to start. + * @param {number} timeoutInSeconds - The amount of time, in seconds, to wait for the workflow instance to start. * @returns {Promise} A Promise that resolves to the workflow instance metadata * or undefined if no such instance is found. */ public async waitForWorkflowCompletion( workflowInstanceId: string, fetchPayloads = true, - timeout: number, + timeoutInSeconds = 60, ): Promise { - const state = await this._innerClient.waitForOrchestrationCompletion(workflowInstanceId, fetchPayloads, timeout); + const state = await this._innerClient.waitForOrchestrationCompletion( + workflowInstanceId, + fetchPayloads, + timeoutInSeconds, + ); if (state != undefined) { return new WorkflowState(state); } diff --git a/src/workflow/client/WorkflowFailureDetails.ts b/src/workflow/client/WorkflowFailureDetails.ts index d2903dd6..240fbc2a 100644 --- a/src/workflow/client/WorkflowFailureDetails.ts +++ b/src/workflow/client/WorkflowFailureDetails.ts @@ -13,6 +13,13 @@ limitations under the License. import { FailureDetails } from "@microsoft/durabletask-js/task/failure-details"; +/** + * Class that represents the details of a task failure. + * + * In most cases, failures are caused by unhandled exceptions in activity or workflow code, in which case instances + * of this class will expose the details of the exception. However, it's also possible that other types of errors could + * result in task failures, in which case there may not be any exception-specific information. + */ export class WorkflowFailureDetails { private readonly failureDetails: FailureDetails; diff --git a/src/workflow/internal/ApiTokenClientInterceptor.ts b/src/workflow/internal/ApiTokenClientInterceptor.ts deleted file mode 100644 index 962847ff..00000000 --- a/src/workflow/internal/ApiTokenClientInterceptor.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2024 The Dapr Authors -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 - http://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. -*/ - -import * as grpc from "@grpc/grpc-js"; -import { Settings } from "../../utils/Settings.util"; - -export function generateInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall { - return (options: any, nextCall: any) => { - return new grpc.InterceptingCall(nextCall(options), { - start: (metadata, listener, next) => { - if (metadata.get("dapr-api-token").length == 0) { - metadata.add("dapr-api-token", Settings.getDefaultApiToken() as grpc.MetadataValue); - } - next(metadata, listener); - }, - }); - }; -} diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index 2af98af3..02b04ecc 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -14,7 +14,40 @@ limitations under the License. import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TWorkflow } from "../../types/workflow/Workflow.type"; +import * as grpc from "@grpc/grpc-js"; +import { Settings } from "../../utils/Settings.util"; +/** + * Gets the name of a function from its definition or string representation. + * + * @param fn - The function for which the name is to be retrieved. Can be either a function or a string representation of a function. + * @returns The name of the function. + * + * @typeparam TWorkflow - The type of the workflow function. + * @typeparam TInput - The type of the input for the workflow activity. + * @typeparam TOutput - The type of the output for the workflow activity. + */ export function getFunctionName(fn: TWorkflow | TWorkflowActivity): string { return fn.name || fn.toString().match(/function\s*([^(]*)\(/)![1]; } + +/** + * Generates a gRPC interceptor function that adds a Dapr API token to the metadata if not already present. + * This interceptor is intended for use with gRPC client calls. + * + * @param options - The gRPC call options object. + * @param nextCall - The next call function in the interceptor chain. + * @returns A gRPC InterceptingCall instance with added functionality to include a Dapr API token in the metadata. + */ +export function generateApiTokenClientInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall { + return (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata, listener, next) => { + if (metadata.get("dapr-api-token").length == 0) { + metadata.add("dapr-api-token", Settings.getDefaultApiToken() as grpc.MetadataValue); + } + next(metadata, listener); + }, + }); + }; +} diff --git a/src/workflow/runtime/WorkflowActivityContext.ts b/src/workflow/runtime/WorkflowActivityContext.ts index 2e357daf..cf29962d 100644 --- a/src/workflow/runtime/WorkflowActivityContext.ts +++ b/src/workflow/runtime/WorkflowActivityContext.ts @@ -13,6 +13,10 @@ limitations under the License. import { ActivityContext } from "@microsoft/durabletask-js"; +/** + * Used by activity to perform actions such as getting activity's name and + * its input. + */ export default class WorkflowActivityContext { private readonly _innerContext: ActivityContext; constructor(innerContext: ActivityContext) { diff --git a/src/workflow/runtime/WorkflowContext.ts b/src/workflow/runtime/WorkflowContext.ts index 8b04febb..240a1d64 100644 --- a/src/workflow/runtime/WorkflowContext.ts +++ b/src/workflow/runtime/WorkflowContext.ts @@ -21,6 +21,10 @@ import { whenAll, whenAny } from "@microsoft/durabletask-js/task"; import { WhenAnyTask } from "@microsoft/durabletask-js/task/when-any-task"; import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; +/** + * Used by workflow to perform actions such as scheduling tasks, durable timers, waiting for external events, + * and for getting basic information about the current workflow. + */ export default class WorkflowContext { private readonly _innerContext: OrchestrationContext; constructor(innerContext: OrchestrationContext) { @@ -74,13 +78,15 @@ export default class WorkflowContext { } /** - * schedule an activity for execution. + * Schedules an activity for execution within the orchestrator. * - * @param {Orchestrator} orchestrator The sub-orchestrator function to call. - * @param {TInput} input The JSON-serializable input value for the sub-orchestrator function. - * @param {string} instanceId The ID to use for the sub-orchestration instance. If not provided, a new GUID will be used. + * @param {TWorkflowActivity | string} activity - The activity function or its name to call. + * @param {TInput} [input] - The JSON-serializable input value for the activity function. + * @returns {Task} - A Durable Task that completes when the activity function completes. * - * @returns {Task} A Durable Task that completes when the sub-orchestrator function completes. + * @typeparam TWorkflowActivity - The type of the activity function. + * @typeparam TInput - The type of the input for the activity. + * @typeparam TOutput - The type of the output for the activity. */ public callActivity(activity: TWorkflowActivity | string, input?: TInput): Task { if (typeof activity === "string") { diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 967db918..0db03e59 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -18,16 +18,23 @@ import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; import WorkflowActivityContext from "./WorkflowActivityContext"; import WorkflowContext from "./WorkflowContext"; -import { generateInterceptors } from "../internal/ApiTokenClientInterceptor"; +import { generateApiTokenClientInterceptors } from "../internal/index"; import { getFunctionName } from "../internal"; +import { Settings } from "../../utils/Settings.util"; +/** + * Contains methods to register workflows and activities. + */ export default class WorkflowRuntime { private worker: TaskHubGrpcWorker; constructor(hostAddress?: string, options?: grpc.ChannelOptions) { const innerOptions = { ...options, - interceptors: [generateInterceptors(), ...(options?.interceptors ?? [])], + interceptors: [generateApiTokenClientInterceptors(), ...(options?.interceptors ?? [])], }; + if (hostAddress === undefined) { + hostAddress = Settings.getDefaultHost() + ":" + Settings.getDefaultGrpcPort(); + } this.worker = new TaskHubGrpcWorker(hostAddress, innerOptions); } diff --git a/src/workflow/runtime/WorkflowRuntimeStatus.ts b/src/workflow/runtime/WorkflowRuntimeStatus.ts index 67291657..df355f9c 100644 --- a/src/workflow/runtime/WorkflowRuntimeStatus.ts +++ b/src/workflow/runtime/WorkflowRuntimeStatus.ts @@ -13,6 +13,9 @@ limitations under the License. import { OrchestrationStatus } from "@microsoft/durabletask-js/orchestration/enum/orchestration-status.enum"; +/** + * Enum describing the runtime status of a workflow. + */ export enum WorkflowRuntimeStatus { RUNNING = OrchestrationStatus.RUNNING, COMPLETED = OrchestrationStatus.COMPLETED, @@ -23,20 +26,32 @@ export enum WorkflowRuntimeStatus { SUSPENDED = OrchestrationStatus.SUSPENDED, } +/** + * Converts an OrchestrationStatus value to the corresponding WorkflowRuntimeStatus enum value. + * + * @param {OrchestrationStatus} val - The OrchestrationStatus value to be converted. + * @returns {WorkflowRuntimeStatus} - The equivalent WorkflowRuntimeStatus enum value. + */ export function fromOrchestrationStatus(val: OrchestrationStatus): WorkflowRuntimeStatus { const values = Object.values(WorkflowRuntimeStatus); const valIdx = values.findIndex((v) => v == (val as number)); - //Return the entry of the WorkflowRuntimeStatus enum at index + // Return the entry of the WorkflowRuntimeStatus enum at index const entries = Object.entries(WorkflowRuntimeStatus); return entries[valIdx][1] as WorkflowRuntimeStatus; } +/** + * Converts an WorkflowRuntimeStatus value to the corresponding OrchestrationStatus enum value. + * + * @param {WorkflowRuntimeStatus} val - The WorkflowRuntimeStatus value to be converted. + * @returns {OrchestrationStatus} - The equivalent OrchestrationStatus enum value. + */ export function toOrchestrationStatus(val: WorkflowRuntimeStatus): OrchestrationStatus { const values = Object.values(OrchestrationStatus); const valIdx = values.findIndex((v) => v == (val as number)); - //Return the entry of the WorkflowRuntimeStatus enum at index + // Return the entry of the WorkflowRuntimeStatus enum at index const entries = Object.entries(OrchestrationStatus); return entries[valIdx][1] as OrchestrationStatus; } diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 096bc871..9924f7db 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import DaprWorkflowClient from "../../../src/workflow/client/WorkflowClient"; +import DaprWorkflowClient from "../../../src/workflow/client/DaprWorkflowClient"; import WorkflowContext from "../../../src/workflow/runtime/WorkflowContext"; import WorkflowRuntime from "../../../src/workflow/runtime/WorkflowRuntime"; import { TWorkflow } from "../../../src/types/workflow/Workflow.type"; diff --git a/test/unit/workflow/workflowRuntimeStatus.test.ts b/test/unit/workflow/workflowRuntimeStatus.test.ts index eb9359b4..c813852b 100644 --- a/test/unit/workflow/workflowRuntimeStatus.test.ts +++ b/test/unit/workflow/workflowRuntimeStatus.test.ts @@ -17,49 +17,98 @@ import { fromOrchestrationStatus, toOrchestrationStatus, } from "../../../src/workflow/runtime/WorkflowRuntimeStatus"; +import { getFunctionName } from "../../../src/workflow/internal"; +import { TWorkflow } from "../../../src/types/workflow/Workflow.type"; +import WorkflowContext from "../../../src/workflow/runtime/WorkflowContext"; +import WorkflowActivityContext from "../../../src/workflow/runtime/WorkflowActivityContext"; describe("Workflow Runtime Status", () => { - const testCases = [ - { - orchestrationStatus: OrchestrationStatus.RUNNING, - workflowRuntimeStatus: WorkflowRuntimeStatus.RUNNING, - }, - - { - orchestrationStatus: OrchestrationStatus.COMPLETED, - workflowRuntimeStatus: WorkflowRuntimeStatus.COMPLETED, - }, - - { - orchestrationStatus: OrchestrationStatus.FAILED, - workflowRuntimeStatus: WorkflowRuntimeStatus.FAILED, - }, - - { - orchestrationStatus: OrchestrationStatus.TERMINATED, - workflowRuntimeStatus: WorkflowRuntimeStatus.TERMINATED, - }, - - { - orchestrationStatus: OrchestrationStatus.CONTINUED_AS_NEW, - workflowRuntimeStatus: WorkflowRuntimeStatus.CONTINUED_AS_NEW, - }, - - { - orchestrationStatus: OrchestrationStatus.PENDING, - workflowRuntimeStatus: WorkflowRuntimeStatus.PENDING, - }, - - { - orchestrationStatus: OrchestrationStatus.SUSPENDED, - workflowRuntimeStatus: WorkflowRuntimeStatus.SUSPENDED, - }, - ]; - - testCases.forEach((testCase) => { - test("Should be able to convert between orchestration status to workflow runtime status", () => { - expect(fromOrchestrationStatus(testCase.orchestrationStatus)).toEqual(testCase.workflowRuntimeStatus); - expect(toOrchestrationStatus(testCase.workflowRuntimeStatus)).toEqual(testCase.orchestrationStatus); + describe("convert runtime status", () => { + const testCases = [ + { + orchestrationStatus: OrchestrationStatus.RUNNING, + workflowRuntimeStatus: WorkflowRuntimeStatus.RUNNING, + }, + + { + orchestrationStatus: OrchestrationStatus.COMPLETED, + workflowRuntimeStatus: WorkflowRuntimeStatus.COMPLETED, + }, + + { + orchestrationStatus: OrchestrationStatus.FAILED, + workflowRuntimeStatus: WorkflowRuntimeStatus.FAILED, + }, + + { + orchestrationStatus: OrchestrationStatus.TERMINATED, + workflowRuntimeStatus: WorkflowRuntimeStatus.TERMINATED, + }, + + { + orchestrationStatus: OrchestrationStatus.CONTINUED_AS_NEW, + workflowRuntimeStatus: WorkflowRuntimeStatus.CONTINUED_AS_NEW, + }, + + { + orchestrationStatus: OrchestrationStatus.PENDING, + workflowRuntimeStatus: WorkflowRuntimeStatus.PENDING, + }, + + { + orchestrationStatus: OrchestrationStatus.SUSPENDED, + workflowRuntimeStatus: WorkflowRuntimeStatus.SUSPENDED, + }, + ]; + + testCases.forEach((testCase) => { + test("Should be able to convert between orchestration status to workflow runtime status", () => { + expect(fromOrchestrationStatus(testCase.orchestrationStatus)).toEqual(testCase.workflowRuntimeStatus); + expect(toOrchestrationStatus(testCase.workflowRuntimeStatus)).toEqual(testCase.orchestrationStatus); + }); + }); + }); + + describe("getFunctionName", () => { + it("should return the name of the function", () => { + const namedFunction = function exampleFunction(): number { + return 1; + }; + + const result = getFunctionName(namedFunction); + + expect(result).toBe("exampleFunction"); + }); + + it("should extract the name from the string representation of the function", () => { + const anonymousFunction = function (): number { + return 1; + }; + + const result = getFunctionName(anonymousFunction); + + expect(result).not.toBeUndefined(); + expect(typeof result).toBe("string"); + }); + + it("should handle TWorkflow type", () => { + const emptyWorkflow: TWorkflow = async (_: WorkflowContext, __: any) => { + return 1; + }; + + const result = getFunctionName(emptyWorkflow); + + expect(result).toBe("emptyWorkflow"); + }); + + it("should handle TWorkflow type", () => { + const emptyWorkflowActivity = async (_: WorkflowActivityContext) => { + return 1; + }; + + const result = getFunctionName(emptyWorkflowActivity); + + expect(result).toBe("emptyWorkflowActivity"); }); }); }); From acc001083db45f87944b52efdc590c973b6dbda5 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Fri, 12 Jan 2024 18:12:28 -0600 Subject: [PATCH 11/18] mitigate issue from sidecar Signed-off-by: kaibocai --- test/e2e/workflow/workflow.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 9924f7db..a24e90f9 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -175,7 +175,9 @@ describe("Workflow", () => { it("should be able to run an single timer", async () => { const delay = 3; const singleTimerWorkflow: TWorkflow = async function* (ctx: WorkflowContext, _: number): any { - yield ctx.createTimer(delay); + // seems there is a issue from durabletask-sidecar. + // TODO: Once transfer to durabletask-go, reset the timer + yield ctx.createTimer(delay + 1); }; workflowRuntime.registerWorkflow(singleTimerWorkflow); From 59db4298d3e05e47d1f9939f9f668f0e8893db5a Mon Sep 17 00:00:00 2001 From: kaibocai Date: Sun, 14 Jan 2024 17:09:10 -0600 Subject: [PATCH 12/18] update darpApiToken set strategy Signed-off-by: kaibocai --- src/types/workflow/WorkflowClientOption.ts | 45 ++++++++++++++++++++++ src/workflow/client/DaprWorkflowClient.ts | 35 +++++++++++------ src/workflow/internal/index.ts | 8 ++-- src/workflow/runtime/WorkflowRuntime.ts | 41 +++++++++++++++----- test/e2e/workflow/workflow.test.ts | 16 ++++++-- 5 files changed, 118 insertions(+), 27 deletions(-) create mode 100644 src/types/workflow/WorkflowClientOption.ts diff --git a/src/types/workflow/WorkflowClientOption.ts b/src/types/workflow/WorkflowClientOption.ts new file mode 100644 index 00000000..8372c8be --- /dev/null +++ b/src/types/workflow/WorkflowClientOption.ts @@ -0,0 +1,45 @@ +/* +Copyright 2022 The Dapr Authors +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 + http://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. +*/ + +import * as grpc from "@grpc/grpc-js"; +import { LoggerOptions } from "../logger/LoggerOptions"; + +export type WorkflowClientOptions = { + /** + * Host location of the Dapr sidecar. + * Default is 127.0.0.1. + */ + clientHost: string; + + /** + * Port of the Dapr sidecar. + * Default is 4001. + */ + clientPort: string; + + /** + * Options related to logging. + */ + logger?: LoggerOptions; + + /** + * API token to authenticate with Dapr. + * See https://docs.dapr.io/operations/security/api-token/. + */ + daprApiToken?: string; + + /** + * options used when initializing a grpc Channel instance. + */ + grpcOptions?: grpc.ChannelOptions; +}; diff --git a/src/workflow/client/DaprWorkflowClient.ts b/src/workflow/client/DaprWorkflowClient.ts index ad746ff9..8dea7f7e 100644 --- a/src/workflow/client/DaprWorkflowClient.ts +++ b/src/workflow/client/DaprWorkflowClient.ts @@ -12,12 +12,12 @@ limitations under the License. */ import { TaskHubGrpcClient } from "@microsoft/durabletask-js"; -import * as grpc from "@grpc/grpc-js"; import { WorkflowState } from "./WorkflowState"; import { generateApiTokenClientInterceptors } from "../internal/index"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; import { Settings } from "../../utils/Settings.util"; +import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; /** * Class that defines client operations for managing workflow instances. @@ -30,20 +30,33 @@ export default class DaprWorkflowClient { /** * Initializes a new instance of the DaprWorkflowClient. - * @param {string | undefined} hostAddress - The address of the Dapr runtime hosting the workflow services. - * @param {grpc.ChannelOptions | undefined} options - Additional options for configuring the gRPC channel. + * @param {WorkflowClientOptions | undefined} options - Additional options for configuring DaprWorkflowClient. */ - constructor(hostAddress?: string, options?: grpc.ChannelOptions) { + constructor(options: Partial = {}) { + const hostAddress = this.generateHostAddress(options); + options.daprApiToken = this.generateDaprApiToken(options); this._innerClient = this.buildInnerClient(hostAddress, options); } - private buildInnerClient(hostAddress?: string, options: grpc.ChannelOptions = {}): TaskHubGrpcClient { - const innerOptions = { - ...options, - interceptors: [generateApiTokenClientInterceptors(), ...(options?.interceptors ?? [])], - }; - if (hostAddress === undefined) { - hostAddress = Settings.getDefaultHost() + ":" + Settings.getDefaultGrpcPort(); + private generateHostAddress(options: Partial): string { + const host = options?.clientHost ?? Settings.getDefaultHost(); + const port = options?.clientPort ?? Settings.getDefaultGrpcPort(); + const hostAddress = `${host}:${port}`; + return hostAddress; + } + + private generateDaprApiToken(options: Partial): string | undefined { + const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); + return daprApiToken; + } + + private buildInnerClient(hostAddress: string, options: Partial): TaskHubGrpcClient { + let innerOptions = options?.grpcOptions; + if (options.daprApiToken !== undefined && options.daprApiToken !== "") { + innerOptions = { + ...innerOptions, + interceptors: [generateApiTokenClientInterceptors(options), ...(innerOptions?.interceptors ?? [])], + }; } return new TaskHubGrpcClient(hostAddress, innerOptions); } diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index 02b04ecc..e912f479 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -15,7 +15,7 @@ import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import * as grpc from "@grpc/grpc-js"; -import { Settings } from "../../utils/Settings.util"; +import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; /** * Gets the name of a function from its definition or string representation. @@ -39,12 +39,14 @@ export function getFunctionName(fn: TWorkflow | TWorkflowActivity grpc.InterceptingCall { +export function generateApiTokenClientInterceptors( + workflowOptions: Partial, +): (options: any, nextCall: any) => grpc.InterceptingCall { return (options: any, nextCall: any) => { return new grpc.InterceptingCall(nextCall(options), { start: (metadata, listener, next) => { if (metadata.get("dapr-api-token").length == 0) { - metadata.add("dapr-api-token", Settings.getDefaultApiToken() as grpc.MetadataValue); + metadata.add("dapr-api-token", workflowOptions.daprApiToken as grpc.MetadataValue); } next(metadata, listener); }, diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 0db03e59..baa4f43a 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -11,7 +11,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as grpc from "@grpc/grpc-js"; import { ActivityContext, OrchestrationContext, TaskHubGrpcWorker } from "@microsoft/durabletask-js"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { TWorkflowActivity } from "../../types/workflow/Activity.type"; @@ -21,21 +20,45 @@ import WorkflowContext from "./WorkflowContext"; import { generateApiTokenClientInterceptors } from "../internal/index"; import { getFunctionName } from "../internal"; import { Settings } from "../../utils/Settings.util"; +import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; /** * Contains methods to register workflows and activities. */ export default class WorkflowRuntime { private worker: TaskHubGrpcWorker; - constructor(hostAddress?: string, options?: grpc.ChannelOptions) { - const innerOptions = { - ...options, - interceptors: [generateApiTokenClientInterceptors(), ...(options?.interceptors ?? [])], - }; - if (hostAddress === undefined) { - hostAddress = Settings.getDefaultHost() + ":" + Settings.getDefaultGrpcPort(); + + /** + * Initializes a new instance of the WorkflowRuntime. + * @param {WorkflowClientOptions | undefined} options - Additional options for configuring WorkflowRuntime. + */ + constructor(options: Partial = {}) { + const hostAddress = this.generateHostAddress(options); + options.daprApiToken = this.generateDaprApiToken(options); + this.worker = this.buildInnerWorker(hostAddress, options); + } + + private generateHostAddress(options: Partial): string { + const host = options?.clientHost ?? Settings.getDefaultHost(); + const port = options?.clientPort ?? Settings.getDefaultGrpcPort(); + const hostAddress = `${host}:${port}`; + return hostAddress; + } + + private generateDaprApiToken(options: Partial): string | undefined { + const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); + return daprApiToken; + } + + private buildInnerWorker(hostAddress: string, options: Partial): TaskHubGrpcWorker { + let innerOptions = options?.grpcOptions; + if (options.daprApiToken !== undefined && options.daprApiToken !== "") { + innerOptions = { + ...innerOptions, + interceptors: [generateApiTokenClientInterceptors(options), ...(innerOptions?.interceptors ?? [])], + }; } - this.worker = new TaskHubGrpcWorker(hostAddress, innerOptions); + return new TaskHubGrpcWorker(hostAddress, innerOptions); } /** diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index a24e90f9..18600307 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -20,15 +20,23 @@ import { WorkflowRuntimeStatus } from "../../../src/workflow/runtime/WorkflowRun import WorkflowActivityContext from "../../../src/workflow/runtime/WorkflowActivityContext"; import { Task } from "@microsoft/durabletask-js/task/task"; +const clientHost = "localhost"; +const clientPort = "4001"; + describe("Workflow", () => { - const grpcEndpoint = "localhost:4001"; let workflowClient: DaprWorkflowClient; let workflowRuntime: WorkflowRuntime; beforeEach(async () => { // Start a worker, which will connect to the sidecar in a background thread - workflowClient = new DaprWorkflowClient(grpcEndpoint); - workflowRuntime = new WorkflowRuntime(grpcEndpoint); + workflowClient = new DaprWorkflowClient({ + clientHost, + clientPort, + }); + workflowRuntime = new WorkflowRuntime({ + clientHost, + clientPort, + }); }); afterEach(async () => { @@ -175,7 +183,7 @@ describe("Workflow", () => { it("should be able to run an single timer", async () => { const delay = 3; const singleTimerWorkflow: TWorkflow = async function* (ctx: WorkflowContext, _: number): any { - // seems there is a issue from durabletask-sidecar. + // seems there is a issue from durabletask-sidecar. // TODO: Once transfer to durabletask-go, reset the timer yield ctx.createTimer(delay + 1); }; From bb7a01e0b2e6ec715824f24a9a3a4c51e17a350a Mon Sep 17 00:00:00 2001 From: kaibocai Date: Sun, 14 Jan 2024 17:16:13 -0600 Subject: [PATCH 13/18] update examples Signed-off-by: kaibocai --- examples/workflow/authoring/package-lock.json | 929 +----------------- .../authoring/src/activity-sequence.ts | 14 +- .../workflow/authoring/src/fanout-fanin.ts | 13 +- .../authoring/src/human-interaction.ts | 13 +- 4 files changed, 32 insertions(+), 937 deletions(-) diff --git a/examples/workflow/authoring/package-lock.json b/examples/workflow/authoring/package-lock.json index 0d2be66a..ebac4724 100644 --- a/examples/workflow/authoring/package-lock.json +++ b/examples/workflow/authoring/package-lock.json @@ -10,11 +10,9 @@ "license": "ISC", "dependencies": { "@dapr/dapr": "file:../../../build", - "@types/node": "^18.16.3", - "prompt-sync": "^4.2.0" + "@types/node": "^18.16.3" }, "devDependencies": { - "@inquirer/prompts": "^3.3.0", "readline-sync": "^1.4.10", "ts-node": "^10.9.1", "typescript": "^5.0.4" @@ -114,188 +112,6 @@ "resolved": "../../../build", "link": true }, - "node_modules/@inquirer/checkbox": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.5.0.tgz", - "integrity": "sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "figures": "^3.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/confirm": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.15.tgz", - "integrity": "sha512-hj8Q/z7sQXsF0DSpLQZVDhWYGN6KLM/gNjjqGkpKwBzljbQofGjn0ueHADy4HUY+OqDHmXuwk/bY+tZyIuuB0w==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-5.1.1.tgz", - "integrity": "sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg==", - "dev": true, - "dependencies": { - "@inquirer/type": "^1.1.5", - "@types/mute-stream": "^0.0.4", - "@types/node": "^20.9.0", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "cli-spinners": "^2.9.1", - "cli-width": "^4.1.0", - "figures": "^3.2.0", - "mute-stream": "^1.0.0", - "run-async": "^3.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/core/node_modules/@types/node": { - "version": "20.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", - "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@inquirer/editor": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-1.2.13.tgz", - "integrity": "sha512-gBxjqt0B9GLN0j6M/tkEcmcIvB2fo9Cw0f5NRqDTkYyB9AaCzj7qvgG0onQ3GVPbMyMbbP4tWYxrBOaOdKpzNA==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2", - "external-editor": "^3.1.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/expand": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-1.1.14.tgz", - "integrity": "sha512-yS6fJ8jZYAsxdxuw2c8XTFMTvMR1NxZAw3LxDaFnqh7BZ++wTQ6rSp/2gGJhMacdZ85osb+tHxjVgx7F+ilv5g==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2", - "figures": "^3.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/input": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-1.2.14.tgz", - "integrity": "sha512-tISLGpUKXixIQue7jypNEShrdzJoLvEvZOJ4QRsw5XTfrIYfoWFqAjMQLerGs9CzR86yAI89JR6snHmKwnNddw==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/password": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-1.1.14.tgz", - "integrity": "sha512-vL2BFxfMo8EvuGuZYlryiyAB3XsgtbxOcFs4H9WI9szAS/VZCAwdVqs8rqEeaAf/GV/eZOghIOYxvD91IsRWSg==", - "dev": true, - "dependencies": { - "@inquirer/input": "^1.2.14", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/prompts": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-3.3.0.tgz", - "integrity": "sha512-BBCqdSnhNs+WziSIo4f/RNDu6HAj4R/Q5nMgJb5MNPFX8sJGCvj9BoALdmR0HTWXyDS7TO8euKj6W6vtqCQG7A==", - "dev": true, - "dependencies": { - "@inquirer/checkbox": "^1.5.0", - "@inquirer/confirm": "^2.0.15", - "@inquirer/core": "^5.1.1", - "@inquirer/editor": "^1.2.13", - "@inquirer/expand": "^1.1.14", - "@inquirer/input": "^1.2.14", - "@inquirer/password": "^1.1.14", - "@inquirer/rawlist": "^1.2.14", - "@inquirer/select": "^1.3.1" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/rawlist": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-1.2.14.tgz", - "integrity": "sha512-xIYmDpYgfz2XGCKubSDLKEvadkIZAKbehHdWF082AyC2I4eHK44RUfXaoOAqnbqItZq4KHXS6jDJ78F2BmQvxg==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/select": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.1.tgz", - "integrity": "sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ==", - "dev": true, - "dependencies": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "figures": "^3.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/@inquirer/type": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.1.5.tgz", - "integrity": "sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA==", - "dev": true, - "engines": { - "node": ">=14.18.0" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -345,26 +161,11 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "18.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true - }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -386,112 +187,12 @@ "node": ">=0.4.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -507,131 +208,12 @@ "node": ">=0.3.1" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prompt-sync": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", - "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", - "dependencies": { - "strip-ansi": "^5.0.0" - } - }, - "node_modules/prompt-sync/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/prompt-sync/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/readline-sync": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", @@ -641,83 +223,6 @@ "node": ">= 0.8.0" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -761,18 +266,6 @@ } } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", @@ -786,32 +279,12 @@ "node": ">=12.20" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -868,157 +341,6 @@ "typescript": "^4.5.5" } }, - "@inquirer/checkbox": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-1.5.0.tgz", - "integrity": "sha512-3cKJkW1vIZAs4NaS0reFsnpAjP0azffYII4I2R7PTI7ZTMg5Y1at4vzXccOH3762b2c2L4drBhpJpf9uiaGNxA==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "figures": "^3.2.0" - } - }, - "@inquirer/confirm": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-2.0.15.tgz", - "integrity": "sha512-hj8Q/z7sQXsF0DSpLQZVDhWYGN6KLM/gNjjqGkpKwBzljbQofGjn0ueHADy4HUY+OqDHmXuwk/bY+tZyIuuB0w==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - } - }, - "@inquirer/core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-5.1.1.tgz", - "integrity": "sha512-IuJyZQUg75+L5AmopgnzxYrgcU6PJKL0hoIs332G1Gv55CnmZrhG6BzNOeZ5sOsTi1YCGOopw4rYICv74ejMFg==", - "dev": true, - "requires": { - "@inquirer/type": "^1.1.5", - "@types/mute-stream": "^0.0.4", - "@types/node": "^20.9.0", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "cli-spinners": "^2.9.1", - "cli-width": "^4.1.0", - "figures": "^3.2.0", - "mute-stream": "^1.0.0", - "run-async": "^3.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "@types/node": { - "version": "20.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", - "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - } - } - }, - "@inquirer/editor": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-1.2.13.tgz", - "integrity": "sha512-gBxjqt0B9GLN0j6M/tkEcmcIvB2fo9Cw0f5NRqDTkYyB9AaCzj7qvgG0onQ3GVPbMyMbbP4tWYxrBOaOdKpzNA==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2", - "external-editor": "^3.1.0" - } - }, - "@inquirer/expand": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-1.1.14.tgz", - "integrity": "sha512-yS6fJ8jZYAsxdxuw2c8XTFMTvMR1NxZAw3LxDaFnqh7BZ++wTQ6rSp/2gGJhMacdZ85osb+tHxjVgx7F+ilv5g==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2", - "figures": "^3.2.0" - } - }, - "@inquirer/input": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-1.2.14.tgz", - "integrity": "sha512-tISLGpUKXixIQue7jypNEShrdzJoLvEvZOJ4QRsw5XTfrIYfoWFqAjMQLerGs9CzR86yAI89JR6snHmKwnNddw==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - } - }, - "@inquirer/password": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-1.1.14.tgz", - "integrity": "sha512-vL2BFxfMo8EvuGuZYlryiyAB3XsgtbxOcFs4H9WI9szAS/VZCAwdVqs8rqEeaAf/GV/eZOghIOYxvD91IsRWSg==", - "dev": true, - "requires": { - "@inquirer/input": "^1.2.14", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2" - } - }, - "@inquirer/prompts": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-3.3.0.tgz", - "integrity": "sha512-BBCqdSnhNs+WziSIo4f/RNDu6HAj4R/Q5nMgJb5MNPFX8sJGCvj9BoALdmR0HTWXyDS7TO8euKj6W6vtqCQG7A==", - "dev": true, - "requires": { - "@inquirer/checkbox": "^1.5.0", - "@inquirer/confirm": "^2.0.15", - "@inquirer/core": "^5.1.1", - "@inquirer/editor": "^1.2.13", - "@inquirer/expand": "^1.1.14", - "@inquirer/input": "^1.2.14", - "@inquirer/password": "^1.1.14", - "@inquirer/rawlist": "^1.2.14", - "@inquirer/select": "^1.3.1" - } - }, - "@inquirer/rawlist": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-1.2.14.tgz", - "integrity": "sha512-xIYmDpYgfz2XGCKubSDLKEvadkIZAKbehHdWF082AyC2I4eHK44RUfXaoOAqnbqItZq4KHXS6jDJ78F2BmQvxg==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "chalk": "^4.1.2" - } - }, - "@inquirer/select": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-1.3.1.tgz", - "integrity": "sha512-EgOPHv7XOHEqiBwBJTyiMg9r57ySyW4oyYCumGp+pGyOaXQaLb2kTnccWI6NFd9HSi5kDJhF7YjA+3RfMQJ2JQ==", - "dev": true, - "requires": { - "@inquirer/core": "^5.1.1", - "@inquirer/type": "^1.1.5", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "figures": "^3.2.0" - } - }, - "@inquirer/type": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.1.5.tgz", - "integrity": "sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA==", - "dev": true - }, "@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -1065,26 +387,11 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, - "@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/node": { "version": "18.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==" }, - "@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true - }, "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -1097,79 +404,12 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true - }, - "cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1182,162 +422,18 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true - }, - "prompt-sync": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", - "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", - "requires": { - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "readline-sync": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", "dev": true }, - "run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -1359,41 +455,18 @@ "yn": "3.1.1" } }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, "typescript": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, - "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index 4d4cd28c..f4c2d30b 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -14,9 +14,17 @@ limitations under the License. import { DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowRuntime, TWorkflow } from "@dapr/dapr"; async function start() { - const grpcEndpoint = "localhost:50001"; - const workflowClient = new DaprWorkflowClient(grpcEndpoint); - const workflowRuntime = new WorkflowRuntime(grpcEndpoint); + // Update the gRPC client and worker to use a local address and port + const clientHost = "localhost"; + const clientPort = "50001" + const workflowClient = new DaprWorkflowClient({ + clientHost, + clientPort, + }); + const workflowRuntime = new WorkflowRuntime({ + clientHost, + clientPort, + }); const hello = async (_: WorkflowActivityContext, name: string) => { return `Hello ${name}!`; diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index a608d683..bc8793b3 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -23,9 +23,16 @@ import { // Wrap the entire code in an immediately-invoked async function async function start() { // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:50001"; - const workflowClient: DaprWorkflowClient = new DaprWorkflowClient(grpcServerAddress); - const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + const clientHost = "localhost"; + const clientPort = "50001" + const workflowClient = new DaprWorkflowClient({ + clientHost, + clientPort, + }); + const workflowRuntime = new WorkflowRuntime({ + clientHost, + clientPort, + }); function getRandomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index c533c188..61f06e9d 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -39,9 +39,16 @@ async function start() { } // Update the gRPC client and worker to use a local address and port - const grpcServerAddress = "localhost:50001"; - const workflowClient: DaprWorkflowClient = new DaprWorkflowClient(grpcServerAddress); - const workflowRuntime: WorkflowRuntime = new WorkflowRuntime(grpcServerAddress); + const clientHost = "localhost"; + const clientPort = "50001" + const workflowClient = new DaprWorkflowClient({ + clientHost, + clientPort, + }); + const workflowRuntime = new WorkflowRuntime({ + clientHost, + clientPort, + }); //Activity function that sends an approval request to the manager const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { From c95ba7734c98e4620e08227cde213d2521e5a9bb Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 15 Jan 2024 08:27:11 -0600 Subject: [PATCH 14/18] minor updates on comments Signed-off-by: kaibocai --- .../authoring/src/activity-sequence.ts | 12 ++++++------ .../workflow/authoring/src/fanout-fanin.ts | 12 ++++++------ .../authoring/src/human-interaction.ts | 16 ++++++++-------- src/types/workflow/WorkflowClientOption.ts | 8 ++++---- src/workflow/client/DaprWorkflowClient.ts | 19 ++++++++++--------- src/workflow/runtime/WorkflowRuntime.ts | 19 ++++++++++--------- test/e2e/workflow/workflow.test.ts | 8 ++++---- 7 files changed, 48 insertions(+), 46 deletions(-) diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index f4c2d30b..f8bb6a6e 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -15,15 +15,15 @@ import { DaprWorkflowClient, WorkflowActivityContext, WorkflowContext, WorkflowR async function start() { // Update the gRPC client and worker to use a local address and port - const clientHost = "localhost"; - const clientPort = "50001" + const daprHost = "localhost"; + const daprPort = "50001"; const workflowClient = new DaprWorkflowClient({ - clientHost, - clientPort, + daprHost, + daprPort, }); const workflowRuntime = new WorkflowRuntime({ - clientHost, - clientPort, + daprHost, + daprPort, }); const hello = async (_: WorkflowActivityContext, name: string) => { diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index bc8793b3..efa844b0 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -23,15 +23,15 @@ import { // Wrap the entire code in an immediately-invoked async function async function start() { // Update the gRPC client and worker to use a local address and port - const clientHost = "localhost"; - const clientPort = "50001" + const daprHost = "localhost"; + const daprPort = "50001"; const workflowClient = new DaprWorkflowClient({ - clientHost, - clientPort, + daprHost, + daprPort, }); const workflowRuntime = new WorkflowRuntime({ - clientHost, - clientPort, + daprHost, + daprPort, }); function getRandomInt(min: number, max: number): number { diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index 61f06e9d..5b67ff27 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -39,18 +39,18 @@ async function start() { } // Update the gRPC client and worker to use a local address and port - const clientHost = "localhost"; - const clientPort = "50001" + const daprHost = "localhost"; + const daprPort = "50001"; const workflowClient = new DaprWorkflowClient({ - clientHost, - clientPort, + daprHost, + daprPort, }); const workflowRuntime = new WorkflowRuntime({ - clientHost, - clientPort, + daprHost, + daprPort, }); - //Activity function that sends an approval request to the manager + // Activity function that sends an approval request to the manager const sendApprovalRequest = async (_: WorkflowActivityContext, order: Order) => { // Simulate some work that takes an amount of time await sleep(3000); @@ -111,7 +111,7 @@ async function start() { const id = await workflowClient.scheduleNewWorkflow(purchaseOrderWorkflow, order); console.log(`Orchestration scheduled with ID: ${id}`); - //prompt for approval asynchronously + // prompt for approval asynchronously promptForApproval(approver, workflowClient, id); // Wait for orchestration completion diff --git a/src/types/workflow/WorkflowClientOption.ts b/src/types/workflow/WorkflowClientOption.ts index 8372c8be..6b7f98d0 100644 --- a/src/types/workflow/WorkflowClientOption.ts +++ b/src/types/workflow/WorkflowClientOption.ts @@ -19,13 +19,13 @@ export type WorkflowClientOptions = { * Host location of the Dapr sidecar. * Default is 127.0.0.1. */ - clientHost: string; + daprHost: string; /** - * Port of the Dapr sidecar. - * Default is 4001. + * Port of the Dapr sidecar running a gRPC server. + * Default is 50001. */ - clientPort: string; + daprPort: string; /** * Options related to logging. diff --git a/src/workflow/client/DaprWorkflowClient.ts b/src/workflow/client/DaprWorkflowClient.ts index 8dea7f7e..af209356 100644 --- a/src/workflow/client/DaprWorkflowClient.ts +++ b/src/workflow/client/DaprWorkflowClient.ts @@ -18,6 +18,7 @@ import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; import { Settings } from "../../utils/Settings.util"; import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; +import { GrpcEndpoint } from "../../network/GrpcEndpoint"; /** * Class that defines client operations for managing workflow instances. @@ -33,19 +34,19 @@ export default class DaprWorkflowClient { * @param {WorkflowClientOptions | undefined} options - Additional options for configuring DaprWorkflowClient. */ constructor(options: Partial = {}) { - const hostAddress = this.generateHostAddress(options); - options.daprApiToken = this.generateDaprApiToken(options); - this._innerClient = this.buildInnerClient(hostAddress, options); + const grpcEndpoint = this.generateEndpoint(options); + options.daprApiToken = this.getDaprApiToken(options); + this._innerClient = this.buildInnerClient(grpcEndpoint.endpoint, options); } - private generateHostAddress(options: Partial): string { - const host = options?.clientHost ?? Settings.getDefaultHost(); - const port = options?.clientPort ?? Settings.getDefaultGrpcPort(); - const hostAddress = `${host}:${port}`; - return hostAddress; + private generateEndpoint(options: Partial): GrpcEndpoint { + const host = options?.daprHost ?? Settings.getDefaultHost(); + const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); + const uri = `${host}:${port}`; + return new GrpcEndpoint(uri); } - private generateDaprApiToken(options: Partial): string | undefined { + private getDaprApiToken(options: Partial): string | undefined { const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); return daprApiToken; } diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index baa4f43a..23b1e890 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -21,6 +21,7 @@ import { generateApiTokenClientInterceptors } from "../internal/index"; import { getFunctionName } from "../internal"; import { Settings } from "../../utils/Settings.util"; import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; +import { GrpcEndpoint } from "../../network/GrpcEndpoint"; /** * Contains methods to register workflows and activities. @@ -33,19 +34,19 @@ export default class WorkflowRuntime { * @param {WorkflowClientOptions | undefined} options - Additional options for configuring WorkflowRuntime. */ constructor(options: Partial = {}) { - const hostAddress = this.generateHostAddress(options); - options.daprApiToken = this.generateDaprApiToken(options); - this.worker = this.buildInnerWorker(hostAddress, options); + const grpcEndpoint = this.generateEndpoint(options); + options.daprApiToken = this.getDaprApiToken(options); + this.worker = this.buildInnerWorker(grpcEndpoint.endpoint, options); } - private generateHostAddress(options: Partial): string { - const host = options?.clientHost ?? Settings.getDefaultHost(); - const port = options?.clientPort ?? Settings.getDefaultGrpcPort(); - const hostAddress = `${host}:${port}`; - return hostAddress; + private generateEndpoint(options: Partial): GrpcEndpoint { + const host = options?.daprHost ?? Settings.getDefaultHost(); + const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); + const uri = `${host}:${port}`; + return new GrpcEndpoint(uri); } - private generateDaprApiToken(options: Partial): string | undefined { + private getDaprApiToken(options: Partial): string | undefined { const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); return daprApiToken; } diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index 18600307..d423fdcd 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -30,12 +30,12 @@ describe("Workflow", () => { beforeEach(async () => { // Start a worker, which will connect to the sidecar in a background thread workflowClient = new DaprWorkflowClient({ - clientHost, - clientPort, + daprHost: clientHost, + daprPort: clientPort, }); workflowRuntime = new WorkflowRuntime({ - clientHost, - clientPort, + daprHost: clientHost, + daprPort: clientPort, }); }); From 41c678597fa58a3027d0d554a6e6c545fc2a15e8 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 15 Jan 2024 08:51:53 -0600 Subject: [PATCH 15/18] remove duplications Signed-off-by: kaibocai --- .../authoring/src/activity-sequence.ts | 3 +++ .../workflow/authoring/src/fanout-fanin.ts | 3 +++ .../authoring/src/human-interaction.ts | 3 +++ src/workflow/client/DaprWorkflowClient.ts | 20 +++----------- src/workflow/internal/index.ts | 26 +++++++++++++++++++ src/workflow/runtime/WorkflowRuntime.ts | 20 +++----------- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/examples/workflow/authoring/src/activity-sequence.ts b/examples/workflow/authoring/src/activity-sequence.ts index f8bb6a6e..97d5c8e7 100644 --- a/examples/workflow/authoring/src/activity-sequence.ts +++ b/examples/workflow/authoring/src/activity-sequence.ts @@ -68,6 +68,9 @@ async function start() { await workflowRuntime.stop(); await workflowClient.stop(); + + // stop the dapr side car + process.exit(0); } start().catch((e) => { diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index efa844b0..65f64274 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -100,6 +100,9 @@ async function start() { // stop worker and client await workflowRuntime.stop(); await workflowClient.stop(); + + // stop the dapr side car + process.exit(0); } start().catch((e) => { diff --git a/examples/workflow/authoring/src/human-interaction.ts b/examples/workflow/authoring/src/human-interaction.ts index 5b67ff27..bff52cc8 100644 --- a/examples/workflow/authoring/src/human-interaction.ts +++ b/examples/workflow/authoring/src/human-interaction.ts @@ -125,6 +125,9 @@ async function start() { // stop worker and client await workflowRuntime.stop(); await workflowClient.stop(); + + // stop the dapr side car + process.exit(0); } async function promptForApproval(approver: string, workflowClient: DaprWorkflowClient, id: string) { diff --git a/src/workflow/client/DaprWorkflowClient.ts b/src/workflow/client/DaprWorkflowClient.ts index af209356..4164794e 100644 --- a/src/workflow/client/DaprWorkflowClient.ts +++ b/src/workflow/client/DaprWorkflowClient.ts @@ -13,12 +13,10 @@ limitations under the License. import { TaskHubGrpcClient } from "@microsoft/durabletask-js"; import { WorkflowState } from "./WorkflowState"; -import { generateApiTokenClientInterceptors } from "../internal/index"; +import { generateApiTokenClientInterceptors, generateEndpoint, getDaprApiToken } from "../internal/index"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import { getFunctionName } from "../internal"; -import { Settings } from "../../utils/Settings.util"; import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; -import { GrpcEndpoint } from "../../network/GrpcEndpoint"; /** * Class that defines client operations for managing workflow instances. @@ -34,23 +32,11 @@ export default class DaprWorkflowClient { * @param {WorkflowClientOptions | undefined} options - Additional options for configuring DaprWorkflowClient. */ constructor(options: Partial = {}) { - const grpcEndpoint = this.generateEndpoint(options); - options.daprApiToken = this.getDaprApiToken(options); + const grpcEndpoint = generateEndpoint(options); + options.daprApiToken = getDaprApiToken(options); this._innerClient = this.buildInnerClient(grpcEndpoint.endpoint, options); } - private generateEndpoint(options: Partial): GrpcEndpoint { - const host = options?.daprHost ?? Settings.getDefaultHost(); - const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); - const uri = `${host}:${port}`; - return new GrpcEndpoint(uri); - } - - private getDaprApiToken(options: Partial): string | undefined { - const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); - return daprApiToken; - } - private buildInnerClient(hostAddress: string, options: Partial): TaskHubGrpcClient { let innerOptions = options?.grpcOptions; if (options.daprApiToken !== undefined && options.daprApiToken !== "") { diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index e912f479..dfeb8030 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -16,6 +16,8 @@ import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TWorkflow } from "../../types/workflow/Workflow.type"; import * as grpc from "@grpc/grpc-js"; import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; +import { Settings } from "../../utils/Settings.util"; +import { GrpcEndpoint } from "../../network/GrpcEndpoint"; /** * Gets the name of a function from its definition or string representation. @@ -31,6 +33,30 @@ export function getFunctionName(fn: TWorkflow | TWorkflowActivity): GrpcEndpoint { + const host = options?.daprHost ?? Settings.getDefaultHost(); + const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); + const uri = `${host}:${port}`; + return new GrpcEndpoint(uri); +} + +/** + * Gets the Dapr API token based on the provided options or defaults. + * + * @param options - An object containing optional settings for the WorkflowClient. + * @returns A string representing the Dapr API token, or undefined if not set. + */ +export function getDaprApiToken(options: Partial): string | undefined { + const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); + return daprApiToken; +} + /** * Generates a gRPC interceptor function that adds a Dapr API token to the metadata if not already present. * This interceptor is intended for use with gRPC client calls. diff --git a/src/workflow/runtime/WorkflowRuntime.ts b/src/workflow/runtime/WorkflowRuntime.ts index 23b1e890..bafcbd30 100644 --- a/src/workflow/runtime/WorkflowRuntime.ts +++ b/src/workflow/runtime/WorkflowRuntime.ts @@ -17,11 +17,9 @@ import { TWorkflowActivity } from "../../types/workflow/Activity.type"; import { TInput, TOutput } from "../../types/workflow/InputOutput.type"; import WorkflowActivityContext from "./WorkflowActivityContext"; import WorkflowContext from "./WorkflowContext"; -import { generateApiTokenClientInterceptors } from "../internal/index"; +import { generateApiTokenClientInterceptors, generateEndpoint, getDaprApiToken } from "../internal/index"; import { getFunctionName } from "../internal"; -import { Settings } from "../../utils/Settings.util"; import { WorkflowClientOptions } from "../../types/workflow/WorkflowClientOption"; -import { GrpcEndpoint } from "../../network/GrpcEndpoint"; /** * Contains methods to register workflows and activities. @@ -34,23 +32,11 @@ export default class WorkflowRuntime { * @param {WorkflowClientOptions | undefined} options - Additional options for configuring WorkflowRuntime. */ constructor(options: Partial = {}) { - const grpcEndpoint = this.generateEndpoint(options); - options.daprApiToken = this.getDaprApiToken(options); + const grpcEndpoint = generateEndpoint(options); + options.daprApiToken = getDaprApiToken(options); this.worker = this.buildInnerWorker(grpcEndpoint.endpoint, options); } - private generateEndpoint(options: Partial): GrpcEndpoint { - const host = options?.daprHost ?? Settings.getDefaultHost(); - const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); - const uri = `${host}:${port}`; - return new GrpcEndpoint(uri); - } - - private getDaprApiToken(options: Partial): string | undefined { - const daprApiToken = options?.daprApiToken ?? Settings.getDefaultApiToken(); - return daprApiToken; - } - private buildInnerWorker(hostAddress: string, options: Partial): TaskHubGrpcWorker { let innerOptions = options?.grpcOptions; if (options.daprApiToken !== undefined && options.daprApiToken !== "") { From 50b4b7dce7f9be09e83bdb1b38f7f426c7962070 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 15 Jan 2024 09:30:39 -0600 Subject: [PATCH 16/18] add docs Signed-off-by: kaibocai --- .../en/js-sdk-docs/js-workflow/_index.md | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 daprdocs/content/en/js-sdk-docs/js-workflow/_index.md diff --git a/daprdocs/content/en/js-sdk-docs/js-workflow/_index.md b/daprdocs/content/en/js-sdk-docs/js-workflow/_index.md new file mode 100644 index 00000000..59431545 --- /dev/null +++ b/daprdocs/content/en/js-sdk-docs/js-workflow/_index.md @@ -0,0 +1,162 @@ +--- +type: docs +title: "How to: Author and manage Dapr Workflow in the JavaScript SDK" +linkTitle: "How to: Author and manage workflows" +weight: 20000 +description: How to get up and running with workflows using the Dapr JavaScript SDK +--- + +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + +Let’s create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/js-sdk/tree/main/examples/workflow), you will: + +- Execute the workflow instance using the [JavaScript workflow worker](https://github.com/dapr/js-sdk/tree/main/src/workflow/runtime/WorkflowRuntime.ts) +- Utilize the JavaScript workflow client and API calls to [start and terminate workflow instances](https://github.com/dapr/js-sdk/tree/main/src/workflow/client/DaprWorkflowClient.ts) + +This example uses the default configuration from `dapr init` in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). + +## Prerequisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + +- Verify you're using the latest proto bindings + +## Set up the environment + +Clone the JavaScript SDK repo and navigate into it. + +```bash +git clone https://github.com/dapr/js-sdk +cd js-sdk +``` + +From the JavaScript SDK root directory, navigate to the Dapr Workflow example. + +```bash +cd examples/workflow/authoring +``` + +Run the following command to install the requirements for running this workflow sample with the Dapr JavaScript SDK. + +```bash +npm install +``` + +## Run the `activity-sequence.ts` + +The `activity-sequence` file registers a workflow and an activity with the Dapr Workflow runtime. The workflow is a sequence of activities that are executed in order. We use DaprWorkflowClient to schedule a new workflow instance and wait for it to complete. + +```typescript +const daprHost = "localhost"; +const daprPort = "50001"; +const workflowClient = new DaprWorkflowClient({ + daprHost, + daprPort, +}); +const workflowRuntime = new WorkflowRuntime({ + daprHost, + daprPort, +}); + +const hello = async (_: WorkflowActivityContext, name: string) => { + return `Hello ${name}!`; +}; + +const sequence: TWorkflow = async function* (ctx: WorkflowContext): any { + const cities: string[] = []; + + const result1 = yield ctx.callActivity(hello, "Tokyo"); + cities.push(result1); + const result2 = yield ctx.callActivity(hello, "Seattle"); + cities.push(result2); + const result3 = yield ctx.callActivity(hello, "London"); + cities.push(result3); + + return cities; +}; + +workflowRuntime.registerWorkflow(sequence).registerActivity(hello); + +// Wrap the worker startup in a try-catch block to handle any errors during startup +try { + await workflowRuntime.start(); + console.log("Workflow runtime started successfully"); +} catch (error) { + console.error("Error starting workflow runtime:", error); +} + +// Schedule a new orchestration +try { + const id = await workflowClient.scheduleNewWorkflow(sequence); + console.log(`Orchestration scheduled with ID: ${id}`); + + // Wait for orchestration completion + const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30); + + console.log(`Orchestration completed! Result: ${state?.serializedOutput}`); +} catch (error) { + console.error("Error scheduling or waiting for orchestration:", error); +} +``` + +In the code above: + +- `workflowRuntime.registerWorkflow(sequence)` registers `sequence` as a workflow in the Dapr Workflow runtime. +- `await workflowRuntime.start();` builds and starts the engine within the Dapr Workflow runtime. +- `await workflowClient.scheduleNewWorkflow(sequence)` schedules a new workflow instance with the Dapr Workflow runtime. +- `await workflowClient.waitForWorkflowCompletion(id, undefined, 30)` waits for the workflow instance to complete. + +In the terminal, execute the following command to kick off the `activity-sequence.ts`: + +```sh +npm run start:dapr:activity-sequence +``` + +**Expected output** + +``` +You're up and running! Both Dapr and your app logs will appear here. + +... + +== APP == Orchestration scheduled with ID: dc040bea-6436-4051-9166-c9294f9d2201 +== APP == Waiting 30 seconds for instance dc040bea-6436-4051-9166-c9294f9d2201 to complete... +== APP == Received "Orchestrator Request" work item with instance id 'dc040bea-6436-4051-9166-c9294f9d2201' +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Rebuilding local state with 0 history event... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1] +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Waiting for 1 task(s) and 0 event(s) to complete... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Returning 1 action(s) +== APP == Received "Activity Request" work item +== APP == Activity hello completed with output "Hello Tokyo!" (14 chars) +== APP == Received "Orchestrator Request" work item with instance id 'dc040bea-6436-4051-9166-c9294f9d2201' +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Rebuilding local state with 3 history event... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1] +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Waiting for 1 task(s) and 0 event(s) to complete... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Returning 1 action(s) +== APP == Received "Activity Request" work item +== APP == Activity hello completed with output "Hello Seattle!" (16 chars) +== APP == Received "Orchestrator Request" work item with instance id 'dc040bea-6436-4051-9166-c9294f9d2201' +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Rebuilding local state with 6 history event... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1] +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Waiting for 1 task(s) and 0 event(s) to complete... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Returning 1 action(s) +== APP == Received "Activity Request" work item +== APP == Activity hello completed with output "Hello London!" (15 chars) +== APP == Received "Orchestrator Request" work item with instance id 'dc040bea-6436-4051-9166-c9294f9d2201' +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Rebuilding local state with 9 history event... +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1] +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Orchestration completed with status COMPLETED +== APP == dc040bea-6436-4051-9166-c9294f9d2201: Returning 1 action(s) +INFO[0006] dc040bea-6436-4051-9166-c9294f9d2201: 'sequence' completed with a COMPLETED status. app_id=activity-sequence-workflow instance=kaibocai-devbox scope=wfengine.backend type=log ver=1.12.3 +== APP == Instance dc040bea-6436-4051-9166-c9294f9d2201 completed +== APP == Orchestration completed! Result: ["Hello Tokyo!","Hello Seattle!","Hello London!"] +``` + +## Next steps + +- [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) +- [Workflow API reference]({{< ref workflow_api.md >}}) From 6fb65443fbb38a528a30f7289c41a20479b29511 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Tue, 16 Jan 2024 07:59:38 -0600 Subject: [PATCH 17/18] fix warnings - remove unused dependencies Signed-off-by: kaibocai --- package-lock.json | 100 ++++++++++++++++++++--------- package.json | 2 - src/workflow/internal/index.ts | 10 ++- test/e2e/workflow/workflow.test.ts | 4 +- 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ad65d63..beae1bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "@types/express": "^4.17.15", "@types/jest": "^27.0.1", "@types/node": "^16.9.1", - "@types/readline-sync": "^1.4.8", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", @@ -40,7 +39,6 @@ "prettier": "^2.4.0", "pretty-quick": "^3.1.3", "ts-jest": "^27.0.5", - "ts-node": "^10.9.1", "typescript": "^4.5.5" } }, @@ -711,6 +709,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -723,6 +723,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1614,25 +1616,33 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.2", @@ -1822,12 +1832,6 @@ "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "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/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -2276,7 +2280,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/argparse": { "version": "2.0.1", @@ -2811,7 +2817,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -2988,6 +2996,8 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -7037,6 +7047,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -7080,6 +7092,8 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -7273,7 +7287,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -7516,6 +7532,8 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -8045,6 +8063,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -8054,6 +8074,8 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -8704,25 +8726,33 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@types/babel__core": { "version": "7.20.2", @@ -8912,12 +8942,6 @@ "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "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/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", @@ -9220,7 +9244,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "argparse": { "version": "2.0.1", @@ -9613,7 +9639,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "cross-spawn": { "version": "7.0.3", @@ -9743,7 +9771,9 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "diff-sequences": { "version": "27.5.1", @@ -12780,6 +12810,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -12800,7 +12832,9 @@ "version": "8.3.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", - "dev": true + "dev": true, + "optional": true, + "peer": true } } }, @@ -12932,7 +12966,9 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "v8-to-istanbul": { "version": "8.1.1", @@ -13121,7 +13157,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 40e8d414..94880528 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "@types/express": "^4.17.15", "@types/jest": "^27.0.1", "@types/node": "^16.9.1", - "@types/readline-sync": "^1.4.8", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^5.1.0", "@typescript-eslint/parser": "^5.1.0", @@ -77,7 +76,6 @@ "prettier": "^2.4.0", "pretty-quick": "^3.1.3", "ts-jest": "^27.0.5", - "ts-node": "^10.9.1", "typescript": "^4.5.5" }, "repository": { diff --git a/src/workflow/internal/index.ts b/src/workflow/internal/index.ts index dfeb8030..a7549e54 100644 --- a/src/workflow/internal/index.ts +++ b/src/workflow/internal/index.ts @@ -30,7 +30,15 @@ import { GrpcEndpoint } from "../../network/GrpcEndpoint"; * @typeparam TOutput - The type of the output for the workflow activity. */ export function getFunctionName(fn: TWorkflow | TWorkflowActivity): string { - return fn.name || fn.toString().match(/function\s*([^(]*)\(/)![1]; + if (fn.name) { + return fn.name; + } else { + const match = fn.toString().match(/function\s*([^(]*)\(/); + if (match === null) { + throw new Error("Unable to determine function name, try to sepecify the workflow/activity name explicitly."); + } + return match[1]; + } } /** diff --git a/test/e2e/workflow/workflow.test.ts b/test/e2e/workflow/workflow.test.ts index d423fdcd..373a0cbe 100644 --- a/test/e2e/workflow/workflow.test.ts +++ b/test/e2e/workflow/workflow.test.ts @@ -199,7 +199,7 @@ describe("Workflow", () => { expectedCompletionSecond += delay * 1000; } expect(expectedCompletionSecond).toBeDefined(); - const actualCompletionSecond = state?.lastUpdatedAt?.getTime(); + const actualCompletionSecond = state?.lastUpdatedAt?.getTime() ?? 0; expect(actualCompletionSecond).toBeDefined(); expect(state).toBeDefined(); @@ -209,7 +209,7 @@ describe("Workflow", () => { expect(state?.runtimeStatus).toEqual(WorkflowRuntimeStatus.COMPLETED); expect(state?.createdAt).toBeDefined(); expect(state?.lastUpdatedAt).toBeDefined(); - expect(expectedCompletionSecond).toBeLessThanOrEqual(actualCompletionSecond!); + expect(expectedCompletionSecond).toBeLessThanOrEqual(actualCompletionSecond); }, 31000); it("should wait for external events with a timeout - true", async () => { From fb4ef7fcd100cd1246e4cc7cef3ec92b56c454c6 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Fri, 19 Jan 2024 07:43:33 -0600 Subject: [PATCH 18/18] minor updates Signed-off-by: kaibocai --- examples/workflow/authoring/src/fanout-fanin.ts | 2 ++ src/workflow/client/DaprWorkflowClient.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/workflow/authoring/src/fanout-fanin.ts b/examples/workflow/authoring/src/fanout-fanin.ts index 65f64274..b74171f0 100644 --- a/examples/workflow/authoring/src/fanout-fanin.ts +++ b/examples/workflow/authoring/src/fanout-fanin.ts @@ -58,6 +58,8 @@ async function start() { await sleep(sleepTime); // Return a result for the given work item, which is also a random number in this case + // For more information about random numbers in workflow please check + // https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-code-constraints?tabs=csharp#random-numbers return Math.floor(Math.random() * 11); } diff --git a/src/workflow/client/DaprWorkflowClient.ts b/src/workflow/client/DaprWorkflowClient.ts index 4164794e..8b761610 100644 --- a/src/workflow/client/DaprWorkflowClient.ts +++ b/src/workflow/client/DaprWorkflowClient.ts @@ -143,7 +143,7 @@ export default class DaprWorkflowClient { * @param {string} workflowInstanceId - The unique identifier of the workflow instance to wait for. * @param {boolean} fetchPayloads - Indicates whether to fetch the workflow instance's * inputs, outputs (true) or omit them (false). - * @param {number} timeoutInSeconds - The amount of time, in seconds, to wait for the workflow instance to start. + * @param {number} timeoutInSeconds - The amount of time, in seconds, to wait for the workflow instance to complete. Defaults to 60 seconds. * @returns {Promise} A Promise that resolves to the workflow instance metadata * or undefined if no such instance is found. */