├── .codeclimate.yml ├── .editorconfig ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── bench.yml │ ├── ci.yml │ ├── e2e.yml │ ├── lint.yml │ ├── notification.yml │ └── pkg-publish.yml ├── .gitignore ├── .npmignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── benchmark ├── .eslintrc.js ├── data │ ├── 10.json │ ├── 100k.json │ ├── 10k.json │ ├── 150.json │ ├── 1M.json │ ├── 1k.json │ └── 50k.json ├── index.js ├── math.service.js ├── memleak-test.js ├── perf-runner.js ├── suites │ ├── cachers.js │ ├── call.js │ ├── common.js │ ├── events.js │ ├── middlewares.js │ ├── serializers.js │ ├── throughput.js │ └── transporters.js ├── test.js ├── user.service.js └── utils.js ├── bin ├── .gitattributes ├── moleculer-runner.js └── moleculer-runner.mjs ├── dev ├── .eslintrc.js ├── .gitignore ├── SafeJsonSerializer.js ├── action-hooks.js ├── async-local-storage.js ├── base.js ├── bigfile-sender.js ├── bigfile.receiver.js ├── breaker.js ├── broadcast-groups.js ├── buffer.js ├── bulkhead.js ├── cache.js ├── caller.js ├── circular.js ├── client.js ├── cluster.js ├── compress.js ├── custom-context.js ├── debounce-throttle.js ├── dev.config.js ├── dev.js ├── direct-call.js ├── duplex-streaming.js ├── empty.service.js ├── encrypt.js ├── errorHandler.js ├── es6-class.js ├── event-store.js ├── event-validator.js ├── event-wildcard.js ├── gossip-viz.js ├── hook-wildcard.js ├── i18n-validator.js ├── index.js ├── inter-ns.js ├── internal-errors.js ├── internal.js ├── issue-1100-receiver.js ├── issue-1100-sender.js ├── issue-1121.js ├── issue-1132.js ├── issue-1137.js ├── issue-1241.js ├── issue-777.js ├── jsrepl.js ├── lifecycle.js ├── logger.js ├── loglevel.js ├── method-mw.js ├── metrics.js ├── middleware_v2.js ├── mw-order.js ├── nats-cert.pem ├── nats-wildcard.js ├── perf.js ├── remote-deps.js ├── retries.js ├── saga.js ├── schema-custom-merge.js ├── serializer-register.js ├── server.js ├── sharding.js ├── shared-obj.js ├── stream-caller.js ├── stream-demo.js ├── stream-echo.js ├── stream-java.js ├── stream-receiver.js ├── stream-sender.js ├── timeout.js ├── tracing.js ├── tracking.js ├── validator-async.js └── validator.js ├── docs ├── MIGRATION_GUIDE_0.13.md ├── MIGRATION_GUIDE_0.14.md ├── Moleculer.code-snippets ├── PROTOCOL.md ├── assets │ ├── become_a_patron_button.png │ ├── logo.png │ ├── microservices-architecture.png │ ├── mixed-architecture.png │ ├── monolith-architecture.png │ ├── patreon.svg │ ├── paypal_donate.svg │ └── project-welcome-page.png ├── profiling.md └── runkit │ └── simple.js ├── examples ├── .eslintrc.js ├── client-server │ ├── client.js │ └── server.js ├── docker-compose.yaml ├── docker │ ├── client │ │ ├── Dockerfile │ │ ├── moleculer.config.js │ │ └── package.json │ ├── docker-compose.yml │ └── worker │ │ ├── Dockerfile │ │ ├── moleculer.config.js │ │ ├── package.json │ │ └── service.js ├── dummy.service.js ├── es6.class.service.js ├── esm │ ├── greeter.service.mjs │ ├── moleculer.config.mjs │ └── welcome.service.cjs ├── file.service.js ├── hot.service.js ├── index.js ├── loadtest │ ├── client.js │ ├── clients.js │ ├── local.js │ ├── nats.js │ └── server.js ├── math.service.js ├── middlewares │ └── index.js ├── multi-nodes │ ├── index.js │ ├── master.js │ ├── node-controller.service.js │ └── node.js ├── post.service.js ├── runner │ ├── .gitignore │ ├── moleculer.config.async.js │ ├── moleculer.config.async.ts │ ├── moleculer.config.js │ └── moleculer.config.ts ├── silent.service.js ├── simple │ └── index.js ├── start-es6.js ├── stat.service.js ├── test.service.js ├── user.service.js └── user.v1.service.js ├── index.d.ts ├── index.js ├── index.mjs ├── jsconfig.json ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── async-storage.js ├── cachers │ ├── base.js │ ├── index.js │ ├── memory-lru.js │ ├── memory.js │ └── redis.js ├── constants.js ├── context.js ├── cpu-usage.js ├── errors.js ├── health.js ├── internals.js ├── lock.js ├── logger-factory.js ├── loggers │ ├── base.js │ ├── bunyan.js │ ├── console.js │ ├── datadog.js │ ├── debug.js │ ├── file.js │ ├── formatted.js │ ├── index.js │ ├── log4js.js │ ├── pino.js │ └── winston.js ├── metrics │ ├── commons.js │ ├── constants.js │ ├── index.js │ ├── rates.js │ ├── registry.js │ ├── reporters │ │ ├── base.js │ │ ├── console.js │ │ ├── csv.js │ │ ├── datadog.js │ │ ├── event.js │ │ ├── index.js │ │ ├── prometheus.js │ │ └── statsd.js │ └── types │ │ ├── base.js │ │ ├── counter.js │ │ ├── gauge.js │ │ ├── histogram.js │ │ ├── index.js │ │ └── info.js ├── middleware.js ├── middlewares │ ├── action-hook.js │ ├── bulkhead.js │ ├── cacher.js │ ├── circuit-breaker.js │ ├── context-tracker.js │ ├── debounce.js │ ├── debugging │ │ ├── action-logger.js │ │ └── transit-logger.js │ ├── error-handler.js │ ├── fallback.js │ ├── hot-reload.js │ ├── index.js │ ├── metrics.js │ ├── retry.js │ ├── throttle.js │ ├── timeout.js │ ├── tracing.js │ ├── transmit │ │ ├── compression.js │ │ └── encryption.js │ └── validator.js ├── packets.js ├── registry │ ├── action-catalog.js │ ├── discoverers │ │ ├── base.js │ │ ├── etcd3.js │ │ ├── index.js │ │ ├── local.js │ │ └── redis.js │ ├── endpoint-action.js │ ├── endpoint-event.js │ ├── endpoint-list.js │ ├── endpoint.js │ ├── event-catalog.js │ ├── index.js │ ├── node-catalog.js │ ├── node.js │ ├── registry.js │ ├── service-catalog.js │ └── service-item.js ├── runner-esm.mjs ├── runner.js ├── serializers │ ├── avro.js │ ├── base.js │ ├── cbor.js │ ├── index.js │ ├── json.js │ ├── msgpack.js │ ├── notepack.js │ ├── proto │ │ ├── packets.proto │ │ └── packets.proto.js │ ├── protobuf.js │ ├── thrift.js │ └── thrift │ │ ├── gen-nodejs │ │ └── packets_types.js │ │ └── packets.thrift ├── service-broker.js ├── service.js ├── strategies │ ├── base.js │ ├── cpu-usage.js │ ├── index.js │ ├── latency.js │ ├── random.js │ ├── round-robin.js │ └── shard.js ├── tracing │ ├── exporters │ │ ├── README.md │ │ ├── base.js │ │ ├── console.js │ │ ├── datadog-simple.js │ │ ├── datadog.js │ │ ├── event-legacy.js │ │ ├── event.js │ │ ├── index.js │ │ ├── jaeger.js │ │ ├── newrelic.js │ │ └── zipkin.js │ ├── index.js │ ├── rate-limiter.js │ ├── span.js │ └── tracer.js ├── transit.js ├── transporters │ ├── amqp.js │ ├── amqp10.js │ ├── base.js │ ├── fake.js │ ├── index.js │ ├── kafka.js │ ├── mqtt.js │ ├── nats.js │ ├── redis.js │ ├── stan.js │ ├── tcp.js │ └── tcp │ │ ├── constants.js │ │ ├── parser.js │ │ ├── tcp-reader.js │ │ ├── tcp-writer.js │ │ └── udp-broadcaster.js ├── utils.js └── validators │ ├── base.js │ ├── fastest.js │ └── index.js └── test ├── .eslintrc.js ├── docker-compose.yml ├── e2e ├── assets │ ├── .gitignore │ └── banner.png ├── scenarios │ ├── balancing │ │ ├── node1.js │ │ ├── node2.js │ │ ├── node3.js │ │ ├── scenario.js │ │ ├── start.sh │ │ └── test.service.js │ ├── basic │ │ ├── node1.js │ │ ├── scenario.js │ │ └── start.sh │ ├── compression │ │ ├── node1.js │ │ ├── scenario.js │ │ └── start.sh │ └── dependencies │ │ ├── scenario.js │ │ └── start.sh ├── services │ ├── aes.service.js │ ├── helper.service.js │ └── scenario.service.js ├── start.sh └── utils.js ├── esm ├── greeter.service.mjs ├── helper.service.js └── moleculer.config.mjs ├── integration ├── __snapshots__ │ └── tracing.spec.js.snap ├── async-storage.spec.js ├── broker-internal.spec.js ├── broker-transit.spec.js ├── broker.spec.js ├── circuit-breaker.spec.js ├── event-balancer.spec.js ├── helpers.js ├── middlewares.spec.js ├── registry.spec.js ├── retry.spec.js ├── service-deps.spec.js ├── service-mixins.spec.js ├── service.lifecycle.spec.js ├── stream.spec.js ├── tracing.spec.js └── validator.spec.js ├── leak-detection ├── index.spc.js └── self-check.spc.js ├── nats.config ├── services ├── greeter.es6.service.js ├── math.service.js ├── posts.service.js ├── users.service.js └── utils │ └── util.service.js ├── typescript ├── hello-world │ ├── .gitignore │ ├── greeter.service.ts │ ├── index.ts │ └── tsconfig.json └── tsd │ ├── Cachers.test-d.ts │ ├── ServiceActions.test-d.ts │ ├── ServiceSchema_lifecycles.test-d.ts │ └── ServiceSettings.test-d.ts └── unit ├── __factories ├── my-context-factory.js ├── my-service-factory.js └── my.service.js ├── async-storage.spec.js ├── cachers ├── base.spec.js ├── index.spec.js ├── lock.spec.js ├── memory-lru.spec.js ├── memory.spec.js └── redis.spec.js ├── context.spec.js ├── cpu-usage.spec.js ├── errors.spec.js ├── health.spec.js ├── internals.spec.js ├── lock.spec.js ├── logger-factory.spec.js ├── loggers ├── base.spec.js ├── bunyan.spec.js ├── console.spec.js ├── datadog.spec.js ├── debug.spec.js ├── file.spec.js ├── formatted.spec.js ├── index.spec.js ├── log4js.spec.js ├── pino.spec.js └── winston.spec.js ├── metrics ├── __snapshots__ │ └── registry.spec.js.snap ├── rates.spec.js ├── registry.spec.js ├── reporters │ ├── __snapshots__ │ │ ├── console.spec.js.snap │ │ ├── csv.spec.js.snap │ │ ├── datadog.spec.js.snap │ │ ├── event.spec.js.snap │ │ └── prometheus.spec.js.snap │ ├── base.spec.js │ ├── console.spec.js │ ├── csv.spec.js │ ├── datadog.spec.js │ ├── event.spec.js │ ├── index.spec.js │ ├── prometheus.spec.js │ └── statsd.spec.js └── types │ ├── __snapshots__ │ ├── counter.spec.js.snap │ ├── gauge.spec.js.snap │ ├── histogram.spec.js.snap │ └── info.spec.js.snap │ ├── base.spec.js │ ├── counter.spec.js │ ├── gauge.spec.js │ ├── histogram.spec.js │ ├── index.spec.js │ └── info.spec.js ├── middleware.spec.js ├── middlewares ├── action-hook.spec.js ├── bulkhead.spec.js ├── circuit-breaker.spec.js ├── context-tracker.spec.js ├── debounce.spec.js ├── debugging │ ├── action-logger.spec.js │ └── transit-logger.spec.js ├── error-handler.spec.js ├── fallback.spec.js ├── metrics.spec.js ├── retry.spec.js ├── throttle.spec.js ├── timeout.spec.js ├── tracing.spec.js └── transmit │ ├── compression.spec.js │ └── encryption.spec.js ├── packets.spec.js ├── registry ├── action-catalog.spec.js ├── discoverers │ ├── base.spec.js │ ├── etcd3.spec.js │ ├── index.spec.js │ ├── local.spec.js │ └── redis.spec.js ├── endpoint-action.spec.js ├── endpoint-event.spec.js ├── endpoint-list.spec.js ├── endpoint.spec.js ├── event-catalog.spec.js ├── node-catalog.spec.js ├── node.spec.js ├── registry.spec.js ├── service-catalog.spec.js └── service-item.spec.js ├── serializers ├── avro.spec.js ├── base.spec.js ├── cbor.spec.js ├── index.spec.js ├── json.spec.js ├── msgpack.spec.js ├── notepack.spec.js ├── protobuf.spec.js └── thrift.spec.js ├── service-broker.spec.js ├── service.spec.js ├── strategies ├── __snapshots__ │ └── shard.spec.js.snap ├── base.spec.js ├── cpu-usage.spec.js ├── index.spec.js ├── latency.spec.js ├── random.spec.js ├── round-robin.spec.js └── shard.spec.js ├── tracing ├── exporters │ ├── __snapshots__ │ │ ├── console.spec.js.snap │ │ ├── event-legacy.spec.js.snap │ │ ├── newrelic.spec.js.snap │ │ └── zipkin.spec.js.snap │ ├── base.spec.js │ ├── console.spec.js │ ├── datadog.spec.js │ ├── event-legacy.spec.js │ ├── event.spec.js │ ├── index.spec.js │ ├── jaeger.spec.js │ ├── newrelic.spec.js │ └── zipkin.spec.js ├── rate-limiter.spec.js ├── span.spec.js └── tracer.spec.js ├── transit.spec.js ├── transporters ├── amqp.spec.js ├── amqp10.spec.js ├── base.spec.js ├── fake.spec.js ├── index.spec.js ├── kafka.spec.js ├── mqtt.spec.js ├── nats.spec.js ├── redis.spec.js ├── stan.spec.js ├── tcp.spec.js └── tcp │ ├── parser.spec.js │ ├── tcp-reader.spec.js │ ├── tcp-writer.spec.js │ └── udp-broadcaster.spec.js ├── utils.js ├── utils.spec.js └── validators ├── base.spec.js ├── fastest.spec.js └── index.spec.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | argument-count: 5 | enabled: false 6 | complex-logic: 7 | enabled: false 8 | file-lines: 9 | enabled: false 10 | method-complexity: 11 | enabled: false 12 | method-count: 13 | enabled: false 14 | method-lines: 15 | enabled: false 16 | nested-control-flow: 17 | enabled: false 18 | return-statements: 19 | enabled: false 20 | similar-code: 21 | enabled: false 22 | identical-code: 23 | enabled: false 24 | 25 | plugins: 26 | duplication: 27 | enabled: false 28 | config: 29 | languages: 30 | - javascript 31 | eslint: 32 | enabled: true 33 | channel: "eslint-7" 34 | fixme: 35 | enabled: true 36 | 37 | exclude_paths: 38 | - test/ 39 | - dev/ 40 | - docs/ 41 | - benchmark/ 42 | - examples/ 43 | - typings/ 44 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = tab 11 | indent_size = 4 12 | space_after_anon_function = true 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | indent_style = space 23 | indent_size = 4 24 | 25 | [{package,bower}.json] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.yml] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.js] 34 | quote_type = "double" 35 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | commonjs: true, 6 | es6: true, 7 | jquery: false, 8 | jest: true, 9 | jasmine: false 10 | }, 11 | extends: ["eslint:recommended", "plugin:security/recommended", "plugin:prettier/recommended"], 12 | parserOptions: { 13 | sourceType: "module", 14 | ecmaVersion: "2018" 15 | }, 16 | plugins: ["node", "promise", "security"], 17 | rules: { 18 | "no-var": ["error"], 19 | "no-console": ["error"], 20 | "no-unused-vars": ["warn"], 21 | "no-trailing-spaces": ["error"], 22 | "security/detect-object-injection": ["off"], 23 | "security/detect-non-literal-require": ["off"], 24 | "security/detect-non-literal-fs-filename": ["off"], 25 | "no-process-exit": ["off"], 26 | "node/no-unpublished-require": 0 27 | }, 28 | ignorePatterns: ["benchmark/test.js", "test/typescript/hello-world/out/*.js"], 29 | overrides: [ 30 | { 31 | files: ["runner-esm.mjs"], 32 | parserOptions: { 33 | sourceType: "module", 34 | ecmaVersion: "2020" // needed to allow import.meta 35 | } 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: moleculer 5 | open_collective: moleculer 6 | #ko_fi: # Replace with a single Ko-fi username 7 | tidelift: "npm/moleculer" 8 | #custom: # Replace with a single custom sponsorship URL 9 | github: moleculerjs 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ## :stop_sign: Do you want to ask a question ? 8 | 9 | Please head to the [Discord chat](https://discord.gg/TSEcDRP) 10 | 11 | ## Prerequisites 12 | 13 | Please answer the following questions for yourself before submitting an issue. 14 | 15 | - [ ] I am running the latest version 16 | - [ ] I checked the documentation and found no answer 17 | - [ ] I checked to make sure that this issue has not already been filed 18 | - [ ] I'm reporting the issue to the correct repository 19 | 20 | ## Current Behavior 21 | 22 | 23 | 24 | ## Expected Behavior 25 | 26 | 27 | 28 | ## Failure Information 29 | 30 | 31 | 32 | ### Steps to Reproduce 33 | 34 | Please provide detailed steps for reproducing the issue. 35 | 36 | 1. step 1 37 | 2. step 2 38 | 3. you get it... 39 | 40 | ### Reproduce code snippet 41 | 43 | 44 | ```js 45 | const broker = new ServiceBroker({ 46 | logger: console, 47 | transporter: "NATS" 48 | }); 49 | 50 | broker.createService({ 51 | name: "test", 52 | actions: { 53 | empty(ctx) { 54 | return "Hello" 55 | } 56 | } 57 | }); 58 | ``` 59 | 60 | ### Context 61 | 62 | Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. 63 | 64 | * Moleculer version: 65 | * NodeJS version: 66 | * Operating System: 67 | 68 | ### Failure Logs 69 | ``` 70 | 71 | ``` 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | ## :stop_sign: Do you want to ask a question ? 8 | 9 | Please head to the [Discord chat](https://discord.gg/TSEcDRP) 10 | 11 | ## Is your feature request related to a problem? Please describe. 12 | 13 | 14 | ## Describe the solution you'd like 15 | 16 | 17 | ## Describe alternatives you've considered 18 | 19 | 20 | ## Additional context 21 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## :memo: Description 2 | 3 | 4 | 5 | ### :dart: Relevant issues 6 | 7 | 8 | ### :gem: Type of change 9 | 10 | 11 | 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] New feature (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 15 | - [ ] This change requires a documentation update 16 | 17 | ### :scroll: Example code 18 | ```js 19 | 20 | ``` 21 | 22 | ## :vertical_traffic_light: How Has This Been Tested? 23 | 24 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 25 | 26 | - [ ] Test A 27 | - [ ] Test B 28 | 29 | ## :checkered_flag: Checklist: 30 | 31 | - [ ] My code follows the style guidelines of this project 32 | - [ ] I have performed a self-review of my own code 33 | - [ ] **I have added tests that prove my fix is effective or that my feature works** 34 | - [ ] **New and existing unit tests pass locally with my changes** 35 | - [ ] I have commented my code, particularly in hard-to-understand areas 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | 14 | # Maintain dependencies for npm 15 | - package-ecosystem: "npm" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | allow: 20 | -dependency-type: "production" 21 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | paths-ignore: 8 | - 'dev/**' 9 | - 'examples/**' 10 | - 'test/**' 11 | - '*.md' 12 | 13 | pull_request: 14 | branches: 15 | - master 16 | paths-ignore: 17 | - 'dev/**' 18 | - 'examples/**' 19 | - 'test/**' 20 | - '*.md' 21 | 22 | jobs: 23 | common: 24 | runs-on: ubuntu-22.04 25 | steps: 26 | - uses: actions/checkout@v3 27 | 28 | - name: Use Node.js 14.x 29 | uses: actions/setup-node@v3 30 | with: 31 | node-version: 14.x 32 | 33 | - name: Cache node modules 34 | uses: actions/cache@v3 35 | env: 36 | cache-name: cache-node-modules 37 | with: 38 | # npm cache files are stored in `~/.npm` on Linux/macOS 39 | path: ~/.npm 40 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 41 | restore-keys: | 42 | ${{ runner.os }}-build-${{ env.cache-name }}- 43 | ${{ runner.os }}-build- 44 | ${{ runner.os }}- 45 | 46 | - name: Install dependencies 47 | run: npm ci 48 | 49 | - name: Worker info 50 | run: | 51 | cat /proc/cpuinfo 52 | cat /proc/meminfo 53 | 54 | - name: Execute benchmark 55 | run: npm run bench common 56 | env: 57 | CI: true 58 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | paths-ignore: 8 | - '*.md' 9 | 10 | pull_request: 11 | branches: 12 | - master 13 | paths-ignore: 14 | - '*.md' 15 | 16 | jobs: 17 | common: 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Use Node.js 14.x 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 14.x 26 | 27 | - name: Cache node modules 28 | uses: actions/cache@v3 29 | env: 30 | cache-name: cache-node-modules 31 | with: 32 | # npm cache files are stored in `~/.npm` on Linux/macOS 33 | path: ~/.npm 34 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 35 | restore-keys: | 36 | ${{ runner.os }}-build-${{ env.cache-name }}- 37 | ${{ runner.os }}-build- 38 | ${{ runner.os }}- 39 | 40 | - name: Install dependencies 41 | run: npm ci 42 | 43 | - name: Check lint 44 | run: npm run lint 45 | env: 46 | CI: true 47 | -------------------------------------------------------------------------------- /.github/workflows/notification.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Discord Notification 4 | 5 | on: 6 | release: 7 | types: 8 | - published 9 | 10 | jobs: 11 | notify: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Discord notification 16 | env: 17 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 18 | uses: Ilshidur/action-discord@master 19 | with: 20 | args: ":tada: **The {{ EVENT_PAYLOAD.repository.name }} {{ EVENT_PAYLOAD.release.tag_name }} has been released.**:tada:\nChangelog: {{EVENT_PAYLOAD.release.html_url}}" 21 | -------------------------------------------------------------------------------- /.github/workflows/pkg-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Any Commit 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | - 'benchmark/**' 6 | - 'dev/**' 7 | - 'examples/**' 8 | - '*.md' 9 | push: 10 | branches: 11 | - '**' 12 | tags: 13 | - '!**' 14 | paths-ignore: 15 | - 'benchmark/**' 16 | - 'dev/**' 17 | - 'examples/**' 18 | - '*.md' 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v3 27 | 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 18 31 | 32 | - name: Cache node modules 33 | uses: actions/cache@v3 34 | env: 35 | cache-name: cache-node-modules 36 | with: 37 | # npm cache files are stored in `~/.npm` on Linux/macOS 38 | path: ~/.npm 39 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 40 | restore-keys: | 41 | ${{ runner.os }}-build-${{ env.cache-name }}- 42 | ${{ runner.os }}-build- 43 | ${{ runner.os }}- 44 | 45 | - name: Install dependencies 46 | run: npm ci 47 | 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 20 51 | 52 | - name: Publish 53 | run: npx pkg-pr-new publish 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules/ 4 | coverage/ 5 | npm-debug.log 6 | stats.json 7 | yarn-error.log 8 | benchmark/results 9 | *.exe 10 | .npmrc 11 | .prettierrc.js 12 | .prettierrc 13 | logs/ 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .github/ 3 | benchmark/ 4 | coverage/ 5 | dev/ 6 | docs/ 7 | examples/ 8 | typings 9 | test/ 10 | thrift.exe 11 | CHANGELOG.md -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "typescript.surveys.enabled": false, 4 | "typescript.tsdk": "node_modules/typescript/lib" 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Icebob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /benchmark/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: ["eslint:recommended"], 8 | parserOptions: { 9 | sourceType: "module", 10 | ecmaVersion: 2018 11 | }, 12 | rules: { 13 | "no-var": ["warn"], 14 | "no-console": ["off"], 15 | "no-unused-vars": ["off"], 16 | "security/detect-possible-timing-attacks": ["off"] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /benchmark/data/10.json: -------------------------------------------------------------------------------- 1 | {"id":100} -------------------------------------------------------------------------------- /benchmark/data/150.json: -------------------------------------------------------------------------------- 1 | {"id":"d4483402-8c34-47cc-8836-a40d37d0fb8f","requestID":"d4483402-8c34-47cc-8836-a40d37d0fb8f123","time":1482958158887,"action":{"name":"users.get"}} -------------------------------------------------------------------------------- /benchmark/data/1k.json: -------------------------------------------------------------------------------- 1 | {"status":200,"data":[{"code":"M6lxRAPkpg","title":"Eius sequi eligendi numquam a quis molestias mollitia exercitationem.","content":"Quibusdam et est maiores similique adipisci qui. Dicta et iure. Officia id voluptates non. Ullam nisi autem occaecati voluptatem adipisci. Fugit temporibus deserunt dolor minima recusandae sed quisquam.","author":{"code":"ON13Kaw74x","username":"janet69","fullName":"Janet Schulist","avatar":"","roles":["user"]},"votes":0,"voters":[],"views":0,"createdAt":"2016-12-18T12:33:41.796Z"},{"code":"6zYm6yBxen","title":"Numquam nihil omnis soluta iusto exercitationem aut enim.","content":"Eligendi cumque qui excepturi esse ut sit. Enim dolores autem. Placeat vel consectetur accusamus dolore amet qui aut modi laborum. Omnis labore molestiae. Eligendi a deserunt sint vel.\r\nCulpa tenetur et dignissimos dolorem dolores enim. Provident mollitia enim expedita eos distinctio dignissimos. Quis sapiente ea.","author":{"code":"r5V3kG3blE","username":"boyd.gerhold","fullName":"Boyd Gerhold","avatar":"","roles":["user"]}}]} -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("./suites/" + (process.argv[2] || "common")); 4 | -------------------------------------------------------------------------------- /benchmark/math.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "math", 5 | actions: { 6 | add(ctx) { 7 | return Number(ctx.params.a) + Number(ctx.params.b); 8 | }, 9 | 10 | sub(ctx) { 11 | return Number(ctx.params.a) - Number(ctx.params.b); 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /benchmark/test.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable */ 3 | 4 | /** 5 | * Call with 6 | * $ node --trace_opt --trace_deopt --allow-natives-syntax test.js 7 | */ 8 | 9 | //Function that contains the pattern to be inspected (using an `eval` statement) 10 | function exampleFunction() { 11 | return 3; 12 | //eval(''); 13 | } 14 | 15 | function printStatus(fn) { 16 | const res = %GetOptimizationStatus(fn); 17 | switch(res) { 18 | case 1: console.log("Function is optimized"); break; 19 | case 2: console.log("Function is not optimized"); break; 20 | case 3: console.log("Function is always optimized"); break; 21 | case 4: console.log("Function is never optimized"); break; 22 | case 6: console.log("Function is maybe deoptimized"); break; 23 | case 7: console.log("Function is optimized by TurboFan"); break; 24 | default: console.log("Unknown optimization status", res); break; 25 | } 26 | } 27 | 28 | //Fill type-info 29 | exampleFunction(); 30 | // 2 calls are needed to go from uninitialized -> pre-monomorphic -> monomorphic 31 | exampleFunction(); 32 | 33 | %OptimizeFunctionOnNextCall(exampleFunction); 34 | //The next call 35 | exampleFunction(); 36 | 37 | //Check 38 | printStatus(exampleFunction); 39 | -------------------------------------------------------------------------------- /benchmark/user.service.js: -------------------------------------------------------------------------------- 1 | let fakerator = require("fakerator")(); 2 | 3 | module.exports = function (broker) { 4 | let users = fakerator.times(fakerator.entity.user, 10); 5 | 6 | users.forEach((user, i) => (user.id = i + 1)); 7 | 8 | return { 9 | name: "users", 10 | actions: { 11 | find: { 12 | cache: true, 13 | handler(ctx) { 14 | return users; 15 | } 16 | }, 17 | 18 | get: { 19 | cache: true, 20 | handler(ctx) { 21 | return users.find(user => user.id == ctx.params.id); 22 | } 23 | }, 24 | 25 | get2: { 26 | cache: { 27 | keys: ["id"] 28 | }, 29 | handler(ctx) { 30 | return users.find(user => user.id == ctx.params.id); 31 | } 32 | }, 33 | 34 | nocache: { 35 | cache: false, 36 | handler(ctx) { 37 | return users.find(user => user.id == ctx.params.id); 38 | } 39 | }, 40 | 41 | validate: { 42 | cache: false, 43 | params: { 44 | id: { type: "number", integer: true, min: 1 } 45 | }, 46 | handler(ctx) { 47 | return users.find(user => user.id == ctx.params.id); 48 | } 49 | }, 50 | 51 | empty(ctx) { 52 | return []; 53 | } 54 | } 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /benchmark/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | module.exports = { 7 | getDataFile(filename, encoding = "utf8") { 8 | return fs.readFileSync(path.join(__dirname, "data", filename), encoding); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /bin/.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf 2 | -------------------------------------------------------------------------------- /bin/moleculer-runner.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* moleculer 4 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 5 | * MIT Licensed 6 | */ 7 | 8 | "use strict"; 9 | 10 | const { Runner } = require("../"); 11 | 12 | const runner = new Runner(); 13 | runner.start(process.argv); 14 | -------------------------------------------------------------------------------- /bin/moleculer-runner.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* moleculer 4 | * Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer) 5 | * MIT Licensed 6 | */ 7 | 8 | import MoleculerRunner from "../src/runner-esm.mjs"; 9 | 10 | const runner = new MoleculerRunner(); 11 | runner.start(process.argv); 12 | -------------------------------------------------------------------------------- /dev/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: ["eslint:recommended", "plugin:security/recommended", "plugin:prettier/recommended"], 8 | parserOptions: { 9 | sourceType: "module", 10 | ecmaVersion: 2018 11 | }, 12 | rules: { 13 | "no-var": ["warn"], 14 | "no-console": ["off"], 15 | "no-unused-vars": ["off"], 16 | "security/detect-possible-timing-attacks": ["off"] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | trash/ 2 | -------------------------------------------------------------------------------- /dev/SafeJsonSerializer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BaseSerializer = require("..").Serializers.Base; 4 | /** 5 | * Safe JSON serializer for Moleculer 6 | * 7 | * @class SafeJSONSerializer 8 | */ 9 | class SafeJSONSerializer extends BaseSerializer { 10 | /** 11 | * Serializer a JS object to Buffer 12 | * 13 | * @param {Object} obj 14 | * @param {String} type of packet 15 | * @returns {Buffer} 16 | * 17 | * @memberof Serializer 18 | */ 19 | serialize(obj) { 20 | const cache = new WeakSet(); 21 | return JSON.stringify(obj, (key, value) => { 22 | if (typeof value === "object" && value !== null) { 23 | if (cache.has(value)) { 24 | return "[Circular]"; 25 | } 26 | cache.add(value); 27 | } 28 | return value; 29 | }); 30 | } 31 | 32 | /** 33 | * Deserialize Buffer to JS object 34 | * 35 | * @param {Buffer} buf 36 | * @param {String} type of packet 37 | * @returns {Object} 38 | * 39 | * @memberof Serializer 40 | */ 41 | deserialize(buf) { 42 | return JSON.parse(buf); 43 | } 44 | } 45 | 46 | module.exports = SafeJSONSerializer; 47 | -------------------------------------------------------------------------------- /dev/async-local-storage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Testing the new AsyncLocalStorage module 5 | * to store the current context in the action into the async local storage. 6 | * 7 | * Please note it works only >= Node 14. 8 | */ 9 | 10 | const ServiceBroker = require("../src/service-broker"); 11 | const { AsyncLocalStorage } = require("async_hooks"); 12 | 13 | const asyncLocalStorage = new AsyncLocalStorage(); 14 | 15 | const AsyncLocalStorageMiddleware = { 16 | localAction(handler) { 17 | return ctx => asyncLocalStorage.run(ctx, () => handler(ctx)); 18 | } 19 | }; 20 | 21 | // Create broker 22 | const broker = new ServiceBroker({ 23 | middlewares: [AsyncLocalStorageMiddleware] 24 | }); 25 | 26 | broker.createService({ 27 | name: "greeter", 28 | actions: { 29 | hello: { 30 | async handler(ctx) { 31 | await this.doSomething(); 32 | return `Hello ${ctx.params.name}`; 33 | } 34 | } 35 | }, 36 | methods: { 37 | async doSomething() { 38 | await Promise.resolve().delay(500); 39 | const ctx = asyncLocalStorage.getStore(); 40 | console.log("Current context params:", ctx ? ctx.params : ""); 41 | } 42 | } 43 | }); 44 | 45 | broker 46 | .start() 47 | .then(() => broker.call("greeter.hello", { name: "Moleculer" })) 48 | .then(res => broker.logger.info("Result:", res)) 49 | .catch(err => broker.logger.error(err)) 50 | .then(() => broker.stop()); 51 | -------------------------------------------------------------------------------- /dev/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let ServiceBroker = require("../src/service-broker"); 4 | 5 | // Create broker 6 | let broker = new ServiceBroker({ 7 | transporter: "NATS", 8 | logger: console, 9 | logLevel: "debug", 10 | hotReload: true 11 | }); 12 | /* 13 | broker.createService({ 14 | name: "test", 15 | actions: { 16 | hello(ctx) { 17 | return "Hello Moleculer!"; 18 | } 19 | } 20 | });*/ 21 | 22 | broker.start().then(() => { 23 | /* 24 | setInterval(() => { 25 | //broker.call("test.hello") 26 | broker.call("math.add", { a: 5, b: 2 }) 27 | .then(res => broker.logger.info(res)) 28 | .catch(err => broker.logger.error(err)); 29 | 30 | }, 1000);*/ 31 | 32 | //setInterval(() => { 33 | /* 34 | setTimeout(() => { 35 | let svc = broker.getLocalService("test"); 36 | broker.destroyService(svc); 37 | }, 10 * 1000);*/ 38 | 39 | //broker.loadService("./examples/hot.service.js"); 40 | 41 | //}, 10 * 1000); 42 | 43 | //broker.loadService("./examples/math.service.js"); 44 | //broker.loadService("./examples/user.service.js"); 45 | 46 | broker.repl(); 47 | }); 48 | -------------------------------------------------------------------------------- /dev/bigfile-sender.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require("fs"); 2 | const { ServiceBroker } = require("../"); 3 | const broker = new ServiceBroker({ 4 | logLevel: "debug", 5 | namespace: "streamissue", 6 | nodeID: "sender", 7 | transporter: "NATS" 8 | }); 9 | 10 | async function start() { 11 | try { 12 | await broker.start(); 13 | console.log("Stream starting..."); 14 | let stream = createReadStream("d:/100MB.zip"); 15 | await broker.call("notpublisher.listener", null, { meta: { danger: "dsadsds" }, stream }); 16 | console.log("Stream sent"); 17 | } catch (err) { 18 | console.error(err); 19 | // process.exit(1); 20 | } 21 | } 22 | 23 | (async () => { 24 | await start(); 25 | })(); 26 | -------------------------------------------------------------------------------- /dev/bigfile.receiver.js: -------------------------------------------------------------------------------- 1 | const { ServiceBroker } = require("../"); 2 | const broker = new ServiceBroker({ 3 | logLevel: "debug", 4 | namespace: "streamissue", 5 | nodeID: "listener", 6 | transporter: "NATS" 7 | /*errorHandler: (err, info) => { 8 | console.error("Error occurred:", err); 9 | }*/ 10 | }); 11 | 12 | async function test(ctx) { 13 | throw new Error("Tehee"); 14 | } 15 | 16 | async function start() { 17 | try { 18 | await broker.start(); 19 | await broker.createService({ 20 | name: "notpublisher", 21 | actions: { listener: async ctx => await test(ctx) } 22 | }); 23 | } catch (err) { 24 | console.error(err); 25 | } 26 | } 27 | 28 | (async () => { 29 | await start(); 30 | })(); 31 | -------------------------------------------------------------------------------- /dev/breaker.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | const { MoleculerServerError } = require("../src/errors"); 3 | 4 | const broker = new ServiceBroker({ 5 | requestTimeout: 2000, 6 | 7 | // Reorder the internal middlewares 8 | internalMiddlewares: false, 9 | middlewares: [ 10 | "ActionHook", 11 | "Validator", 12 | "Bulkhead", 13 | "Cacher", 14 | "ContextTracker", 15 | // Changes here start 16 | "Timeout", 17 | "CircuitBreaker", 18 | // Changes here end 19 | "Retry", 20 | "Fallback", 21 | "ErrorHandler", 22 | "Tracing", 23 | "Metrics", 24 | "Debounce", 25 | "Throttle" 26 | ], 27 | 28 | circuitBreaker: { 29 | enabled: true, 30 | threshold: 0.5, 31 | windowTime: 10, 32 | minRequestCount: 5, 33 | halfOpenTime: 5 * 1000 34 | } 35 | }); 36 | 37 | broker.createService({ 38 | name: "greeter", 39 | actions: { 40 | welcome: { 41 | async handler(ctx) { 42 | await broker.Promise.delay(Math.random() * 4000); 43 | return `Hello ${ctx.params.name}`; 44 | } 45 | } 46 | } 47 | }); 48 | 49 | broker.start().then(() => { 50 | broker.repl(); 51 | setInterval(async () => { 52 | try { 53 | const res = await broker.call("greeter.welcome", { name: "John" }); 54 | broker.logger.info(res); 55 | } catch (err) { 56 | broker.logger.error(err.message); 57 | } 58 | }, 1000); 59 | }); 60 | -------------------------------------------------------------------------------- /dev/circular.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | //const { removeCircularRefs } = require("../src/utils"); 5 | 6 | // Create broker #1 7 | const broker1 = new ServiceBroker({ 8 | namespace: "circular", 9 | nodeID: "node-1", 10 | transporter: "NATS", 11 | serializer: "JSON" 12 | }); 13 | 14 | // Create broker #2 15 | const broker2 = new ServiceBroker({ 16 | namespace: "circular", 17 | nodeID: "node-2", 18 | transporter: "NATS", 19 | serializer: "JSON" 20 | }); 21 | 22 | const myProp = { 23 | a: 5, 24 | b: { 25 | c: 100 26 | }, 27 | f: () => "F" 28 | }; 29 | 30 | myProp.b.d = myProp; 31 | 32 | broker1.createService({ 33 | name: "test", 34 | actions: { 35 | hello: { 36 | myProp, 37 | handler(ctx) { 38 | return "Hello"; 39 | } 40 | } 41 | } 42 | }); 43 | 44 | Promise.all([broker1.start(), broker2.start()]).then(() => broker1.repl()); 45 | -------------------------------------------------------------------------------- /dev/cluster.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const os = require("os"), 4 | cluster = require("cluster"), 5 | stopSignals = [ 6 | "SIGHUP", 7 | "SIGINT", 8 | "SIGQUIT", 9 | "SIGILL", 10 | "SIGTRAP", 11 | "SIGABRT", 12 | "SIGBUS", 13 | "SIGFPE", 14 | "SIGUSR1", 15 | "SIGSEGV", 16 | "SIGUSR2", 17 | "SIGTERM" 18 | ], 19 | production = true; //process.env.NODE_ENV == "production"; 20 | 21 | let stopping = false; 22 | 23 | cluster.on("disconnect", function (worker) { 24 | if (production) { 25 | if (!stopping) { 26 | cluster.fork(); 27 | } 28 | } else process.exit(1); 29 | }); 30 | 31 | if (cluster.isMaster) { 32 | const workerCount = process.env.NODE_CLUSTER_WORKERS || os.cpus().length; 33 | console.log(`Starting ${workerCount} workers...`); 34 | for (let i = 0; i < workerCount; i++) { 35 | const worker = cluster.fork(); 36 | } 37 | 38 | if (production) { 39 | stopSignals.forEach(function (signal) { 40 | process.on(signal, function () { 41 | console.log(`Got ${signal}, stopping workers...`); 42 | stopping = true; 43 | cluster.disconnect(function () { 44 | console.log("All workers stopped, exiting."); 45 | process.exit(0); 46 | }); 47 | }); 48 | }); 49 | } 50 | } else { 51 | const worker = cluster.worker; 52 | const hostname = os.hostname(); 53 | worker.process.argv.push(hostname + "-client-" + worker.id); 54 | 55 | require("./client.js"); 56 | } 57 | -------------------------------------------------------------------------------- /dev/compress.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const Middlewares = require("..").Middlewares; 5 | const ServiceBroker = require("../src/service-broker"); 6 | 7 | // Create broker 8 | const broker = new ServiceBroker({ 9 | transporter: "NATS", 10 | logLevel: { 11 | "TX-COMPRESS": "debug", 12 | "*": "info" 13 | }, 14 | middlewares: [Middlewares.Transmit.Compression()] 15 | }); 16 | 17 | broker 18 | .start() 19 | .then(() => broker.repl()) 20 | .then(() => {}) 21 | .catch(err => broker.logger.error(err)); 22 | -------------------------------------------------------------------------------- /dev/custom-context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const Context = require("../src/context"); 5 | 6 | class MyContext extends Context { 7 | throwApiError(type, data, message) { 8 | console.log("Helloooooo"); 9 | } 10 | } 11 | 12 | const broker = new ServiceBroker({ 13 | nodeID: "dev-" + process.pid, 14 | logger: true, 15 | //logLevel: "debug", 16 | //transporter: "TCP", 17 | ContextFactory: MyContext 18 | }); 19 | 20 | broker.createService({ 21 | name: "test", 22 | actions: { 23 | test(ctx) { 24 | ctx.throwApiError(); 25 | } 26 | } 27 | }); 28 | 29 | broker 30 | .start() 31 | //.then(() => broker.repl()) 32 | /*.delay(2000) 33 | .then(() => { 34 | console.log("Destroy hot service"); 35 | broker.destroyService(svc); 36 | })*/ 37 | //.delay(1000) 38 | .then(() => broker.call("test.test").then(res => broker.logger.info(res))); 39 | -------------------------------------------------------------------------------- /dev/debounce-throttle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | 5 | const broker = new ServiceBroker({ 6 | logger: true 7 | }); 8 | 9 | broker.createService({ 10 | name: "test", 11 | events: { 12 | debounce: { 13 | debounce: 5000, 14 | handler(ctx) { 15 | this.logger.info("Debounced event received."); 16 | } 17 | }, 18 | throttle: { 19 | throttle: 5000, 20 | handler(ctx) { 21 | this.logger.info("Throttled event received."); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | broker.start().then(() => broker.repl()); 28 | -------------------------------------------------------------------------------- /dev/dev.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | To test run: 5 | 6 | node bin/moleculer-runner.js --config dev/dev.config.js --repl 7 | 8 | Call the service and you will see middleware messages in console 9 | 10 | mol $ call test.hello 11 | 12 | */ 13 | 14 | function myMiddleware() { 15 | return handler => async ctx => { 16 | ctx.broker.logger.warn(">> MW1-before (from config)"); 17 | const res = await handler(ctx); 18 | ctx.broker.logger.warn("<< MW1-after (from config)"); 19 | return res; 20 | }; 21 | } 22 | 23 | module.exports = { 24 | namespace: "config-test", 25 | transporter: "TCP", 26 | logger: true, 27 | logLevel: "debug", 28 | 29 | middlewares: [myMiddleware()], 30 | 31 | created(broker) { 32 | broker.logger.warn("--- Broker created (from config)!"); 33 | }, 34 | 35 | started(broker) { 36 | broker.logger.warn("--- Broker started (from config)!"); 37 | 38 | broker.createService({ 39 | name: "test", 40 | actions: { 41 | hello(ctx) { 42 | return "Hello"; 43 | } 44 | } 45 | }); 46 | 47 | return broker.Promise.delay(2000).then(() => broker.call("$node.list")); 48 | }, 49 | 50 | stopped(broker) { 51 | return broker.Promise.delay(2000).then(() => broker.logger.warn("--- Broker stopped")); 52 | }, 53 | replCommands: [ 54 | { 55 | command: "hello ", 56 | description: "Call the greeter.hello service with name", 57 | alias: "hi", 58 | options: [{ option: "-u, --uppercase", description: "Uppercase the name" }], 59 | types: { 60 | string: ["name"], 61 | boolean: ["u", "uppercase"] 62 | }, 63 | //parse(command, args) {}, 64 | //validate(args) {}, 65 | //help(args) {}, 66 | allowUnknownOptions: true, 67 | action(broker, args) { 68 | const name = args.options.uppercase ? args.name.toUpperCase() : args.name; 69 | return broker.call("greeter.hello", { name }).then(console.log); 70 | } 71 | } 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /dev/dev.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | metrics: { 5 | enabled: true 6 | }, 7 | 8 | tracing: { 9 | enabled: true, 10 | sampling: { 11 | rate: 1.0 12 | }, 13 | events: true, 14 | exporter: { 15 | type: "Event" 16 | } 17 | } 18 | }); 19 | 20 | broker.createService({ 21 | name: "greeter", 22 | actions: { 23 | welcome: { 24 | handler(ctx) { 25 | return `Hello ${ctx.params.name}`; 26 | } 27 | } 28 | }, 29 | events: { 30 | "$tracing.spans"(ctx) { 31 | this.logger.info("Span event received", ctx.params); 32 | } 33 | } 34 | }); 35 | 36 | broker 37 | .start() 38 | .then(() => broker.repl()) 39 | .then(() => broker.call("greeter.welcome", { name: "Icebob" })) 40 | .then(res => broker.logger.info("Result:", res)) 41 | .catch(err => broker.logger.error(err)); 42 | -------------------------------------------------------------------------------- /dev/direct-call.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const svc = { 4 | name: "greeter", 5 | actions: { 6 | welcome: { 7 | handler(ctx) { 8 | return `Hello on ${this.broker.nodeID}`; 9 | } 10 | } 11 | } 12 | }; 13 | 14 | const broker1 = new ServiceBroker({ 15 | nodeID: "node-1", 16 | transporter: "NATS", 17 | registry: { strategy: "RoundRobin", preferLocal: true } 18 | }); 19 | const broker2 = new ServiceBroker({ 20 | nodeID: "node-2", 21 | transporter: "NATS", 22 | registry: { strategy: "RoundRobin", preferLocal: true } 23 | }); 24 | 25 | broker1.createService(svc); 26 | broker2.createService(svc); 27 | 28 | Promise.all([broker1.start(), broker2.start()]) 29 | .delay(2000) 30 | .then(async () => { 31 | broker1 32 | .call("greeter.welcome", {}, { nodeID: "node-2" }) 33 | .then(res => broker1.logger.info("Result:", res)); 34 | broker1 35 | .call("greeter.welcome", {}, { nodeID: "node-2" }) 36 | .then(res => broker1.logger.info("Result:", res)); 37 | broker1 38 | .call("greeter.welcome", {}, { nodeID: "node-2" }) 39 | .then(res => broker1.logger.info("Result:", res)); 40 | }); 41 | -------------------------------------------------------------------------------- /dev/empty.service.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/dev/empty.service.js -------------------------------------------------------------------------------- /dev/encrypt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const Middlewares = require("..").Middlewares; 5 | const ServiceBroker = require("../src/service-broker"); 6 | 7 | // Create broker 8 | const broker = new ServiceBroker({ 9 | transporter: "NATS", 10 | logLevel: { 11 | "TX-COMPRESS": "debug", 12 | "*": "info" 13 | }, 14 | middlewares: [ 15 | Middlewares.Transmit.Encryption( 16 | "12345678901234567890123456789012", 17 | "aes-256-cbc", 18 | "1234567890123456" 19 | ) 20 | ] 21 | }); 22 | 23 | broker 24 | .start() 25 | .then(() => broker.repl()) 26 | .then(() => {}) 27 | .catch(err => broker.logger.error(err)); 28 | -------------------------------------------------------------------------------- /dev/errorHandler.js: -------------------------------------------------------------------------------- 1 | const { ServiceBroker } = require(".."); 2 | 3 | const broker = new ServiceBroker({ 4 | errorHandler(err, info) { 5 | this.logger.warn("Error handled:", err); 6 | } 7 | }); 8 | 9 | broker.createService({ 10 | name: "greeter", 11 | actions: { 12 | async hello(ctx) { 13 | throw new Error("Something went wrong"); 14 | } 15 | }, 16 | events: { 17 | "test.event": [ 18 | () => { 19 | console.log("Called first handler"); 20 | throw new Error("Error in event handler"); 21 | }, 22 | () => { 23 | console.log("Called second handler"); 24 | } 25 | ] 26 | }, 27 | started() {} 28 | }); 29 | 30 | broker.createService({ 31 | name: "events", 32 | events: { 33 | "test.event": () => { 34 | console.log("Called third handler"); 35 | } 36 | } 37 | }); 38 | 39 | (async function () { 40 | await broker.start(); 41 | 42 | try { 43 | await broker.call("greeter2.hello"); 44 | //broker.broadcast("test.event", { a: 5 }); 45 | } catch (err) { 46 | broker.logger.error("Catched error", err); 47 | } 48 | 49 | broker.repl(); 50 | })(); 51 | -------------------------------------------------------------------------------- /dev/es6-class.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | const broker = new ServiceBroker(); 3 | 4 | broker.loadService("./examples/es6.class.service.js"); 5 | broker.start(); 6 | -------------------------------------------------------------------------------- /dev/event-store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let ServiceBroker = require("../src/service-broker"); 4 | 5 | let broker = new ServiceBroker({ 6 | logger: true, 7 | logLevel: "debug" 8 | }); 9 | 10 | broker.createService({ 11 | name: "event-store", 12 | events: { 13 | "ES.**"(payload, sender, event) { 14 | this.store({ 15 | event, 16 | payload 17 | }).then(() => { 18 | this.broker.emit(event.slice(3), payload); 19 | }); 20 | } 21 | }, 22 | 23 | methods: { 24 | store(event) { 25 | this.logger.info(`STORE '${event.event}' event.`); 26 | // Do something... 27 | return Promise.resolve(event); 28 | } 29 | } 30 | }); 31 | 32 | broker.createService({ 33 | name: "target", 34 | events: { 35 | "user.created"(payload) { 36 | this.logger.info("User created event RECEIVED!", payload); 37 | } 38 | } 39 | }); 40 | 41 | broker.start().then(() => broker.emit("ES.user.created", { name: "John" })); 42 | -------------------------------------------------------------------------------- /dev/event-validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | 5 | const broker = new ServiceBroker({ 6 | errorHandler(err, info) { 7 | this.logger.warn("Error catched:", err); 8 | } 9 | }); 10 | 11 | broker.createService({ 12 | name: "mailer", 13 | events: { 14 | "send.mail": { 15 | params: { 16 | from: "string|optional", 17 | to: "email", 18 | subject: "string" 19 | }, 20 | handler(ctx) { 21 | this.logger.info("Event received", ctx.params); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | broker 28 | .start() 29 | .then(() => broker.repl()) 30 | .then(() => 31 | broker.emit("send.mail", { 32 | to: "a@b.c" 33 | //subject: "Test" 34 | }) 35 | ); 36 | -------------------------------------------------------------------------------- /dev/event-wildcard.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | logger: console, 5 | transporter: "NATS", 6 | disableBalancer: true 7 | }); 8 | 9 | broker.createService({ 10 | name: "test", 11 | actions: { 12 | emit(ctx) { 13 | ctx.emit("this.is.an.event"); 14 | } 15 | }, 16 | events: { 17 | "**"(data, sender, eventName) { 18 | if (eventName == "this.is.an.event") this.logger.info("event triggered", eventName); 19 | } 20 | } 21 | }); 22 | 23 | broker.createService({ 24 | name: "test2", 25 | events: { 26 | "*.is.an.event"(data, sender, eventName) { 27 | if (eventName == "this.is.an.event") this.logger.info("event triggered2", eventName); 28 | } 29 | } 30 | }); 31 | 32 | broker.start().then(() => { 33 | broker.repl(); 34 | 35 | return broker.call("test.emit"); 36 | }); 37 | -------------------------------------------------------------------------------- /dev/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2]; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + (moduleName || "dev")); 7 | -------------------------------------------------------------------------------- /dev/internal-errors.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | logger: false, 5 | cacher: "Redis", 6 | 7 | created(broker) { 8 | broker.localBus.on("*.error", payload => { 9 | console.error("locaBus error", payload); 10 | }); 11 | } 12 | }); 13 | 14 | broker.createService({ 15 | name: "error-tracker", 16 | 17 | events: { 18 | "$**.error": { 19 | handler(ctx) { 20 | console.error("error tracker error", ctx.params); 21 | } 22 | } 23 | } 24 | }); 25 | 26 | broker.start().catch(err => broker.logger.error(err)); 27 | -------------------------------------------------------------------------------- /dev/internal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let ServiceBroker = require("../src/service-broker"); 4 | 5 | // Create broker 6 | let broker = new ServiceBroker({ 7 | metrics: true, 8 | internalServices: { 9 | $node: { 10 | actions: { 11 | hello(ctx) { 12 | return `Hello ${ctx.params.name || "Anonymous"}!`; 13 | }, 14 | options: false 15 | } 16 | } 17 | } 18 | }); 19 | 20 | broker.start().then(() => { 21 | broker.repl(); 22 | }); 23 | -------------------------------------------------------------------------------- /dev/issue-1100-receiver.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const TransitLogger = require("../src/middlewares/debugging/transit-logger"); 5 | const TransmitCompression = require("../src/middlewares/transmit/compression"); 6 | const Stream = require("stream"); 7 | 8 | const broker = new ServiceBroker({ 9 | nodeID: "receiver", 10 | transporter: "NATS", 11 | serializer: "JSON", 12 | 13 | requestTimeout: 10 * 1000, 14 | 15 | middlewares: [ 16 | TransmitCompression({ method: "gzip" }) 17 | /*TransitLogger({ 18 | folder: "logs/transit" 19 | })*/ 20 | ] 21 | }); 22 | 23 | broker.createService({ 24 | name: "receiver", 25 | /** 26 | * Actions 27 | */ 28 | actions: { 29 | receive: { 30 | async handler(ctx) { 31 | // ! called two times if meta is "large" 32 | this.logger.info("call receive handler", ctx.params, ctx.meta); 33 | if (ctx.params) { 34 | const participants = []; 35 | ctx.params.on("data", d => participants.push(d)); 36 | ctx.params.on("end", () => 37 | this.logger.info( 38 | "received stream data", 39 | participants.length, 40 | ctx.meta, 41 | participants 42 | ) 43 | ); 44 | return "OK"; 45 | } else { 46 | this.logger.error("No stream", ctx.params, ctx.meta); 47 | return "no stream"; 48 | } 49 | } 50 | } 51 | } 52 | }); 53 | 54 | broker.start().then(() => broker.repl()); 55 | -------------------------------------------------------------------------------- /dev/issue-1100-sender.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const TransitLogger = require("../src/middlewares/debugging/transit-logger"); 5 | const TransmitCompression = require("../src/middlewares/transmit/compression"); 6 | const Stream = require("stream"); 7 | 8 | const broker = new ServiceBroker({ 9 | nodeID: "sender", 10 | transporter: "NATS", 11 | serializer: "JSON", 12 | 13 | requestTimeout: 10 * 1000, 14 | 15 | middlewares: [ 16 | TransmitCompression({ method: "gzip" }) 17 | /*TransitLogger({ 18 | folder: "logs/transit" 19 | })*/ 20 | ] 21 | }); 22 | 23 | broker.createService({ 24 | name: "sender", 25 | dependencies: ["receiver"], 26 | 27 | actions: { 28 | send: { 29 | async handler(ctx) { 30 | const participants = []; 31 | for (let i = 0; i < 100000; i++) { 32 | participants.push({ entry: i }); 33 | } 34 | const stream = new Stream.Readable(); 35 | stream.push(Buffer.from(JSON.stringify(participants))); 36 | stream.push(null); 37 | this.logger.info("sending stream..."); 38 | const res = await ctx.call("receiver.receive", stream, { 39 | meta: { 40 | //! meta data is missing on receiver side 41 | testMeta: "testMeta", 42 | participants: participants.slice(0, 100) 43 | } 44 | }); 45 | this.logger.info("finished sending stream", res); 46 | return res; 47 | } 48 | } 49 | } 50 | }); 51 | 52 | broker.start().then(async () => { 53 | broker.repl(); 54 | await broker.Promise.delay(2000); 55 | broker.logger.info("Calling send..."); 56 | await broker.call("sender.send"); 57 | }); 58 | -------------------------------------------------------------------------------- /dev/issue-1121.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | const fs = require("fs"); 3 | 4 | const broker = new ServiceBroker(); 5 | 6 | broker.createService({ 7 | name: "service-creator", 8 | actions: { 9 | createService(ctx) { 10 | return this.broker.createService({ 11 | name: `${ctx.params.name}-${ctx.params.id}` 12 | }); 13 | } 14 | }, 15 | events: { 16 | hello(ctx) {} 17 | } 18 | }); 19 | 20 | broker.start().then(() => { 21 | broker.repl(); 22 | 23 | let id = 1; 24 | setInterval(() => { 25 | broker 26 | .call("service-creator.createService", { name: "Service", id: id++ }) 27 | .then(res => { 28 | broker.logger.info(res.name); 29 | return res; 30 | }) 31 | .then(async res => broker.destroyService(res)) 32 | .then(() => { 33 | broker.logger.info( 34 | `broker.registry.nodes.localNode.services.length: ${broker.registry.nodes.localNode.services.length}` 35 | ); 36 | broker.logger.info( 37 | `broker.registry.actions.actions.length: ${broker.registry.actions.actions.size}` 38 | ); 39 | broker.logger.info( 40 | `broker.registry.events.events.length: ${broker.registry.events.events.size}` 41 | ); 42 | broker.logger.info(`broker.services.length: ${broker.services.length}`); 43 | 44 | // fs.writeFileSync( 45 | // "./nodeInfo.json", 46 | // JSON.stringify(broker.registry.getLocalNodeInfo(), null, 2) 47 | // ); 48 | }) 49 | .catch(err => broker.logger.error(err.message)); 50 | }, 1000); 51 | }); 52 | -------------------------------------------------------------------------------- /dev/issue-1132.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const brokerConfig = { 4 | logLevel: "error", 5 | transporter: "NATS" 6 | }; 7 | 8 | async function benchmark() { 9 | console.log("Starting brokers..."); 10 | 11 | const broker1 = new ServiceBroker({ 12 | nodeID: "node-1", 13 | ...brokerConfig 14 | }); 15 | 16 | const broker2 = new ServiceBroker({ 17 | nodeID: "node-2", 18 | ...brokerConfig 19 | }); 20 | 21 | for (let i = 0; i < 1000; i++) { 22 | broker1.createService({ 23 | name: `broker1-service${i}` 24 | }); 25 | } 26 | for (let i = 0; i < 1000; i++) { 27 | broker2.createService({ 28 | name: `broker2-service${i}` 29 | }); 30 | } 31 | 32 | console.time("Startup time"); 33 | await Promise.all([broker1.start(), broker2.start()]); 34 | console.timeLog("Startup time"); 35 | await Promise.all([broker1.stop(), broker2.stop()]); 36 | } 37 | 38 | benchmark(); 39 | -------------------------------------------------------------------------------- /dev/issue-1241.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | middlewares: [ 5 | { 6 | call(next) { 7 | return (actionName, params, opts) => { 8 | const p = next(actionName, params, opts); 9 | 10 | const pp = p.then(res => { 11 | return res; 12 | }); 13 | 14 | pp.ctx = p.ctx; 15 | 16 | return pp; 17 | }; 18 | } 19 | } 20 | ] 21 | }); 22 | 23 | broker.createService({ 24 | name: "statusCodeTest", 25 | 26 | actions: { 27 | testNotFound: { 28 | rest: "GET /testNotFound", 29 | handler(ctx) { 30 | ctx.meta.$statusCode = 404; 31 | } 32 | } 33 | } 34 | }); 35 | 36 | broker.createService({ 37 | name: "test", 38 | actions: { 39 | hello: { 40 | async handler(ctx) { 41 | await ctx.call("statusCodeTest.testNotFound"); 42 | this.logger.info("Context meta", ctx.meta); 43 | } 44 | } 45 | } 46 | }); 47 | 48 | broker.start().then(() => { 49 | broker.repl(); 50 | 51 | broker.call("test.hello").then(res => { 52 | console.log("Result:", res); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /dev/jsrepl.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ logger: true }); 4 | broker.createService({ 5 | name: "greeter", 6 | actions: { 7 | hello(ctx) { 8 | return "Hello!"; 9 | } 10 | } 11 | }); 12 | 13 | broker.start().then(() => (require("repl").start("mol $ ").context.broker = broker)); 14 | -------------------------------------------------------------------------------- /dev/lifecycle.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | nodeID: "node-" + process.pid, 5 | transporter: "NATS" 6 | }); 7 | 8 | broker.createService({ 9 | name: "greeter", 10 | actions: { 11 | hello: { 12 | rest: "hello", 13 | handler(ctx) { 14 | return "Hello World"; 15 | } 16 | } 17 | } 18 | }); 19 | 20 | broker.createService({ 21 | name: "$listener", 22 | events: { 23 | "$broker.started"(payload, sender, event) { 24 | this.logger.info(event); 25 | }, 26 | "$broker.stopped"(payload, sender, event) { 27 | this.logger.info(event); 28 | }, 29 | 30 | "$transporter.connected"(payload, sender, event) { 31 | this.logger.info(event); 32 | }, 33 | "$transporter.disconnected"(payload, sender, event) { 34 | this.logger.info(event); 35 | }, 36 | 37 | "$node.connected"({ node }, sender, event) { 38 | this.logger.info(event, node.id); 39 | }, 40 | "$node.updated"({ node }, sender, event) { 41 | this.logger.info(event, node.id); 42 | }, 43 | "$node.disconnected"({ node }, sender, event) { 44 | this.logger.info(event, node.id); 45 | }, 46 | 47 | "$services.changed"(payload, sender, event) { 48 | this.logger.info(event); 49 | }, 50 | 51 | "$circuit-breaker.opened"(payload, sender, event) { 52 | this.logger.info(event); 53 | }, 54 | "$circuit-breaker.half-opened"(payload, sender, event) { 55 | this.logger.info(event); 56 | }, 57 | "$circuit-breaker.closed"(payload, sender, event) { 58 | this.logger.info(event); 59 | } 60 | } 61 | }); 62 | 63 | broker.start().then(() => { 64 | broker.repl(); 65 | 66 | setTimeout(() => broker.loadService("./examples/hot.service.js"), 5000); 67 | }); 68 | -------------------------------------------------------------------------------- /dev/method-mw.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const util = require("util"); 5 | 6 | const MW = { 7 | // Wrap local method calls 8 | localMethod(handler, method) { 9 | return params => { 10 | console.log( 11 | `The '${method.name}' method is called in '${method.service.fullName}' service.`, 12 | params 13 | ); 14 | const res = handler(params); 15 | if (method.uppercase) return res.toUpperCase(); 16 | 17 | return res; 18 | }; 19 | } 20 | }; 21 | 22 | const broker = new ServiceBroker({ 23 | nodeID: "mw", 24 | middlewares: [MW] 25 | }); 26 | 27 | const svc = broker.createService({ 28 | name: "test", 29 | actions: { 30 | hello(ctx) { 31 | return this.hello(ctx.params); 32 | } 33 | }, 34 | methods: { 35 | hello: { 36 | uppercase: true, 37 | handler(params) { 38 | return `Hello ${params.name}`; 39 | } 40 | }, 41 | hello2(params) { 42 | return `Hello2 ${params.name}`; 43 | } 44 | } 45 | }); 46 | 47 | broker 48 | .start() 49 | .then(() => broker.repl()) 50 | .then(() => { 51 | broker.call("test.hello", { name: "John" }).then(res => broker.logger.info("Res:", res)); 52 | 53 | broker.logger.info("Svc", svc.hello({ name: "Jane" })); 54 | }); 55 | -------------------------------------------------------------------------------- /dev/mw-order.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker, Middlewares } = require("../"); 4 | 5 | Middlewares.register("MyMiddleware", { 6 | name: "MyMiddleware", 7 | localAction(next, action) { 8 | return async ctx => { 9 | ctx.broker.logger.info("MyMW before"); 10 | const res = await next(ctx); 11 | ctx.broker.logger.info("MyMW after"); 12 | return res; 13 | }; 14 | } 15 | }); 16 | 17 | const broker = new ServiceBroker({ 18 | nodeID: "mw", 19 | cacher: "Memory", 20 | internalMiddlewares: false, 21 | middlewares: ["Cacher", "ActionHook", "MyMiddleware"] 22 | }); 23 | 24 | broker.createService({ 25 | name: "test", 26 | actions: { 27 | hello: { 28 | cache: true, 29 | hooks: { 30 | before: ctx => ctx.broker.logger.info(" Hook before"), 31 | after: (ctx, res) => { 32 | ctx.broker.logger.info(" Hook after"); 33 | return res; 34 | } 35 | }, 36 | handler(ctx) { 37 | broker.logger.info(" Call action"); 38 | return `Hello ${ctx.params.name}`; 39 | } 40 | } 41 | } 42 | }); 43 | 44 | broker.start().then(async () => { 45 | broker.repl(); 46 | 47 | await broker.call("test.hello", { name: "John" }); 48 | await broker.call("test.hello", { name: "John" }); 49 | await broker.call("test.hello", { name: "John" }); 50 | }); 51 | -------------------------------------------------------------------------------- /dev/nats-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE+zCCAuOgAwIBAgIJAPESHOXUX4uMMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xNzEyMjAxMTQ5MzFaFw0xODEyMjAxMTQ5MzFaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBANHMuxQcI2hBEgCTw9uloCgGq2ou8SXB8sEYkKXaKXSsHvJq1b1MWhXvyR6l 6 | VrBwvlLRRHS083lSVTPQLbzdmE0wCJJrUM1UX2+8BwJL3z+WKMTbN/ykhDdInhfl 7 | hGPIHsQtiHkP3XmcpZzlIYFy7FPWzda4+E9p3lzv6DqrSFhd+MfPgySQNRow9XMU 8 | qtrWYGCfgsRYO2jesgheLUfra4XktVxssfaJPNnPCarXWcBFyO5cF6Xz7D8qcGsU 9 | 6RdXwtCwcs8lktvjLlFeHPX2xXPb7hX48IM5qhGtrRNny2Sb7DfbJm2LIuN3plTC 10 | R9LH/fcDLbGetHNGYYKpBhNf8Gq0PVoKhbaOXsQSQZkhFD0qZa3fuROZSR4dnnnS 11 | zJntoQKwd1m0otbolhyahLK4/yl0LuUTTsRbWj3CcWICPLuul0jyLFsRRAF7ohsB 12 | Ox/gK4lYWjZd6L9XTDMP50PYY8sxpGNCgsYpC01i7iGPs2XpSwcNV6ELK+PT3R6f 13 | UoWm4e4MK26r+D3ESiieulx6aex/d358HAAwgWO/VSbI5S3bqPuhuruFtQoYqfXZ 14 | sAyFzBjPZMvRQlEiiJ/kbrkB5PMba/+qrAOhxhGCtZG+oNAq8J1XBWE/nj9LvHfr 15 | hHpztRTGGfEaovVzWm9xkWSW5Galy13wqzD1DJVYo/bfkknVAgMBAAGjUDBOMB0G 16 | A1UdDgQWBBS0VjcwfC1WmWtYdErM2EAnUiVWqjAfBgNVHSMEGDAWgBS0VjcwfC1W 17 | mWtYdErM2EAnUiVWqjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAl 18 | wJyDJm+APhBhSfrXik+HP5Yanr+0TJSMDsrJHKmoYlRe6vw0OMpPvehscli8DP+w 19 | +qNXHZVACWPdRFRvYG+EJ3Ta38SzuQ07qxN3v7Fu7VPBR9iXG4VwNUH02+fVqnYK 20 | 3t52SNJTytM4VhohAdBQEnpWWLv70ZNr7uUZyPTVLw3NBxtYaIK9Iw8w8QTWyHyC 21 | GM/Xg0HvPaxDO/2oOro+905KgjvkpV1W6mn2xYR7/zxfwnS5DcNexRshWnnJdX7+ 22 | hunrcSFZuMyMMJlUcNCSoC5sJ3v2nzcXZLZNVV1wwJcOR0fP6J6g0vc7Jd0aa7W9 23 | 0y52sL1EeXJGPDMirdSXD7pY/ogXbYmn06dx44iEHOLig+ki+ZOFysLxvRxtTiWg 24 | S54qtVQurwbq3LpaUJQAZ71V3+gDJ1EncvtZCW3nb3fqH05lQI/xRDj2ZycdvgMK 25 | ATQ2TE/TGjnsWxZp4bFWiwzDN+DFthhi8mxhzpvAwBEnM+ub4ynp2b1VtJopFck7 26 | gg5LRGc7KbIbTWO/tfJBpsNtnxNErinsXr3oRmapqYMt0AuYpBxtPa7CfMYEMkwS 27 | 3DVSimeXsSeoNzK1PTZ0DNNBOSbQbB/9VZDwjG1p0iMdhZFk+hJHOi9CYln31YXW 28 | soIsIJhu98gyi+qUZ0TMbqAjR2K/n2BmrJS3OokkDw== 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /dev/nats-wildcard.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | nodeID: "broker-1", 5 | transporter: { 6 | type: "NATS" 7 | }, 8 | metrics: true, 9 | logLevel: "debug", 10 | disableBalancer: true 11 | }); 12 | 13 | broker.createService({ 14 | name: "test", 15 | 16 | events: { 17 | "config.site.**.changed": payload => { 18 | broker.logger.info(payload); 19 | }, 20 | "config.mail.**.changed": () => {}, 21 | "config.accounts.**.changed": () => {} 22 | } 23 | }); 24 | 25 | async function start() { 26 | await broker.start(); 27 | 28 | broker.repl(); 29 | 30 | setInterval(async () => { 31 | await broker.emit("config.site.test.changed", { data: 123 }); 32 | }, 1000); 33 | } 34 | 35 | start(); 36 | -------------------------------------------------------------------------------- /dev/perf.js: -------------------------------------------------------------------------------- 1 | let ServiceBroker = require("../src/service-broker"); 2 | const broker = new ServiceBroker({ 3 | logger: console 4 | }); 5 | 6 | broker.createService({ 7 | name: "math", 8 | actions: { 9 | add({ params }) { 10 | return params.a + params.b; 11 | } 12 | } 13 | }); 14 | 15 | let c = 0; 16 | function work() { 17 | c++; 18 | broker 19 | .call("math.add", { a: 5, b: 3 }) 20 | .then(() => setImmediate(work)) 21 | .catch(broker.logger.error); 22 | } 23 | 24 | broker 25 | .start() 26 | .then(() => { 27 | let startTime = Date.now(); 28 | setInterval(() => { 29 | let rps = c / ((Date.now() - startTime) / 1000); 30 | broker.logger.info(Number(rps.toFixed(0)).toLocaleString(), "req/s"); 31 | c = 0; 32 | startTime = Date.now(); 33 | }, 1000); 34 | }) 35 | .then(() => work()); 36 | -------------------------------------------------------------------------------- /dev/schema-custom-merge.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | const Service = require("../src/service"); 3 | 4 | const broker = new ServiceBroker({ logLevel: "warn" }); 5 | 6 | // Add custom merge logic for "foobar" schema 7 | // property as static method of Service. 8 | Service.mergeSchemaFoobar = function (src, target) { 9 | return [src, target].join("-"); 10 | }; 11 | 12 | broker.createService({ 13 | name: "greeter", 14 | mixins: [ 15 | { 16 | foobar: "Bar" 17 | } 18 | ], 19 | 20 | foobar: "Foo", 21 | 22 | started() { 23 | this.logger.warn("Foobar:", this.schema.foobar); 24 | // Print: "Foo-Bar" 25 | } 26 | }); 27 | 28 | broker.start(); 29 | -------------------------------------------------------------------------------- /dev/serializer-register.js: -------------------------------------------------------------------------------- 1 | const { ServiceBroker, Serializers } = require(".."); 2 | const SafeJSON = require("./SafeJsonSerializer"); 3 | Serializers.register("SafeJSON", SafeJSON); 4 | 5 | const broker = new ServiceBroker({ 6 | serializer: "SafeJSON" 7 | }); 8 | 9 | broker.createService({ 10 | name: "test", 11 | events: { 12 | async "some.thing"(ctx) { 13 | this.logger.info(" ---- event triggered", ctx.eventName); 14 | await this.Promise.delay(1000); 15 | this.logger.info(" ---- event finished", ctx.eventName); 16 | } 17 | } 18 | }); 19 | 20 | broker.start().then(async () => { 21 | broker.repl(); 22 | 23 | broker.logger.info("Emitting event"); 24 | await broker.broadcast("some.thing"); 25 | broker.logger.info("Emitted event"); 26 | }); 27 | -------------------------------------------------------------------------------- /dev/shared-obj.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // !!! WORK-IN-PROGRESS !!! 4 | 5 | const ServiceBroker = require("../src/service-broker"); 6 | const { isString } = require("../src/utils"); 7 | const ObservableSlim = require("observable-slim"); 8 | 9 | // Create broker #1 10 | const broker1 = new ServiceBroker({ 11 | namespace: "streaming", 12 | nodeID: "client-" + process.pid, 13 | transporter: "NATS", 14 | serializer: "JSON", 15 | logger: console, 16 | logLevel: "info" 17 | }); 18 | 19 | // Create broker #2 20 | const broker2 = new ServiceBroker({ 21 | namespace: "streaming", 22 | nodeID: "encrypter-" + process.pid, 23 | transporter: "NATS", 24 | serializer: "JSON", 25 | logger: console, 26 | logLevel: "info" 27 | }); 28 | 29 | function createSharedObj(data, notifier) { 30 | return ObservableSlim.create(test, true, notifier); 31 | } 32 | 33 | const SharedObj = function (opts) { 34 | let self = null; 35 | 36 | const res = { 37 | events: {}, 38 | created() { 39 | self = this; 40 | } 41 | }; 42 | 43 | const sharedObjects = []; 44 | 45 | const getOnChanges = name => changes => { 46 | console.log(name, JSON.stringify(changes)); 47 | self.broadcast(`sharedObject.${name}`, changes); 48 | }; 49 | 50 | if (opts) { 51 | if (!Array.isArray(opts)) opts = [opts]; 52 | 53 | opts.forEach(opt => { 54 | const name = isString(opt) ? opt : opt.name; 55 | sharedObjects[opt] = createSharedObj({}, getOnChanges(opt)); 56 | res.events[`sharedObject.${name}`] = function (changes) { 57 | // TODO: apply changes 58 | console.log("Received changes:", changes); 59 | }; 60 | }); 61 | } 62 | 63 | return res; 64 | }; 65 | 66 | broker2.createService({ 67 | name: "aes", 68 | actions: {} 69 | }); 70 | 71 | broker1.Promise.all([broker1.start(), broker2.start()]) 72 | .delay(2000) 73 | .then(() => broker1.repl()); 74 | -------------------------------------------------------------------------------- /dev/stream-caller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const kleur = require("kleur"); 7 | const crypto = require("crypto"); 8 | 9 | const password = "moleculer"; 10 | 11 | // Create broker #1 12 | const broker = new ServiceBroker({ 13 | nodeID: "caller-" + process.pid, 14 | transporter: "NATS", 15 | serializer: "JSON", 16 | logger: console, 17 | logLevel: "debug" 18 | }); 19 | 20 | let origHash; 21 | 22 | broker 23 | .start() 24 | .delay(2000) 25 | .then(() => { 26 | //broker.repl(); 27 | 28 | const fileName = "d://1.pdf"; 29 | const fileName2 = "d://2.pdf"; 30 | 31 | return getSHA(fileName).then(hash1 => { 32 | origHash = hash1; 33 | broker.logger.info("Original SHA:", hash1); 34 | 35 | const startTime = Date.now(); 36 | 37 | const stream = fs.createReadStream(fileName); 38 | 39 | broker 40 | .call("aes.encrypt", stream) 41 | .then(stream => broker.call("aes.decrypt", stream)) 42 | .then(stream => { 43 | const s = fs.createWriteStream(fileName2); 44 | stream.pipe(s); 45 | s.on("close", () => { 46 | broker.logger.info("Time:", Date.now() - startTime + "ms"); 47 | getSHA(fileName2).then(hash => { 48 | broker.logger.info("Received SHA:", hash); 49 | 50 | if (hash != origHash) { 51 | broker.logger.error(kleur.red().bold("Hash mismatch!")); 52 | } else { 53 | broker.logger.info(kleur.green().bold("Hash OK!")); 54 | } 55 | }); 56 | 57 | broker.stop(); 58 | }); 59 | }); 60 | }); 61 | }); 62 | 63 | function getSHA(fileName) { 64 | return new Promise((resolve, reject) => { 65 | let hash = crypto.createHash("sha1"); 66 | let stream = fs.createReadStream(fileName); 67 | stream.on("error", err => reject(err)); 68 | stream.on("data", chunk => hash.update(chunk)); 69 | stream.on("end", () => resolve(hash.digest("hex"))); 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /dev/stream-demo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const { Transform } = require("stream"); 5 | 6 | /* 7 | It works with text: 8 | echo "hello" | node dev/stream-demo.js 9 | 10 | or with files: 11 | Linux: echo test.txt | node dev/stream-demo.js 12 | Windows: type test.txt | node dev\stream-demo.js 13 | 14 | */ 15 | 16 | // Create broker #1 17 | const broker1 = new ServiceBroker({ 18 | nodeID: "client-" + process.pid, 19 | transporter: "TCP", 20 | logger: console, 21 | logLevel: "info" 22 | }); 23 | 24 | // Create broker #2 25 | const broker2 = new ServiceBroker({ 26 | nodeID: "converter-" + process.pid, 27 | transporter: "TCP", 28 | logger: console, 29 | logLevel: "info" 30 | }); 31 | 32 | broker2.createService({ 33 | name: "text-converter", 34 | actions: { 35 | upper(ctx) { 36 | return ctx.params.pipe( 37 | new Transform({ 38 | transform: function (chunk, encoding, done) { 39 | this.push(chunk.toString().toUpperCase()); 40 | return done(); 41 | } 42 | }) 43 | ); 44 | } 45 | } 46 | }); 47 | 48 | broker1.Promise.all([broker1.start(), broker2.start()]) 49 | .then(() => broker1.waitForServices("text-converter")) 50 | .then(() => { 51 | broker1.call("text-converter.upper", process.stdin).then(stream => { 52 | console.log( 53 | "\nWrite something to the console and press ENTER. The data is transferred via streams:" 54 | ); 55 | stream.pipe(process.stdout); 56 | 57 | stream.on("end", () => { 58 | broker1.logger.warn("Stream is ended. Stopping brokers..."); 59 | 60 | broker2.stop(); 61 | broker1.stop(); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /dev/stream-echo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { ServiceBroker } = require("../"); 4 | 5 | const broker = new ServiceBroker({ 6 | //namespace: "streaming", 7 | nodeID: "node-echo", 8 | //transporter: "nats://192.168.51.100:4222", 9 | transporter: "redis://192.168.51.100:6379", 10 | serializer: "MsgPack", 11 | logger: console, 12 | logLevel: "info" 13 | }); 14 | 15 | broker.createService({ 16 | name: "echo", 17 | actions: { 18 | reply(ctx) { 19 | return ctx.params; 20 | } 21 | } 22 | }); 23 | 24 | broker.start().then(() => broker.repl()); 25 | -------------------------------------------------------------------------------- /dev/timeout.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const ServiceBroker = require("../src/service-broker"); 4 | const E = require("../src/errors"); 5 | 6 | const broker = new ServiceBroker({ 7 | requestTimeout: 5 * 1000 8 | }); 9 | 10 | broker.createService({ 11 | name: "test", 12 | actions: { 13 | slow: { 14 | timeout: 2000, 15 | async handler(ctx) { 16 | await this.Promise.delay(3000); 17 | return "OK"; 18 | } 19 | } 20 | } 21 | }); 22 | 23 | broker 24 | .start() 25 | .then(() => broker.repl()) 26 | .then(() => broker.Promise.delay(1000)) 27 | .then(() => broker.logger.info("Calling action...")) 28 | .then(() => broker.call("test.slow", null, { timeout: 4000 })) 29 | .then(res => broker.logger.info(res)) 30 | .catch(err => broker.logger.error(err.message)); 31 | -------------------------------------------------------------------------------- /dev/validator-async.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | validator: { 5 | type: "Fastest", 6 | options: { 7 | useNewCustomCheckerFunction: true, 8 | messages: { 9 | invalidOwner: "Invalid user ID in '{field}'!" 10 | } 11 | } 12 | }, 13 | tracing: { 14 | enabled: true, 15 | exporter: "Console" 16 | } 17 | }); 18 | 19 | broker.createService({ 20 | name: "users", 21 | actions: { 22 | isValid: { 23 | params: { 24 | id: "number" 25 | }, 26 | async handler(ctx) { 27 | return !!(ctx.params.id % 2); 28 | } 29 | } 30 | } 31 | }); 32 | 33 | broker.createService({ 34 | name: "posts", 35 | actions: { 36 | create: { 37 | params: { 38 | $$async: true, 39 | title: "string", 40 | owner: { 41 | type: "number", 42 | custom: async (value, errors, schema, name, parent, context) => { 43 | const ctx = context.meta; 44 | 45 | const res = await ctx.call("users.isValid", { id: value }); 46 | if (res !== true) errors.push({ type: "invalidOwner", actual: value }); 47 | return value; 48 | } 49 | } 50 | }, 51 | handler(ctx) { 52 | return `Post created for owner '${ctx.params.owner}'`; 53 | } 54 | } 55 | } 56 | }); 57 | 58 | broker 59 | .start() 60 | .then(() => broker.repl()) 61 | .then(() => broker.call("posts.create", { title: "Post #1", owner: 2 })) 62 | .then(res => broker.logger.info("Result:", res)) 63 | .catch(err => broker.logger.error(err)); 64 | -------------------------------------------------------------------------------- /dev/validator.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../src/service-broker"); 2 | 3 | const broker = new ServiceBroker({ 4 | validator: { 5 | type: "Fastest", 6 | options: { 7 | paramName: "myParams", 8 | messages: { 9 | required: "Missing field!" 10 | } 11 | } 12 | } 13 | }); 14 | 15 | broker.createService({ 16 | name: "greeter", 17 | actions: { 18 | welcome: { 19 | myParams: { 20 | name: "string" 21 | }, 22 | handler(ctx) { 23 | return `Hello ${ctx.params.name}`; 24 | } 25 | } 26 | } 27 | }); 28 | 29 | broker 30 | .start() 31 | .then(() => broker.repl()) 32 | .then(() => broker.call("greeter.welcome")) 33 | .then(res => broker.logger.info("Result:", res)) 34 | .catch(err => broker.logger.error(err)); 35 | -------------------------------------------------------------------------------- /docs/PROTOCOL.md: -------------------------------------------------------------------------------- 1 | title: Protocol 2 | --- 3 | 4 | The protocol documentation moved to a separated repo: https://github.com/moleculer-framework/protocol 5 | -------------------------------------------------------------------------------- /docs/assets/become_a_patron_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/become_a_patron_button.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/assets/microservices-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/microservices-architecture.png -------------------------------------------------------------------------------- /docs/assets/mixed-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/mixed-architecture.png -------------------------------------------------------------------------------- /docs/assets/monolith-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/monolith-architecture.png -------------------------------------------------------------------------------- /docs/assets/project-welcome-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/docs/assets/project-welcome-page.png -------------------------------------------------------------------------------- /docs/runkit/simple.js: -------------------------------------------------------------------------------- 1 | let { ServiceBroker } = require("moleculer"); 2 | 3 | // Create a broker 4 | let broker = new ServiceBroker(); 5 | 6 | // Create a new service 7 | broker.createService({ 8 | name: "math", 9 | actions: { 10 | add(ctx) { 11 | return ctx.params.a + ctx.params.b 12 | } 13 | } 14 | }); 15 | 16 | // Call the service action 17 | let result = await broker.call("math.add", { a: 5, b: 3}); 18 | console.log("5 + 3 = " + result); 19 | 20 | -------------------------------------------------------------------------------- /examples/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: ["eslint:recommended"], 8 | parserOptions: { 9 | sourceType: "module", 10 | ecmaVersion: 2018 11 | }, 12 | rules: { 13 | "no-var": ["warn"], 14 | "no-console": ["off"], 15 | "no-unused-vars": ["off"], 16 | "security/detect-possible-timing-attacks": ["off"] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /examples/client-server/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let _ = require("lodash"); 4 | 5 | let ServiceBroker = require("../../src/service-broker"); 6 | 7 | let transporter = process.env.TRANSPORTER || "TCP"; 8 | 9 | // Create broker 10 | let broker = new ServiceBroker({ 11 | namespace: "multi", 12 | nodeID: process.argv[2] || "server-" + process.pid, 13 | transporter, 14 | //serializer: "ProtoBuf", 15 | 16 | logger: console, 17 | logLevel: process.env.LOGLEVEL 18 | }); 19 | 20 | broker.createService({ 21 | name: "math", 22 | actions: { 23 | add(ctx) { 24 | // if (randomInt(100) > 90) { 25 | // this.logger.info(kleur.bold.red("Throw random error...")); 26 | // throw new MoleculerError("Random error!", 510); 27 | // } 28 | 29 | this.logger.info( 30 | _.padEnd(`${ctx.params.count}. Add ${ctx.params.a} + ${ctx.params.b}`, 20), 31 | `(from: ${ctx.nodeID})` 32 | ); 33 | 34 | return { 35 | count: ctx.params.count, 36 | res: Number(ctx.params.a) + Number(ctx.params.b) 37 | }; 38 | } 39 | }, 40 | 41 | events: { 42 | "echo.event"(data, sender) { 43 | this.logger.info( 44 | `<< MATH: Echo event received from ${sender}. Counter: ${data.counter}. Send reply...` 45 | ); 46 | this.broker.emit("reply.event", data); 47 | } 48 | } 49 | }); 50 | 51 | broker 52 | .start() 53 | .then(() => { 54 | setInterval(() => broker.broadcast("echo.broadcast"), 5 * 1000); 55 | /*setInterval(() => { 56 | const fs = require("fs"); 57 | const list = broker.registry.nodes.toArray().map(node => _.pick(node, ["id", "seq", "offlineSince", "available", "hostname", "port", "ipList", "udpAddress"])); 58 | fs.writeFileSync("./" + broker.nodeID + "-nodes.json", JSON.stringify(list, null, 2)); 59 | }, 1000);*/ 60 | }) 61 | .then(() => broker.repl()); 62 | -------------------------------------------------------------------------------- /examples/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.0" 2 | 3 | services: 4 | 5 | nats: 6 | image: nats 7 | ports: 8 | - "4222:4222" -------------------------------------------------------------------------------- /examples/docker/client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8-alpine 2 | 3 | RUN mkdir /app 4 | WORKDIR /app 5 | 6 | COPY package.json . 7 | 8 | ENV NODE_ENV=production 9 | 10 | RUN npm install --production 11 | 12 | COPY moleculer.config.js ./ 13 | 14 | EXPOSE 4445 15 | EXPOSE 9229 16 | 17 | CMD ["npm", "start"] 18 | -------------------------------------------------------------------------------- /examples/docker/client/moleculer.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const hostname = require("os").hostname(); 4 | 5 | module.exports = { 6 | namespace: "docker", 7 | nodeID: `client-${hostname}`, 8 | logger: true, 9 | logLevel: "info", 10 | transporter: { 11 | type: "TCP", 12 | options: {} 13 | }, 14 | 15 | started(broker) { 16 | let reqCount = 0; 17 | broker.waitForServices("worker").then(() => { 18 | setInterval(() => { 19 | let payload = { 20 | n: 1 + Math.floor(Math.random() * 49), 21 | c: ++reqCount 22 | }; 23 | let p = broker.call("worker.fibo", payload); 24 | if (p.ctx) 25 | broker.logger.info( 26 | `${reqCount}. Send request 'fibo(${payload.n})' to ${ 27 | p.ctx.nodeID ? p.ctx.nodeID : "some node" 28 | } (queue: ${broker.transit.pendingRequests.size})...` 29 | ); 30 | 31 | p.then(res => { 32 | broker.logger.info( 33 | `${payload.c}. fibo(${payload.n}) = ${res} (from: ${p.ctx.nodeID})` 34 | ); 35 | }).catch(err => { 36 | broker.logger.warn( 37 | `${reqCount}. Request 'fibo(${payload.n})' ERROR! ${err.message}` 38 | ); 39 | }); 40 | }, 1000); 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /examples/docker/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "Client node for Docker example", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "moleculer-runner --repl", 8 | "start": "node --inspect=0.0.0.0:9229 node_modules/moleculer/bin/moleculer-runner", 9 | "docker": "docker build -t moleculer-client ." 10 | }, 11 | "keywords": [], 12 | "author": "Icebob", 13 | "license": "MIT", 14 | "dependencies": { 15 | "moleculer": "github:moleculerjs/moleculer#next" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.0" 2 | 3 | services: 4 | 5 | client: 6 | build: 7 | context: ./client 8 | image: moleculer-client 9 | environment: 10 | LOGLEVEL: "info" 11 | 12 | restart: always 13 | # networks: 14 | # - moleculer 15 | 16 | worker: 17 | build: 18 | context: ./worker 19 | image: moleculer-worker 20 | environment: 21 | LOGLEVEL: "info" 22 | 23 | restart: always 24 | # networks: 25 | # - moleculer 26 | 27 | # networks: 28 | # moleculer: 29 | # driver: overlay 30 | -------------------------------------------------------------------------------- /examples/docker/worker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8-alpine 2 | 3 | RUN mkdir /app 4 | WORKDIR /app 5 | 6 | COPY package.json . 7 | 8 | ENV NODE_ENV=production 9 | 10 | RUN npm install --production 11 | 12 | COPY moleculer.config.js service.js ./ 13 | 14 | EXPOSE 4445 15 | EXPOSE 9229 16 | 17 | CMD ["npm", "start"] 18 | -------------------------------------------------------------------------------- /examples/docker/worker/moleculer.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const hostname = require("os").hostname(); 4 | 5 | module.exports = { 6 | namespace: "docker", 7 | nodeID: `worker-${hostname}`, 8 | logger: true, 9 | logLevel: "info", 10 | transporter: { 11 | type: "TCP", 12 | options: {} 13 | }, 14 | hotReload: true 15 | }; 16 | -------------------------------------------------------------------------------- /examples/docker/worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker", 3 | "version": "1.0.0", 4 | "description": "Worker services for Docker example", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "moleculer-runner --repl --hot service.js", 8 | "start": "node --inspect=0.0.0.0:9229 node_modules/moleculer/bin/moleculer-runner service.js", 9 | "docker": "docker build -t moleculer-worker ." 10 | }, 11 | "keywords": [], 12 | "author": "Icebob", 13 | "license": "MIT", 14 | "dependencies": { 15 | "moleculer": "github:moleculerjs/moleculer#next" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/docker/worker/service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "worker", 5 | 6 | actions: { 7 | fibo: { 8 | params: { 9 | n: { type: "number", positive: true } 10 | }, 11 | handler(ctx) { 12 | let num = Number(ctx.params.n); 13 | let a = 1, 14 | b = 0, 15 | temp; 16 | 17 | while (num >= 0) { 18 | temp = a; 19 | a = a + b; 20 | b = temp; 21 | num--; 22 | } 23 | 24 | this.logger.info(`'fibo' request received from ${ctx.nodeID}. Reply result:`, b); 25 | 26 | return b; 27 | } 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /examples/dummy.service.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /examples/es6.class.service.js: -------------------------------------------------------------------------------- 1 | const Service = require("../src/service"); 2 | 3 | class GreeterService extends Service { 4 | constructor(broker) { 5 | super(broker); 6 | 7 | this.parseServiceSchema({ 8 | name: "greeter", 9 | version: "v2", 10 | meta: { 11 | scalable: true 12 | }, 13 | // dependencies: [ 14 | // "auth", 15 | // "users" 16 | // ], 17 | 18 | settings: { 19 | upperCase: true 20 | }, 21 | actions: { 22 | hello: this.hello, 23 | welcome: { 24 | cache: { 25 | keys: ["name"] 26 | }, 27 | params: { 28 | name: "string" 29 | }, 30 | handler: this.welcome 31 | } 32 | }, 33 | events: { 34 | "user.created": this.userCreated 35 | }, 36 | created: this.serviceCreated, 37 | started: this.serviceStarted, 38 | stopped: this.serviceStopped 39 | }); 40 | } 41 | 42 | // Action handler 43 | hello() { 44 | return "Hello Moleculer"; 45 | } 46 | 47 | // Action handler 48 | welcome(ctx) { 49 | return this.sayWelcome(ctx.params.name); 50 | } 51 | 52 | // Private method 53 | sayWelcome(name) { 54 | this.logger.info("Say hello to", name); 55 | return `Welcome, ${this.settings.upperCase ? name.toUpperCase() : name}`; 56 | } 57 | 58 | // Event handler 59 | userCreated(user) { 60 | this.broker.call("mail.send", { user }); 61 | } 62 | 63 | serviceCreated() { 64 | this.logger.info("ES6 Service created."); 65 | } 66 | 67 | serviceStarted() { 68 | this.logger.info("ES6 Service started."); 69 | } 70 | 71 | serviceStopped() { 72 | this.logger.info("ES6 Service stopped."); 73 | } 74 | } 75 | 76 | module.exports = GreeterService; 77 | -------------------------------------------------------------------------------- /examples/esm/greeter.service.mjs: -------------------------------------------------------------------------------- 1 | import { Errors } from "../../index.mjs"; 2 | 3 | export default { 4 | name: "greeter", 5 | 6 | /** 7 | * Settings 8 | */ 9 | settings: {}, 10 | 11 | /** 12 | * Dependencies 13 | */ 14 | dependencies: [], 15 | 16 | /** 17 | * Actions 18 | */ 19 | actions: { 20 | /** 21 | * Say a 'Hello' action. 22 | * 23 | * @returns 24 | */ 25 | hello: { 26 | rest: { 27 | method: "GET", 28 | path: "/hello" 29 | }, 30 | async handler() { 31 | return "Hello Moleculer"; 32 | } 33 | }, 34 | 35 | danger: { 36 | handler() { 37 | throw new Errors.MoleculerError("Danger!", 500); 38 | } 39 | } 40 | }, 41 | 42 | /** 43 | * Events 44 | */ 45 | events: {}, 46 | 47 | /** 48 | * Methods 49 | */ 50 | methods: {}, 51 | 52 | /** 53 | * Service created lifecycle event handler 54 | */ 55 | created() {}, 56 | 57 | /** 58 | * Service started lifecycle event handler 59 | */ 60 | async started() { 61 | this.logger.info("ESM service loaded."); 62 | }, 63 | 64 | /** 65 | * Service stopped lifecycle event handler 66 | */ 67 | async stopped() {} 68 | }; 69 | -------------------------------------------------------------------------------- /examples/esm/moleculer.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Test: 3 | * 4 | * node bin/moleculer-runner.mjs -e --repl --hot --config examples/esm/moleculer.config.mjs examples/esm/greeter.service.mjs examples/esm/welcome.service.cjs 5 | */ 6 | export default { 7 | namespace: "bbb", 8 | logger: true, 9 | logLevel: "debug", 10 | //transporter: "TCP" 11 | hotReload: true, 12 | 13 | created(broker) { 14 | broker.logger.info("ESM Config loaded!"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /examples/esm/welcome.service.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "welcome", 3 | 4 | /** 5 | * Settings 6 | */ 7 | settings: {}, 8 | 9 | /** 10 | * Dependencies 11 | */ 12 | dependencies: [], 13 | 14 | /** 15 | * Actions 16 | */ 17 | actions: { 18 | /** 19 | * Welcome, a username 20 | * 21 | * @param {String} name - User name 22 | */ 23 | welcome: { 24 | rest: "/welcome", 25 | params: { 26 | name: "string" 27 | }, 28 | /** @param {Context} ctx */ 29 | async handler(ctx) { 30 | return `Welcome, ${ctx.params.name}`; 31 | } 32 | } 33 | }, 34 | 35 | /** 36 | * Events 37 | */ 38 | events: {}, 39 | 40 | /** 41 | * Methods 42 | */ 43 | methods: {}, 44 | 45 | /** 46 | * Service created lifecycle event handler 47 | */ 48 | created() {}, 49 | 50 | /** 51 | * Service started lifecycle event handler 52 | */ 53 | async started() { 54 | this.logger.info("CJS service loaded."); 55 | }, 56 | 57 | /** 58 | * Service stopped lifecycle event handler 59 | */ 60 | async stopped() {} 61 | }; 62 | -------------------------------------------------------------------------------- /examples/file.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "file", 5 | actions: { 6 | html: { 7 | responseType: "text/html", 8 | handler() { 9 | return ` 10 | 11 | 12 |

Hello API Gateway!

13 | 14 | 15 | 16 | `; 17 | } 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/hot.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "hot", 5 | metadata: { 6 | scalable: true, 7 | priority: 5 8 | }, 9 | 10 | actions: { 11 | hello() { 12 | return "Hello Moleculer!"; 13 | } 14 | }, 15 | events: { 16 | "test.event"(c) { 17 | this.logger.info("Event", c); 18 | } 19 | }, 20 | created() { 21 | this.logger.info(">>> Service created!"); 22 | }, 23 | 24 | started() { 25 | this.logger.info(">>> Service started!"); 26 | }, 27 | 28 | stopped() { 29 | this.logger.info(">>> Service stopped!"); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const moduleName = process.argv[2] || "simple"; 4 | process.argv.splice(2, 1); 5 | 6 | require("./" + moduleName); 7 | -------------------------------------------------------------------------------- /examples/loadtest/clients.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | "use strict"; 4 | 5 | const os = require("os"), 6 | cluster = require("cluster"), 7 | stopSignals = [ 8 | "SIGHUP", 9 | "SIGINT", 10 | "SIGQUIT", 11 | "SIGILL", 12 | "SIGTRAP", 13 | "SIGABRT", 14 | "SIGBUS", 15 | "SIGFPE", 16 | "SIGUSR1", 17 | "SIGSEGV", 18 | "SIGUSR2", 19 | "SIGTERM" 20 | ], 21 | production = true; //process.env.NODE_ENV == "production"; 22 | 23 | let stopping = false; 24 | 25 | cluster.on("disconnect", function (worker) { 26 | if (production) { 27 | if (!stopping) { 28 | cluster.fork(); 29 | } 30 | } else process.exit(1); 31 | }); 32 | 33 | if (cluster.isMaster) { 34 | const workerCount = process.env.NODE_CLUSTER_WORKERS || os.cpus().length; 35 | console.log(`Starting ${workerCount} workers...`); 36 | for (let i = 0; i < workerCount; i++) { 37 | let worker = cluster.fork(); 38 | } 39 | 40 | if (production) { 41 | stopSignals.forEach(function (signal) { 42 | process.on(signal, function () { 43 | console.log(`Got ${signal}, stopping workers...`); 44 | stopping = true; 45 | cluster.disconnect(function () { 46 | console.log("All workers stopped, exiting."); 47 | process.exit(0); 48 | }); 49 | }); 50 | }); 51 | } 52 | } else { 53 | let worker = cluster.worker; 54 | //console.log(worker); 55 | let hostname = os.hostname(); 56 | worker.process.argv.push(hostname + "-client-" + worker.id); 57 | 58 | require("./client.js"); 59 | } 60 | -------------------------------------------------------------------------------- /examples/loadtest/local.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | "use strict"; 4 | 5 | let random = require("lodash/random"); 6 | let os = require("os"); 7 | let hostname = os.hostname(); 8 | 9 | let ServiceBroker = require("../../src/service-broker"); 10 | 11 | // Create broker 12 | let broker = new ServiceBroker({ 13 | nodeID: process.argv[2] || hostname + "-server", 14 | logger: null, 15 | transporter: null 16 | //metrics: true 17 | }); 18 | 19 | broker.createService({ 20 | name: "math", 21 | actions: { 22 | add: { 23 | handler(ctx) { 24 | return Number(ctx.params.a) + Number(ctx.params.b); 25 | } 26 | } 27 | } 28 | }); 29 | 30 | let payload = { a: random(0, 100), b: random(0, 100) }; 31 | 32 | let count = 0; 33 | 34 | function work() { 35 | broker 36 | .call("math.add", payload) 37 | .then(res => { 38 | if ((count++ % 10) * 1000) { 39 | // Fast cycle 40 | work(); 41 | } else { 42 | // Slow cycle 43 | setImmediate(() => work()); 44 | } 45 | return res; 46 | }) 47 | .catch(err => { 48 | throw err; 49 | }); 50 | } 51 | 52 | broker.start().then(() => { 53 | count = 0; 54 | 55 | setTimeout(() => { 56 | let startTime = Date.now(); 57 | work(); 58 | 59 | setInterval(() => { 60 | if (count > 0) { 61 | let rps = count / ((Date.now() - startTime) / 1000); 62 | console.log(Number(rps.toFixed(0)).toLocaleString(), "req/s"); 63 | count = 0; 64 | startTime = Date.now(); 65 | } 66 | }, 1000); 67 | }, 1000); 68 | }); 69 | -------------------------------------------------------------------------------- /examples/loadtest/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | "use strict"; 4 | 5 | let ServiceBroker = require("../../src/service-broker"); 6 | let { padStart } = require("lodash"); 7 | 8 | let os = require("os"); 9 | let hostname = os.hostname(); 10 | 11 | let transporter = process.env.TRANSPORTER || "TCP"; 12 | 13 | let count = 0; 14 | let sum = 0; 15 | let maxTime = null; 16 | 17 | // Create broker 18 | let broker = new ServiceBroker({ 19 | namespace: "loadtest", 20 | nodeID: process.argv[2] || hostname + "-server", 21 | transporter, 22 | logger: console, 23 | logLevel: "warn", 24 | //metrics: true, 25 | registry: { 26 | discoverer: process.env.DISCOVERER || "Local" 27 | } 28 | }); 29 | 30 | broker.createService({ 31 | name: "math", 32 | actions: { 33 | add(ctx) { 34 | count++; 35 | return Number(ctx.params.a) + Number(ctx.params.b); 36 | } 37 | } 38 | }); 39 | 40 | broker.createService({ 41 | name: "perf", 42 | actions: { 43 | reply(ctx) { 44 | count++; 45 | return ctx.params; 46 | } 47 | } 48 | }); 49 | //broker.loadService(__dirname + "/../rest.service"); 50 | 51 | broker.start(); 52 | 53 | console.log( 54 | "Server started. nodeID: ", 55 | broker.nodeID, 56 | " TRANSPORTER:", 57 | transporter, 58 | " PID:", 59 | process.pid 60 | ); 61 | 62 | setInterval(() => { 63 | if (count > 0) { 64 | console.log( 65 | broker.nodeID, 66 | ":", 67 | padStart(Number(count.toFixed(0)).toLocaleString(), 8), 68 | "req/s" 69 | ); 70 | count = 0; 71 | } 72 | }, 1000); 73 | -------------------------------------------------------------------------------- /examples/math.service.js: -------------------------------------------------------------------------------- 1 | const { MoleculerError } = require("../src/errors"); 2 | 3 | module.exports = { 4 | name: "math", 5 | actions: { 6 | add(ctx) { 7 | return Number(ctx.params.a) + Number(ctx.params.b); 8 | }, 9 | 10 | sub(ctx) { 11 | return Number(ctx.params.a) - Number(ctx.params.b); 12 | }, 13 | 14 | mult: { 15 | params: { 16 | a: "number", 17 | b: "number" 18 | }, 19 | handler(ctx) { 20 | return Number(ctx.params.a) * Number(ctx.params.b); 21 | } 22 | }, 23 | 24 | div: { 25 | params: { 26 | a: { type: "number", convert: true }, 27 | b: { type: "number", notEqual: 0, convert: true } 28 | }, 29 | handler(ctx) { 30 | let a = Number(ctx.params.a); 31 | let b = Number(ctx.params.b); 32 | if (b != 0 && !Number.isNaN(b)) return a / b; 33 | else throw new MoleculerError("Divide by zero!", 422, null, ctx.params); 34 | } 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /examples/multi-nodes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const cluster = require("cluster"); 4 | 5 | process.env.TRANSPORTER = "Redis"; 6 | process.env.DISCOVERER = "Redis"; 7 | //process.env.DISCOVERER_SERIALIZER = "MsgPack"; 8 | process.env.NODE_COUNT = 2; 9 | 10 | if (cluster.isMaster) { 11 | cluster.setupMaster({ 12 | serialization: "json" 13 | }); 14 | require("./master.js"); 15 | } else { 16 | require("./node.js"); 17 | } 18 | -------------------------------------------------------------------------------- /examples/runner/.gitignore: -------------------------------------------------------------------------------- 1 | svc 2 | -------------------------------------------------------------------------------- /examples/runner/moleculer.config.async.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | This is an async external configuration file (downloaded via HTTP request) for Moleculer Runner 5 | 6 | Start Broker 7 | 8 | Windows: 9 | node bin\moleculer-runner.js -c examples/runner/moleculer.config.async.js -r examples/user.service.js 10 | 11 | Linux: 12 | node ./bin/moleculer-runner -c examples/runner/moleculer.config.async.js -r examples/user.service.js 13 | 14 | */ 15 | 16 | const fetch = require("node-fetch"); 17 | 18 | module.exports = async function () { 19 | const res = await fetch("https://pastebin.com/raw/SLZRqfHX"); 20 | return await res.json(); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/runner/moleculer.config.async.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | /** 4 | * Test: 5 | * 6 | * npx ts-node -T bin\moleculer-runner.js -c examples\runner\moleculer.config.async.ts -r examples/user.service.js 7 | */ 8 | export default async function () { 9 | const res = await fetch("https://pastebin.com/raw/SLZRqfHX"); 10 | return await res.json(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/runner/moleculer.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | This is an external configuration file for Moleculer Runner 5 | 6 | Start Broker 7 | 8 | Windows: 9 | node bin\moleculer-runner.js -c examples\runner\moleculer.config.js -r examples/user.service.js 10 | 11 | Linux: 12 | node ./bin/moleculer-runner -c examples/runner/moleculer.config.js -r examples/user.service.js 13 | 14 | */ 15 | 16 | module.exports = { 17 | namespace: "bbb", 18 | logger: true, 19 | logLevel: "debug", 20 | //transporter: "TCP" 21 | hotReload: true, 22 | 23 | created(broker) { 24 | console.log("myVar", broker.options.myVar); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /examples/runner/moleculer.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Test: 3 | * 4 | * npx ts-node -T bin\moleculer-runner.js -c examples\runner\moleculer.config.ts -r examples/user.service.js 5 | */ 6 | export default { 7 | namespace: "bbb", 8 | logger: true, 9 | logLevel: "debug", 10 | //transporter: "TCP" 11 | hotReload: true, 12 | 13 | created(broker) { 14 | broker.logger.info("Typescript configuration loaded!"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /examples/silent.service.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "silent", 3 | settings: { 4 | silent: true 5 | }, 6 | actions: { 7 | topsecret: { 8 | protected: true, 9 | handler() { 10 | return "Only accessible locally!"; 11 | } 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /examples/simple/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let ServiceBroker = require("../../src/service-broker"); 4 | 5 | // Create broker 6 | let broker = new ServiceBroker({ 7 | logger: console, 8 | transporter: null 9 | }); 10 | 11 | // Load service 12 | broker.loadService(__dirname + "/../math.service.js"); 13 | //broker.loadService(__dirname + "/../dummy.service.js"); 14 | 15 | // Call actions 16 | broker 17 | .start() 18 | .then(() => { 19 | return broker 20 | .call("math.add", { a: 5, b: 3 }) 21 | .then(res => broker.logger.info(" 5 + 3 =", res)); 22 | }) 23 | .then(() => { 24 | return broker 25 | .call("math.sub", { a: 9, b: 2 }) 26 | .then(res => broker.logger.info(" 9 - 2 =", res)); 27 | }) 28 | .then(() => { 29 | return broker 30 | .call("math.mult", { a: 3, b: 4 }) 31 | .then(res => broker.logger.info(" 3 * 4 =", res)); 32 | }) 33 | .then(() => { 34 | return broker 35 | .call("math.div", { a: 8, b: 4 }) 36 | .then(res => broker.logger.info(" 8 / 4 =", res)); 37 | }) 38 | .then(() => { 39 | // Divide by zero! Throw error... 40 | return broker 41 | .call("math.div", { a: 5, b: 0 }) 42 | .then(res => broker.logger.info(" 5 / 0 =", res)); 43 | }) 44 | .catch(err => { 45 | broker.logger.error( 46 | `Error occurred! Action: '${err.ctx.action.name}', Message: ${err.code} - ${err.message}` 47 | ); 48 | if (err.data) broker.logger.error("Error data:", err.data); 49 | }); 50 | 51 | //broker.repl(); 52 | 53 | // Please note, the process will exit because we didn't define transporter or API gateway which can keep-alive the event-loop. 54 | -------------------------------------------------------------------------------- /examples/start-es6.js: -------------------------------------------------------------------------------- 1 | const { ServiceBroker } = require("../"); 2 | 3 | const broker = new ServiceBroker({ 4 | namespace: "test", 5 | logger: console, 6 | hotReload: true 7 | }); 8 | 9 | broker.loadService("./examples/es6.class.service.js"); 10 | broker.start(); 11 | -------------------------------------------------------------------------------- /examples/test.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "test", 5 | actions: { 6 | fatal() { 7 | this.logger.fatal("Fatal error!"); 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /examples/user.v1.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let _ = require("lodash"); 4 | let fakerator = require("fakerator")(); 5 | let Service = require("../src/service"); 6 | 7 | let users = fakerator.times(fakerator.entity.user, 10); 8 | 9 | _.each(users, (user, i) => { 10 | user.id = i + 1; 11 | delete user.avatar; 12 | delete user.gravatar; 13 | delete user.dob; 14 | delete user.website; 15 | delete user.address; 16 | delete user.ip; 17 | }); 18 | 19 | module.exports = function (broker) { 20 | return new Service(broker, { 21 | name: "users", 22 | version: 1, 23 | actions: { 24 | find: { 25 | cache: false, 26 | handler(ctx) { 27 | //this.logger.debug("Find users..."); 28 | return users; 29 | //return _.cloneDeep(users); 30 | } 31 | }, 32 | 33 | get: { 34 | cache: true, 35 | handler(ctx) { 36 | //this.logger.debug("Get user...", ctx.params); 37 | return this.findByID(ctx.params.id); 38 | } 39 | } 40 | }, 41 | 42 | methods: { 43 | findByID(id) { 44 | return _.cloneDeep(users.find(user => user.id == id)); 45 | } 46 | } 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { 10 | CIRCUIT_CLOSE, 11 | CIRCUIT_HALF_OPEN, 12 | CIRCUIT_HALF_OPEN_WAIT, 13 | CIRCUIT_OPEN 14 | } = require("./src/constants"); 15 | 16 | module.exports = { 17 | ServiceBroker: require("./src/service-broker"), 18 | Loggers: require("./src/loggers"), 19 | Service: require("./src/service"), 20 | Context: require("./src/context"), 21 | 22 | Cachers: require("./src/cachers"), 23 | 24 | Transporters: require("./src/transporters"), 25 | Serializers: require("./src/serializers"), 26 | Strategies: require("./src/strategies"), 27 | Validators: require("./src/validators"), 28 | Validator: require("./src/validators/fastest"), // deprecated 29 | TracerExporters: require("./src/tracing/exporters"), 30 | MetricTypes: require("./src/metrics/types"), 31 | MetricReporters: require("./src/metrics/reporters"), 32 | METRIC: require("./src/metrics/constants"), 33 | 34 | Transit: require("./src/transit"), 35 | 36 | Registry: require("./src/registry"), 37 | Discoverers: require("./src/registry/discoverers"), 38 | 39 | Middlewares: require("./src/middlewares"), 40 | 41 | Errors: require("./src/errors"), 42 | 43 | Runner: require("./src/runner"), 44 | Utils: require("./src/utils"), 45 | 46 | CIRCUIT_CLOSE, 47 | CIRCUIT_HALF_OPEN, 48 | CIRCUIT_HALF_OPEN_WAIT, 49 | CIRCUIT_OPEN, 50 | 51 | MOLECULER_VERSION: require("./src/service-broker").MOLECULER_VERSION, 52 | PROTOCOL_VERSION: require("./src/service-broker").PROTOCOL_VERSION, 53 | INTERNAL_MIDDLEWARES: require("./src/service-broker").INTERNAL_MIDDLEWARES 54 | }; 55 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | import mod from "./index.js"; 2 | 3 | export default mod; 4 | export const CIRCUIT_CLOSE = mod.CIRCUIT_CLOSE; 5 | export const CIRCUIT_HALF_OPEN = mod.CIRCUIT_HALF_OPEN; 6 | export const CIRCUIT_HALF_OPEN_WAIT = mod.CIRCUIT_HALF_OPEN_WAIT; 7 | export const CIRCUIT_OPEN = mod.CIRCUIT_OPEN; 8 | export const Cachers = mod.Cachers; 9 | export const Context = mod.Context; 10 | export const Discoverers = mod.Discoverers; 11 | export const Errors = mod.Errors; 12 | export const INTERNAL_MIDDLEWARES = mod.INTERNAL_MIDDLEWARES; 13 | export const Loggers = mod.Loggers; 14 | export const METRIC = mod.METRIC; 15 | export const MOLECULER_VERSION = mod.MOLECULER_VERSION; 16 | export const MetricReporters = mod.MetricReporters; 17 | export const MetricTypes = mod.MetricTypes; 18 | export const Middlewares = mod.Middlewares; 19 | export const PROTOCOL_VERSION = mod.PROTOCOL_VERSION; 20 | export const Registry = mod.Registry; 21 | export const Runner = mod.Runner; 22 | export const Serializers = mod.Serializers; 23 | export const Service = mod.Service; 24 | export const ServiceBroker = mod.ServiceBroker; 25 | export const Strategies = mod.Strategies; 26 | export const TracerExporters = mod.TracerExporters; 27 | export const Transit = mod.Transit; 28 | export const Transporters = mod.Transporters; 29 | export const Utils = mod.Utils; 30 | export const Validator = mod.Validator; 31 | export const Validators = mod.Validators; 32 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": false 4 | }, 5 | "exclude": [ 6 | "node_modules" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useTabs: true, 3 | printWidth: 100, 4 | trailingComma: "none", 5 | tabWidth: 4, 6 | singleQuote: false, 7 | semi: true, 8 | bracketSpacing: true, 9 | arrowParens: "avoid", 10 | overrides: [ 11 | { 12 | files: "*.md", 13 | options: { 14 | useTabs: false 15 | } 16 | }, 17 | { 18 | files: "*.json", 19 | options: { 20 | tabWidth: 2, 21 | useTabs: false 22 | } 23 | } 24 | ] 25 | }; 26 | -------------------------------------------------------------------------------- /src/async-storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const asyncHooks = require("async_hooks"); 10 | const executionAsyncId = asyncHooks.executionAsyncId; 11 | 12 | class AsyncStorage { 13 | constructor(broker) { 14 | this.broker = broker; 15 | 16 | this.hook = asyncHooks.createHook({ 17 | init: this._init.bind(this), 18 | //before: this._before.bind(this), 19 | //after: this._after.bind(this), 20 | destroy: this._destroy.bind(this), 21 | promiseResolve: this._destroy.bind(this) 22 | }); 23 | 24 | this.executionAsyncId = executionAsyncId; 25 | 26 | this.store = new Map(); 27 | } 28 | 29 | enable() { 30 | this.hook.enable(); 31 | } 32 | 33 | disable() { 34 | this.hook.disable(); 35 | } 36 | 37 | stop() { 38 | this.hook.disable(); 39 | this.store.clear(); 40 | } 41 | 42 | getAsyncId() { 43 | return executionAsyncId(); 44 | } 45 | 46 | setSessionData(data) { 47 | const currentUid = executionAsyncId(); 48 | this.store.set(currentUid, { 49 | data, 50 | owner: currentUid 51 | }); 52 | } 53 | 54 | getSessionData() { 55 | const currentUid = executionAsyncId(); 56 | const item = this.store.get(currentUid); 57 | return item ? item.data : null; 58 | } 59 | 60 | _init(asyncId, type, triggerAsyncId) { 61 | // Skip TIMERWRAP type 62 | if (type === "TIMERWRAP") return; 63 | 64 | const item = this.store.get(triggerAsyncId); 65 | if (item) { 66 | this.store.set(asyncId, item); 67 | } 68 | } 69 | 70 | _destroy(asyncId) { 71 | const item = this.store.get(asyncId); 72 | if (item) { 73 | this.store.delete(asyncId); 74 | //if (item.owner == asyncId) 75 | // item.data = null; 76 | } 77 | } 78 | } 79 | 80 | module.exports = AsyncStorage; 81 | -------------------------------------------------------------------------------- /src/cachers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString, isInheritedClass } = require("../utils"); 10 | const { BrokerOptionsError } = require("../errors"); 11 | 12 | const Cachers = { 13 | Base: require("./base"), 14 | Memory: require("./memory"), 15 | MemoryLRU: require("./memory-lru"), 16 | Redis: require("./redis") 17 | }; 18 | 19 | function getByName(name) { 20 | /* istanbul ignore next */ 21 | if (!name) return null; 22 | 23 | let n = Object.keys(Cachers).find(n => n.toLowerCase() == name.toLowerCase()); 24 | if (n) return Cachers[n]; 25 | } 26 | 27 | /** 28 | * Resolve cacher by name 29 | * 30 | * @param {object|string} opt 31 | * @returns {Cacher} 32 | */ 33 | function resolve(opt) { 34 | if (isObject(opt) && isInheritedClass(opt, Cachers.Base)) { 35 | return opt; 36 | } else if (opt === true) { 37 | return new Cachers.Memory(); 38 | } else if (isString(opt)) { 39 | let CacherClass = getByName(opt); 40 | if (CacherClass) return new CacherClass(); 41 | 42 | if (opt.startsWith("redis://") || opt.startsWith("rediss://")) CacherClass = Cachers.Redis; 43 | 44 | if (CacherClass) return new CacherClass(opt); 45 | else throw new BrokerOptionsError(`Invalid cacher type '${opt}'.`, { type: opt }); 46 | } else if (isObject(opt)) { 47 | let CacherClass = getByName(opt.type || "Memory"); 48 | if (CacherClass) return new CacherClass(opt.options); 49 | else throw new BrokerOptionsError(`Invalid cacher type '${opt.type}'.`, { type: opt.type }); 50 | } 51 | 52 | return null; 53 | } 54 | 55 | function register(name, value) { 56 | Cachers[name] = value; 57 | } 58 | 59 | module.exports = Object.assign(Cachers, { resolve, register }); 60 | -------------------------------------------------------------------------------- /src/lock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | class Lock { 10 | constructor() { 11 | this.locked = new Map(); 12 | } 13 | 14 | acquire(key /*, ttl*/) { 15 | let locked = this.locked.get(key); 16 | if (!locked) { 17 | // not locked 18 | locked = []; 19 | this.locked.set(key, locked); 20 | return Promise.resolve(); 21 | } else { 22 | return new Promise(resolve => locked.push(resolve)); 23 | } 24 | } 25 | 26 | isLocked(key) { 27 | return !!this.locked.get(key); 28 | } 29 | 30 | release(key) { 31 | let locked = this.locked.get(key); 32 | if (locked) { 33 | if (locked.length > 0) { 34 | locked.shift()(); // Release the lock 35 | } else { 36 | this.locked.delete(key); 37 | } 38 | } 39 | return Promise.resolve(); 40 | } 41 | } 42 | 43 | module.exports = Lock; 44 | -------------------------------------------------------------------------------- /src/loggers/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const _ = require("lodash"); 10 | const { match, isObject, isString } = require("../utils"); 11 | 12 | const LEVELS = ["fatal", "error", "warn", "info", "debug", "trace"]; 13 | 14 | /** 15 | * Logger base class. 16 | * 17 | * @class BaseLogger 18 | */ 19 | class BaseLogger { 20 | /** 21 | * Creates an instance of BaseLogger. 22 | * 23 | * @param {Object} opts 24 | * @memberof BaseLogger 25 | */ 26 | constructor(opts) { 27 | this.opts = _.defaultsDeep(opts, { 28 | level: "info", 29 | createLogger: null 30 | }); 31 | this.Promise = Promise; // default promise before logger is initialized 32 | } 33 | 34 | /** 35 | * Initialize logger. 36 | * 37 | * @param {LoggerFactory} loggerFactory 38 | */ 39 | init(loggerFactory) { 40 | this.loggerFactory = loggerFactory; 41 | this.broker = this.loggerFactory.broker; 42 | this.Promise = this.broker.Promise; 43 | } 44 | 45 | /** 46 | * Stopping logger 47 | */ 48 | stop() { 49 | return this.Promise.resolve(); 50 | } 51 | 52 | getLogLevel(mod) { 53 | mod = mod ? mod.toUpperCase() : ""; 54 | 55 | const level = this.opts.level; 56 | if (isString(level)) return level; 57 | 58 | if (isObject(level)) { 59 | if (level[mod]) return level[mod]; 60 | 61 | // Find with matching 62 | const key = Object.keys(level).find(m => match(mod, m) && m !== "**"); 63 | if (key) return level[key]; 64 | else if (level["**"]) { 65 | return level["**"]; 66 | } 67 | } 68 | 69 | /* istanbul ignore next */ 70 | return null; 71 | } 72 | 73 | getLogHandler(/*bindings*/) { 74 | return null; 75 | } 76 | } 77 | 78 | BaseLogger.LEVELS = LEVELS; 79 | 80 | module.exports = BaseLogger; 81 | -------------------------------------------------------------------------------- /src/loggers/bunyan.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseLogger = require("./base"); 10 | const _ = require("lodash"); 11 | const { isFunction } = require("../utils"); 12 | 13 | /** 14 | * Bunyan logger for Moleculer 15 | * 16 | * https://github.com/trentm/node-bunyan 17 | * 18 | * @class BunyanLogger 19 | * @extends {BaseLogger} 20 | */ 21 | class BunyanLogger extends BaseLogger { 22 | /** 23 | * Creates an instance of BunyanLogger. 24 | * @param {Object} opts 25 | * @memberof BunyanLogger 26 | */ 27 | constructor(opts) { 28 | super(opts); 29 | 30 | this.opts = _.defaultsDeep(this.opts, { 31 | bunyan: { 32 | name: "moleculer" 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Initialize logger. 39 | * 40 | * @param {LoggerFactory} loggerFactory 41 | */ 42 | init(loggerFactory) { 43 | super.init(loggerFactory); 44 | 45 | try { 46 | this.bunyan = require("bunyan").createLogger(this.opts.bunyan); 47 | } catch (err) { 48 | /* istanbul ignore next */ 49 | this.broker.fatal( 50 | "The 'bunyan' package is missing! Please install it with 'npm install bunyan --save' command!", 51 | err, 52 | true 53 | ); 54 | } 55 | } 56 | 57 | /** 58 | * 59 | * @param {object} bindings 60 | */ 61 | getLogHandler(bindings) { 62 | let level = bindings ? this.getLogLevel(bindings.mod) : null; 63 | if (!level) return null; 64 | 65 | const logger = isFunction(this.opts.createLogger) 66 | ? this.opts.createLogger(level, bindings) 67 | : this.bunyan.child({ level, ...bindings }); 68 | 69 | return (type, args) => logger[type](...args); 70 | } 71 | } 72 | 73 | module.exports = BunyanLogger; 74 | -------------------------------------------------------------------------------- /src/loggers/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | /* eslint-disable no-console */ 8 | 9 | "use strict"; 10 | 11 | const FormattedLogger = require("./formatted"); 12 | const kleur = require("kleur"); 13 | 14 | /** 15 | * Console logger for Moleculer 16 | * 17 | * @class ConsoleLogger 18 | * @extends {FormattedLogger} 19 | */ 20 | class ConsoleLogger extends FormattedLogger { 21 | /** 22 | * Creates an instance of ConsoleLogger. 23 | * @param {Object} opts 24 | * @memberof ConsoleLogger 25 | */ 26 | constructor(opts) { 27 | super(opts); 28 | 29 | this.maxPrefixLength = 0; 30 | } 31 | 32 | init(loggerFactory) { 33 | super.init(loggerFactory); 34 | 35 | if (!this.opts.colors) kleur.enabled = false; 36 | } 37 | 38 | /** 39 | * 40 | * @param {object} bindings 41 | */ 42 | getLogHandler(bindings) { 43 | const level = bindings ? this.getLogLevel(bindings.mod) : null; 44 | if (!level) return null; 45 | 46 | const levelIdx = FormattedLogger.LEVELS.indexOf(level); 47 | const formatter = this.getFormatter(bindings); 48 | 49 | return (type, args) => { 50 | const typeIdx = FormattedLogger.LEVELS.indexOf(type); 51 | if (typeIdx > levelIdx) return; 52 | 53 | const pargs = formatter(type, args); 54 | switch (type) { 55 | case "fatal": 56 | case "error": 57 | return console.error(...pargs); 58 | case "warn": 59 | return console.warn(...pargs); 60 | default: 61 | return console.log(...pargs); 62 | } 63 | }; 64 | } 65 | } 66 | 67 | module.exports = ConsoleLogger; 68 | -------------------------------------------------------------------------------- /src/loggers/debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseLogger = require("./base"); 10 | const _ = require("lodash"); 11 | const { isFunction } = require("../utils"); 12 | 13 | /** 14 | * Debug logger for Moleculer 15 | * 16 | * https://github.com/visionmedia/debug 17 | * 18 | * @class DebugLogger 19 | * @extends {BaseLogger} 20 | */ 21 | class DebugLogger extends BaseLogger { 22 | /** 23 | * Creates an instance of DebugLogger. 24 | * @param {Object} opts 25 | * @memberof DebugLogger 26 | */ 27 | constructor(opts) { 28 | super(opts); 29 | 30 | this.opts = _.defaultsDeep(this.opts, {}); 31 | } 32 | 33 | /** 34 | * Initialize logger. 35 | * 36 | * @param {LoggerFactory} loggerFactory 37 | */ 38 | init(loggerFactory) { 39 | super.init(loggerFactory); 40 | 41 | try { 42 | this.debug = require("debug")("moleculer"); 43 | } catch (err) { 44 | /* istanbul ignore next */ 45 | this.broker.fatal( 46 | "The 'debug' package is missing! Please install it with 'npm install debug --save' command!", 47 | err, 48 | true 49 | ); 50 | } 51 | } 52 | 53 | /** 54 | * 55 | * @param {object} bindings 56 | */ 57 | getLogHandler(bindings) { 58 | const mod = bindings ? bindings.mod : null; 59 | const level = this.getLogLevel(mod); 60 | if (!level) return null; 61 | 62 | const levelIdx = BaseLogger.LEVELS.indexOf(level); 63 | 64 | const logger = isFunction(this.opts.createLogger) 65 | ? this.opts.createLogger(level, bindings) 66 | : this.debug.extend(mod); 67 | 68 | return (type, args) => { 69 | const typeIdx = BaseLogger.LEVELS.indexOf(type); 70 | if (typeIdx > levelIdx) return; 71 | 72 | return logger(...args); 73 | }; 74 | } 75 | } 76 | 77 | module.exports = DebugLogger; 78 | -------------------------------------------------------------------------------- /src/loggers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString, isInheritedClass } = require("../utils"); 10 | const { BrokerOptionsError } = require("../errors"); 11 | const Base = require("./base"); 12 | 13 | const Loggers = { 14 | Base, 15 | Formatted: require("./formatted"), 16 | 17 | Bunyan: require("./bunyan"), 18 | Console: require("./console"), 19 | Datadog: require("./datadog"), 20 | Debug: require("./debug"), 21 | File: require("./file"), 22 | Log4js: require("./log4js"), 23 | Pino: require("./pino"), 24 | Winston: require("./winston"), 25 | 26 | LEVELS: Base.LEVELS 27 | }; 28 | 29 | function getByName(name) { 30 | /* istanbul ignore next */ 31 | if (!name) return null; 32 | 33 | let n = Object.keys(Loggers).find(n => n.toLowerCase() == name.toLowerCase()); 34 | if (n) return Loggers[n]; 35 | } 36 | 37 | /** 38 | * Resolve reporter by name 39 | * 40 | * @param {object|string} opt 41 | * @returns {Reporter} 42 | * @memberof ServiceBroker 43 | */ 44 | function resolve(opt) { 45 | if (isObject(opt) && isInheritedClass(opt, Loggers.Base)) { 46 | return opt; 47 | } else if (isString(opt)) { 48 | let LoggerClass = getByName(opt); 49 | if (LoggerClass) return new LoggerClass(); 50 | } else if (isObject(opt)) { 51 | let LoggerClass = getByName(opt.type); 52 | if (LoggerClass) return new LoggerClass(opt.options); 53 | else 54 | throw new BrokerOptionsError(`Invalid logger configuration. Type: '${opt.type}'`, { 55 | type: opt.type 56 | }); 57 | } 58 | 59 | throw new BrokerOptionsError(`Invalid logger configuration: '${opt}'`, { type: opt }); 60 | } 61 | 62 | function register(name, value) { 63 | Loggers[name] = value; 64 | } 65 | 66 | module.exports = Object.assign(Loggers, { resolve, register }); 67 | -------------------------------------------------------------------------------- /src/metrics/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const METRIC = require("./constants"); 10 | 11 | const MetricRegistry = require("./registry"); 12 | const BaseMetric = require("./types/base"); 13 | const CounterMetric = require("./types/counter"); 14 | const GaugeMetric = require("./types/gauge"); 15 | const HistrogramMetric = require("./types/histogram"); 16 | const InfoMetric = require("./types/info"); 17 | 18 | const Reporters = require("./reporters"); 19 | 20 | module.exports = { 21 | METRIC: METRIC, 22 | 23 | MetricRegistry, 24 | 25 | BaseMetric, 26 | CounterMetric, 27 | GaugeMetric, 28 | HistrogramMetric, 29 | InfoMetric, 30 | 31 | Reporters 32 | }; 33 | -------------------------------------------------------------------------------- /src/metrics/rates.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const INTERVAL = 5; 10 | const SECONDS_PER_MINUTE = 60.0; 11 | 12 | // https://github.com/dropwizard/metrics/blob/4.0-maintenance/metrics-core/src/main/java/com/codahale/metrics/EWMA.java 13 | /* istanbul ignore next 14 | function getAlpha(min) { 15 | return 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / min); 16 | } 17 | */ 18 | 19 | class MetricRate { 20 | constructor(metric, item, min) { 21 | this.metric = metric; 22 | this.item = item; 23 | this.min = min; 24 | //this.alpha = getAlpha(min); 25 | 26 | this.rate = 0; 27 | 28 | this.lastValue = 0; 29 | this.lastTickTime = Date.now(); 30 | this.value = null; 31 | 32 | this.timer = setInterval(() => this.tick(), INTERVAL * 1000).unref(); 33 | } 34 | 35 | update(value) { 36 | this.value = value; 37 | } 38 | 39 | tick() { 40 | // Get elapsed seconds 41 | const now = Date.now(); 42 | const elapsedSec = (now - this.lastTickTime) / 1000; 43 | this.lastTickTime = now; 44 | 45 | // Get difference between new and old value 46 | const diff = this.value - this.lastValue; 47 | this.lastValue = this.value; 48 | 49 | // Calculate the current requests/minute 50 | const oneMinRate = (diff / elapsedSec) * SECONDS_PER_MINUTE; 51 | 52 | // Weighted calculation 53 | let rate = this.rate + (oneMinRate - this.rate) * 0.5; 54 | // EWMA: const rate = this.rate + (this.alpha * (oneMinRate - this.rate)); 55 | 56 | // Rounding 57 | if (Math.abs(rate) < 0.05) rate = 0; 58 | const changed = Math.abs(rate - this.rate) > 0.01; 59 | 60 | this.rate = rate; 61 | 62 | if (changed) this.metric.changed(this.item.value, this.item.labels, now); 63 | } 64 | 65 | reset() { 66 | this.lastValue = 0; 67 | this.value = null; 68 | 69 | this.rate = 0; 70 | } 71 | } 72 | module.exports = MetricRate; 73 | -------------------------------------------------------------------------------- /src/metrics/reporters/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString, isInheritedClass } = require("../../utils"); 10 | const { BrokerOptionsError } = require("../../errors"); 11 | 12 | const Reporters = { 13 | Base: require("./base"), 14 | Console: require("./console"), 15 | CSV: require("./csv"), 16 | Event: require("./event"), 17 | Datadog: require("./datadog"), 18 | Prometheus: require("./prometheus"), 19 | StatsD: require("./statsd") 20 | }; 21 | 22 | function getByName(name) { 23 | /* istanbul ignore next */ 24 | if (!name) return null; 25 | 26 | let n = Object.keys(Reporters).find(n => n.toLowerCase() == name.toLowerCase()); 27 | if (n) return Reporters[n]; 28 | } 29 | 30 | /** 31 | * Resolve reporter by name 32 | * 33 | * @param {object|string} opt 34 | * @returns {Reporter} 35 | * @memberof ServiceBroker 36 | */ 37 | function resolve(opt) { 38 | if (isObject(opt) && isInheritedClass(opt, Reporters.Base)) { 39 | return opt; 40 | } else if (isString(opt)) { 41 | let ReporterClass = getByName(opt); 42 | if (ReporterClass) return new ReporterClass(); 43 | } else if (isObject(opt)) { 44 | let ReporterClass = getByName(opt.type); 45 | if (ReporterClass) return new ReporterClass(opt.options); 46 | else 47 | throw new BrokerOptionsError(`Invalid metric reporter type '${opt.type}'.`, { 48 | type: opt.type 49 | }); 50 | } 51 | 52 | throw new BrokerOptionsError(`Invalid metric reporter type '${opt}'.`, { type: opt }); 53 | } 54 | 55 | function register(name, value) { 56 | Reporters[name] = value; 57 | } 58 | 59 | module.exports = Object.assign(Reporters, { resolve, register }); 60 | -------------------------------------------------------------------------------- /src/metrics/types/counter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const GaugeMetric = require("./gauge"); 10 | const METRIC = require("../constants"); 11 | 12 | /** 13 | * Counter metric class. 14 | * 15 | * @class CounterMetric 16 | * @extends {GaugeMetric} 17 | */ 18 | class CounterMetric extends GaugeMetric { 19 | /** 20 | * Creates an instance of CounterMetric. 21 | * @param {Object} opts 22 | * @param {MetricRegistry} registry 23 | * @memberof CounterMetric 24 | */ 25 | constructor(opts, registry) { 26 | super(opts, registry); 27 | this.type = METRIC.TYPE_COUNTER; 28 | } 29 | 30 | /** 31 | * Disabled decrement method. 32 | * 33 | * @memberof CounterMetric 34 | */ 35 | decrement() { 36 | throw new Error("Counter can't be decreased."); 37 | } 38 | } 39 | 40 | module.exports = CounterMetric; 41 | -------------------------------------------------------------------------------- /src/metrics/types/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { BrokerOptionsError } = require("../../errors"); 10 | 11 | const Types = { 12 | Base: require("./base"), 13 | Counter: require("./counter"), 14 | Gauge: require("./gauge"), 15 | Histogram: require("./histogram"), 16 | Info: require("./info") 17 | }; 18 | 19 | /** 20 | * Get MetricType class by name. 21 | * 22 | * @param {String} name 23 | * @returns 24 | */ 25 | function getByName(name) { 26 | /* istanbul ignore next */ 27 | if (!name) return null; 28 | 29 | let n = Object.keys(Types).find(n => n.toLowerCase() == name.toLowerCase()); 30 | if (n) return Types[n]; 31 | } 32 | 33 | /** 34 | * Resolve metric type by name 35 | * 36 | * @param {string} type 37 | * @returns {BaseMetric} 38 | * @memberof ServiceBroker 39 | */ 40 | function resolve(type) { 41 | const TypeClass = getByName(type); 42 | if (!TypeClass) throw new BrokerOptionsError(`Invalid metric type '${type}'.`, { type }); 43 | 44 | return TypeClass; 45 | } 46 | 47 | function register(name, value) { 48 | Types[name] = value; 49 | } 50 | 51 | module.exports = Object.assign(Types, { resolve, register }); 52 | -------------------------------------------------------------------------------- /src/middlewares/cacher.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isFunction, deprecate } = require("../utils"); 10 | 11 | module.exports = function CacherMiddleware(broker) { 12 | if (broker.cacher) { 13 | const mw = broker.cacher.middleware(); 14 | if (isFunction(mw)) { 15 | deprecate( 16 | "Validator middleware returning a Function is deprecated. Return a middleware object instead." 17 | ); 18 | 19 | return { 20 | name: "Cacher", 21 | localAction: mw 22 | }; 23 | } 24 | 25 | return mw; 26 | } 27 | 28 | return null; 29 | }; 30 | -------------------------------------------------------------------------------- /src/middlewares/debounce.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = function debounceMiddleware(broker) { 10 | function wrapEventDebounceMiddleware(handler, event) { 11 | if (event.debounce > 0) { 12 | let timer; 13 | 14 | return function debounceMiddleware(ctx) { 15 | if (timer) clearTimeout(timer); 16 | 17 | timer = setTimeout(() => { 18 | timer = null; 19 | return handler(ctx); 20 | }, event.debounce); 21 | 22 | return broker.Promise.resolve(); 23 | }.bind(this); 24 | } 25 | return handler; 26 | } 27 | 28 | return { 29 | name: "Debounce", 30 | 31 | localEvent: wrapEventDebounceMiddleware 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const Middlewares = { 10 | ActionHook: require("./action-hook"), 11 | Cacher: require("./cacher"), 12 | Validator: require("./validator"), 13 | Bulkhead: require("./bulkhead"), 14 | ContextTracker: require("./context-tracker"), 15 | CircuitBreaker: require("./circuit-breaker"), 16 | Timeout: require("./timeout"), 17 | Retry: require("./retry"), 18 | Fallback: require("./fallback"), 19 | ErrorHandler: require("./error-handler"), 20 | Metrics: require("./metrics"), 21 | Tracing: require("./tracing"), 22 | 23 | Debounce: require("./debounce"), 24 | Throttle: require("./throttle"), 25 | 26 | HotReload: require("./hot-reload"), 27 | 28 | Transmit: { 29 | Encryption: require("./transmit/encryption"), 30 | Compression: require("./transmit/compression") 31 | }, 32 | 33 | Debugging: { 34 | TransitLogger: require("./debugging/transit-logger"), 35 | ActionLogger: require("./debugging/action-logger") 36 | } 37 | }; 38 | 39 | function register(name, value) { 40 | Middlewares[name] = value; 41 | } 42 | 43 | module.exports = Object.assign(Middlewares, { register }); 44 | -------------------------------------------------------------------------------- /src/middlewares/throttle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = function throttleMiddleware(broker) { 10 | function wrapEventThrottleMiddleware(handler, event) { 11 | if (event.throttle > 0) { 12 | let lastInvoke = 0; 13 | 14 | return function throttleMiddleware(ctx) { 15 | const now = Date.now(); 16 | if (now - lastInvoke < event.throttle) { 17 | return broker.Promise.resolve(); 18 | } 19 | lastInvoke = now; 20 | return handler(ctx); 21 | }.bind(this); 22 | } 23 | return handler; 24 | } 25 | 26 | return { 27 | name: "Throttle", 28 | 29 | localEvent: wrapEventThrottleMiddleware 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /src/middlewares/transmit/encryption.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const crypto = require("crypto"); 10 | 11 | /** 12 | * This is a AES encryption middleware to protect the whole 13 | * Moleculer transporter communication with AES. 14 | * 15 | * @param {String|Buffer} password 16 | * @param {String?} algorithm 17 | * @param {String|Buffer?} iv 18 | */ 19 | module.exports = function EncryptionMiddleware(password, algorithm = "aes-256-cbc", iv) { 20 | if (!password || password.length == 0) { 21 | /* istanbul ignore next */ 22 | throw new Error("Must be set a password for encryption"); 23 | } 24 | 25 | let logger; 26 | 27 | return { 28 | name: "Encryption", 29 | 30 | created() { 31 | logger = this.logger; 32 | /* istanbul ignore next */ 33 | this.logger.info(`The transmission is ENCRYPTED by '${algorithm}'.`); 34 | }, 35 | 36 | transporterSend(next) { 37 | return (topic, data, meta) => { 38 | const encrypter = iv 39 | ? crypto.createCipheriv(algorithm, password, iv) 40 | : crypto.createCipher(algorithm, password); 41 | const res = Buffer.concat([encrypter.update(data), encrypter.final()]); 42 | return next(topic, res, meta); 43 | }; 44 | }, 45 | 46 | transporterReceive(next) { 47 | return (cmd, data, s) => { 48 | try { 49 | const decrypter = iv 50 | ? crypto.createDecipheriv(algorithm, password, iv) 51 | : crypto.createDecipher(algorithm, password); 52 | const res = Buffer.concat([decrypter.update(data), decrypter.final()]); 53 | return next(cmd, res, s); 54 | } catch (err) { 55 | logger.error("Received packet decryption error.", err); 56 | } 57 | }; 58 | } 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /src/middlewares/validator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isFunction, deprecate } = require("../utils"); 10 | 11 | module.exports = function ValidatorMiddleware(broker) { 12 | if (broker.validator && isFunction(broker.validator.middleware)) { 13 | const mw = broker.validator.middleware(broker); 14 | if (isFunction(mw)) { 15 | deprecate( 16 | "Validator middleware returning a Function is deprecated. Return a middleware object instead." 17 | ); 18 | return { 19 | name: "Validator", 20 | localAction: mw 21 | }; 22 | } 23 | return mw; 24 | } 25 | 26 | return null; 27 | }; 28 | -------------------------------------------------------------------------------- /src/packets.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | // Packet types 10 | const PACKET_UNKNOWN = "???"; 11 | const PACKET_EVENT = "EVENT"; 12 | const PACKET_REQUEST = "REQ"; 13 | const PACKET_RESPONSE = "RES"; 14 | const PACKET_DISCOVER = "DISCOVER"; 15 | const PACKET_INFO = "INFO"; 16 | const PACKET_DISCONNECT = "DISCONNECT"; 17 | const PACKET_HEARTBEAT = "HEARTBEAT"; 18 | const PACKET_PING = "PING"; 19 | const PACKET_PONG = "PONG"; 20 | 21 | const PACKET_GOSSIP_REQ = "GOSSIP_REQ"; 22 | const PACKET_GOSSIP_RES = "GOSSIP_RES"; 23 | const PACKET_GOSSIP_HELLO = "GOSSIP_HELLO"; 24 | 25 | const DATATYPE_UNDEFINED = 0; 26 | const DATATYPE_NULL = 1; 27 | const DATATYPE_JSON = 2; 28 | const DATATYPE_BUFFER = 3; 29 | 30 | /** 31 | * Packet for transporters 32 | * 33 | * @class Packet 34 | */ 35 | class Packet { 36 | /** 37 | * Creates an instance of Packet. 38 | * 39 | * @param {String} type 40 | * @param {String} target 41 | * @param {any} payload 42 | * 43 | * @memberof Packet 44 | */ 45 | constructor(type, target, payload) { 46 | this.type = type || PACKET_UNKNOWN; 47 | this.target = target; 48 | this.payload = payload || {}; 49 | } 50 | } 51 | 52 | module.exports = { 53 | PACKET_UNKNOWN, 54 | PACKET_EVENT, 55 | PACKET_REQUEST, 56 | PACKET_RESPONSE, 57 | PACKET_DISCOVER, 58 | PACKET_INFO, 59 | PACKET_DISCONNECT, 60 | PACKET_HEARTBEAT, 61 | PACKET_PING, 62 | PACKET_PONG, 63 | PACKET_GOSSIP_REQ, 64 | PACKET_GOSSIP_RES, 65 | PACKET_GOSSIP_HELLO, 66 | 67 | DATATYPE_UNDEFINED, 68 | DATATYPE_NULL, 69 | DATATYPE_JSON, 70 | DATATYPE_BUFFER, 71 | 72 | Packet 73 | }; 74 | -------------------------------------------------------------------------------- /src/registry/discoverers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { BrokerOptionsError } = require("../../errors"); 10 | const { isObject, isString, isInheritedClass } = require("../../utils"); 11 | 12 | const Discoverers = { 13 | Base: require("./base"), 14 | Local: require("./local"), 15 | Etcd3: require("./etcd3"), 16 | Redis: require("./redis") 17 | }; 18 | 19 | function getByName(name) { 20 | /* istanbul ignore next */ 21 | if (!name) return null; 22 | 23 | let n = Object.keys(Discoverers).find(n => n.toLowerCase() == name.toLowerCase()); 24 | if (n) return Discoverers[n]; 25 | } 26 | 27 | /** 28 | * Resolve discoverer by name 29 | * 30 | * @param {object|string} opt 31 | * @returns {Discoverer} 32 | * @memberof ServiceBroker 33 | */ 34 | function resolve(opt) { 35 | if (isObject(opt) && isInheritedClass(opt, Discoverers.Base)) { 36 | return opt; 37 | } else if (isString(opt)) { 38 | let DiscovererClass = getByName(opt); 39 | if (DiscovererClass) return new DiscovererClass(); 40 | 41 | if (opt.startsWith("redis://") || opt.startsWith("rediss://")) 42 | return new Discoverers.Redis(opt); 43 | 44 | if (opt.startsWith("etcd3://")) return new Discoverers.Etcd3(opt); 45 | 46 | throw new BrokerOptionsError(`Invalid Discoverer type '${opt}'.`, { type: opt }); 47 | } else if (isObject(opt)) { 48 | let DiscovererClass = getByName(opt.type || "Local"); 49 | if (DiscovererClass) return new DiscovererClass(opt.options); 50 | else 51 | throw new BrokerOptionsError(`Invalid Discoverer type '${opt.type}'.`, { 52 | type: opt.type 53 | }); 54 | } 55 | 56 | return new Discoverers.Local(); 57 | } 58 | 59 | function register(name, value) { 60 | Discoverers[name] = value; 61 | } 62 | 63 | module.exports = Object.assign(Discoverers, { resolve, register }); 64 | -------------------------------------------------------------------------------- /src/registry/discoverers/local.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseDiscoverer = require("./base"); 10 | 11 | /** 12 | * Local (built-in) Discoverer class 13 | * 14 | * @class Discoverer 15 | */ 16 | class LocalDiscoverer extends BaseDiscoverer { 17 | /** 18 | * Creates an instance of Discoverer. 19 | * 20 | * @memberof LocalDiscoverer 21 | */ 22 | constructor(opts) { 23 | super(opts); 24 | } 25 | 26 | /** 27 | * Initialize Discoverer 28 | * 29 | * @param {any} registry 30 | * 31 | * @memberof LocalDiscoverer 32 | */ 33 | init(registry) { 34 | super.init(registry); 35 | } 36 | 37 | /** 38 | * Discover a new or old node. 39 | * 40 | * @param {String} nodeID 41 | */ 42 | discoverNode(nodeID) { 43 | if (!this.transit) return this.Promise.resolve(); 44 | return this.transit.discoverNode(nodeID); 45 | } 46 | 47 | /** 48 | * Discover all nodes (after connected) 49 | */ 50 | discoverAllNodes() { 51 | if (!this.transit) return this.Promise.resolve(); 52 | return this.transit.discoverNodes(); 53 | } 54 | 55 | /** 56 | * Local service registry has been changed. We should notify remote nodes. 57 | * 58 | * @param {String} nodeID 59 | */ 60 | sendLocalNodeInfo(nodeID) { 61 | if (!this.transit) return this.Promise.resolve(); 62 | 63 | const info = this.broker.getLocalNodeInfo(); 64 | 65 | const p = 66 | !nodeID && this.broker.options.disableBalancer 67 | ? this.transit.tx.makeBalancedSubscriptions() 68 | : this.Promise.resolve(); 69 | return p.then(() => this.transit.sendNodeInfo(info, nodeID)); 70 | } 71 | } 72 | 73 | module.exports = LocalDiscoverer; 74 | -------------------------------------------------------------------------------- /src/registry/endpoint-action.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const Endpoint = require("./endpoint"); 10 | 11 | /** 12 | * Endpoint class for actions 13 | * 14 | * @class ActionEndpoint 15 | * @extends {Endpoint} 16 | */ 17 | class ActionEndpoint extends Endpoint { 18 | /** 19 | * Creates an instance of ActionEndpoint. 20 | * @param {Registry} registry 21 | * @param {ServiceBroker} broker 22 | * @param {Node} node 23 | * @param {ServiceItem} service 24 | * @param {any} action 25 | * @memberof ActionEndpoint 26 | */ 27 | constructor(registry, broker, node, service, action) { 28 | super(registry, broker, node); 29 | 30 | this.service = service; 31 | this.action = action; 32 | 33 | this.name = `${this.id}:${this.action.name}`; 34 | } 35 | 36 | /** 37 | * Update properties 38 | * 39 | * @param {any} action 40 | * @memberof ActionEndpoint 41 | */ 42 | update(action) { 43 | this.action = action; 44 | } 45 | } 46 | 47 | module.exports = ActionEndpoint; 48 | -------------------------------------------------------------------------------- /src/registry/endpoint-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const Endpoint = require("./endpoint"); 10 | 11 | /** 12 | * Endpoint class for events 13 | * 14 | * @class EventEndpoint 15 | * @extends {Endpoint} 16 | */ 17 | class EventEndpoint extends Endpoint { 18 | /** 19 | * Creates an instance of EventEndpoint. 20 | * @param {Registry} registry 21 | * @param {ServiceBroker} broker 22 | * @param {Node} node 23 | * @param {Service} service 24 | * @param {any} event 25 | * @memberof EventEndpoint 26 | */ 27 | constructor(registry, broker, node, service, event) { 28 | super(registry, broker, node); 29 | 30 | this.service = service; 31 | this.event = event; 32 | } 33 | 34 | /** 35 | * Update properties 36 | * 37 | * @param {any} event 38 | * @memberof EventEndpoint 39 | */ 40 | update(event) { 41 | this.event = event; 42 | } 43 | } 44 | 45 | module.exports = EventEndpoint; 46 | -------------------------------------------------------------------------------- /src/registry/endpoint.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Endpoint class 11 | * 12 | * @class Endpoint 13 | */ 14 | class Endpoint { 15 | /** 16 | * Creates an instance of Endpoint. 17 | * @param {Registry} registry 18 | * @param {ServiceBroker} broker 19 | * @param {Node} node 20 | * @memberof Endpoint 21 | */ 22 | constructor(registry, broker, node) { 23 | this.registry = registry; 24 | this.broker = broker; 25 | 26 | this.id = node.id; 27 | this.node = node; 28 | 29 | this.local = node.id === broker.nodeID; 30 | this.state = true; 31 | } 32 | 33 | destroy() {} 34 | 35 | /** 36 | * Get availability 37 | * 38 | * @readonly 39 | * @memberof Endpoint 40 | */ 41 | get isAvailable() { 42 | return this.state; 43 | } 44 | 45 | update() {} 46 | } 47 | 48 | module.exports = Endpoint; 49 | -------------------------------------------------------------------------------- /src/registry/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const Registry = require("./registry"); 10 | 11 | module.exports = Registry; 12 | 13 | module.exports.Endpoint = require("./endpoint"); 14 | -------------------------------------------------------------------------------- /src/registry/service-item.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Service class 11 | * 12 | * @class ServiceItem 13 | */ 14 | class ServiceItem { 15 | /** 16 | * Creates an instance of ServiceItem. 17 | * 18 | * @param {Node} node 19 | * @param {Object} service 20 | * @param {Boolean} local 21 | * @memberof ServiceItem 22 | */ 23 | constructor(node, service, local) { 24 | this.node = node; 25 | this.name = service.name; 26 | this.fullName = service.fullName; 27 | this.version = service.version; 28 | this.settings = service.settings; 29 | this.metadata = service.metadata || {}; 30 | 31 | this.local = !!local; 32 | 33 | this.actions = {}; 34 | this.events = {}; 35 | } 36 | 37 | /** 38 | * Check the service equals params 39 | * 40 | * @param {String} fullName 41 | * @param {String} nodeID 42 | * @returns 43 | * @memberof ServiceItem 44 | */ 45 | equals(fullName, nodeID) { 46 | return this.fullName == fullName && (nodeID == null || this.node.id == nodeID); 47 | } 48 | 49 | /** 50 | * Update service properties 51 | * 52 | * @param {any} svc 53 | * @memberof ServiceItem 54 | */ 55 | update(svc) { 56 | this.fullName = svc.fullName; 57 | this.version = svc.version; 58 | this.settings = svc.settings; 59 | this.metadata = svc.metadata || {}; 60 | } 61 | 62 | /** 63 | * Add action to service 64 | * 65 | * @param {any} action 66 | * @memberof ServiceItem 67 | */ 68 | addAction(action) { 69 | this.actions[action.name] = action; 70 | } 71 | 72 | /** 73 | * Add event to service 74 | * 75 | * @param {any} event 76 | * @memberof ServiceItem 77 | */ 78 | addEvent(event) { 79 | this.events[event.name] = event; 80 | } 81 | } 82 | 83 | module.exports = ServiceItem; 84 | -------------------------------------------------------------------------------- /src/serializers/cbor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseSerializer = require("./base"); 10 | const _ = require("lodash"); 11 | 12 | /** 13 | * CBOR serializer for Moleculer 14 | * 15 | * https://github.com/kriszyp/cbor-x 16 | * 17 | * @class CborSerializer 18 | */ 19 | class CborSerializer extends BaseSerializer { 20 | /** 21 | * Creates an instance of CborSerializer. 22 | * 23 | * Available options: 24 | * https://github.com/kriszyp/cbor-x#options 25 | * 26 | * @param {Object} opts 27 | * 28 | * @memberof Serializer 29 | */ 30 | constructor(opts) { 31 | super(opts); 32 | this.opts = _.defaultsDeep(opts, { useRecords: false, useTag259ForMaps: false }); 33 | } 34 | 35 | /** 36 | * Initialize Serializer 37 | * 38 | * @param {any} broker 39 | * 40 | * @memberof Serializer 41 | */ 42 | init(broker) { 43 | super.init(broker); 44 | 45 | try { 46 | const Cbor = require("cbor-x"); 47 | this.encoder = new Cbor.Encoder(this.opts); 48 | } catch (err) { 49 | /* istanbul ignore next */ 50 | this.broker.fatal( 51 | "The 'cbor-x' package is missing! Please install it with 'npm install cbor-x --save' command!", 52 | err, 53 | true 54 | ); 55 | } 56 | } 57 | 58 | /** 59 | * Serializer a JS object to Buffer 60 | * 61 | * @param {Object} obj 62 | * @returns {Buffer} 63 | * 64 | * @memberof Serializer 65 | */ 66 | serialize(obj) { 67 | const res = this.encoder.encode(obj); 68 | return res; 69 | } 70 | 71 | /** 72 | * Deserialize Buffer to JS object 73 | * 74 | * @param {Buffer} str 75 | * @returns {Object} 76 | * 77 | * @memberof Serializer 78 | */ 79 | deserialize(buf) { 80 | const res = this.encoder.decode(buf); 81 | return res; 82 | } 83 | } 84 | 85 | module.exports = CborSerializer; 86 | -------------------------------------------------------------------------------- /src/serializers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString, isInheritedClass } = require("../utils"); 10 | const { BrokerOptionsError } = require("../errors"); 11 | 12 | const Serializers = { 13 | Base: require("./base"), 14 | JSON: require("./json"), 15 | Avro: require("./avro"), 16 | MsgPack: require("./msgpack"), 17 | ProtoBuf: require("./protobuf"), 18 | Thrift: require("./thrift"), 19 | Notepack: require("./notepack"), 20 | CBOR: require("./cbor") 21 | }; 22 | 23 | function getByName(name) { 24 | /* istanbul ignore next */ 25 | if (!name) return null; 26 | 27 | let n = Object.keys(Serializers).find(n => n.toLowerCase() == name.toLowerCase()); 28 | if (n) return Serializers[n]; 29 | } 30 | 31 | /** 32 | * Resolve serializer by name 33 | * 34 | * @param {object|string} opt 35 | * @returns {Serializer} 36 | * @memberof ServiceBroker 37 | */ 38 | function resolve(opt) { 39 | if (isObject(opt) && isInheritedClass(opt, Serializers.Base)) { 40 | return opt; 41 | } else if (isString(opt)) { 42 | let SerializerClass = getByName(opt); 43 | if (SerializerClass) return new SerializerClass(); 44 | else throw new BrokerOptionsError(`Invalid serializer type '${opt}'.`, { type: opt }); 45 | } else if (isObject(opt)) { 46 | let SerializerClass = getByName(opt.type || "JSON"); 47 | if (SerializerClass) return new SerializerClass(opt.options); 48 | else 49 | throw new BrokerOptionsError(`Invalid serializer type '${opt.type}'.`, { 50 | type: opt.type 51 | }); 52 | } 53 | 54 | return new Serializers.JSON(); 55 | } 56 | 57 | function register(name, value) { 58 | Serializers[name] = value; 59 | } 60 | 61 | module.exports = Object.assign(Serializers, { resolve, register }); 62 | -------------------------------------------------------------------------------- /src/serializers/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseSerializer = require("./base"); 10 | /** 11 | * JSON serializer for Moleculer 12 | * 13 | * @class JSONSerializer 14 | */ 15 | class JSONSerializer extends BaseSerializer { 16 | /** 17 | * Creates an instance of JSONSerializer. 18 | * 19 | * @memberof JSONSerializer 20 | */ 21 | constructor() { 22 | super(); 23 | } 24 | 25 | /** 26 | * Serializer a JS object to Buffer 27 | * 28 | * @param {Object} obj 29 | * @param {String} type of packet 30 | * @returns {Buffer} 31 | * 32 | * @memberof Serializer 33 | */ 34 | serialize(obj) { 35 | return Buffer.from(JSON.stringify(obj)); 36 | } 37 | 38 | /** 39 | * Deserialize Buffer to JS object 40 | * 41 | * @param {Buffer} buf 42 | * @param {String} type of packet 43 | * @returns {Object} 44 | * 45 | * @memberof Serializer 46 | */ 47 | deserialize(buf) { 48 | return JSON.parse(buf); 49 | } 50 | } 51 | 52 | module.exports = JSONSerializer; 53 | -------------------------------------------------------------------------------- /src/serializers/msgpack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseSerializer = require("./base"); 10 | 11 | /** 12 | * MessagePack serializer for Moleculer 13 | * 14 | * https://github.com/mcollina/msgpack5 15 | * 16 | * @class MsgPackSerializer 17 | */ 18 | class MsgPackSerializer extends BaseSerializer { 19 | /** 20 | * Initialize Serializer 21 | * 22 | * @param {any} broker 23 | * 24 | * @memberof Serializer 25 | */ 26 | init(broker) { 27 | super.init(broker); 28 | 29 | try { 30 | this.msgpack = require("msgpack5")(); 31 | } catch (err) { 32 | /* istanbul ignore next */ 33 | this.broker.fatal( 34 | "The 'msgpack5' package is missing! Please install it with 'npm install msgpack5 --save' command!", 35 | err, 36 | true 37 | ); 38 | } 39 | } 40 | 41 | /** 42 | * Serializer a JS object to Buffer 43 | * 44 | * @param {Object} obj 45 | * @returns {Buffer} 46 | * 47 | * @memberof Serializer 48 | */ 49 | serialize(obj) { 50 | const res = this.msgpack.encode(obj); 51 | return res; 52 | } 53 | 54 | /** 55 | * Deserialize Buffer to JS object 56 | * 57 | * @param {Buffer} str 58 | * @returns {Object} 59 | * 60 | * @memberof Serializer 61 | */ 62 | deserialize(buf) { 63 | const res = this.msgpack.decode(buf); 64 | return res; 65 | } 66 | } 67 | 68 | module.exports = MsgPackSerializer; 69 | -------------------------------------------------------------------------------- /src/serializers/notepack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BaseSerializer = require("./base"); 4 | /** 5 | * JSON serializer for Moleculer 6 | * 7 | * @class JSONSerializer 8 | */ 9 | class NotepackSerializer extends BaseSerializer { 10 | /** 11 | * Initialize Serializer 12 | * 13 | * @param {any} broker 14 | * 15 | * @memberof Serializer 16 | */ 17 | init(broker) { 18 | super.init(broker); 19 | 20 | try { 21 | this.codec = require("notepack.io"); 22 | } catch (err) { 23 | /* istanbul ignore next */ 24 | this.broker.fatal( 25 | "The 'notepack.io' package is missing! Please install it with 'npm install notepack.io --save' command!", 26 | err, 27 | true 28 | ); 29 | } 30 | } 31 | 32 | /** 33 | * Serializer a JS object to Buffer 34 | * 35 | * @param {Object} obj 36 | * @param {String} type of packet 37 | * @returns {Buffer} 38 | * 39 | * @memberof Serializer 40 | */ 41 | serialize(obj) { 42 | return this.codec.encode(obj); 43 | } 44 | 45 | /** 46 | * Deserialize Buffer to JS object 47 | * 48 | * @param {Buffer} buf 49 | * @param {String} type of packet 50 | * @returns {Object} 51 | * 52 | * @memberof Serializer 53 | */ 54 | deserialize(buf) { 55 | return this.codec.decode(buf); 56 | } 57 | } 58 | 59 | module.exports = NotepackSerializer; 60 | -------------------------------------------------------------------------------- /src/strategies/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Base strategy class 11 | * 12 | * @class BaseStrategy 13 | */ 14 | class BaseStrategy { 15 | /** 16 | * Constructor 17 | * 18 | * @param {ServiceRegistry} registry 19 | * @param {ServiceBroker} broker 20 | * @param {Object?} opts 21 | */ 22 | constructor(registry, broker, opts) { 23 | this.registry = registry; 24 | this.broker = broker; 25 | this.opts = opts || {}; 26 | } 27 | 28 | /** 29 | * Select an endpoint. 30 | * 31 | * @param {Array} list 32 | * @param {Context?} ctx 33 | * 34 | * @memberof BaseStrategy 35 | */ 36 | select(/*list, ctx*/) { 37 | /* istanbul ignore next */ 38 | throw new Error("Not implemented method!"); 39 | } 40 | } 41 | 42 | module.exports = BaseStrategy; 43 | -------------------------------------------------------------------------------- /src/strategies/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString } = require("../utils"); 10 | const { BrokerOptionsError } = require("../errors"); 11 | 12 | const Strategies = { 13 | Base: require("./base"), 14 | RoundRobin: require("./round-robin"), 15 | Random: require("./random"), 16 | CpuUsage: require("./cpu-usage"), 17 | Latency: require("./latency"), 18 | Shard: require("./shard") 19 | }; 20 | 21 | function getByName(name) { 22 | /* istanbul ignore next */ 23 | if (!name) return null; 24 | 25 | let n = Object.keys(Strategies).find(n => n.toLowerCase() == name.toLowerCase()); 26 | if (n) return Strategies[n]; 27 | } 28 | 29 | /** 30 | * Resolve strategy by name 31 | * 32 | * @param {object|string} opt 33 | * @returns {Strategy} 34 | * @memberof ServiceBroker 35 | */ 36 | function resolve(opt) { 37 | if (Object.prototype.isPrototypeOf.call(Strategies.Base, opt)) { 38 | return opt; 39 | } else if (isString(opt)) { 40 | let StrategyClass = getByName(opt); 41 | if (StrategyClass) return StrategyClass; 42 | else throw new BrokerOptionsError(`Invalid strategy type '${opt}'.`, { type: opt }); 43 | } else if (isObject(opt)) { 44 | let StrategyClass = getByName(opt.type || "RoundRobin"); 45 | if (StrategyClass) return StrategyClass; 46 | else 47 | throw new BrokerOptionsError(`Invalid strategy type '${opt.type}'.`, { 48 | type: opt.type 49 | }); 50 | } 51 | 52 | return Strategies.RoundRobin; 53 | } 54 | 55 | function register(name, value) { 56 | Strategies[name] = value; 57 | } 58 | 59 | module.exports = Object.assign(Strategies, { resolve, register }); 60 | -------------------------------------------------------------------------------- /src/strategies/random.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { random } = require("lodash"); 10 | const BaseStrategy = require("./base"); 11 | 12 | /** 13 | * Random strategy class 14 | * 15 | * @class RandomStrategy 16 | */ 17 | class RandomStrategy extends BaseStrategy { 18 | select(list) { 19 | return list[random(0, list.length - 1)]; 20 | } 21 | } 22 | 23 | module.exports = RandomStrategy; 24 | -------------------------------------------------------------------------------- /src/strategies/round-robin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const BaseStrategy = require("./base"); 10 | 11 | /** 12 | * Round-robin strategy class 13 | * 14 | * @class RoundRobinStrategy 15 | */ 16 | class RoundRobinStrategy extends BaseStrategy { 17 | constructor(registry, broker, opts) { 18 | super(registry, broker, opts); 19 | 20 | this.counter = 0; 21 | } 22 | 23 | select(list) { 24 | // Reset counter 25 | if (this.counter >= list.length) { 26 | this.counter = 0; 27 | } 28 | return list[this.counter++]; 29 | } 30 | } 31 | 32 | module.exports = RoundRobinStrategy; 33 | -------------------------------------------------------------------------------- /src/tracing/exporters/README.md: -------------------------------------------------------------------------------- 1 | # Moleculer Trace Exporters 2 | 3 | ## Running Jaeger 4 | 5 | ```bash 6 | docker run -d --name jaeger -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14250:14250 -p 14268:14268 -p 14269:14269 jaegertracing/all-in-one:latest 7 | ``` 8 | 9 | **UI:** http://:16686/ 10 | 11 | ## Running Zipkin 12 | 13 | ```bash 14 | docker run -d -p 9411:9411 --name=zipkin openzipkin/zipkin 15 | ``` 16 | 17 | ## Running DataDog Agent 18 | 19 | ```bash 20 | docker run -d --name dd-agent --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock:ro -v /proc/:/host/proc/:ro -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro -e DD_API_KEY=123456 -e DD_APM_ENABLED=true -e DD_APM_NON_LOCAL_TRAFFIC=true -p 8126:8126 datadog/agent:latest 21 | ``` 22 | -------------------------------------------------------------------------------- /src/tracing/exporters/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { isObject, isString, isInheritedClass } = require("../../utils"); 10 | const { BrokerOptionsError } = require("../../errors"); 11 | 12 | const Exporters = { 13 | Base: require("./base"), 14 | Console: require("./console"), 15 | Datadog: require("./datadog"), 16 | //DatadogSimple: require("./datadog-simple"), 17 | Event: require("./event"), 18 | EventLegacy: require("./event-legacy"), 19 | Jaeger: require("./jaeger"), 20 | Zipkin: require("./zipkin"), 21 | NewRelic: require("./newrelic") 22 | }; 23 | 24 | function getByName(name) { 25 | /* istanbul ignore next */ 26 | if (!name) return null; 27 | 28 | let n = Object.keys(Exporters).find(n => n.toLowerCase() == name.toLowerCase()); 29 | if (n) return Exporters[n]; 30 | } 31 | 32 | /** 33 | * Resolve exporter by name 34 | * 35 | * @param {object|string} opt 36 | * @returns {Exporters.Base} 37 | * @memberof ServiceBroker 38 | */ 39 | function resolve(opt) { 40 | if (isObject(opt) && isInheritedClass(opt, Exporters.Base)) { 41 | return opt; 42 | } else if (isString(opt)) { 43 | let ExporterClass = getByName(opt); 44 | if (ExporterClass) return new ExporterClass(); 45 | else throw new BrokerOptionsError(`Invalid tracing exporter type '${opt}'.`, { type: opt }); 46 | } else if (isObject(opt)) { 47 | let ExporterClass = getByName(opt.type); 48 | if (ExporterClass) return new ExporterClass(opt.options); 49 | else 50 | throw new BrokerOptionsError(`Invalid tracing exporter type '${opt.type}'.`, { 51 | type: opt.type 52 | }); 53 | } 54 | 55 | throw new BrokerOptionsError(`Invalid tracing exporter type '${opt}'.`, { type: opt }); 56 | } 57 | 58 | function register(name, value) { 59 | Exporters[name] = value; 60 | } 61 | 62 | module.exports = Object.assign(Exporters, { resolve, register }); 63 | -------------------------------------------------------------------------------- /src/tracing/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | module.exports = { 10 | Tracer: require("./tracer"), 11 | Span: require("./span"), 12 | Exporters: require("./exporters") 13 | }; 14 | -------------------------------------------------------------------------------- /src/tracing/rate-limiter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const _ = require("lodash"); 10 | 11 | /** 12 | * Rate Limiter class for Tracing. 13 | * 14 | * Inspired by 15 | * https://github.com/jaegertracing/jaeger-client-node/blob/master/src/rate_limiter.js 16 | * 17 | * @class RateLimiter 18 | */ 19 | class RateLimiter { 20 | constructor(opts) { 21 | this.opts = _.defaultsDeep(opts, { 22 | tracesPerSecond: 1 23 | }); 24 | 25 | this.lastTime = Date.now(); 26 | this.balance = 0; 27 | this.maxBalance = this.opts.tracesPerSecond < 1 ? 1 : this.opts.tracesPerSecond; 28 | } 29 | 30 | check(cost = 1) { 31 | const now = Date.now(); 32 | const elapsedTime = (now - this.lastTime) / 1000; 33 | this.lastTime = now; 34 | 35 | this.balance += elapsedTime * this.opts.tracesPerSecond; 36 | if (this.balance > this.maxBalance) this.balance = this.maxBalance; 37 | 38 | if (this.balance >= cost) { 39 | this.balance -= cost; 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | } 46 | 47 | module.exports = RateLimiter; 48 | -------------------------------------------------------------------------------- /src/validators/fastest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const Validator = require("fastest-validator"); 10 | const { ValidationError } = require("../errors"); 11 | const BaseValidator = require("./base"); 12 | const _ = require("lodash"); 13 | 14 | class FastestValidator extends BaseValidator { 15 | constructor(opts) { 16 | super(opts); 17 | this.validator = new Validator(this.opts); 18 | } 19 | 20 | /** 21 | * Compile a validation schema to a checker function. 22 | * Need a clone because FV manipulate the schema (removing $$... props) 23 | * 24 | * @param {any} schema 25 | * @returns {Function} 26 | */ 27 | compile(schema) { 28 | return this.validator.compile(_.cloneDeep(schema)); 29 | } 30 | 31 | /** 32 | * Validate params against the schema 33 | * @param {any} params 34 | * @param {any} schema 35 | * @returns {boolean} 36 | */ 37 | validate(params, schema) { 38 | const res = this.validator.validate(params, _.cloneDeep(schema)); 39 | if (res !== true) throw new ValidationError("Parameters validation error!", null, res); 40 | 41 | return true; 42 | } 43 | 44 | /** 45 | * Convert the specific validation schema to 46 | * the Moleculer (fastest-validator) validation schema format. 47 | * 48 | * @param {any} schema 49 | * @returns {Object} 50 | */ 51 | convertSchemaToMoleculer(schema) { 52 | return schema; 53 | } 54 | } 55 | 56 | module.exports = FastestValidator; 57 | -------------------------------------------------------------------------------- /src/validators/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * moleculer 3 | * Copyright (c) 2020 MoleculerJS (https://github.com/moleculerjs/moleculer) 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | const { BrokerOptionsError } = require("../errors"); 10 | const { isObject, isString, isInheritedClass } = require("../utils"); 11 | 12 | const Validators = { 13 | Base: require("./base"), 14 | Fastest: require("./fastest") 15 | }; 16 | 17 | function getByName(name) { 18 | /* istanbul ignore next */ 19 | if (!name) return null; 20 | 21 | let n = Object.keys(Validators).find(n => n.toLowerCase() == name.toLowerCase()); 22 | if (n) return Validators[n]; 23 | } 24 | 25 | /** 26 | * Resolve validator by name 27 | * 28 | * @param {object|string} opt 29 | * @returns {Validator} 30 | * @memberof ServiceBroker 31 | */ 32 | function resolve(opt) { 33 | if (isObject(opt) && isInheritedClass(opt, Validators.Base)) { 34 | return opt; 35 | } else if (isString(opt)) { 36 | let ValidatorClass = getByName(opt); 37 | if (ValidatorClass) return new ValidatorClass(); 38 | 39 | throw new BrokerOptionsError(`Invalid Validator type '${opt}'.`, { type: opt }); 40 | } else if (isObject(opt)) { 41 | let ValidatorClass = getByName(opt.type || "Fastest"); 42 | if (ValidatorClass) return new ValidatorClass(opt.options); 43 | else 44 | throw new BrokerOptionsError(`Invalid Validator type '${opt.type}'.`, { 45 | type: opt.type 46 | }); 47 | } 48 | 49 | return new Validators.Fastest(); 50 | } 51 | 52 | function register(name, value) { 53 | Validators[name] = value; 54 | } 55 | 56 | module.exports = Object.assign(Validators, { resolve, register }); 57 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | "no-unused-vars": ["off"] 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | nats: 5 | image: nats:2 6 | ports: 7 | - "4222:4222" 8 | 9 | redis: 10 | image: redis:alpine 11 | ports: 12 | - "6379:6379" 13 | 14 | mqtt: 15 | image: ncarlier/mqtt 16 | ports: 17 | - "1883:1883" 18 | 19 | rabbitmq: 20 | image: rabbitmq:3-management 21 | ports: 22 | - "5672:5672" 23 | - "15672:15672" 24 | 25 | stan: 26 | image: nats-streaming 27 | ports: 28 | - "4222:4222" 29 | volumes: 30 | - ./nats.config:/nats.config 31 | command: "-c /nats.config" 32 | 33 | activemq: 34 | image: rmohr/activemq 35 | ports: 36 | - "5672:5672" 37 | - "8161:8161" 38 | 39 | zookeeper: 40 | image: bitnami/zookeeper:3.9 41 | environment: 42 | - ALLOW_ANONYMOUS_LOGIN=yes 43 | ports: 44 | - "2181:2181" 45 | 46 | kafka: 47 | image: bitnami/kafka:3.9 48 | environment: 49 | - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 50 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT 51 | - KAFKA_LISTENERS=PLAINTEXT://:9092,EXTERNAL://:9093 52 | - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://127.0.0.1:9093 53 | - KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT 54 | - ALLOW_PLAINTEXT_LISTENER=yes 55 | - KAFKA_ENABLE_KRAFT=no 56 | depends_on: 57 | - zookeeper 58 | ports: 59 | - "9092:9092" 60 | - "9093:9093" 61 | 62 | etcd3: 63 | image: 'bitnami/etcd:latest' 64 | environment: 65 | - ALLOW_NONE_AUTHENTICATION=yes 66 | - ETCD_ADVERTISE_CLIENT_URLS=http://127.0.0.1:2379 67 | ports: 68 | - 2379:2379 69 | restart: unless-stopped 70 | -------------------------------------------------------------------------------- /test/e2e/assets/.gitignore: -------------------------------------------------------------------------------- 1 | received.png 2 | -------------------------------------------------------------------------------- /test/e2e/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moleculerjs/moleculer/250c7e247b2e610665f897a60b547ee7a700b6aa/test/e2e/assets/banner.png -------------------------------------------------------------------------------- /test/e2e/scenarios/balancing/node1.js: -------------------------------------------------------------------------------- 1 | const { createNode, logEventEmitting } = require("../../utils"); 2 | 3 | const broker = createNode("node1"); 4 | broker.loadService("./test.service.js"); 5 | 6 | broker.createService({ 7 | name: "users", 8 | events: { 9 | "user.created"(ctx) { 10 | logEventEmitting(this, ctx); 11 | } 12 | } 13 | }); 14 | 15 | broker.createService({ 16 | name: "payment", 17 | events: { 18 | "user.created"(ctx) { 19 | logEventEmitting(this, ctx); 20 | } 21 | } 22 | }); 23 | 24 | broker.start(); 25 | -------------------------------------------------------------------------------- /test/e2e/scenarios/balancing/node2.js: -------------------------------------------------------------------------------- 1 | const { createNode, logEventEmitting } = require("../../utils"); 2 | 3 | const broker = createNode("node2"); 4 | broker.loadService("./test.service.js"); 5 | 6 | broker.createService({ 7 | name: "mail", 8 | events: { 9 | "user.created"(ctx) { 10 | logEventEmitting(this, ctx); 11 | } 12 | } 13 | }); 14 | 15 | broker.start(); 16 | -------------------------------------------------------------------------------- /test/e2e/scenarios/balancing/node3.js: -------------------------------------------------------------------------------- 1 | const { createNode, logEventEmitting } = require("../../utils"); 2 | 3 | const broker = createNode("node3"); 4 | broker.loadService("./test.service.js"); 5 | 6 | broker.createService({ 7 | name: "payment", 8 | events: { 9 | "user.created"(ctx) { 10 | logEventEmitting(this, ctx); 11 | } 12 | } 13 | }); 14 | 15 | broker.start(); 16 | -------------------------------------------------------------------------------- /test/e2e/scenarios/balancing/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Transporter: $TRANSPORTER"; 4 | echo "Serializer: $SERIALIZER"; 5 | echo "Discoverer: $DISCOVERER"; 6 | 7 | export NAMESPACE=balancing; 8 | 9 | echo "Start balancing scenario..."; 10 | node node1.js & \ 11 | node node2.js & \ 12 | node node3.js & \ 13 | node scenario.js 14 | 15 | case $TRANSPORTER in 16 | (NATS|STAN|AMQP) 17 | export DISABLEBALANCER=true; 18 | export NAMESPACE=balancing-disabled; 19 | echo "Start balancing scenario with disabled balancer..."; 20 | 21 | node node1.js & \ 22 | node node2.js & \ 23 | node node3.js & \ 24 | node scenario.js 25 | ;; 26 | esac 27 | -------------------------------------------------------------------------------- /test/e2e/scenarios/balancing/test.service.js: -------------------------------------------------------------------------------- 1 | const { logActionCalling, logEventEmitting } = require("../../utils"); 2 | 3 | module.exports = { 4 | name: "test", 5 | 6 | actions: { 7 | work: { 8 | handler(ctx) { 9 | logActionCalling(this, ctx); 10 | return true; 11 | } 12 | }, 13 | 14 | hello: { 15 | handler(ctx) { 16 | const { delay = 0, crash = false } = ctx.params; 17 | 18 | if (crash && this.broker.nodeID == "node1") return this.broker.stop(); 19 | 20 | return this.Promise.delay(delay).then(() => { 21 | logActionCalling(this, ctx); 22 | return { 23 | i: ctx.params.i, 24 | nodeID: this.broker.nodeID 25 | }; 26 | }); 27 | } 28 | } 29 | }, 30 | 31 | events: { 32 | "sample.event"(ctx) { 33 | logEventEmitting(this, ctx); 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /test/e2e/scenarios/basic/node1.js: -------------------------------------------------------------------------------- 1 | const { createNode, logEventEmitting } = require("../../utils"); 2 | const path = require("path"); 3 | 4 | const broker = createNode("node1"); 5 | broker.loadService(path.join(__dirname, "../../services/aes.service.js")); 6 | 7 | broker.createService({ 8 | name: "echo", 9 | 10 | actions: { 11 | reply: { 12 | handler(ctx) { 13 | return { 14 | params: ctx.params, 15 | meta: ctx.meta, 16 | response: { 17 | a: "Hey", 18 | b: 3333, 19 | c: true, 20 | d: { 21 | e: 122.34, 22 | f: [1, 2, 3], 23 | g: null 24 | } 25 | } 26 | }; 27 | } 28 | } 29 | }, 30 | 31 | events: { 32 | "sample.event"(ctx) { 33 | logEventEmitting(this, ctx); 34 | } 35 | } 36 | }); 37 | 38 | broker.start(); 39 | -------------------------------------------------------------------------------- /test/e2e/scenarios/basic/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Start basic scenario..."; 4 | echo "Transporter: $TRANSPORTER"; 5 | echo "Serializer: $SERIALIZER"; 6 | echo "Discoverer: $DISCOVERER"; 7 | 8 | export NAMESPACE=basic; 9 | node node1.js & \ 10 | node scenario.js 11 | -------------------------------------------------------------------------------- /test/e2e/scenarios/compression/node1.js: -------------------------------------------------------------------------------- 1 | const { createNode, getStreamSHA } = require("../../utils"); 2 | const _ = require("lodash"); 3 | const Stream = require("stream"); 4 | const TransmitCompression = require("../../../../src/middlewares/transmit/compression"); 5 | 6 | const broker = createNode("node1", { 7 | middlewares: [TransmitCompression({ method: "gzip" })] 8 | }); 9 | 10 | broker.createService({ 11 | name: "echo", 12 | 13 | actions: { 14 | replyStream: { 15 | async handler(ctx) { 16 | const meta = _.cloneDeep(ctx.meta); 17 | const hash = await getStreamSHA(ctx.params); 18 | 19 | return { 20 | meta, 21 | hash 22 | }; 23 | } 24 | }, 25 | 26 | sendStream: { 27 | async handler(ctx) { 28 | const participants = []; 29 | for (let i = 0; i < 100000; i++) { 30 | participants.push({ entry: i }); 31 | } 32 | 33 | const stream = new Stream.Readable(); 34 | stream.push(Buffer.from(JSON.stringify(participants))); 35 | stream.push(null); 36 | 37 | ctx.meta.resMeta = "resMeta"; 38 | ctx.meta.participants = participants.slice(0, 100); 39 | 40 | return stream; 41 | } 42 | } 43 | } 44 | }); 45 | 46 | broker.start(); 47 | -------------------------------------------------------------------------------- /test/e2e/scenarios/compression/scenario.js: -------------------------------------------------------------------------------- 1 | const { assert, createNode, executeScenarios, addScenario, getStreamSHA } = require("../../utils"); 2 | const Stream = require("stream"); 3 | const TransmitCompression = require("../../../../src/middlewares/transmit/compression"); 4 | 5 | const broker = createNode("supervisor", { 6 | middlewares: [TransmitCompression({ method: "gzip" })] 7 | }); 8 | broker.loadService("../../services/scenario.service.js"); 9 | 10 | const participants = []; 11 | for (let i = 0; i < 100000; i++) { 12 | participants.push({ entry: i }); 13 | } 14 | 15 | addScenario("send a stream with big metadata", async () => { 16 | await broker.call("$scenario.clear"); 17 | // ---- ^ SETUP ^ --- 18 | 19 | const stream = new Stream.Readable(); 20 | stream.push(Buffer.from(JSON.stringify(participants))); 21 | stream.push(null); 22 | 23 | const meta = { 24 | reqMeta: "reqMeta", 25 | participants: participants.slice(0, 100) 26 | }; 27 | 28 | const res = await broker.call("echo.replyStream", stream, { meta }); 29 | 30 | // ---- ˇ ASSERTS ˇ --- 31 | assert(res, { 32 | hash: "8e06ca55aa3b98c482545819b522a327a720fcd8", 33 | meta 34 | }); 35 | }); 36 | 37 | addScenario("receive a stream with big metadata", async () => { 38 | await broker.call("$scenario.clear"); 39 | // ---- ^ SETUP ^ --- 40 | 41 | const p = broker.call("echo.sendStream"); 42 | const res = await p; 43 | 44 | const hash = await getStreamSHA(res); 45 | 46 | // ---- ˇ ASSERTS ˇ --- 47 | assert(hash, "8e06ca55aa3b98c482545819b522a327a720fcd8"); 48 | assert(p.ctx.meta, { 49 | resMeta: "resMeta", 50 | participants: participants.slice(0, 100) 51 | }); 52 | }); 53 | 54 | executeScenarios(broker, ["echo"]); 55 | -------------------------------------------------------------------------------- /test/e2e/scenarios/compression/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Skipping this test because we will solve this issues later" 4 | exit 0 5 | 6 | echo "Start compression scenario..."; 7 | echo "Transporter: $TRANSPORTER"; 8 | echo "Serializer: $SERIALIZER"; 9 | echo "Discoverer: $DISCOVERER"; 10 | 11 | export NAMESPACE=compression; 12 | node node1.js & \ 13 | node scenario.js 14 | -------------------------------------------------------------------------------- /test/e2e/scenarios/dependencies/scenario.js: -------------------------------------------------------------------------------- 1 | const { assert, createNode, executeScenarios, addScenario } = require("../../utils"); 2 | 3 | const FLOW = []; 4 | 5 | /////////////// NODE - 1 /////////////// 6 | const broker1 = createNode("node-1"); 7 | const locationSchema = { 8 | name: "location", 9 | 10 | // depends on device.service at node-2 11 | dependencies: ["device"], 12 | 13 | async started() { 14 | FLOW.push("location.service started"); 15 | } 16 | }; 17 | 18 | const tenantSchema = { 19 | name: "tenant", 20 | 21 | actions: { 22 | add: { 23 | handler(ctx) { 24 | FLOW.push("tenant.add action called"); 25 | return "tenant.add action"; 26 | } 27 | } 28 | }, 29 | 30 | async started() { 31 | FLOW.push("tenant.service started"); 32 | } 33 | }; 34 | 35 | broker1.createService(locationSchema); 36 | broker1.createService(tenantSchema); 37 | 38 | addScenario("Test service dependency start flow", async () => { 39 | assert(FLOW, [ 40 | "tenant.service started", 41 | "device.service started", 42 | "tenant.add action called", 43 | "response from tenant: tenant.add action", 44 | "location.service started" 45 | ]); 46 | }); 47 | 48 | /////////////// NODE - 2 /////////////// 49 | const broker2 = createNode("node-2"); 50 | const assetSchema = { 51 | name: "device", 52 | 53 | // Depends on tenant.service located at node-1 54 | dependencies: ["tenant"], 55 | 56 | async started() { 57 | FLOW.push("device.service started"); 58 | 59 | const result = await this.broker.call("tenant.add"); 60 | 61 | FLOW.push("response from tenant: " + result); 62 | } 63 | }; 64 | broker2.createService(assetSchema); 65 | broker2.start(); 66 | 67 | executeScenarios(broker1); 68 | -------------------------------------------------------------------------------- /test/e2e/scenarios/dependencies/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Start Service Dependency scenario..."; 4 | echo "Transporter: $TRANSPORTER"; 5 | echo "Discoverer: $DISCOVERER"; 6 | 7 | export NAMESPACE=depedency; 8 | node scenario.js 9 | -------------------------------------------------------------------------------- /test/e2e/services/aes.service.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const crypto = require("crypto"); 3 | 4 | const iv = Buffer.from(crypto.randomBytes(16)); 5 | const password = Buffer.from(crypto.randomBytes(32)); 6 | 7 | module.exports = { 8 | name: "aes", 9 | actions: { 10 | encrypt(ctx) { 11 | const encrypter = crypto.createCipheriv("aes-256-ctr", password, iv); 12 | return ctx.params.pipe(encrypter); 13 | }, 14 | 15 | decrypt(ctx) { 16 | const decrypter = crypto.createDecipheriv("aes-256-ctr", password, iv); 17 | return ctx.params.pipe(decrypter); 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/e2e/services/helper.service.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "$helper", 3 | 4 | events: { 5 | async $shutdown(ctx) { 6 | this.logger.info("Shutting down node...", ctx.params); 7 | await Promise.delay(1000); 8 | process.exit(ctx.params.error ? 2 : 0); 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /test/e2e/services/scenario.service.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "$scenario", 3 | 4 | created() { 5 | this.CALLED_ACTIONS = []; 6 | this.EMITTED_EVENTS = []; 7 | }, 8 | 9 | actions: { 10 | clear(ctx) { 11 | this.CALLED_ACTIONS.length = 0; 12 | this.EMITTED_EVENTS.length = 0; 13 | }, 14 | 15 | getCalledActions(ctx) { 16 | return this.CALLED_ACTIONS; 17 | }, 18 | 19 | getEmittedEvents(ctx) { 20 | return this.EMITTED_EVENTS; 21 | } 22 | }, 23 | 24 | events: { 25 | "$scenario.action.called"(ctx) { 26 | this.CALLED_ACTIONS.push(ctx.params); 27 | }, 28 | 29 | "$scenario.event.emitted"(ctx) { 30 | this.EMITTED_EVENTS.push(ctx.params); 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/e2e/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for dir in scenarios/*/ ; do 6 | pushd $dir; 7 | ./start.sh; 8 | popd; 9 | done 10 | -------------------------------------------------------------------------------- /test/esm/greeter.service.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "greeter", 3 | 4 | actions: { 5 | hello: { 6 | async handler() { 7 | return "Hello Moleculer"; 8 | } 9 | }, 10 | 11 | welcome: { 12 | params: { 13 | name: "string|no-empty" 14 | }, 15 | async handler(ctx) { 16 | const name = await ctx.call("helper.uppercase", ctx.params.name); 17 | return `Welcome, ${name}!`; 18 | } 19 | } 20 | }, 21 | 22 | started() { 23 | if (this.broker.namespace != "esm") { 24 | this.broker.logger.error("Configuration is not loaded correctly!"); 25 | process.exit(1); 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /test/esm/helper.service.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "helper", 3 | actions: { 4 | uppercase: { 5 | handler(ctx) { 6 | return ctx.params.toUpperCase(); 7 | } 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /test/esm/moleculer.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | namespace: "esm", 3 | tracing: { 4 | enabled: true, 5 | exporter: [ 6 | { 7 | type: "Console", 8 | options: { 9 | colors: true 10 | } 11 | } 12 | ] 13 | }, 14 | 15 | async started(broker) { 16 | try { 17 | await broker.call("greeter.hello"); 18 | const res = await broker.call("greeter.welcome", { name: "esm" }); 19 | broker.logger.info(""); 20 | broker.logger.info("Result: ", res); 21 | broker.logger.info(""); 22 | if (res != "Welcome, ESM!") throw new Error("Result is mismatch!"); 23 | else await broker.stop(); 24 | } catch (err) { 25 | broker.logger.error(err); 26 | process.exit(1); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/integration/async-storage.spec.js: -------------------------------------------------------------------------------- 1 | const AsyncStorage = require("../../src/async-storage"); 2 | 3 | describe("Test Async Storage class", () => { 4 | it("should set broker & create store", () => { 5 | const broker = {}; 6 | const storage = new AsyncStorage(broker); 7 | 8 | expect(storage.broker).toBe(broker); 9 | expect(storage.store).toBeInstanceOf(Map); 10 | }); 11 | 12 | it("should store context for async thread", () => { 13 | const broker = {}; 14 | const storage = new AsyncStorage(broker); 15 | 16 | const context = { a: 5 }; 17 | 18 | return Promise.resolve() 19 | .then(() => { 20 | storage.setSessionData(context); 21 | expect(storage.getSessionData()).toBe(context); 22 | }) 23 | .then(() => { 24 | expect(storage.getSessionData()).toBe(context); 25 | }); 26 | /*.then(() => new Promise(resolve => setTimeout(resolve, 50))); 27 | .then(() => { 28 | // TODO: need fix 29 | expect(storage.getSessionData()).toBe(context); 30 | }); 31 | */ 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/integration/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const ServiceBroker = require("../../src/service-broker"); 5 | 6 | const H = { 7 | createNode(opts, services) { 8 | let node = new ServiceBroker(_.defaultsDeep(opts, { logger: false, transporter: "Fake" })); 9 | if (services) H.addServices(node, services); 10 | return node; 11 | }, 12 | 13 | addServices(broker, services) { 14 | services.forEach(service => broker.createService(_.cloneDeep(service))); 15 | }, 16 | 17 | removeServices(broker, serviceNames) { 18 | serviceNames.forEach(name => { 19 | let svc = broker.getLocalService(name); 20 | if (svc) broker.destroyService(svc); 21 | }); 22 | }, 23 | 24 | hasService(broker, fullName, nodeID) { 25 | return broker.registry.services.has(fullName, nodeID); 26 | }, 27 | 28 | hasAction(broker, name) { 29 | return broker.registry.actions.get(name) != null; 30 | }, 31 | 32 | isActionAvailable(broker, name) { 33 | return broker.registry.actions.isAvailable(name); 34 | }, 35 | 36 | getNode(broker, nodeID) { 37 | return broker.registry.nodes.get(nodeID); 38 | }, 39 | 40 | getActionNodes(broker, actionName) { 41 | const list = broker.registry.actions.get(actionName); 42 | if (list) return list.endpoints.map(ep => ep.id); 43 | 44 | /* istanbul ignore next */ 45 | return []; 46 | }, 47 | 48 | getEventNodes(broker, eventName) { 49 | return broker.registry.events.getAllEndpoints(eventName).map(node => node.id); 50 | } 51 | }; 52 | 53 | module.exports = H; 54 | -------------------------------------------------------------------------------- /test/integration/validator.spec.js: -------------------------------------------------------------------------------- 1 | const ServiceBroker = require("../../src/service-broker"); 2 | const { ValidationError } = require("../../src/errors"); 3 | 4 | describe("Test broker validator with actions", () => { 5 | let schema = { 6 | name: "test", 7 | actions: { 8 | withValidation: { 9 | params: { 10 | a: { type: "number" }, 11 | b: { type: "number" } 12 | }, 13 | handler: jest.fn(() => 123) 14 | }, 15 | withoutValidation: { 16 | handler() {} 17 | } 18 | } 19 | }; 20 | 21 | const broker = new ServiceBroker({ logger: false }); 22 | broker.validator.validate = jest.fn(); 23 | broker.createService(schema); 24 | 25 | beforeAll(() => broker.start()); 26 | afterAll(() => broker.stop()); 27 | 28 | it("shouldn't wrap validation, if action can't contain params settings", () => { 29 | return broker.call("test.withoutValidation").then(() => { 30 | expect(broker.validator.validate).toHaveBeenCalledTimes(0); 31 | }); 32 | }); 33 | 34 | it("should call handler, if params are correct", () => { 35 | schema.actions.withValidation.handler.mockClear(); 36 | let p = { a: 5, b: 10 }; 37 | return broker.call("test.withValidation", p).then(res => { 38 | expect(res).toBe(123); 39 | expect(schema.actions.withValidation.handler).toHaveBeenCalledTimes(1); 40 | }); 41 | }); 42 | 43 | it("should throw ValidationError, if params is not correct", () => { 44 | schema.actions.withValidation.handler.mockClear(); 45 | let p = { a: 5, b: "asd" }; 46 | return broker.call("test.withValidation", p).catch(err => { 47 | expect(err).toBeInstanceOf(ValidationError); 48 | expect(schema.actions.withValidation.handler).toHaveBeenCalledTimes(0); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/leak-detection/self-check.spc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const memwatch = require("@icebob/node-memwatch"); 4 | 5 | jest.setTimeout(3 * 60 * 1000); // 3mins 6 | 7 | describe("leak detector", function () { 8 | // let leakCB = jest.fn(); 9 | // memwatch.on("leak", leakCB); 10 | 11 | it("should detect a basic leak", done => { 12 | const hd = new memwatch.HeapDiff(); 13 | let iterations = 0; 14 | const leaks = []; 15 | const interval = setInterval(() => { 16 | if (iterations >= 10) { 17 | memwatch.gc(); 18 | const diff = hd.end(); 19 | console.log(diff); // eslint-disable-line no-console 20 | expect(diff.change.size_bytes).toBeGreaterThan(50 * 1024 * 1024); 21 | clearInterval(interval); 22 | return done(); 23 | } 24 | iterations++; 25 | for (let i = 0; i < 1000000; i++) { 26 | const str = "leaky string"; 27 | leaks.push(str); 28 | } 29 | }, 10); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/nats.config: -------------------------------------------------------------------------------- 1 | store_limits: { 2 | # Override some global limits 3 | max_channels: 1000 4 | } 5 | -------------------------------------------------------------------------------- /test/services/greeter.es6.service.js: -------------------------------------------------------------------------------- 1 | const Service = require("../../src/service"); 2 | 3 | class GreeterService extends Service { 4 | constructor(broker) { 5 | super(broker); 6 | 7 | this.parseServiceSchema({ 8 | name: "greeter", 9 | version: "v2", 10 | meta: { 11 | scalable: true 12 | }, 13 | // dependencies: [ 14 | // "auth", 15 | // "users" 16 | // ], 17 | 18 | settings: { 19 | upperCase: true 20 | }, 21 | actions: { 22 | hello: this.hello, 23 | welcome: { 24 | cache: { 25 | keys: ["name"] 26 | }, 27 | params: { 28 | name: "string" 29 | }, 30 | handler: this.welcome 31 | } 32 | }, 33 | events: { 34 | "user.created": this.userCreated 35 | }, 36 | created: this.serviceCreated, 37 | started: this.serviceStarted, 38 | stopped: this.serviceStopped 39 | }); 40 | } 41 | 42 | // Action handler 43 | hello() { 44 | return "Hello Moleculer"; 45 | } 46 | 47 | // Action handler 48 | welcome(ctx) { 49 | return this.sayWelcome(ctx.params.name); 50 | } 51 | 52 | // Private method 53 | sayWelcome(name) { 54 | this.logger.info("Say hello to", name); 55 | return `Welcome, ${this.settings.upperCase ? name.toUpperCase() : name}`; 56 | } 57 | 58 | // Event handler 59 | userCreated(user) { 60 | this.broker.call("mail.send", { user }); 61 | } 62 | 63 | serviceCreated() { 64 | this.logger.info("ES6 Service created."); 65 | } 66 | 67 | serviceStarted() { 68 | this.logger.info("ES6 Service started."); 69 | } 70 | 71 | serviceStopped() { 72 | this.logger.info("ES6 Service stopped."); 73 | } 74 | } 75 | 76 | module.exports = GreeterService; 77 | -------------------------------------------------------------------------------- /test/services/math.service.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "math", 3 | actions: { 4 | add(ctx) { 5 | return Number(ctx.params.a) + Number(ctx.params.b); 6 | }, 7 | 8 | sub(ctx) { 9 | return Number(ctx.params.a) - Number(ctx.params.b); 10 | }, 11 | 12 | mult: { 13 | params: { 14 | a: { type: "number" }, 15 | b: { type: "number" } 16 | }, 17 | handler(ctx) { 18 | return Number(ctx.params.a) * Number(ctx.params.b); 19 | } 20 | }, 21 | 22 | div(ctx) { 23 | let a = Number(ctx.params.a); 24 | let b = Number(ctx.params.b); 25 | if (b != 0) return a / b; 26 | else throw new Error("Divide by zero!"); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/services/users.service.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const fakerator = require("fakerator")(); 3 | const { ValidationError } = require("../../src/errors"); 4 | 5 | const { delay } = require("../../src/utils"); 6 | 7 | let users = fakerator.times(fakerator.entity.user, 10); 8 | 9 | _.each(users, (user, i) => (user.id = i + 1)); 10 | let c = 0; 11 | 12 | module.exports = function (broker) { 13 | return new broker.ServiceFactory(broker, { 14 | name: "users", 15 | 16 | actions: { 17 | find: { 18 | cache: false, 19 | handler() { 20 | //this.logger.debug("Find users..."); 21 | return users; 22 | //return _.cloneDeep(users); 23 | } 24 | }, 25 | 26 | get: { 27 | cache: { 28 | keys: ["id"] 29 | }, 30 | handler(ctx) { 31 | //this.logger.debug("Get user...", ctx.params); 32 | return this.findByID(ctx.params.id); 33 | } 34 | }, 35 | 36 | dangerous() { 37 | //return Promise.reject(new Error("Something went wrong!")); 38 | return Promise.reject(new ValidationError("Wrong params!")); 39 | }, 40 | 41 | delayed() { 42 | c++; 43 | return Promise.resolve() 44 | .then(delay(c < 3 ? 6000 : 1000)) 45 | .then(() => { 46 | return users; 47 | }); 48 | } 49 | }, 50 | 51 | methods: { 52 | findByID(id) { 53 | return _.cloneDeep(users.find(user => user.id == id)); 54 | } 55 | } 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /test/services/utils/util.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "test" 5 | }; 6 | -------------------------------------------------------------------------------- /test/typescript/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /test/typescript/hello-world/index.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as path from "path"; 4 | import { ServiceBroker } from "../../../"; 5 | 6 | const broker = new ServiceBroker({ 7 | logger: true, 8 | metrics: { 9 | enabled: true, 10 | }, 11 | tracing: { 12 | enabled: true, 13 | exporter: [ 14 | { 15 | type: "Console", 16 | options: { 17 | colors: true 18 | } 19 | } 20 | ] 21 | } 22 | }); 23 | 24 | broker.loadService(path.join(__dirname, "greeter.service.ts")); 25 | 26 | (async function() { 27 | try { 28 | await broker.start(); 29 | 30 | await broker.call("greeter.hello"); 31 | const res = await broker.call("greeter.welcome", { name: "Typescript" }); 32 | broker.logger.info(`Result: ${res}`); 33 | if (res != "Welcome, TYPESCRIPT") 34 | throw new Error("Result is mismatch!"); 35 | else 36 | await broker.stop(); 37 | 38 | } catch(err) { 39 | console.log(err); 40 | process.exit(1); 41 | } 42 | })(); 43 | -------------------------------------------------------------------------------- /test/typescript/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ "es2015" ], 5 | "sourceMap": false, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "isolatedModules": false, 9 | "experimentalDecorators": true, 10 | "emitDecoratorMetadata": true, 11 | "declaration": false, 12 | "noImplicitAny": false, 13 | "removeComments": true, 14 | "noLib": false, 15 | "strict": true, 16 | "strictFunctionTypes": false, 17 | "preserveConstEnums": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "outDir": "./out", 20 | "baseUrl": ".", 21 | "paths": { 22 | "*": [ 23 | "*" 24 | ] 25 | } 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "typings/main", 30 | "typings/main.d.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/typescript/tsd/ServiceActions.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from "tsd"; 2 | import { 3 | Service, 4 | ServiceBroker, 5 | ServiceAction, 6 | ServiceActions, 7 | ServiceSettingSchema, 8 | } from "../../../index"; 9 | 10 | const broker = new ServiceBroker({ logger: false, transporter: "fake" }); 11 | 12 | class TestService extends Service { 13 | constructor(broker: ServiceBroker) { 14 | super(broker); 15 | 16 | this.parseServiceSchema({ 17 | name: "test1", 18 | actions: { 19 | foo: { 20 | async handler() { 21 | expectType>(this); 22 | expectType(testService.actions); 23 | }, 24 | }, 25 | bar() { 26 | this.actions.foo(); // check `this` ref in `foo`, should not throw error; 27 | 28 | expectType>(this); 29 | expectType(testService.actions); 30 | }, 31 | }, 32 | }); 33 | } 34 | } 35 | 36 | const testService = new TestService(broker); 37 | 38 | expectType(testService.actions); 39 | expectType(testService.actions.foo); 40 | expectType(testService.actions.bar); 41 | -------------------------------------------------------------------------------- /test/unit/__factories/my-context-factory.js: -------------------------------------------------------------------------------- 1 | const Context = require("../../../src/context"); 2 | 3 | class MyContext extends Context { 4 | constructor(opts) { 5 | super(opts); 6 | this.myProp = "a"; 7 | } 8 | } 9 | 10 | module.exports = MyContext; 11 | -------------------------------------------------------------------------------- /test/unit/__factories/my-service-factory.js: -------------------------------------------------------------------------------- 1 | const Service = require("../../../src/service"); 2 | 3 | class MyService extends Service { 4 | constructor(broker, schema) { 5 | super(broker, schema); 6 | this.myProp = 123; 7 | } 8 | } 9 | 10 | module.exports = MyService; 11 | -------------------------------------------------------------------------------- /test/unit/__factories/my.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | name: "my" 5 | }; 6 | -------------------------------------------------------------------------------- /test/unit/cpu-usage.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const os = require("os"); 4 | jest.useFakeTimers(); 5 | 6 | const getCpuUsage = require("../../src/cpu-usage"); 7 | 8 | describe("getCpuUsage", () => { 9 | it("should report cpu usage", () => { 10 | os.cpus = jest 11 | .fn() 12 | .mockImplementationOnce(() => [ 13 | { 14 | times: { 15 | user: 1, 16 | nice: 2, 17 | sys: 3, 18 | idle: 4, 19 | irq: 5 20 | } 21 | } 22 | ]) 23 | .mockImplementationOnce(() => [ 24 | { 25 | times: { 26 | user: 2, 27 | nice: 3, 28 | sys: 4, 29 | idle: 5, 30 | irq: 6 31 | } 32 | } 33 | ]) 34 | .mockImplementationOnce(() => [ 35 | { 36 | times: { 37 | user: 3, 38 | nice: 3, 39 | sys: 3, 40 | idle: 3, 41 | irq: 3 42 | } 43 | } 44 | ]); 45 | 46 | const result = getCpuUsage(100); 47 | jest.runAllTimers(); 48 | return expect(result).resolves.toEqual({ avg: 70, usages: [70] }); 49 | }); 50 | 51 | it("should return rejected promise on missing cpu data", () => { 52 | os.cpus = jest.fn().mockImplementationOnce(() => undefined); 53 | 54 | const result = getCpuUsage(100); 55 | jest.runAllTimers(); 56 | return expect(result).rejects.toBeInstanceOf(Error); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/unit/health.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const H = require("../../src/health"); 4 | const ServiceBroker = require("../../src/service-broker"); 5 | 6 | describe("Test health status methods", () => { 7 | const broker = new ServiceBroker({ logger: false, transporter: "fake" }); 8 | 9 | beforeAll(() => broker.start()); 10 | afterAll(() => broker.stop()); 11 | 12 | it("should return health status", () => { 13 | let res = H.getHealthStatus(broker); 14 | 15 | expect(res).toBeDefined(); 16 | expect(res.cpu).toBeDefined(); 17 | expect(res.cpu.load1).toBeDefined(); 18 | expect(res.cpu.load5).toBeDefined(); 19 | expect(res.cpu.load15).toBeDefined(); 20 | expect(res.cpu.cores).toBeDefined(); 21 | expect(res.cpu.utilization).toBeDefined(); 22 | 23 | expect(res.mem).toBeDefined(); 24 | expect(res.mem.free).toBeDefined(); 25 | expect(res.mem.total).toBeDefined(); 26 | expect(res.mem.percent).toBeDefined(); 27 | 28 | expect(res.os).toBeDefined(); 29 | expect(res.os.uptime).toBeDefined(); 30 | expect(res.os.type).toBeDefined(); 31 | expect(res.os.release).toBeDefined(); 32 | expect(res.os.hostname).toBeDefined(); 33 | expect(res.os.arch).toBeDefined(); 34 | expect(res.os.platform).toBeDefined(); 35 | expect(res.os.user).toBeDefined(); 36 | expect(res.net).toBeDefined(); 37 | expect(res.net.ip).toBeDefined(); 38 | expect(res.client).toBeDefined(); 39 | expect(res.process).toBeDefined(); 40 | expect(res.process.pid).toBeDefined(); 41 | expect(res.process.memory).toBeDefined(); 42 | expect(res.process.uptime).toBeDefined(); 43 | expect(res.process.argv).toBeDefined(); 44 | expect(res.time).toBeDefined(); 45 | expect(res.time.now).toBeDefined(); 46 | expect(res.time.iso).toBeDefined(); 47 | expect(res.time.utc).toBeDefined(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/lock.spec.js: -------------------------------------------------------------------------------- 1 | const Lock = require("../../src/lock"); 2 | 3 | describe("Test lock", () => { 4 | let key = "abc123"; 5 | it("should lock", () => { 6 | const lock = new Lock(); 7 | 8 | return lock.acquire(key).then(() => { 9 | expect(lock.isLocked(key)).toBeTruthy(); 10 | return lock.release(key).then(() => { 11 | expect(lock.isLocked(key)).toBeFalsy(); 12 | }); 13 | }); 14 | }); 15 | 16 | it("2 locks", () => { 17 | const lock = new Lock(); 18 | let released = 0; 19 | return Promise.all([ 20 | lock.acquire(key).then(() => { 21 | expect(lock.isLocked(key)).toBeTruthy(); 22 | return new Promise(function (resolve) { 23 | setTimeout(() => { 24 | released = 1; 25 | lock.release(key).then(() => { 26 | expect(lock.isLocked(key)).toBeFalsy(); 27 | resolve(); 28 | }); 29 | }, 200); 30 | }); 31 | }), 32 | lock.acquire(key).then(() => { 33 | expect(lock.isLocked(key)).toBeTruthy(); 34 | expect(released).toBe(1); 35 | return lock.release(key).then(() => { 36 | expect(lock.isLocked(key)).toBeFalsy(); 37 | }); 38 | }) 39 | ]); 40 | }); 41 | 42 | it("should lock the concurrency call", () => { 43 | let taskes = [1, 2, 3, 4]; 44 | let globalValue = 0; 45 | const lock = new Lock(); 46 | return Promise.all( 47 | taskes.map(task => { 48 | return lock.acquire(key).then(() => { 49 | globalValue = task; 50 | return new Promise(function (resolve) { 51 | setTimeout(() => { 52 | expect(globalValue).toEqual(task); 53 | lock.release(key).then(() => { 54 | resolve(); 55 | }); 56 | }, Math.random() * 500); 57 | }); 58 | }); 59 | }) 60 | ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/metrics/__snapshots__/registry.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Metric Registry Test Constructor should create with custom options 1`] = ` 4 | Object { 5 | "collectInterval": 5, 6 | "collectProcessMetrics": false, 7 | "defaultAgeBuckets": 10, 8 | "defaultAggregator": "sum", 9 | "defaultBuckets": Array [ 10 | 1, 11 | 2, 12 | 3, 13 | 4, 14 | 5, 15 | 100, 16 | 250, 17 | 500, 18 | 1000, 19 | 2500, 20 | 5000, 21 | 10000, 22 | ], 23 | "defaultMaxAgeSeconds": 60, 24 | "defaultQuantiles": Array [ 25 | 0.1, 26 | 0.5, 27 | 0.9, 28 | 0.99, 29 | 0.999, 30 | ], 31 | "enabled": false, 32 | "reporter": "Prometheus", 33 | } 34 | `; 35 | 36 | exports[`Test Metric Registry Test Constructor should create with default options 1`] = ` 37 | Object { 38 | "collectInterval": 5, 39 | "collectProcessMetrics": false, 40 | "defaultAgeBuckets": 10, 41 | "defaultAggregator": "sum", 42 | "defaultBuckets": Array [ 43 | 1, 44 | 5, 45 | 10, 46 | 25, 47 | 50, 48 | 100, 49 | 250, 50 | 500, 51 | 1000, 52 | 2500, 53 | 5000, 54 | 10000, 55 | ], 56 | "defaultMaxAgeSeconds": 60, 57 | "defaultQuantiles": Array [ 58 | 0.5, 59 | 0.9, 60 | 0.95, 61 | 0.99, 62 | 0.999, 63 | ], 64 | "enabled": true, 65 | "reporter": false, 66 | } 67 | `; 68 | -------------------------------------------------------------------------------- /test/unit/metrics/types/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Base Metric class Test generateSnapshot method should generate a snapshot 1`] = ` 4 | Array [ 5 | Object { 6 | "key": "", 7 | "labels": Object {}, 8 | "timestamp": 1111, 9 | "value": 3, 10 | }, 11 | Object { 12 | "key": "1", 13 | "labels": Object { 14 | "a": 1, 15 | }, 16 | "timestamp": 2222, 17 | "value": 4, 18 | }, 19 | Object { 20 | "key": "5", 21 | "labels": Object { 22 | "a": 5, 23 | }, 24 | "timestamp": 3333, 25 | "value": 5, 26 | }, 27 | Object { 28 | "key": "\\"John\\"", 29 | "labels": Object { 30 | "a": "John", 31 | }, 32 | "timestamp": 4444, 33 | "value": 6, 34 | }, 35 | ] 36 | `; 37 | -------------------------------------------------------------------------------- /test/unit/metrics/types/__snapshots__/gauge.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Base Metric class Test generateSnapshot method should generate a snapshot 1`] = ` 4 | Array [ 5 | Object { 6 | "key": "", 7 | "labels": Object {}, 8 | "rate": 123, 9 | "timestamp": 1111, 10 | "value": 3, 11 | }, 12 | Object { 13 | "key": "1", 14 | "labels": Object { 15 | "a": 1, 16 | }, 17 | "rate": 123, 18 | "timestamp": 2222, 19 | "value": 4, 20 | }, 21 | Object { 22 | "key": "5", 23 | "labels": Object { 24 | "a": 5, 25 | }, 26 | "rate": 123, 27 | "timestamp": 3333, 28 | "value": 5, 29 | }, 30 | Object { 31 | "key": "\\"John\\"", 32 | "labels": Object { 33 | "a": "John", 34 | }, 35 | "rate": 123, 36 | "timestamp": 4444, 37 | "value": 6, 38 | }, 39 | ] 40 | `; 41 | -------------------------------------------------------------------------------- /test/unit/metrics/types/__snapshots__/histogram.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Base Metric class Test generateSnapshot & generateItemSnapshot methods should generate snapshot 1`] = ` 4 | Array [ 5 | Object { 6 | "buckets": Object { 7 | "1": 0, 8 | "10": 5, 9 | "2": 1, 10 | "20": 5, 11 | "5": 3, 12 | "8": 5, 13 | }, 14 | "count": 5, 15 | "key": "", 16 | "labels": Object {}, 17 | "lastValue": 5, 18 | "max": 8, 19 | "mean": 4.8, 20 | "min": 2, 21 | "quantiles": Object { 22 | "0.1": 2, 23 | "0.5": 5, 24 | "0.9": 8, 25 | }, 26 | "rate": 123, 27 | "stdDev": 2.3874672772626644, 28 | "sum": 24, 29 | "timestamp": 1558295472803, 30 | "variance": 5.7, 31 | }, 32 | Object { 33 | "buckets": Object { 34 | "1": 1, 35 | "10": 4, 36 | "2": 3, 37 | "20": 4, 38 | "5": 4, 39 | "8": 4, 40 | }, 41 | "count": 4, 42 | "key": "5", 43 | "labels": Object { 44 | "a": 5, 45 | }, 46 | "lastValue": 4, 47 | "max": 4, 48 | "mean": 2.25, 49 | "min": 1, 50 | "quantiles": Object { 51 | "0.1": 1, 52 | "0.5": 2, 53 | "0.9": 4, 54 | }, 55 | "rate": 123, 56 | "stdDev": 1.2583057392117916, 57 | "sum": 9, 58 | "timestamp": 1558295472823, 59 | "variance": 1.5833333333333333, 60 | }, 61 | ] 62 | `; 63 | 64 | exports[`Test TimeWindowQuantiles class should generate snapshot 1`] = ` 65 | Object { 66 | "max": 8, 67 | "mean": 3.8333333333333335, 68 | "min": 0, 69 | "quantiles": Object { 70 | "0.1": 0, 71 | "0.5": 2, 72 | "0.9": 8, 73 | }, 74 | "stdDev": 2.9944392908634274, 75 | "variance": 8.966666666666665, 76 | } 77 | `; 78 | -------------------------------------------------------------------------------- /test/unit/metrics/types/__snapshots__/info.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Base Metric class Test generateSnapshot method should generate a snapshot 1`] = ` 4 | Array [ 5 | Object { 6 | "key": "", 7 | "labels": Object {}, 8 | "timestamp": 1111, 9 | "value": "John", 10 | }, 11 | Object { 12 | "key": "1", 13 | "labels": Object { 14 | "a": 1, 15 | }, 16 | "timestamp": 2222, 17 | "value": "Jane", 18 | }, 19 | Object { 20 | "key": "5", 21 | "labels": Object { 22 | "a": 5, 23 | }, 24 | "timestamp": 3333, 25 | "value": "Adam", 26 | }, 27 | Object { 28 | "key": "true", 29 | "labels": Object { 30 | "a": true, 31 | }, 32 | "timestamp": 4444, 33 | "value": "Steve", 34 | }, 35 | ] 36 | `; 37 | -------------------------------------------------------------------------------- /test/unit/metrics/types/index.spec.js: -------------------------------------------------------------------------------- 1 | const { BrokerOptionsError } = require("../../../../src/errors"); 2 | const MetricTypes = require("../../../../src/metrics/types"); 3 | 4 | describe("Test MetricTypes resolver", () => { 5 | it("should throw error", () => { 6 | expect(() => MetricTypes.resolve()).toThrowError(BrokerOptionsError); 7 | expect(() => MetricTypes.resolve("xyz")).toThrowError(BrokerOptionsError); 8 | }); 9 | 10 | it("should resolve metric types by string", () => { 11 | expect(MetricTypes.resolve("counter")).toBe(MetricTypes.Counter); 12 | expect(MetricTypes.resolve("gauge")).toBe(MetricTypes.Gauge); 13 | expect(MetricTypes.resolve("histogram")).toBe(MetricTypes.Histogram); 14 | expect(MetricTypes.resolve("info")).toBe(MetricTypes.Info); 15 | }); 16 | }); 17 | 18 | describe("Test MetricTypes register", () => { 19 | class MyCustom {} 20 | 21 | it("should throw error if type if not correct", () => { 22 | expect(() => { 23 | MetricTypes.resolve("MyCustom"); 24 | }).toThrowError(BrokerOptionsError); 25 | }); 26 | 27 | it("should register new type", () => { 28 | MetricTypes.register("MyCustom", MyCustom); 29 | expect(MetricTypes.MyCustom).toBe(MyCustom); 30 | }); 31 | 32 | it("should find the new type", () => { 33 | const type = MetricTypes.resolve("MyCustom"); 34 | expect(type).toBe(MyCustom); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/packets.spec.js: -------------------------------------------------------------------------------- 1 | const P = require("../../src/packets"); 2 | 3 | describe("Test base Packet", () => { 4 | it("create Packet without type", () => { 5 | let packet = new P.Packet(); 6 | expect(packet).toBeDefined(); 7 | expect(packet.type).toBe(P.PACKET_UNKNOWN); 8 | expect(packet.target).toBeUndefined(); 9 | expect(packet.payload).toEqual({}); 10 | }); 11 | 12 | it("create Packet with type & target", () => { 13 | let packet = new P.Packet(P.PACKET_EVENT, "node-2"); 14 | expect(packet).toBeDefined(); 15 | expect(packet.type).toBe(P.PACKET_EVENT); 16 | expect(packet.target).toBe("node-2"); 17 | expect(packet.payload).toEqual({}); 18 | }); 19 | 20 | it("create Packet with type & target & payload", () => { 21 | let packet = new P.Packet(P.PACKET_EVENT, "node-2", { a: 5 }); 22 | expect(packet).toBeDefined(); 23 | expect(packet.type).toBe(P.PACKET_EVENT); 24 | expect(packet.target).toBe("node-2"); 25 | expect(packet.payload).toBeDefined(); 26 | expect(packet.payload.a).toBe(5); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/registry/endpoint-action.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let ActionEndpoint = require("../../../src/registry/endpoint-action"); 4 | let ServiceBroker = require("../../../src/service-broker"); 5 | 6 | describe("Test ActionEndpoint", () => { 7 | let broker = new ServiceBroker({ logger: false }); 8 | let registry = broker.registry; 9 | 10 | let node = { id: "server-1" }; 11 | let service = { name: "test" }; 12 | let action = { name: "test.hello" }; 13 | let ep; 14 | 15 | it("should set properties", () => { 16 | ep = new ActionEndpoint(registry, broker, node, service, action); 17 | 18 | expect(ep).toBeDefined(); 19 | expect(ep.registry).toBe(registry); 20 | expect(ep.broker).toBe(broker); 21 | expect(ep.node).toBe(node); 22 | expect(ep.service).toBe(service); 23 | expect(ep.action).toBe(action); 24 | 25 | expect(ep.isAvailable).toBe(true); 26 | }); 27 | 28 | it("shoud update action", () => { 29 | let newAction = { name: "test.hello2" }; 30 | 31 | ep.update(newAction); 32 | 33 | expect(ep.action).toBe(newAction); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/registry/endpoint-event.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let EventEndpoint = require("../../../src/registry/endpoint-event"); 4 | let ServiceBroker = require("../../../src/service-broker"); 5 | 6 | describe("Test EventEndpoint", () => { 7 | let broker = new ServiceBroker({ logger: false }); 8 | let registry = broker.registry; 9 | 10 | let node = { id: "server-1" }; 11 | let service = { name: "test" }; 12 | let event = { name: "test.hello" }; 13 | let ep; 14 | 15 | it("should set properties", () => { 16 | ep = new EventEndpoint(registry, broker, node, service, event); 17 | 18 | expect(ep).toBeDefined(); 19 | expect(ep.registry).toBe(registry); 20 | expect(ep.broker).toBe(broker); 21 | expect(ep.node).toBe(node); 22 | expect(ep.service).toBe(service); 23 | expect(ep.event).toBe(event); 24 | 25 | expect(ep.isAvailable).toBe(true); 26 | }); 27 | 28 | it("shoud update event", () => { 29 | let newEvent = { name: "test.hello2" }; 30 | 31 | ep.update(newEvent); 32 | 33 | expect(ep.event).toBe(newEvent); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/registry/endpoint.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let Endpoint = require("../../../src/registry/endpoint"); 4 | let ServiceBroker = require("../../../src/service-broker"); 5 | 6 | describe("Test Endpoint", () => { 7 | let broker = new ServiceBroker({ logger: false }); 8 | let registry = broker.registry; 9 | 10 | let node = { id: "server-1" }; 11 | let ep; 12 | 13 | it("should set properties", () => { 14 | ep = new Endpoint(registry, broker, node); 15 | 16 | expect(ep).toBeDefined(); 17 | expect(ep.registry).toBe(registry); 18 | expect(ep.broker).toBe(broker); 19 | expect(ep.id).toBe(node.id); 20 | expect(ep.node).toBe(node); 21 | expect(ep.local).toBe(false); 22 | expect(ep.state).toBe(true); 23 | 24 | expect(ep.isAvailable).toBe(true); 25 | }); 26 | 27 | it("shoud unAvailable", () => { 28 | ep.state = false; 29 | expect(ep.isAvailable).toBe(false); 30 | }); 31 | 32 | it("should create local ep", () => { 33 | let newNode = { id: broker.nodeID }; 34 | let ep = new Endpoint(registry, broker, newNode); 35 | 36 | expect(ep).toBeDefined(); 37 | expect(ep.registry).toBe(registry); 38 | expect(ep.broker).toBe(broker); 39 | expect(ep.id).toBe(newNode.id); 40 | expect(ep.node).toBe(newNode); 41 | expect(ep.local).toBe(true); 42 | expect(ep.state).toBe(true); 43 | 44 | expect(ep.isAvailable).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/serializers/index.spec.js: -------------------------------------------------------------------------------- 1 | const { BrokerOptionsError } = require("../../../src/errors"); 2 | const Serializers = require("../../../src/serializers"); 3 | 4 | describe("Test Serializers resolver", () => { 5 | it("should resolve null from undefined", () => { 6 | let serializer = Serializers.resolve(); 7 | expect(serializer).toBeInstanceOf(Serializers.JSON); 8 | }); 9 | 10 | it("should resolve JSONSerializer from obj without type", () => { 11 | let serializer = Serializers.resolve({}); 12 | expect(serializer).toBeInstanceOf(Serializers.JSON); 13 | }); 14 | 15 | it("should resolve JSONSerializer from obj", () => { 16 | let serializer = Serializers.resolve({ type: "JSON" }); 17 | expect(serializer).toBeInstanceOf(Serializers.JSON); 18 | }); 19 | 20 | it("should resolve AvroSerializer from string with Avro type", () => { 21 | let serializer = Serializers.resolve("avro"); 22 | expect(serializer).toBeInstanceOf(Serializers.Avro); 23 | }); 24 | 25 | it("should throw error if type if not correct", () => { 26 | expect(() => { 27 | Serializers.resolve("xyz"); 28 | }).toThrowError(BrokerOptionsError); 29 | 30 | expect(() => { 31 | Serializers.resolve({ type: "xyz" }); 32 | }).toThrowError(BrokerOptionsError); 33 | }); 34 | }); 35 | 36 | describe("Test Serializers register", () => { 37 | class MyCustom {} 38 | 39 | it("should throw error if type if not correct", () => { 40 | expect(() => { 41 | Serializers.resolve("MyCustom"); 42 | }).toThrowError(BrokerOptionsError); 43 | }); 44 | 45 | it("should register new type", () => { 46 | Serializers.register("MyCustom", MyCustom); 47 | expect(Serializers.MyCustom).toBe(MyCustom); 48 | }); 49 | 50 | it("should find the new type", () => { 51 | const serializer = Serializers.resolve("MyCustom"); 52 | expect(serializer).toBeInstanceOf(MyCustom); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/serializers/json.spec.js: -------------------------------------------------------------------------------- 1 | const { cloneDeep } = require("lodash"); 2 | const P = require("../../../src/packets"); 3 | const JSONSerializer = require("../../../src/serializers/json"); 4 | 5 | describe("Test JSONSerializer constructor", () => { 6 | it("should create an empty options", () => { 7 | let serializer = new JSONSerializer(); 8 | expect(serializer).toBeDefined(); 9 | expect(serializer.serialize).toBeDefined(); 10 | expect(serializer.deserialize).toBeDefined(); 11 | }); 12 | }); 13 | 14 | describe("Test JSONSerializer", () => { 15 | let serializer = new JSONSerializer(); 16 | serializer.init(); 17 | 18 | it("should serialize the event packet", () => { 19 | const obj = { 20 | ver: "4", 21 | sender: "node-100", 22 | id: "8b3c7371-7f0a-4aa2-b734-70ede29e1bbb", 23 | event: "user.created", 24 | data: { 25 | a: 5, 26 | b: "Test" 27 | }, 28 | broadcast: true, 29 | meta: {}, 30 | level: 1, 31 | needAck: false 32 | }; 33 | const s = serializer.serialize(cloneDeep(obj), P.PACKET_EVENT); 34 | expect(s.length).toBe(177); 35 | 36 | const res = serializer.deserialize(s, P.PACKET_EVENT); 37 | expect(res).not.toBe(obj); 38 | expect(res).toEqual(obj); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/strategies/base.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const BaseStrategy = require("../../../src/strategies/base"); 4 | const ServiceBroker = require("../../../src/service-broker"); 5 | 6 | describe("Test BaseStrategy", () => { 7 | const broker = new ServiceBroker({ logger: false }); 8 | 9 | it("should load local variables", () => { 10 | const strategy = new BaseStrategy(broker.registry, broker); 11 | 12 | expect(strategy.registry).toBe(broker.registry); 13 | expect(strategy.broker).toBe(broker); 14 | expect(strategy.opts).toEqual({}); 15 | 16 | expect(strategy.select).toBeInstanceOf(Function); 17 | }); 18 | 19 | it("should load with options", () => { 20 | const opts = { a: 5 }; 21 | const strategy = new BaseStrategy(broker.registry, broker, opts); 22 | 23 | expect(strategy.registry).toBe(broker.registry); 24 | expect(strategy.broker).toBe(broker); 25 | expect(strategy.opts).toBe(opts); 26 | 27 | expect(strategy.select).toBeInstanceOf(Function); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/strategies/random.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const RandomStrategy = require("../../../src/strategies/random"); 4 | const { extendExpect } = require("../utils"); 5 | 6 | extendExpect(expect); 7 | 8 | describe("Test RandomStrategy", () => { 9 | it("test with empty opts", () => { 10 | const strategy = new RandomStrategy(); 11 | 12 | const list = [{ a: "hello" }, { b: "world" }]; 13 | 14 | expect(strategy.select(list)).toBeAnyOf(list); 15 | expect(strategy.select(list)).toBeAnyOf(list); 16 | expect(strategy.select(list)).toBeAnyOf(list); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/strategies/round-robin.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const RoundRobinStrategy = require("../../../src/strategies/round-robin"); 4 | 5 | describe("Test RoundRobinStrategy", () => { 6 | it("get endpoint in order", () => { 7 | let strategy = new RoundRobinStrategy(); 8 | expect(strategy.counter).toBe(0); 9 | 10 | const list = [{ a: "hello" }, { b: "world" }]; 11 | 12 | let value = strategy.select(list); 13 | expect(strategy.counter).toBe(1); 14 | expect(value).toBe(list[0]); 15 | 16 | value = strategy.select(list); 17 | expect(strategy.counter).toBe(2); 18 | expect(value).toBe(list[1]); 19 | 20 | value = strategy.select(list); 21 | expect(strategy.counter).toBe(1); 22 | expect(value).toBe(list[0]); 23 | 24 | value = strategy.select(list); 25 | expect(strategy.counter).toBe(2); 26 | expect(value).toBe(list[1]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/tracing/exporters/__snapshots__/event-legacy.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test Event Legacy tracing exporter class Test generateMetricPayload should convert errored span to legacy payload 1`] = ` 4 | Object { 5 | "action": Object { 6 | "name": "posts.find", 7 | "rawName": "find", 8 | }, 9 | "callerNodeID": "other-node", 10 | "duration": 50, 11 | "endTime": 1050, 12 | "error": Object { 13 | "code": 512, 14 | "message": "Something happened", 15 | "name": "MoleculerRetryableError", 16 | }, 17 | "fromCache": true, 18 | "id": "span-id-123", 19 | "level": 5, 20 | "nodeID": "node-123", 21 | "parent": "parent-id-123", 22 | "remoteCall": true, 23 | "requestID": "trace-id-123", 24 | "service": Object { 25 | "fullName": "v1.posts", 26 | "name": "posts", 27 | "version": 1, 28 | }, 29 | "startTime": 1000, 30 | } 31 | `; 32 | 33 | exports[`Test Event Legacy tracing exporter class Test generateMetricPayload should convert normal span to legacy payload 1`] = ` 34 | Object { 35 | "action": Object { 36 | "name": "posts.find", 37 | "rawName": "find", 38 | }, 39 | "callerNodeID": "other-node", 40 | "duration": 50, 41 | "endTime": 1050, 42 | "fromCache": true, 43 | "id": "span-id-123", 44 | "level": 5, 45 | "nodeID": "node-123", 46 | "parent": "parent-id-123", 47 | "remoteCall": true, 48 | "requestID": "trace-id-123", 49 | "service": Object { 50 | "fullName": "v1.posts", 51 | "name": "posts", 52 | "version": 1, 53 | }, 54 | "startTime": 1000, 55 | } 56 | `; 57 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable no-console */ 2 | "use strict"; 3 | 4 | module.exports = { 5 | protectReject(err) { 6 | if (err && err.stack) { 7 | console.error(err); 8 | console.error(err.stack); 9 | } 10 | expect(err).toBe(true); 11 | }, 12 | 13 | extendExpect(expect) { 14 | expect.extend({ 15 | toBeAnyOf(received, expected) { 16 | let pass = false; 17 | for (const item of expected) { 18 | if (received === item) { 19 | pass = true; 20 | break; 21 | } 22 | } 23 | 24 | let list = expected.map(item => item.toString()).join(", "); 25 | let message = `Expected ${received.toString()} to be any of [${list}]`; 26 | 27 | return { 28 | actual: received, 29 | message: () => message, 30 | pass 31 | }; 32 | } 33 | }); 34 | } 35 | }; 36 | --------------------------------------------------------------------------------