@weave-js/core 0.9.4 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/lib/ExtendableError.js +33 -0
  2. package/lib/broker/context.js +25 -13
  3. package/lib/broker/{default-options.js → defaultOptions.js} +25 -6
  4. package/lib/broker/index.js +224 -194
  5. package/lib/buildRuntime.js +70 -0
  6. package/lib/cache/adapters/base.js +143 -0
  7. package/lib/cache/adapters/inMemory.js +154 -0
  8. package/lib/cache/adapters/inMemoryLru.js +140 -0
  9. package/lib/cache/adapters/index.js +46 -0
  10. package/lib/cache/base.js +49 -44
  11. package/lib/cache/lock.js +21 -4
  12. package/lib/constants.js +8 -5
  13. package/lib/errorHandler.js +19 -0
  14. package/lib/errors.js +17 -10
  15. package/lib/helper/{define-action.js → defineAction.js} +3 -1
  16. package/lib/helper/{define-broker-options.js → defineBrokerOptions.js} +2 -0
  17. package/lib/helper/{define-service.js → defineService.js} +4 -1
  18. package/lib/index.js +17 -92
  19. package/lib/logger/format/asHumanReadable.js +4 -3
  20. package/lib/logger/index.js +7 -1
  21. package/lib/logger/levels.js +8 -7
  22. package/lib/logger/tools.js +4 -0
  23. package/lib/logger/utils/format.js +18 -4
  24. package/lib/metrics/constants.js +8 -6
  25. package/lib/metrics/types/gauge.js +1 -1
  26. package/lib/middlewares/action-hooks/index.js +62 -14
  27. package/lib/middlewares/bulkhead/index.js +5 -4
  28. package/lib/middlewares/cache/index.js +106 -0
  29. package/lib/middlewares/circuit-breaker/index.js +41 -16
  30. package/lib/middlewares/context-tracker/index.js +3 -3
  31. package/lib/middlewares/error-handler/index.js +3 -3
  32. package/lib/middlewares/index.js +2 -1
  33. package/lib/middlewares/metrics/getMiddlewareWrapper.js +2 -2
  34. package/lib/middlewares/metrics/index.js +2 -2
  35. package/lib/middlewares/retry/index.js +11 -10
  36. package/lib/middlewares/timeout/index.js +5 -5
  37. package/lib/middlewares/tracing/index.js +27 -14
  38. package/lib/middlewares/tracing/tags.js +43 -0
  39. package/lib/middlewares/validator/index.js +6 -6
  40. package/lib/registry/{action-endpoint.js → actionEndpoint.js} +0 -0
  41. package/lib/registry/collections/{action-collection.js → actionCollection.js} +2 -2
  42. package/lib/registry/collections/{endpoint-collection.js → endpointCollection.js} +19 -3
  43. package/lib/registry/collections/{event-collection.js → eventCollection.js} +15 -9
  44. package/lib/registry/collections/{node-collection.js → nodeCollection.js} +1 -1
  45. package/lib/registry/collections/{service-collection.js → serviceCollection.js} +2 -2
  46. package/lib/registry/{event-endpoint.js → eventEndpoint.js} +1 -1
  47. package/lib/registry/node.js +1 -1
  48. package/lib/registry/registry.js +19 -38
  49. package/lib/registry/service/service.js +2 -2
  50. package/lib/registry/{service-item.js → serviceItem.js} +1 -1
  51. package/lib/runtime/{init-action-invoker.js → initActionInvoker.js} +3 -3
  52. package/lib/runtime/initCache.js +19 -0
  53. package/lib/runtime/{init-context-factory.js → initContextFactory.js} +2 -2
  54. package/lib/runtime/{init-eventbus.js → initEventbus.js} +11 -10
  55. package/lib/runtime/{init-logger.js → initLogger.js} +0 -0
  56. package/lib/runtime/{init-metrics.js → initMetrics.js} +11 -10
  57. package/lib/runtime/{init-middleware-manager.js → initMiddlewareManager.js} +1 -1
  58. package/lib/runtime/{init-registry.js → initRegistry.js} +1 -1
  59. package/lib/runtime/{init-service-manager.js → initServiceManager.js} +0 -0
  60. package/lib/runtime/{init-tracing.js → initTracing.js} +2 -1
  61. package/lib/runtime/{init-transport.js → initTransport.js} +0 -0
  62. package/lib/runtime/{init-uuid-factory.js → initUuidFactory.js} +0 -0
  63. package/lib/runtime/{init-validator.js → initValidator.js} +1 -1
  64. package/lib/tracing/collectors/base.js +11 -19
  65. package/lib/tracing/collectors/event.js +2 -2
  66. package/lib/tracing/collectors/index.js +2 -2
  67. package/lib/tracing/span.js +8 -6
  68. package/lib/transport/InboundTransformStream.js +75 -0
  69. package/lib/transport/adapters/{adapter-base.js → adapteBase.js} +16 -1
  70. package/lib/transport/adapters/adapters.js +1 -1
  71. package/lib/transport/adapters/dummy/index.js +3 -3
  72. package/lib/transport/adapters/index.js +1 -1
  73. package/lib/transport/adapters/tcp/discovery/index.js +45 -31
  74. package/lib/transport/adapters/tcp/index.js +20 -13
  75. package/lib/transport/adapters/tcp/tcpReader.js +1 -0
  76. package/lib/transport/adapters/tcp/tcpWriteStream.js +1 -1
  77. package/lib/transport/adapters/tcp/tcpWriter.js +2 -2
  78. package/lib/transport/createMessage.js +11 -0
  79. package/lib/transport/createTransport.js +215 -172
  80. package/lib/transport/errorPayloadFactory.js +18 -0
  81. package/lib/transport/{message-handlers.js → messageHandlers.js} +92 -23
  82. package/lib/transport/messageTypes.js +23 -0
  83. package/lib/types.js +86 -75
  84. package/lib/utils/index.js +1 -1
  85. package/lib/utils/options.js +4 -2
  86. package/lib/utils/{restore-error.js → restoreError.js} +1 -1
  87. package/package.json +6 -12
  88. package/CHANGELOG.md +0 -1317
  89. package/lib/cache/index.js +0 -48
  90. package/lib/cache/memory.js +0 -91
  91. package/lib/middlewares/tracing/buildTags.js +0 -17
  92. package/lib/new-errors.js +0 -25
  93. package/lib/runtime/init-cache.js +0 -10
  94. package/lib/tracing/collectors/console.js +0 -34
  95. package/lib/tracing/index.js +0 -3
  96. package/lib/tracing/tracer.js +0 -64
  97. package/lib/transport/message-types.js +0 -59
  98. package/lib/transport/message.js +0 -16
