From 48eee90e5d7ea1cb0cd16dcffdff475e5c34291e Mon Sep 17 00:00:00 2001 From: tmorin Date: Wed, 22 Dec 2021 17:25:56 +0100 Subject: [PATCH] feat(ceb-messaging-moleculer): add an implementation which leverage on Moleculer --- .idea/ceb.iml | 1 + README.md | 1 + README.typedoc.md | 1 + book/SUMMARY.md | 2 +- book/messaging/implementation.md | 50 -- book/messaging/implementations.md | 89 +++ book/packages.md | 1 + lerna.json | 1 + .../ceb-messaging-adapter-purify/package.json | 3 +- packages/ceb-messaging-moleculer/README.md | 24 + .../ceb-messaging-moleculer/package-lock.json | 742 ++++++++++++++++++ packages/ceb-messaging-moleculer/package.json | 67 ++ .../src/command.spec.ts | 158 ++++ .../ceb-messaging-moleculer/src/command.ts | 212 +++++ .../ceb-messaging-moleculer/src/common.ts | 4 + .../ceb-messaging-moleculer/src/event.spec.ts | 94 +++ packages/ceb-messaging-moleculer/src/event.ts | 200 +++++ .../src/gateway.spec.ts | 69 ++ .../ceb-messaging-moleculer/src/gateway.ts | 50 ++ packages/ceb-messaging-moleculer/src/index.ts | 5 + .../ceb-messaging-moleculer/src/query.spec.ts | 78 ++ packages/ceb-messaging-moleculer/src/query.ts | 162 ++++ .../tsconfig.build.json | 6 + .../ceb-messaging-moleculer/tsconfig.json | 6 + packages/ceb-messaging-simple/README.md | 4 +- packages/ceb-messaging-simple/package.json | 3 +- .../ceb-messaging-simple/src/command.spec.ts | 12 +- packages/ceb-messaging-simple/src/command.ts | 2 +- packages/ceb-messaging-simple/src/gateway.ts | 2 +- 29 files changed, 1985 insertions(+), 64 deletions(-) delete mode 100644 book/messaging/implementation.md create mode 100644 book/messaging/implementations.md create mode 100644 packages/ceb-messaging-moleculer/README.md create mode 100644 packages/ceb-messaging-moleculer/package-lock.json create mode 100644 packages/ceb-messaging-moleculer/package.json create mode 100644 packages/ceb-messaging-moleculer/src/command.spec.ts create mode 100644 packages/ceb-messaging-moleculer/src/command.ts create mode 100644 packages/ceb-messaging-moleculer/src/common.ts create mode 100644 packages/ceb-messaging-moleculer/src/event.spec.ts create mode 100644 packages/ceb-messaging-moleculer/src/event.ts create mode 100644 packages/ceb-messaging-moleculer/src/gateway.spec.ts create mode 100644 packages/ceb-messaging-moleculer/src/gateway.ts create mode 100644 packages/ceb-messaging-moleculer/src/index.ts create mode 100644 packages/ceb-messaging-moleculer/src/query.spec.ts create mode 100644 packages/ceb-messaging-moleculer/src/query.ts create mode 100644 packages/ceb-messaging-moleculer/tsconfig.build.json create mode 100644 packages/ceb-messaging-moleculer/tsconfig.json diff --git a/.idea/ceb.iml b/.idea/ceb.iml index 99ad31a8..a99e86e6 100644 --- a/.idea/ceb.iml +++ b/.idea/ceb.iml @@ -22,6 +22,7 @@ + diff --git a/README.md b/README.md index 378d4cca..ca5404a6 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ A built-in implementation of the Event/Message architecture: - [ceb-messaging-core](./packages/ceb-messaging-core) - [ceb-messaging-inversion](./packages/ceb-messaging-inversion) +- [ceb-messaging-moleculer](./packages/ceb-messaging-moleculer) - [ceb-messaging-simple](./packages/ceb-messaging-simple) - [ceb-messaging-simple-builder](./packages/ceb-messaging-simple-builder) - [ceb-messaging-builder-core](./packages/ceb-messaging-builder-core) diff --git a/README.typedoc.md b/README.typedoc.md index 642c61eb..d135a3e7 100644 --- a/README.typedoc.md +++ b/README.typedoc.md @@ -37,6 +37,7 @@ A built-in implementation of the Event/Message architecture: - [ceb-messaging-core](modules/_tmorin_ceb_messaging_core.html) - [ceb-messaging-inversion](modules/_tmorin_ceb_messaging_inversion.html) +- [ceb-messaging-moleculer](modules/_tmorin_ceb_messaging_moleculer.html) - [ceb-messaging-simple](modules/_tmorin_ceb_messaging_simple.html) - [ceb-messaging-simple-builder](modules/_tmorin_ceb_messaging_simple_builder.html) - [ceb-messaging-simple-inversion](modules/_tmorin_ceb_messaging_simple_inversion.html) diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 15422c44..dc707dca 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -11,7 +11,7 @@ - [Messages](messaging/messages.md) - [Gateway](messaging/gateway.md) - [Inversion integration](messaging/inversion.md) - - [Reference Implementation](messaging/implementation.md) + - [Implementations](messaging/implementations.md) - [Adapters](messaging/adapters.md) - [Elements](elements/README.md) - [ElementBuilder](elements/ElementBuilder.md) diff --git a/book/messaging/implementation.md b/book/messaging/implementation.md deleted file mode 100644 index 2204bb27..00000000 --- a/book/messaging/implementation.md +++ /dev/null @@ -1,50 +0,0 @@ -# Reference Implementation - -> The reference implementation is defined in the NPM package [@tmorin/ceb-messaging-simple](https://www.npmjs.com/package/@tmorin/ceb-messaging-simple). - -The reference implementation relies on an in-memory and single process approach. -So that, the implementation is free of network or any other concerns related to distributed systems. - -## The SimpleGateway - -A SimpleGateway instance can be got from the following three approaches: the global instance, the factory method or the constructor. - -### The global instance - -A global instance of the SimpleGateway is available from the static field `SimpleGateway.GOBAL`. -It's a lazy property, in fact the instance is only created once at its first get. - -```typescript -{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_global.ts}} -``` - -### The factory method - -A SimpleGateway instance can be easily created using the factory method, i.e. the static method `SimpleGateway.create()`. -The method returns a fresh new SimpleGateway instance at each call. - -```typescript -{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_factory.ts}} -``` - -### The constructor - -The constructor approach provides a fine grain control of the Gateway dependencies: the CommandBus, the QueryBus, the EventBus and the GatewayObserver. - -```typescript -{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_constructor.ts}} -``` - -## The Inversion Module - -The package provides an Inversion Module which can be used to create (optionally) and publish the SimpleGateway instance on the registry. - -Create a container with the default module behavior, i.e. the SimpleGateway will be created from scratch automatically: -```typescript -{{#include ../../packages/ceb-book-samples/src/messaging/implementation-inversion-default.ts}} -``` - -Create a container with a provided SimpleGateway instance: -```typescript -{{#include ../../packages/ceb-book-samples/src/messaging/implementation-inversion-global.ts}} -``` diff --git a/book/messaging/implementations.md b/book/messaging/implementations.md new file mode 100644 index 00000000..f429e91c --- /dev/null +++ b/book/messaging/implementations.md @@ -0,0 +1,89 @@ +# Implementations + +## The reference implementation + +> The reference implementation is defined in the NPM package [@tmorin/ceb-messaging-simple](https://www.npmjs.com/package/@tmorin/ceb-messaging-simple). + +The reference implementation relies on an in-memory and single process approach. +So that, the implementation is free of network or any other concerns related to distributed systems. + +### The SimpleGateway + +A SimpleGateway instance can be got from the following three approaches: the global instance, the factory method or the constructor. + +#### The global instance + +A global instance of the SimpleGateway is available from the static field `SimpleGateway.GOBAL`. +It's a lazy property, in fact the instance is only created once at its first get. + +```typescript +{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_global.ts}} +``` + +#### The factory method + +A SimpleGateway instance can be easily created using the factory method, i.e. the static method `SimpleGateway.create()`. +The method returns a fresh new SimpleGateway instance at each call. + +```typescript +{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_factory.ts}} +``` + +#### The constructor + +The constructor approach provides a fine grain control of the Gateway dependencies: the CommandBus, the QueryBus, the EventBus and the GatewayObserver. + +```typescript +{{#include ../../packages/ceb-book-samples/src/messaging/implementation-create_constructor.ts}} +``` + +### The Inversion Module + +The package provides an Inversion Module which can be used to create (optionally) and publish the SimpleGateway instance on the registry. + +Create a container with the default module behavior, i.e. the SimpleGateway will be created from scratch automatically: +```typescript +{{#include ../../packages/ceb-book-samples/src/messaging/implementation-inversion-default.ts}} +``` + +Create a container with a provided SimpleGateway instance: +```typescript +{{#include ../../packages/ceb-book-samples/src/messaging/implementation-inversion-global.ts}} +``` + +## The Moleculer implementation + +> The Moleculer implementation is defined in the NPM package [@tmorin/ceb-messaging-moleculer](https://www.npmjs.com/package/@tmorin/ceb-messaging-moleculer). + +The [Moleculer] implementation leverages on the features provided by the microservices framework. + +## Management of Commands and Queries + +There is one [Moleculer service] per command or query types. +For instance, the command type `CommandA` will be managed by the service `CommandA`. + +About commands, each service provides two [Moleculer actions]: `execute` and `executeAndForget`. +The first one executes the command handler and return the result. +The second one just executes the command handler at the next clock thick. +For instance, the command type `CommandA` can be executed within the Moleculer world with the actions `CommandA.execute` and `CommandA.executeAndForget`. +Each action accepts only one parameter: the command. + +About queries, each service provides only one action: `execute`. +The action executes the query handler and return the result. +For instance, the query type `QueryA` can be executed within the Moleculer world with the action `QueryA.execute`. +The action accepts only one parameter: the query. + +## Management of Events + +The Events are managed by a single [Moleculer service]: `EventBus`. +Each time an Event is published, the type of [Moleculer event] is `Event.MESSAGE_TYPE`. +For instance, when the Event `EventA` is published, the Moleculer event name is `EventBus.EventA`. + +By default, the implementation publish messaging using the _balanced_ mode. +Because of the single Moleculer service `EventBus`, it means each Event will only be handled by only one service in the cluster. + + +[Moleculer]: https://moleculer.services +[Moleculer service]: https://moleculer.services/docs/0.14/actions.html +[Moleculer actions]: https://moleculer.services/docs/0.14/actions.html +[Moleculer event]: https://moleculer.services/docs/0.14/events.html diff --git a/book/packages.md b/book/packages.md index ccdb9f09..bd978f8b 100644 --- a/book/packages.md +++ b/book/packages.md @@ -14,6 +14,7 @@ Inversion Of Control: Event/Message Architecture: - [@tmorin/ceb-messaging-core](https://www.npmjs.com/package/@tmorin/ceb-messaging-core) - [@tmorin/ceb-messaging-inversion](https://www.npmjs.com/package/@tmorin/ceb-messaging-inversion) +- [@tmorin/ceb-messaging-moleculer](https://www.npmjs.com/package/@tmorin/ceb-messaging-moleculer) - [@tmorin/ceb-messaging-simple](https://www.npmjs.com/package/@tmorin/ceb-messaging-simple) - [@tmorin/ceb-messaging-simple-builder](https://www.npmjs.com/package/@tmorin/ceb-messaging-simple-builder) - [@tmorin/ceb-messaging-testing](https://www.npmjs.com/package/@tmorin/ceb-messaging-testing) diff --git a/lerna.json b/lerna.json index 217b0292..80347e93 100644 --- a/lerna.json +++ b/lerna.json @@ -17,6 +17,7 @@ "packages/ceb-messaging-builder-inversion", "packages/ceb-messaging-core", "packages/ceb-messaging-inversion", + "packages/ceb-messaging-moleculer", "packages/ceb-messaging-simple", "packages/ceb-messaging-simple-builder", "packages/ceb-messaging-simple-inversion", diff --git a/packages/ceb-messaging-adapter-purify/package.json b/packages/ceb-messaging-adapter-purify/package.json index d44165f8..092b2738 100644 --- a/packages/ceb-messaging-adapter-purify/package.json +++ b/packages/ceb-messaging-adapter-purify/package.json @@ -15,7 +15,8 @@ "cqrs", "bus", "event-bus", - "message-bus" + "message-bus", + "purify" ], "homepage": "https://tmorin.github.io/ceb", "bugs": { diff --git a/packages/ceb-messaging-moleculer/README.md b/packages/ceb-messaging-moleculer/README.md new file mode 100644 index 00000000..bf83d16e --- /dev/null +++ b/packages/ceb-messaging-moleculer/README.md @@ -0,0 +1,24 @@ +# @tmorin/ceb-messaging-moleculer + +[![npm version](https://badge.fury.io/js/%40tmorin%2Fceb-messaging-moleculer.svg)](https://badge.fury.io/js/%40tmorin%2Fceb-messaging-moleculer) +[![skypack.dev](https://img.shields.io/badge/-skypack.dev-blueviolet.svg)](https://www.skypack.dev/view/@tmorin/ceb-messaging-moleculer) +[![doc](https://img.shields.io/badge/-doc-informational.svg)](https://tmorin.github.io/ceb) +[![api](https://img.shields.io/badge/-api-informational.svg)](https://tmorin.github.io/ceb/api/modules/_tmorin_ceb_messaging_moleculer.html) + +> The package is part of the `` library. +> It provides an implementation of the messaging model leveraging on Moleculer. + +## Install + +The NPM package is compliant [CommonJs](https://flaviocopes.com/commonjs) and [ES Module](https://flaviocopes.com/es-modules). + +```bash +npm install @tmorin/ceb-messaging-moleculer +``` + +## License + +Released under the [MIT license]. + +[Custom Elements (v1)]: https://html.spec.whatwg.org/multipage/custom-elements.html +[MIT license]: http://opensource.org/licenses/MIT diff --git a/packages/ceb-messaging-moleculer/package-lock.json b/packages/ceb-messaging-moleculer/package-lock.json new file mode 100644 index 00000000..657f710c --- /dev/null +++ b/packages/ceb-messaging-moleculer/package-lock.json @@ -0,0 +1,742 @@ +{ + "name": "@tmorin/ceb-messaging-moleculer", + "version": "7.0.2", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@tmorin/ceb-messaging-moleculer", + "version": "7.0.2", + "license": "MIT", + "devDependencies": { + "moleculer": "^0.14.18" + }, + "peerDependencies": { + "moleculer": "*" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "dev": true, + "dependencies": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", + "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "dev": true + }, + "node_modules/fastest-validator": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.12.0.tgz", + "integrity": "sha512-Qc7oCVO9hAPz5GUONmToIoa95YWzoe7SLsrjIXTfCFf6HFQXxxWePXe8D+Kp/XCrr5H/pMJwP2xprW07wYv/BQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moleculer": { + "version": "0.14.18", + "resolved": "https://registry.npmjs.org/moleculer/-/moleculer-0.14.18.tgz", + "integrity": "sha512-5+bVT0TC1MfKq3YkOKQxCr9KbdCBmfs+9AZKJBYyO+ewEIiElHZBs3NuFeHLO+wO8ti0qMh2R31r28vEyCI2tA==", + "dev": true, + "dependencies": { + "args": "^5.0.1", + "eventemitter2": "^6.4.5", + "fastest-validator": "^1.12.0", + "glob": "^7.2.0", + "ipaddr.js": "^2.0.1", + "kleur": "^4.1.4", + "lodash": "^4.17.21", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.1", + "recursive-watch": "^1.1.4" + }, + "bin": { + "moleculer-runner": "bin/moleculer-runner.js" + }, + "engines": { + "node": ">= 10.x.x" + }, + "funding": { + "url": "https://github.com/moleculerjs/moleculer?sponsor=1" + }, + "peerDependencies": { + "amqplib": "^0.7.0 || ^0.8.0", + "avsc": "^5.0.0", + "bunyan": "^1.0.0", + "cbor-x": "^0.8.3", + "dd-trace": "^0.33.0 || ^0.34.0 || ^0.35.0 || ^0.36.0 || >=1.0.0 <1.6.0", + "debug": "^4.0.0", + "etcd3": "^1.0.0", + "ioredis": "^4.0.0", + "jaeger-client": "^3.0.0", + "kafka-node": "^5.0.0", + "log4js": "^6.0.0", + "mqtt": "^4.0.0", + "msgpack5": "^5.0.0", + "nats": "^1.0.0 || ^2.0.0", + "node-nats-streaming": "^0.0.51 || ^0.2.0 || ^0.3.0", + "notepack.io": "2.0.0", + "pino": "^6.0.0 || ^7.0.0", + "protobufjs": "^6.0.0", + "redlock": "^4.0.0", + "rhea-promise": "^1.0.0 || ^2.0.0", + "thrift": "^0.12.0", + "winston": "^3.0.0" + }, + "peerDependenciesMeta": { + "amqplib": { + "optional": true + }, + "avsc": { + "optional": true + }, + "bunyan": { + "optional": true + }, + "cbor-x": { + "optional": true + }, + "dd-trace": { + "optional": true + }, + "debug": { + "optional": true + }, + "etcd3": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "jaeger-client": { + "optional": true + }, + "kafka-node": { + "optional": true + }, + "log4js": { + "optional": true + }, + "mqtt": { + "optional": true + }, + "msgpack5": { + "optional": true + }, + "nats": { + "optional": true + }, + "node-nats-streaming": { + "optional": true + }, + "notepack.io": { + "optional": true + }, + "pino": { + "optional": true + }, + "protobufjs": { + "optional": true + }, + "redlock": { + "optional": true + }, + "rhea-promise": { + "optional": true + }, + "thrift": { + "optional": true + }, + "winston": { + "optional": true + } + } + }, + "node_modules/mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recursive-watch": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/recursive-watch/-/recursive-watch-1.1.4.tgz", + "integrity": "sha512-fWejAmdLi7B/jipBUjTLnqId+PK+573fbGNbdaNA/AiAnQAx6OYOLCGWRs0W5+PyM1rLzZSWK2f40QpHSR49PQ==", + "dev": true, + "dependencies": { + "ttl": "^1.3.0" + }, + "bin": { + "recursive-watch": "bin.js" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "node_modules/ttl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ttl/-/ttl-1.3.1.tgz", + "integrity": "sha512-+bGy9iDAqg3WSfc2ZrprToSPJhZjqy7vUv9wupQzsiv+BVPVx1T2a6G4T0290SpQj+56Toaw9BiLO5j5Bd7QzA==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "dev": true, + "requires": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eventemitter2": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", + "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "dev": true + }, + "fastest-validator": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.12.0.tgz", + "integrity": "sha512-Qc7oCVO9hAPz5GUONmToIoa95YWzoe7SLsrjIXTfCFf6HFQXxxWePXe8D+Kp/XCrr5H/pMJwP2xprW07wYv/BQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "moleculer": { + "version": "0.14.18", + "resolved": "https://registry.npmjs.org/moleculer/-/moleculer-0.14.18.tgz", + "integrity": "sha512-5+bVT0TC1MfKq3YkOKQxCr9KbdCBmfs+9AZKJBYyO+ewEIiElHZBs3NuFeHLO+wO8ti0qMh2R31r28vEyCI2tA==", + "dev": true, + "requires": { + "args": "^5.0.1", + "eventemitter2": "^6.4.5", + "fastest-validator": "^1.12.0", + "glob": "^7.2.0", + "ipaddr.js": "^2.0.1", + "kleur": "^4.1.4", + "lodash": "^4.17.21", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.1", + "recursive-watch": "^1.1.4" + } + }, + "mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true + }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "recursive-watch": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/recursive-watch/-/recursive-watch-1.1.4.tgz", + "integrity": "sha512-fWejAmdLi7B/jipBUjTLnqId+PK+573fbGNbdaNA/AiAnQAx6OYOLCGWRs0W5+PyM1rLzZSWK2f40QpHSR49PQ==", + "dev": true, + "requires": { + "ttl": "^1.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "ttl": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ttl/-/ttl-1.3.1.tgz", + "integrity": "sha512-+bGy9iDAqg3WSfc2ZrprToSPJhZjqy7vUv9wupQzsiv+BVPVx1T2a6G4T0290SpQj+56Toaw9BiLO5j5Bd7QzA==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/packages/ceb-messaging-moleculer/package.json b/packages/ceb-messaging-moleculer/package.json new file mode 100644 index 00000000..adeeb8e9 --- /dev/null +++ b/packages/ceb-messaging-moleculer/package.json @@ -0,0 +1,67 @@ +{ + "name": "@tmorin/ceb-messaging-moleculer", + "version": "7.0.2", + "license": "MIT", + "description": "The package is part of the `` library. It provides an implementation of the messaging model leveraging on Moleculer.", + "keywords": [ + "custom-element-builder", + "custom-elements-v1", + "custom-elements", + "custom-element", + "typescript", + "typescript-library", + "javascript-library", + "messaging", + "cqrs", + "bus", + "event-bus", + "message-bus", + "moleculer" + ], + "homepage": "https://tmorin.github.io/ceb", + "bugs": { + "url": "https://github.com/tmorin/ceb/issues" + }, + "repository": { + "type": "git", + "url": "git@github.com:tmorin/ceb.git" + }, + "author": { + "name": "Thibault Morin", + "url": "https://tmorin.github.io" + }, + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "import": "./dist/mjs/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/mjs/index.js" + } + }, + "types": "dist/types/index.d.ts", + "main": "dist/cjs/index.js", + "module": "dist/mjs/index.js", + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "npm run build:lib && npm run build:module && npm run build:fix", + "build:fix": "../../scripts/fix-dist.js", + "build:lib": "tsc -p tsconfig.build.json --module CommonJS --outDir dist/cjs --declaration --declarationDir dist/types", + "build:module": "tsc -p tsconfig.build.json --module ESNext --outDir dist/mjs", + "test": "mocha --require ts-node/register src/**/*.spec.ts", + "test:watch": "npm test -- --watch --watch-files '**/*.ts'" + }, + "dependencies": { + "@tmorin/ceb-messaging-core": "^7.0.2" + }, + "peerDependencies": { + "moleculer": "*" + }, + "devDependencies": { + "moleculer": "^0.14.18" + } +} diff --git a/packages/ceb-messaging-moleculer/src/command.spec.ts b/packages/ceb-messaging-moleculer/src/command.spec.ts new file mode 100644 index 00000000..a40eb279 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/command.spec.ts @@ -0,0 +1,158 @@ +import { spy } from "sinon" +import chai, { assert } from "chai" +import chasAsPromised from "chai-as-promised" +import { Action, Command, Event, GatewayEmitter, MessageBuilder, Result } from "@tmorin/ceb-messaging-core" +import { MoleculerEventBus } from "./event" +import { MoleculerCommandBus } from "./command" +import { ServiceBroker } from "moleculer" + +chai.use(chasAsPromised) + +function createCommandA(body: string): Command { + return MessageBuilder.command("CommandA").body(body).build() +} + +function createResultA(action: Action, body: string): Result { + return MessageBuilder.result(action).body(body).build() +} + +function createEventA(body: string): Event { + return MessageBuilder.event("EventA").body(body).build() +} + +describe("MoleculerCommandBus", function () { + const commandA = createCommandA("hello") + const broker = new ServiceBroker({ logger: false }) + let emitter: GatewayEmitter + let eventBus: MoleculerEventBus + let bus: MoleculerCommandBus + beforeEach(async function () { + emitter = new GatewayEmitter() + eventBus = new MoleculerEventBus(emitter, broker) + bus = new MoleculerCommandBus(eventBus, emitter, broker) + }) + afterEach(async function () { + emitter.off() + await eventBus.dispose() + await bus.dispose() + await broker.stop() + }) + + describe("when sync handler failed", () => { + beforeEach(async () => { + bus.handle("CommandA", () => { + throw new Error("an error has been thrown") + }) + return broker.start() + }) + describe("#execute", () => { + it("should notify", function (done) { + bus.observer.on("command_handler_failed", () => { + done() + }) + bus.execute(commandA).catch(spy()) + }) + it("should failed", async function () { + await assert.isRejected(bus.execute(commandA), "an error has been thrown") + }) + }) + describe("#executeAndForget", () => { + it("should notify", function (done) { + bus.observer.on("command_handler_failed", () => { + done() + }) + bus.executeAndForget(commandA) + }) + }) + }) + + describe("when async handler failed", () => { + beforeEach(async () => { + bus.handle("CommandA", async () => { + throw new Error("an error has been thrown") + }) + return broker.start() + }) + describe("#execute", () => { + it("should notify", function (done) { + bus.observer.on("command_handler_failed", () => { + done() + }) + bus.execute(commandA).catch(spy()) + }) + it("should failed", async function () { + await assert.isRejected(bus.execute(commandA), "an error has been thrown") + }) + }) + describe("#executeAndForget", () => { + it("should notify", function (done) { + bus.observer.on("command_handler_failed", () => { + done() + }) + bus.executeAndForget(commandA) + }) + }) + }) + + describe("when handler return nothing", () => { + beforeEach(async () => { + bus.handle("CommandA", spy()) + return broker.start() + }) + describe("#execute", () => { + it("should return EmptyResult", async function () { + const resultA = await bus.execute(commandA) + broker.logger.info("resultA", resultA) + assert.property(resultA, "kind", "result") + assert.property(resultA.headers, "messageType", "empty") + }) + }) + }) + + describe("when handler return a result", () => { + beforeEach(async () => { + bus.handle, Result>("CommandA", (command) => ({ + result: createResultA(command, command.body), + })) + return broker.start() + }) + describe("#execute", () => { + it("should return the result", async function () { + const resultA = await bus.execute(commandA) + assert.property(resultA, "kind", "result") + assert.property(resultA, "body", "hello") + }) + }) + }) + + describe("when handler return a result and events", () => { + describe("#execute", () => { + beforeEach(async () => { + bus.handle, Result, [Event]>("CommandA", () => ({ + events: [createEventA("hello")], + })) + return broker.start() + }) + it("should return the result", function (done) { + eventBus.subscribe("EventA", () => { + done() + }) + bus.execute(commandA).catch(spy()) + }) + }) + describe("#executeAndForget", () => { + beforeEach(async () => { + bus.handle, Result, [Event]>("CommandA", () => ({ + events: [createEventA("hello")], + })) + return broker.start() + }) + it("should return the result", function (done) { + eventBus.subscribe("EventA", () => { + done() + }) + bus.executeAndForget(commandA) + }) + }) + }) +}) diff --git a/packages/ceb-messaging-moleculer/src/command.ts b/packages/ceb-messaging-moleculer/src/command.ts new file mode 100644 index 00000000..3f82b2a7 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/command.ts @@ -0,0 +1,212 @@ +import { + Command, + CommandBus, + CommandBusNotificationMap, + CommandHandler, + CommandHandlerOutputSync, + CommandResult, + EmittableCommandBus, + Event, + EventBus, + MessageBuilder, + ObservableCommandBus, + Removable, + Result, +} from "@tmorin/ceb-messaging-core" +import { Context, Service, ServiceBroker } from "moleculer" +import { MoleculerExecuteActionOptions } from "./common" + +/** + * The map of the internal events for commands handling. + */ +export interface MoleculerCommandBusNotificationMap extends CommandBusNotificationMap { + moleculer_service_destruction_failed: { + name: string + bus: CommandBus + error: Error + } + command_execution_failed: { + command: Command + bus: CommandBus + error: Error + } +} + +export interface MoleculerObservableCommandBus extends ObservableCommandBus { + /** + * Listen to an internal event. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + on( + type: K, + listener: (event: MoleculerCommandBusNotificationMap[K]) => any + ): this + + /** + * Remove listeners. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + off( + type?: K, + listener?: (event: MoleculerCommandBusNotificationMap[K]) => any + ): this +} + +/** + * The emitter view of an an {@link CommandBus}. + */ +export interface MoleculerEmitterCommandBus extends EmittableCommandBus { + /** + * Emit an internal event. + * @param type the type + * @param event the event + * @template K the type of the internal event + */ + emit(type: K, event: MoleculerCommandBusNotificationMap[K]): void +} + +/** + * Implementation of the {@link CommandBus} for Moleculer. + */ +export class MoleculerCommandBus implements CommandBus { + constructor( + private readonly eventBus: EventBus, + private readonly emitter: MoleculerEmitterCommandBus, + private readonly broker: ServiceBroker, + private readonly services: Set = new Set() + ) {} + + get observer(): MoleculerObservableCommandBus { + return this.emitter + } + + private static publishEvents = []>( + eventBus: EventBus, + output: CommandHandlerOutputSync + ): CommandResult | void { + if (output) { + output.events?.forEach((event) => eventBus.publish(event)) + return output.result + } + } + + private static createService = []>( + commandType: string, + handler: CommandHandler, + broker: ServiceBroker, + commandBus: CommandBus, + eventBus: EventBus, + emitter: MoleculerEmitterCommandBus + ): Service { + return broker.createService({ + name: commandType, + actions: { + execute(context: Context): Promise { + const command = context.params as C + // @ts-ignore + return Promise.resolve>((async () => handler(command))()) + .then((output) => MoleculerCommandBus.publishEvents(eventBus, output)) + .then((result) => result || MessageBuilder.result(command).type("empty").build()) + .catch((error: Error) => { + emitter.emit("command_handler_failed", { + bus: commandBus, + command, + error, + }) + throw error + }) + }, + executeAndForget(context: Context): void { + const command = context.params as C + Promise.resolve>((async () => handler(command))()) + .then((output) => MoleculerCommandBus.publishEvents(eventBus, output)) + .then((result) => result || MessageBuilder.result(command).type("empty").build()) + .catch((error: Error) => { + emitter.emit("command_handler_failed", { + bus: commandBus, + command, + error, + }) + }) + }, + }, + }) + } + + handle = []>( + commandType: string, + handler: CommandHandler + ): Removable { + if (this.broker.services.find((s) => s.name === commandType)) { + throw new Error(`the command type ${commandType} is already handled`) + } + + const service = MoleculerCommandBus.createService( + commandType, + handler, + this.broker, + this, + this.eventBus, + this.emitter + ) + + this.services.add(service) + + return { + remove: () => { + this.services.delete(service) + this.broker.destroyService(service).catch((error: Error) => + this.emitter.emit("moleculer_service_destruction_failed", { + bus: this, + error, + name: service.name, + }) + ) + }, + } + } + + execute( + command: C, + options?: Partial + ): Promise { + const commandName = `${command.headers.messageType}.execute` + return this.broker.call(commandName, command, { + ...options, + requestID: command.headers.messageId, + }) + } + + executeAndForget(command: C): void { + const commandName = `${command.headers.messageType}.executeAndForget` + this.broker + .call(commandName, command, { + requestID: command.headers.messageId, + }) + .catch((error: Error) => { + this.emitter.emit("command_execution_failed", { + bus: this, + command, + error, + }) + }) + } + + async dispose(): Promise { + await Promise.all( + Array.from(this.services).map((service) => { + this.broker.destroyService(service).catch((error: Error) => + this.emitter.emit("moleculer_service_destruction_failed", { + bus: this, + error, + name: service.name, + }) + ) + }) + ) + } +} diff --git a/packages/ceb-messaging-moleculer/src/common.ts b/packages/ceb-messaging-moleculer/src/common.ts new file mode 100644 index 00000000..ccf1d07c --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/common.ts @@ -0,0 +1,4 @@ +import { ExecuteActionOptions } from "@tmorin/ceb-messaging-core" +import { CallingOptions } from "moleculer" + +export type MoleculerExecuteActionOptions = ExecuteActionOptions & CallingOptions diff --git a/packages/ceb-messaging-moleculer/src/event.spec.ts b/packages/ceb-messaging-moleculer/src/event.spec.ts new file mode 100644 index 00000000..aae44e89 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/event.spec.ts @@ -0,0 +1,94 @@ +import chai, { assert } from "chai" +import chasAsPromised from "chai-as-promised" +import { MoleculerEventBus } from "./event" +import { ServiceBroker } from "moleculer" +import { Event, EventListener, GatewayEmitter, MessageBuilder, SubscribeOptions } from "@tmorin/ceb-messaging-core" +import { spy } from "sinon" + +chai.use(chasAsPromised) + +function createEventA(body: string): Event { + return MessageBuilder.event("EventA").body(body).build() +} + +describe("MoleculerEventBus", function () { + const broker = new ServiceBroker({ logger: false }) + let listeners: Map, Partial]>> + let emitter: GatewayEmitter + let eventBus: MoleculerEventBus + + beforeEach(async () => { + listeners = new Map() + emitter = new GatewayEmitter() + eventBus = new MoleculerEventBus(emitter, broker, {}, listeners) + await broker.start() + }) + afterEach(async function () { + emitter.off() + await eventBus?.dispose() + await broker.stop() + }) + + it("should publish event", function (done) { + broker.logger.info("in `it`") + eventBus.subscribe("EventA", (event) => { + assert.property(event, "body", "hello") + done() + }) + + const eventA = createEventA("hello") + eventBus.publish(eventA) + }) + + it("should listen to once", function (done) { + eventBus.subscribe( + "EventA", + () => + setTimeout(() => { + assert.equal(listeners.size, 1) + assert.equal(listeners.get("EventA")?.size, 0) + done() + }, 0), + { once: true } + ) + const eventA = createEventA("hello") + eventBus.publish(eventA) + }) + + it("should remove listener", function () { + const remover = eventBus.subscribe("EventA", spy()) + assert.equal(listeners.size, 1) + assert.equal(listeners.get("EventA")?.size, 1) + remover.remove() + assert.equal(listeners.size, 1) + assert.equal(listeners.get("EventA")?.size, 0) + }) + + it("should notify on failed sync listener", function (done) { + const eventA = createEventA("hello") + const errorA = new Error("an error") + eventBus.subscribe("EventA", () => { + throw errorA + }) + eventBus.observer.on("event_listener_failed", ({ bus, event, error }) => { + assert.equal(bus, bus) + assert.equal(event, eventA) + assert.equal(error, error) + done() + }) + eventBus.publish(eventA) + }) + + it("should notify on failed async listener", function (done) { + const eventA = createEventA("hello") + const errorA = new Error("an error") + eventBus.subscribe("EventA", () => Promise.reject(errorA)) + eventBus.observer.on("event_listener_failed", ({ bus, event, error }) => { + assert.equal(bus, bus) + assert.equal(event, eventA) + assert.equal(error, error) + done() + }) + eventBus.publish(eventA) + }) +}) diff --git a/packages/ceb-messaging-moleculer/src/event.ts b/packages/ceb-messaging-moleculer/src/event.ts new file mode 100644 index 00000000..27cce958 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/event.ts @@ -0,0 +1,200 @@ +import { + EmittableEventBus, + Event, + EventBus, + EventBusNotificationMap, + EventListener, + ObservableEventBus, + Removable, + SubscribeOptions, +} from "@tmorin/ceb-messaging-core" +import { Context, Service, ServiceBroker, ServiceEvent } from "moleculer" +import { type } from "typedoc/dist/lib/output/themes/default/partials/type" + +/** + * The map of the internal events for events handling. + */ +export interface MoleculerEventBusNotificationMap extends EventBusNotificationMap { + moleculer_service_destruction_failed: { + name: string + bus: EventBus + error: Error + } + event_broadcasting_failed: { + event: Event + bus: EventBus + error: Error + } + event_emitting_failed: { + event: Event + bus: EventBus + error: Error + } +} + +export interface MoleculerObservableEventBus extends ObservableEventBus { + /** + * Listen to an internal event. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + on( + type: K, + listener: (event: MoleculerEventBusNotificationMap[K]) => any + ): this + + /** + * Remove listeners. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + off( + type?: K, + listener?: (event: MoleculerEventBusNotificationMap[K]) => any + ): this +} + +/** + * The emitter view of an an {@link EventBus}. + */ +export interface MoleculerEmitterEventBus extends EmittableEventBus { + /** + * Emit an internal event. + * @param type the type + * @param event the event + * @template K the type of the internal event + */ + emit(type: K, event: MoleculerEventBusNotificationMap[K]): void +} + +function createMoleculerService( + bus: EventBus, + emitter: MoleculerEmitterEventBus, + broker: ServiceBroker, + listeners: Map, Partial]>>, + options: Partial +) { + const serviceEvent: ServiceEvent = { + handler: (context: Context) => { + const event = context.params as Event + emitter.emit("event_received", { + bus, + event, + }) + const eventType = event.headers.messageType + listeners.get(eventType)?.forEach((entry) => { + const [listener, options] = entry + Promise.resolve((async () => listener(event))()) + .catch((error: Error) => { + emitter.emit("event_listener_failed", { + bus, + event, + error, + }) + }) + .finally(() => { + if (options.once) { + listeners.get(eventType)?.delete(entry) + } + }) + }) + }, + } + + if (options.moleculerGroup) { + serviceEvent.group = options.moleculerGroup + } + + return broker.createService({ + name: "EventBus", + events: { + "EventBus.*": serviceEvent, + }, + }) +} + +export type MoleculerEventBusOptions = { + publicationMode: "balanced" | "broadcast" + moleculerGroup: string +} + +/** + * Implementation of the {@link EventBus} for Moleculer. + */ +export class MoleculerEventBus implements EventBus { + public readonly service: Service + + constructor( + private readonly emitter: MoleculerEmitterEventBus, + private readonly broker: ServiceBroker, + private readonly options: Partial = {}, + private readonly listeners: Map, Partial]>> = new Map() + ) { + this.service = createMoleculerService(this, emitter, broker, listeners, options) + } + + get observer(): MoleculerObservableEventBus { + return this.emitter + } + + subscribe( + eventType: string, + listener: EventListener, + options?: Partial + ): Removable { + if (!this.listeners.has(eventType)) { + this.listeners.set(eventType, new Set()) + } + const entry: [EventListener, Partial] = [listener, { ...options }] + this.listeners.get(eventType)?.add(entry) + return { + remove: () => { + this.listeners.get(eventType)?.delete(entry) + }, + } + } + + publish(...events: Array): void { + events.forEach((event) => { + const eventName = `${this.service.name}.${event.headers.messageType}` + + const groupsFromArray: Array = Array.isArray(event.headers.moleculerGroups) + ? event.headers.moleculerGroups.map((v) => String(v)) + : [] + const groupFromString: Array = + typeof event.headers.moleculerGroup === "string" ? [event.headers.moleculerGroup] : [] + const groups = groupsFromArray.concat(groupFromString) + + if (this.options.publicationMode === "broadcast") { + this.broker.broadcast(eventName, event, groups).catch((error: Error) => { + this.emitter.emit("event_broadcasting_failed", { + event, + bus: this, + error, + }) + }) + } else { + this.broker.emit(eventName, event, groups).catch((error: Error) => { + this.emitter.emit("event_emitting_failed", { + event, + bus: this, + error, + }) + }) + } + }) + } + + async dispose(): Promise { + this.listeners.clear() + await this.broker.destroyService(this.service).catch((error: Error) => + this.emitter.emit("moleculer_service_destruction_failed", { + bus: this, + error, + name: this.service.name, + }) + ) + } +} diff --git a/packages/ceb-messaging-moleculer/src/gateway.spec.ts b/packages/ceb-messaging-moleculer/src/gateway.spec.ts new file mode 100644 index 00000000..38972492 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/gateway.spec.ts @@ -0,0 +1,69 @@ +import chai, { assert } from "chai" +import chasAsPromised from "chai-as-promised" +import { Action, Command, Event, GatewayEmitter, MessageBuilder, Query, Result } from "@tmorin/ceb-messaging-core" +import { MoleculerCommandBus } from "./command" +import { MoleculerEventBus } from "./event" +import { MoleculerQueryBus } from "./query" +import { MoleculerGateway } from "./gateway" +import { ServiceBroker } from "moleculer" + +chai.use(chasAsPromised) + +function createCommandA(body: string): Command { + return MessageBuilder.command("CommandA").body(body).build() +} + +function createQueryA(body: string): Query { + return MessageBuilder.query("QueryA").body(body).build() +} + +function createResultA(action: Action, body: string): Result { + return MessageBuilder.result(action).body(body).build() +} + +function createEventA(body: string): Event { + return MessageBuilder.event("EventA").body(body).build() +} + +describe("MoleculerObservableGateway", function () { + const commandA = createCommandA("hello") + const queryA = createQueryA("hello") + const eventA = createEventA("hello") + const broker = new ServiceBroker({ logger: false }) + let emitter: GatewayEmitter + let eventBus: MoleculerEventBus + let commandBus: MoleculerCommandBus + let queryBus: MoleculerQueryBus + let gateway: MoleculerGateway + beforeEach(async function () { + emitter = new GatewayEmitter() + eventBus = new MoleculerEventBus(emitter, broker) + commandBus = new MoleculerCommandBus(eventBus, emitter, broker) + queryBus = new MoleculerQueryBus(emitter, broker) + gateway = new MoleculerGateway(eventBus, commandBus, queryBus, emitter) + commandBus.handle, Result>("CommandA", (command) => ({ + result: createResultA(command, command.body), + })) + queryBus.handle, Result>("QueryA", (query) => createResultA(query, query.body)) + await broker.start() + }) + afterEach(async function () { + await gateway?.dispose() + await broker.stop() + }) + + it("should execute command", async function () { + const resultA = await commandBus.execute>(commandA) + assert.property(resultA, "body", "hello") + }) + + it("should execute query", async function () { + const resultA = await queryBus.execute>(queryA) + assert.property(resultA, "body", "hello") + }) + + it("should execute subscribe to event", function (done) { + eventBus.subscribe("EventA", () => done()) + eventBus.publish(eventA) + }) +}) diff --git a/packages/ceb-messaging-moleculer/src/gateway.ts b/packages/ceb-messaging-moleculer/src/gateway.ts new file mode 100644 index 00000000..30ed20d3 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/gateway.ts @@ -0,0 +1,50 @@ +import { EmittableGateway, Gateway, ObservableGateway } from "@tmorin/ceb-messaging-core" +import { MoleculerEventBus } from "./event" +import { MoleculerCommandBus } from "./command" +import { MoleculerQueryBus } from "./query" + +/** + * The symbol used to register {@link MoleculerGateway}. + * + * @example Creation and destruction + * ```typescript + * import { Gateway } from "@tmorin/ceb-messaging-core" + * import { MoleculerGateway } from "@tmorin/ceb-messaging-moleculer" + * const gateway : Gateway = MoleculerGateway.create() + * gateway.dispose().catche(e => console.error(e)) + * ``` + * + * @example Global instance + * ```typescript + * import { MessageBuilder } from "@tmorin/ceb-messaging-core" + * import { MoleculerGateway } from "@tmorin/ceb-messaging-moleculer" + * const event = MessageBuilder.event("EventA").build() + * MoleculerGateway.GLOBAL.events.publish(event) + * ``` + */ +export const MoleculerGatewaySymbol = Symbol.for("ceb/inversion/MoleculerGateway") + +export class MoleculerGateway implements Gateway { + constructor( + readonly events: MoleculerEventBus, + readonly commands: MoleculerCommandBus, + readonly queries: MoleculerQueryBus, + readonly emitter: EmittableGateway, + readonly observer: ObservableGateway = emitter + ) {} + + /** + * Dispose all channels: + * + * - events + * - commands + * - queries + * - observer + */ + async dispose() { + await this.events.dispose() + await this.commands.dispose() + await this.queries.dispose() + this.observer.off() + } +} diff --git a/packages/ceb-messaging-moleculer/src/index.ts b/packages/ceb-messaging-moleculer/src/index.ts new file mode 100644 index 00000000..21b60543 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/index.ts @@ -0,0 +1,5 @@ +export * from "./command" +export * from "./common" +export * from "./event" +export * from "./gateway" +export * from "./query" diff --git a/packages/ceb-messaging-moleculer/src/query.spec.ts b/packages/ceb-messaging-moleculer/src/query.spec.ts new file mode 100644 index 00000000..42a01f10 --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/query.spec.ts @@ -0,0 +1,78 @@ +import chai, { assert } from "chai" +import chasAsPromised from "chai-as-promised" +import { Action, GatewayEmitter, MessageBuilder, Query, QueryHandler, Result } from "@tmorin/ceb-messaging-core" +import { spy } from "sinon" +import { MoleculerQueryBus } from "./query" +import { ServiceBroker } from "moleculer" +import { MoleculerEventBus } from "./event" +import { MoleculerCommandBus } from "./command" + +chai.use(chasAsPromised) + +function createQueryA(body: string): Query { + return MessageBuilder.query("QueryA").body(body).build() +} + +function createResultA(action: Action, body: string): Result { + return MessageBuilder.result(action).body(body).build() +} + +describe("SimpleQueryBus", function () { + const queryA = createQueryA("hello") + const broker = new ServiceBroker({ logger: false }) + let emitter: GatewayEmitter + let bus: MoleculerQueryBus + beforeEach(async function () { + emitter = new GatewayEmitter() + bus = new MoleculerQueryBus(emitter, broker) + }) + afterEach(async function () { + emitter.off() + await bus.dispose() + await broker.stop() + }) + + describe("a query handler", function () { + beforeEach(async () => { + bus.handle, Result>("QueryA", (query) => { + return createResultA(query, query.body) + }) + await broker.start() + }) + it("should handle a query", async function () { + const resultA = await bus.execute(queryA) + assert.property(resultA, "body", "hello") + }) + }) + + describe("a failing query handler", function () { + beforeEach(async () => { + bus.handle("QueryA", () => { + throw new Error("an error has been thrown") + }) + await broker.start() + }) + it("should failed when sync handler failed", async function () { + await assert.isRejected(bus.execute(queryA), "an error has been thrown") + }) + }) + + describe("an async failing query handler", function () { + beforeEach(async () => { + bus.handle("QueryA", () => Promise.reject(Error("an error has been thrown"))) + await broker.start() + }) + it("should failed when async handler failed", async function () { + await assert.isRejected(bus.execute(queryA), "an error has been thrown") + }) + it("should notify when handler failed", function (done) { + bus.observer.on("query_handler_failed", ({ bus, query, error }) => { + assert.equal(bus, bus) + assert.equal(query, queryA) + assert.equal(error, error) + done() + }) + bus.execute(queryA).catch(spy()) + }) + }) +}) diff --git a/packages/ceb-messaging-moleculer/src/query.ts b/packages/ceb-messaging-moleculer/src/query.ts new file mode 100644 index 00000000..5a76a84d --- /dev/null +++ b/packages/ceb-messaging-moleculer/src/query.ts @@ -0,0 +1,162 @@ +import { + EmittableQueryBus, + MessageBuilder, + ObservableQueryBus, + Query, + QueryBus, + QueryBusNotificationMap, + QueryHandler, + QueryResult, + Removable, + Result, +} from "@tmorin/ceb-messaging-core" +import { Context, Service, ServiceBroker } from "moleculer" +import { MoleculerExecuteActionOptions } from "./common" + +/** + * The map of the internal events for querys handling. + */ +export interface MoleculerQueryBusNotificationMap extends QueryBusNotificationMap { + moleculer_service_destruction_failed: { + name: string + bus: QueryBus + error: Error + } + query_execution_failed: { + query: Query + bus: QueryBus + error: Error + } +} + +export interface MoleculerObservableQueryBus extends ObservableQueryBus { + /** + * Listen to an internal event. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + on( + type: K, + listener: (event: MoleculerQueryBusNotificationMap[K]) => any + ): this + + /** + * Remove listeners. + * @param type the type of the event + * @param listener the listener + * @template K the type of the internal event + */ + off( + type?: K, + listener?: (event: MoleculerQueryBusNotificationMap[K]) => any + ): this +} + +/** + * The emitter view of an an {@link QueryBus}. + */ +export interface MoleculerEmitterQueryBus extends EmittableQueryBus { + /** + * Emit an internal event. + * @param type the type + * @param event the event + * @template K the type of the internal event + */ + emit(type: K, event: MoleculerQueryBusNotificationMap[K]): void +} + +/** + * Implementation of the {@link QueryBus} for Moleculer. + */ +export class MoleculerQueryBus implements QueryBus { + constructor( + private readonly emitter: MoleculerEmitterQueryBus, + private readonly broker: ServiceBroker, + private readonly services: Set = new Set() + ) {} + + get observer(): MoleculerObservableQueryBus { + return this.emitter + } + + private static createService( + queryType: string, + handler: QueryHandler, + broker: ServiceBroker, + queryBus: QueryBus, + emitter: MoleculerEmitterQueryBus + ): Service { + return broker.createService({ + name: queryType, + actions: { + execute(context: Context): Promise { + const query = context.params as Q + // @ts-ignore + return Promise.resolve>((async () => handler(query))()) + .then((result) => result || MessageBuilder.result(query).type("empty").build()) + .catch((error: Error) => { + emitter.emit("query_handler_failed", { + bus: queryBus, + query, + error, + }) + throw error + }) + }, + }, + }) + } + + handle( + queryType: string, + handler: QueryHandler + ): Removable { + if (this.broker.services.find((s) => s.name === queryType)) { + throw new Error(`the query type ${queryType} is already handled`) + } + + const service = MoleculerQueryBus.createService(queryType, handler, this.broker, this, this.emitter) + + this.services.add(service) + + return { + remove: () => { + this.services.delete(service) + this.broker.destroyService(service).catch((error: Error) => + this.emitter.emit("moleculer_service_destruction_failed", { + bus: this, + error, + name: service.name, + }) + ) + }, + } + } + + execute( + query: C, + options?: Partial + ): Promise { + const queryName = `${query.headers.messageType}.execute` + console.log("options", options) + return this.broker.call(queryName, query, { + ...options, + requestID: query.headers.messageId, + }) + } + + async dispose(): Promise { + await Promise.all( + Array.from(this.services).map((service) => { + this.broker.destroyService(service).catch((error: Error) => + this.emitter.emit("moleculer_service_destruction_failed", { + bus: this, + error, + name: service.name, + }) + ) + }) + ) + } +} diff --git a/packages/ceb-messaging-moleculer/tsconfig.build.json b/packages/ceb-messaging-moleculer/tsconfig.build.json new file mode 100644 index 00000000..3a564a82 --- /dev/null +++ b/packages/ceb-messaging-moleculer/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "src" + ] +} diff --git a/packages/ceb-messaging-moleculer/tsconfig.json b/packages/ceb-messaging-moleculer/tsconfig.json new file mode 100644 index 00000000..2e690402 --- /dev/null +++ b/packages/ceb-messaging-moleculer/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src" + ] +} diff --git a/packages/ceb-messaging-simple/README.md b/packages/ceb-messaging-simple/README.md index e1e9c105..08a524c9 100644 --- a/packages/ceb-messaging-simple/README.md +++ b/packages/ceb-messaging-simple/README.md @@ -6,8 +6,8 @@ [![api](https://img.shields.io/badge/-api-informational.svg)](https://tmorin.github.io/ceb/api/modules/_tmorin_ceb_messaging_simple.html) > The package is part of the `` library. -> It provides a specialization of the core artifacts which leverages on a vanilla TypeScript/JavaScript environment. - +> It provides an implementation of the messaging model leveraging on a vanilla TypeScript/JavaScript environment. +> ## Install The NPM package is compliant [CommonJs](https://flaviocopes.com/commonjs) and [ES Module](https://flaviocopes.com/es-modules). diff --git a/packages/ceb-messaging-simple/package.json b/packages/ceb-messaging-simple/package.json index 66d7844b..7b27bca5 100644 --- a/packages/ceb-messaging-simple/package.json +++ b/packages/ceb-messaging-simple/package.json @@ -2,7 +2,7 @@ "name": "@tmorin/ceb-messaging-simple", "version": "7.0.2", "license": "MIT", - "description": "The package is part of the `` library. It provides a specialization of the core artifacts which leverages on a vanilla TypeScript/JavaScript environment.", + "description": "The package is part of the `` library. It provides an implementation of the messaging model leveraging on a vanilla TypeScript/JavaScript environment.", "keywords": [ "custom-element-builder", "custom-elements-v1", @@ -55,7 +55,6 @@ "test:watch": "npm test -- --watch --watch-files '**/*.ts'" }, "dependencies": { - "@tmorin/ceb-inversion-core": "^7.0.2", "@tmorin/ceb-messaging-core": "^7.0.2" } } diff --git a/packages/ceb-messaging-simple/src/command.spec.ts b/packages/ceb-messaging-simple/src/command.spec.ts index c4336c00..2dff5485 100644 --- a/packages/ceb-messaging-simple/src/command.spec.ts +++ b/packages/ceb-messaging-simple/src/command.spec.ts @@ -48,7 +48,7 @@ describe("SimpleCommandBus", function () { describe("#execute", () => { it("should notify", function (done) { const commandA = createCommandA("hello") - bus.observe.on("command_handler_not_found", () => { + bus.observer.on("command_handler_not_found", () => { done() }) bus.execute(commandA).catch(spy()) @@ -61,7 +61,7 @@ describe("SimpleCommandBus", function () { describe("#executeAndForget", () => { it("should notify", function (done) { const commandA = createCommandA("hello") - bus.observe.on("command_handler_not_found", () => { + bus.observer.on("command_handler_not_found", () => { done() }) bus.executeAndForget(commandA) @@ -76,7 +76,7 @@ describe("SimpleCommandBus", function () { bus.handle("CommandA", () => { throw new Error("an error has been thrown") }) - bus.observe.on("command_handler_failed", () => { + bus.observer.on("command_handler_failed", () => { done() }) bus.execute(commandA).catch(spy()) @@ -95,7 +95,7 @@ describe("SimpleCommandBus", function () { bus.handle("CommandA", () => { throw new Error("an error has been thrown") }) - bus.observe.on("command_handler_failed", () => { + bus.observer.on("command_handler_failed", () => { done() }) bus.executeAndForget(commandA) @@ -110,7 +110,7 @@ describe("SimpleCommandBus", function () { bus.handle("CommandA", async () => { throw new Error("an error has been thrown") }) - bus.observe.on("command_handler_failed", () => { + bus.observer.on("command_handler_failed", () => { done() }) bus.execute(commandA).catch(spy()) @@ -129,7 +129,7 @@ describe("SimpleCommandBus", function () { bus.handle("CommandA", async () => { throw new Error("an error has been thrown") }) - bus.observe.on("command_handler_failed", () => { + bus.observer.on("command_handler_failed", () => { done() }) bus.executeAndForget(commandA) diff --git a/packages/ceb-messaging-simple/src/command.ts b/packages/ceb-messaging-simple/src/command.ts index 640bc427..ca5c53be 100644 --- a/packages/ceb-messaging-simple/src/command.ts +++ b/packages/ceb-messaging-simple/src/command.ts @@ -28,7 +28,7 @@ export class SimpleCommandBus implements CommandBus, Disposable { private readonly handlers = new Map>() ) {} - get observe(): ObservableCommandBus { + get observer(): ObservableCommandBus { return this.emitter } diff --git a/packages/ceb-messaging-simple/src/gateway.ts b/packages/ceb-messaging-simple/src/gateway.ts index c12b6310..3d033852 100644 --- a/packages/ceb-messaging-simple/src/gateway.ts +++ b/packages/ceb-messaging-simple/src/gateway.ts @@ -65,7 +65,7 @@ export class SimpleGateway implements Gateway { * - events * - commands * - queries - * - observe + * - observer */ async dispose() { await this.events.dispose()