@@ -0,0 +1,33 @@
1
+ class ExtendableError extends Error {
2
+ constructor (message = '') {
3
+ super(message)
4
+
5
+ Object.defineProperty(this, 'name', {
6
+ configurable: true,
7
+ enumerable: false,
8
+ value: this.constructor.name,
9
+ writable: true
10
+ })
11
+
12
+ Object.defineProperty(this, 'message', {
13
+ configurable: true,
14
+ enumerable: false,
15
+ value: message,
16
+ writable: true
17
+ })
18
+
19
+ if (Object.prototype.hasOwnProperty.call(Error, 'captureStackTrace')) {
20
+ Error.captureStackTrace(this, this.constructor)
21
+ return
22
+ }
23
+
24
+ Object.defineProperty(this, 'stack', {
25
+ configurable: true,
26
+ enumerable: false,
27
+ value: new Error(message).stack,
28
+ writable: true
29
+ })
30
+ }
31
+ }
32
+
33
+ module.exports = { ExtendableError }
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Author: Kevin Ries (kevin@fachw3rk.de)
3
3
  * -----
4
- * Copyright 2020 Fachwerk
4
+ * Copyright 2021 Fachwerk
5
5
  */
6
6
  'use strict'
7
7
 
@@ -9,6 +9,7 @@
9
9
  * @typedef {import('../types').Context} Context
10
10
  * @typedef {import('../types').Runtime} Runtime
11
11
  * @typedef {import('../types').ContextPromise} ContextPromise
12
+ * @typedef {import('../types').Endpoint} Endpoint
12
13
  */
13
14
 
14
15
  const { uuid, isFunction, isStream, isStreamObjectMode } = require('@weave-js/utils')
@@ -85,6 +86,15 @@ exports.createContext = (runtime) => {
85
86
  })
86
87
  },
87
88
  startSpan (name, options) {
89
+ options = Object.assign({
90
+ id: this.id,
91
+ traceId: this.requestId,
92
+ parentId: this.parentId,
93
+ type: 'action',
94
+ service: this.service,
95
+ sampled: this.tracing
96
+ }, options)
97
+
88
98
  if (this.span) {
89
99
  this.span = this.span.startChildSpan(name, options)
90
100
  } else {
@@ -103,20 +113,22 @@ exports.createContext = (runtime) => {
103
113
  * @returns {Context} New copied context
104
114
  */
105
115
  copy () {
106
- const newContext = exports.createContext(runtime)
116
+ const contextCopy = exports.createContext(runtime)
107
117
 
108
- newContext.nodeId = this.nodeId
109
- newContext.options = this.options
110
- newContext.data = this.data
111
- newContext.meta = this.meta
112
- newContext.parentContext = this.parentContext
113
- newContext.callerNodeId = this.callerNodeId
114
- newContext.level = this.level
115
- newContext.eventName = this.eventName
116
- newContext.eventType = this.eventType
117
- newContext.eventGroups = this.eventGroups
118
+ contextCopy.nodeId = this.nodeId
119
+ contextCopy.options = this.options
120
+ contextCopy.data = this.data
121
+ contextCopy.meta = this.meta
122
+ contextCopy.parentContext = this.parentContext
123
+ contextCopy.callerNodeId = this.callerNodeId
124
+ contextCopy.requestId = this.requestId
125
+ contextCopy.tracing = this.tracing
126
+ contextCopy.level = this.level
127
+ contextCopy.eventName = this.eventName
128
+ contextCopy.eventType = this.eventType
129
+ contextCopy.eventGroups = this.eventGroups
118
130
 
119
- return newContext
131
+ return contextCopy
120
132
  }
121
133
  }
122
134
 
@@ -5,11 +5,12 @@
5
5
  /*
6
6
  * Author: Kevin Ries (kevin@fachw3rk.de)
7
7
  * -----
8
- * Copyright 2020 Fachwerk
8
+ * Copyright 2021 Fachwerk
9
9
  */
10
10
 
11
11
  /** @module weave */
12
12
  const os = require('os')
13
+ const { createInMemoryCache } = require('../cache/adapters/inMemory.js')
13
14
  const { loadBalancingStrategy } = require('../constants')
14
15
 
15
16
  /**
@@ -28,8 +29,11 @@ exports.getDefaultOptions = () => {
28
29
  },
29
30
  cache: {
30
31
  enabled: false,
31
- adapter: 'memory',
32
- ttl: 3000
32
+ adapter: createInMemoryCache(),
33
+ ttl: 3000,
34
+ lock: {
35
+ enabled: false
36
+ }
33
37
  },
34
38
  circuitBreaker: {
35
39
  enabled: false,
@@ -49,7 +53,10 @@ exports.getDefaultOptions = () => {
49
53
  heartbeatTimeout: 10 * 1000,
50
54
  offlineNodeCheckInterval: 30 * 1000,
51
55
  maxOfflineTime: 1000 * 60 * 10,
52
- maxChunkSize: 256 * 1024
56
+ maxChunkSize: 256 * 1024,
57
+ streams: {
58
+ handleBackpressure: true
59
+ }
53
60
  },
54
61
  errorHandler: null,
55
62
  loadInternalMiddlewares: true,
@@ -58,7 +65,7 @@ exports.getDefaultOptions = () => {
58
65
  collectCommonMetrics: true,
59
66
  collectInterval: 5000,
60
67
  adapters: [],
61
- defaultBuckets: [1, 5, 10, 20, 25, 30, 50, 100, 250, 500, 1000, 2500, 5000, 10000]
68
+ defaultBuckets: [1, 5, 10, 20, 25, 30, 40, 50, 100, 250, 500, 1000, 2500, 5000, 10000]
62
69
  },
63
70
  middlewares: null,
64
71
  logger: {
@@ -72,7 +79,19 @@ exports.getDefaultOptions = () => {
72
79
  tracing: {
73
80
  enabled: false,
74
81
  samplingRate: 1.0,
75
- collectors: []
82
+ collectors: [],
83
+ actions: {
84
+ data: false,
85
+ tags: {}
86
+ },
87
+ events: {
88
+ data: false,
89
+ tags: {}
90
+ },
91
+ errors: {
92
+ fields: ['name', 'message', 'code', 'type', 'data'],
93
+ stackTrace: false
94
+ }
76
95
  },
77
96
  namespace: '',
78
97
  registry: {
@@ -6,9 +6,9 @@
6
6
  */
7
7
 
8
8
  // node packages
9
+ const { isFunction } = require('@weave-js/utils')
9
10
  const path = require('path')
10
11
  const glob = require('glob')
11
- const { isFunction } = require('@weave-js/utils')
12
12
  const Middlewares = require('../middlewares')
13
13
 
14
14
  /**
@@ -29,238 +29,268 @@ exports.createBrokerInstance = (runtime) => {
29
29
  log,
30
30
  services,
31
31
  transport,
32
- metrics
32
+ metrics,
33
+ cache
33
34
  } = runtime
34
35
 
35
36
  // Log Messages
36
37
  log.info(`Initializing #weave node version ${version}`)
37
38
  log.info(`Node Id: ${options.nodeId}`)
38
39
 
40
+ // Output namespace
39
41
  if (options.namespace) {
40
42
  log.info(`Namespace: ${options.namespace}`)
41
43
  }
42
44
 
45
+ // Init metrics
43
46
  if (metrics) {
44
47
  metrics.init()
45
48
  }
46
49
 
50
+ // Init cache
51
+ if (cache) {
52
+ cache.init()
53
+ }
54
+
47
55
  // broker object
48
56
  /** @type {Broker} */
49
- const broker = {
50
- nodeId: options.nodeId,
51
- version,
52
- options,
53
- runtime,
54
- bus,
55
- validator,
56
- contextFactory,
57
- log,
58
- createLogger: runtime.createLogger,
59
- getUUID: () => runtime.generateUUID(),
60
- registry,
61
- getNextActionEndpoint (actionName, options = {}) {
62
- return registry.getNextAvailableActionEndpoint(actionName, options)
63
- },
64
- emit: eventBus.emit.bind(this),
65
- broadcast: eventBus.broadcast.bind(this),
66
- broadcastLocal: eventBus.broadcastLocal.bind(this),
67
- call: runtime.actionInvoker.call.bind(this),
68
- multiCall: runtime.actionInvoker.multiCall.bind(this),
69
- waitForServices: services.waitForServices.bind(this),
70
- createService: services.createService.bind(this),
71
- /**
72
- * Load and register a service from file.
73
- * @param {string} fileName Path to the service file.
74
- * @returns {Service} Service
75
- */
76
- loadService (fileName) {
77
- const filePath = path.resolve(fileName)
78
- const schema = require(filePath)
79
- const service = this.createService(schema)
80
-
81
- // If the "watchSevrices" option is set - add service to service watcher.
82
- if (options.watchServices) {
83
- service.filename = fileName
84
- services.watchService.call(this, service)
85
- }
57
+ const broker = Object.create(null)
58
+
59
+ broker.runtime = runtime
60
+ broker.registry = registry
61
+ broker.bus = bus
62
+ broker.nodeId = options.nodeId
63
+ broker.version = version
64
+ broker.options = options
65
+ broker.validator = validator
66
+ broker.contextFactory = contextFactory
67
+ broker.log = log
68
+ broker.createLogger = runtime.createLogger
69
+
70
+ broker.getUUID = function () {
71
+ return runtime.generateUUID()
72
+ }
86
73
 
87
- return service
88
- },
89
- /**
90
- * Load services from a folder.
91
- * @param {string} [folder='./services'] Path of the folder.
92
- * @param {string} [fileMask='*.service.js'] Pattern of the service files
93
- * @returns {number} Amount of services
94
- */
95
- loadServices (folder = './services', fileMask = '*.service.js') {
96
- const serviceFiles = glob.sync(path.join(folder, fileMask))
97
-
98
- log.info(`Searching services in folder '${folder}' with name pattern '${fileMask}'.`)
99
- log.info(`${serviceFiles.length} services found.`)
100
-
101
- serviceFiles.forEach(fileName => this.loadService(fileName))
102
- return serviceFiles.length
103
- },
104
- /**
105
- * Starts the broker.
106
- * @returns {Promise} Promise
107
- */
108
- async start () {
109
- const startTime = Date.now()
110
- await middlewareHandler.callHandlersAsync('starting', [runtime], true)
111
-
112
- if (transport) {
113
- await transport.connect()
114
- }
74
+ broker.getNextActionEndpoint = function (actionName, options = {}) {
75
+ return registry.getNextAvailableActionEndpoint(actionName, options)
76
+ }
115
77
 
116
- try {
117
- await Promise.all(services.serviceList.map(service => service.start()))
118
- } catch (error) {
119
- log.error(error, 'Unable to start all services')
120
- clearInterval(options.waitForServiceInterval)
121
- throw error
122
- }
78
+ broker.emit = eventBus.emit.bind(broker)
123
79
 
124
- runtime.state.isStarted = true
125
- eventBus.broadcastLocal('$broker.started')
126
- registry.generateLocalNodeInfo(true)
80
+ broker.broadcast = eventBus.broadcast.bind(broker)
127
81
 
128
- if (transport) {
129
- await transport.setReady()
130
- }
82
+ broker.broadcastLocal = eventBus.broadcastLocal.bind(broker)
131
83
 
132
- await middlewareHandler.callHandlersAsync('started', [runtime], true)
84
+ broker.call = runtime.actionInvoker.call.bind(broker)
133
85
 
134
- if (runtime.state.isStarted && isFunction(options.started)) {
135
- options.started.call(this)
136
- }
86
+ broker.multiCall = runtime.actionInvoker.multiCall.bind(broker)
137
87
 
138
- const duration = Date.now() - startTime
139
- log.info(`Node "${options.nodeId}" with ${services.serviceList.length} services successfully started in ${duration}ms.`)
140
- },
141
- /**
142
- * Stops the broker.
143
- * @returns {Promise} Promise
144
- */
145
- async stop () {
146
- runtime.state.isStarted = false
147
- log.info('Shutting down the node...')
148
-
149
- await middlewareHandler.callHandlersAsync('stopping', [runtime], true)
150
-
151
- try {
152
- await Promise.all(services.serviceList.map(service => service.stop()))
153
- } catch (error) {
154
- log.error(error, 'Unable to stop all services.')
155
- throw error
156
- }
88
+ broker.waitForServices = services.waitForServices.bind(broker)
157
89
 
158
- if (transport) {
159
- await transport.disconnect()
160
- }
90
+ broker.createService = services.createService.bind(broker)
161
91
 
162
- if (runtime.cache) {
163
- log.debug('Stopping caching adapters.')
164
- await runtime.cache.stop()
165
- }
92
+ /**
93
+ * Global error handler of the broker.
94
+ * @param {*} error Error
95
+ * @returns {void}
96
+ */
97
+ broker.handleError = runtime.handleError
166
98
 
167
- if (runtime.metrics) {
168
- log.debug('Stopping metrics.')
169
- await runtime.metrics.stop()
170
- }
99
+ broker.fatalError = runtime.fatalError
171
100
 
172
- if (runtime.tracer) {
173
- log.debug('Stopping tracing adapters.')
174
- await runtime.tracer.stop()
175
- }
101
+ /**
102
+ * Load and register a service from file.
103
+ * @param {string} fileName Path to the service file.
104
+ * @returns {Service} Service
105
+ */
106
+ broker.loadService = function (fileName) {
107
+ const filePath = path.resolve(fileName)
108
+ const schema = require(filePath)
109
+ const service = broker.createService(schema)
110
+
111
+ // If the "watchSevrices" option is set - add service to service watcher.
112
+ if (options.watchServices) {
113
+ service.filename = fileName
114
+ services.watchService.call(broker, service)
115
+ }
176
116
 
177
- await middlewareHandler.callHandlersAsync('stopped', [runtime], true)
117
+ return service
118
+ }
178
119
 
179
- if (!runtime.state.isStarted && isFunction(options.stopped)) {
180
- options.stopped.call(runtime)
181
- }
120
+ broker.loadServices = function (folder = './services', fileMask = '*.service.js') {
121
+ const serviceFiles = glob.sync(path.join(folder, fileMask))
182
122
 
183
- log.info('The node was successfully shut down. Bye bye! 👋')
184
-
185
- eventBus.broadcastLocal('$broker.stopped')
186
-
187
- process.removeListener('beforeExit', onClose)
188
- process.removeListener('exit', onClose)
189
- process.removeListener('SIGINT', onClose)
190
- process.removeListener('SIGTERM', onClose)
191
-
192
- // todo: handle errors
193
- },
194
- /**
195
- * Send a ping to connected nodes.
196
- * @param {*} nodeId Node id
197
- * @param {number} [timeout=3000] Ping timeout
198
- * @returns {Array} Ping result
199
- */
200
- ping (nodeId, timeout = 3000) {
201
- if (transport && transport.isConnected) {
202
- if (nodeId) {
203
- return new Promise((resolve) => {
204
- const timeoutTimer = setTimeout(() => {
205
- bus.off('$node.pong', pongHandler)
206
- return resolve(null)
207
- }, timeout)
123
+ log.info(`Searching services in folder '${folder}' with name pattern '${fileMask}'.`)
124
+ log.info(`${serviceFiles.length} services found.`)
208
125
 
209
- const pongHandler = pong => {
210
- clearTimeout(timeoutTimer)
211
- bus.off('$node.pong', pongHandler)
212
- resolve(pong)
213
- }
126
+ serviceFiles.forEach(fileName => broker.loadService(fileName))
127
+ return serviceFiles.length
128
+ }
214
129
 
215
- bus.on('$node.pong', pongHandler)
216
- transport.sendPing(nodeId)
217
- })
218
- } else {
219
- // handle arrays
220
- const pongs = {}
130
+ /**
131
+ * Starts the broker.
132
+ * @returns {Promise} Promise
133
+ */
134
+ broker.start = async function () {
135
+ const startTime = Date.now()
136
+ await middlewareHandler.callHandlersAsync('starting', [runtime], true)
137
+
138
+ // If transport is used, we connect the transport adapter.
139
+ if (transport) {
140
+ await transport.connect()
141
+ }
142
+
143
+ try {
144
+ await Promise.all(services.serviceList.map(service => service.start()))
145
+ } catch (error) {
146
+ log.error(error, 'Unable to start all services')
147
+ clearInterval(options.waitForServiceInterval)
148
+ throw error
149
+ }
221
150
 
222
- const nodes = registry.getNodeList({})
223
- .filter(node => !node.isLocal)
224
- .map(node => node.id)
151
+ runtime.state.isStarted = true
152
+ eventBus.broadcastLocal('$broker.started')
153
+ // refresh local node information
154
+ registry.generateLocalNodeInfo(true)
225
155
 
226
- const onFlight = new Set(nodes)
156
+ // If transport is used, we set the transport ready to inform the other nodes
157
+ if (transport) {
158
+ await transport.setReady()
159
+ }
160
+
161
+ await middlewareHandler.callHandlersAsync('started', [runtime], true)
162
+
163
+ if (runtime.state.isStarted && isFunction(options.started)) {
164
+ options.started.call(broker)
165
+ }
227
166
 
228
- nodes.forEach(nodeId => {
229
- pongs[nodeId] = null
230
- })
167
+ const duration = Date.now() - startTime
168
+ log.info(`Node "${options.nodeId}" with ${services.serviceList.length} services successfully started in ${duration}ms.`)
169
+ }
170
+
171
+ /**
172
+ * Stops the broker.
173
+ * @returns {Promise} Promise
174
+ */
175
+ broker.stop = async function () {
176
+ runtime.state.isStarted = false
177
+ log.info('Shutting down the node...')
178
+
179
+ await middlewareHandler.callHandlersAsync('stopping', [runtime], true)
180
+
181
+ // Stop services
182
+ try {
183
+ await Promise.all(services.serviceList.map(service => service.stop()))
184
+ } catch (error) {
185
+ log.error(error, 'Unable to stop all services.')
186
+ throw error
187
+ }
188
+
189
+ // Disconnect transports
190
+ if (transport) {
191
+ await transport.disconnect()
192
+ }
193
+
194
+ // Stop cache
195
+ if (runtime.cache) {
196
+ log.debug('Stopping caching adapters.')
197
+ await runtime.cache.stop()
198
+ }
231
199
 
232
- return new Promise((resolve) => {
233
- // todo: handle timeout
234
- const timeoutTimer = setTimeout(() => {
200
+ // Stop metrics
201
+ if (runtime.metrics) {
202
+ log.debug('Stopping metrics.')
203
+ await runtime.metrics.stop()
204
+ }
205
+
206
+ // Stop tracers
207
+ if (runtime.tracer) {
208
+ log.debug('Stopping tracing adapters.')
209
+ await runtime.tracer.stop()
210
+ }
211
+
212
+ // Call "stopped" middleware method.
213
+ await middlewareHandler.callHandlersAsync('stopped', [runtime], true)
214
+
215
+ // Call "stopped" lifecycle hook
216
+ if (!runtime.state.isStarted && isFunction(options.stopped)) {
217
+ options.stopped.call(runtime)
218
+ }
219
+
220
+ log.info('The node was successfully shut down. Bye bye! 👋')
221
+
222
+ eventBus.broadcastLocal('$broker.stopped')
223
+
224
+ process.removeListener('beforeExit', onClose)
225
+ process.removeListener('exit', onClose)
226
+ process.removeListener('SIGINT', onClose)
227
+ process.removeListener('SIGTERM', onClose)
228
+
229
+ // todo: handle errors
230
+ }
231
+
232
+ /**
233
+ * Ping other nodes
234
+ * @param {string=} nodeId Node ID
235
+ * @param {number} timeout Timeout
236
+ * @returns {Object<string, number>} Result
237
+ */
238
+ broker.ping = function (nodeId, timeout = 3000) {
239
+ if (transport && transport.isConnected) {
240
+ if (nodeId) {
241
+ return new Promise((resolve) => {
242
+ const timeoutTimer = setTimeout(() => {
243
+ bus.off('$node.pong', pongHandler)
244
+ return resolve(null)
245
+ }, timeout)
246
+
247
+ const pongHandler = pong => {
248
+ clearTimeout(timeoutTimer)
249
+ bus.off('$node.pong', pongHandler)
250
+ resolve(pong)
251
+ }
252
+
253
+ bus.on('$node.pong', pongHandler)
254
+ transport.sendPing(nodeId)
255
+ })
256
+ } else {
257
+ // handle arrays
258
+ const pongs = {}
259
+
260
+ const nodes = registry.nodeCollection.list({})
261
+ .filter(node => !node.isLocal)
262
+ .map(node => node.id)
263
+
264
+ const onFlight = new Set(nodes)
265
+
266
+ nodes.forEach(nodeId => {
267
+ pongs[nodeId] = null
268
+ })
269
+
270
+ return new Promise((resolve) => {
271
+ // todo: handle timeout
272
+ const timeoutTimer = setTimeout(() => {
273
+ bus.off('$node.pong', pongHandler)
274
+ resolve(pongs)
275
+ }, timeout)
276
+
277
+ const pongHandler = pong => {
278
+ pongs[pong.nodeId] = pong
279
+ onFlight.delete(pong.nodeId)
280
+ if (onFlight.size === 0) {
281
+ clearTimeout(timeoutTimer)
235
282
  bus.off('$node.pong', pongHandler)
236
283
  resolve(pongs)
237
- }, timeout)
238
-
239
- const pongHandler = pong => {
240
- pongs[pong.nodeId] = pong
241
- onFlight.delete(pong.nodeId)
242
- if (onFlight.size === 0) {
243
- clearTimeout(timeoutTimer)
244
- bus.off('$node.pong', pongHandler)
245
- resolve(pongs)
246
- }
247
284
  }
285
+ }
248
286
 
249
- bus.on('$node.pong', pongHandler)
250
- nodes.map(nodeId => transport.sendPing(nodeId))
251
- })
252
- }
287
+ bus.on('$node.pong', pongHandler)
288
+ nodes.map(nodeId => transport.sendPing(nodeId))
289
+ })
253
290
  }
291
+ }
254
292
 
255
- return Promise.resolve(nodeId ? null : {})
256
- },
257
- /**
258
- * Global error handler of the broker.
259
- * @param {*} error Error
260
- * @returns {void}
261
- */
262
- handleError: runtime.handleError,
263
- fatalError: runtime.fatalError
293
+ return Promise.resolve(nodeId ? null : {})
264
294
  }
265
295
 
266
296
  // Register internal broker events
@@ -296,7 +326,7 @@ exports.createBrokerInstance = (runtime) => {
296
326
 
297
327
  // Cache
298
328
  if (runtime.cache) {
299
- middlewareHandler.add(runtime.cache.middleware)
329
+ middlewareHandler.add(Middlewares.Cache)
300
330
  }
301
331
 
302
332
  // Context tracking
@@ -338,10 +368,11 @@ exports.createBrokerInstance = (runtime) => {
338
368
  runtime.eventBus.broadcast = middlewareHandler.wrapMethod('broadcast', runtime.eventBus.broadcast)
339
369
  runtime.eventBus.broadcastLocal = middlewareHandler.wrapMethod('broadcastLocal', runtime.eventBus.broadcastLocal)
340
370
 
341
- // Wrap Brober methods
371
+ // Wrap broker methods
342
372
  broker.createService = middlewareHandler.wrapMethod('createService', broker.createService)
343
373
  broker.loadService = middlewareHandler.wrapMethod('loadService', broker.loadService)
344
374
  broker.loadServices = middlewareHandler.wrapMethod('loadServices', broker.loadServices)
375
+ broker.ping = middlewareHandler.wrapMethod('ping', broker.ping)
345
376
  }
346
377
 
347
378
  // Run "beforeRegisterMiddlewares" hook
@@ -373,4 +404,3 @@ exports.createBrokerInstance = (runtime) => {
373
404
 
374
405
  return broker
375
406
  }
376
-