@weave-js/core 0.14.0 → 0.14.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.
@@ -27,8 +27,8 @@ exports.createContext = (runtime) => {
27
27
  const context = {
28
28
  id: null,
29
29
  nodeId: runtime.nodeId || null,
30
+ parentId: null,
30
31
  callerNodeId: null,
31
- parentContext: null,
32
32
  endpoint: null,
33
33
  data: {},
34
34
  meta: {},
@@ -44,6 +44,7 @@ exports.createContext = (runtime) => {
44
44
  },
45
45
  duration: 0,
46
46
  stopTime: 0,
47
+ isCachedResult: false,
47
48
  setData (newParams) {
48
49
  this.data = newParams || {};
49
50
  },
@@ -137,21 +138,21 @@ exports.createContext = (runtime) => {
137
138
  contextCopy.options = this.options;
138
139
  contextCopy.data = this.data;
139
140
  contextCopy.meta = this.meta;
140
- contextCopy.parentContext = this.parentContext;
141
+ contextCopy.parentId = this.parentId;
141
142
  contextCopy.callerNodeId = this.callerNodeId;
142
143
  contextCopy.requestId = this.requestId;
143
144
  contextCopy.tracing = this.tracing;
145
+ contextCopy.span = this.span;
144
146
  contextCopy.level = this.level;
145
147
  contextCopy.eventName = this.eventName;
146
148
  contextCopy.eventType = this.eventType;
147
149
  contextCopy.eventGroups = this.eventGroups;
150
+ contextCopy.isCachedResult = this.isCachedResult;
148
151
 
149
152
  return contextCopy;
150
153
  }
151
154
  };
152
155
 
153
-
154
-
155
156
  // Generate context Id
156
157
  if (!context.id) {
157
158
  // Use UUID factory from broker options
@@ -80,6 +80,7 @@ exports.getDefaultOptions = () => {
80
80
  enabled: false,
81
81
  samplingRate: 1.0,
82
82
  collectors: [],
83
+ defaultTags: null,
83
84
  actions: {
84
85
  data: false,
85
86
  response: false,
@@ -5,7 +5,6 @@
5
5
  * @typedef {import('../types.js').Transport} Transport
6
6
  */
7
7
 
8
- // node packages
9
8
  const { isFunction } = require('@weave-js/utils');
10
9
  const path = require('path');
11
10
  const glob = require('glob');
@@ -31,26 +30,21 @@ exports.createBrokerInstance = (runtime) => {
31
30
  transport
32
31
  } = runtime;
33
32
 
34
- // Log Messages
35
33
  log.info(`Initializing #weave node version ${version}`);
36
34
  log.info(`Node Id: ${options.nodeId}`);
37
35
 
38
- // Output namespace
39
36
  if (options.namespace) {
40
37
  log.info(`Namespace: ${options.namespace}`);
41
38
  }
42
39
 
43
- // Init metrics
44
40
  if (runtime.metrics) {
45
41
  runtime.metrics.init();
46
42
  }
47
43
 
48
- // Init cache
49
44
  if (runtime.cache) {
50
45
  runtime.cache.init();
51
46
  }
52
47
 
53
- // broker object
54
48
  /** @type {Broker} */
55
49
  const broker = Object.create(null);
56
50
 
@@ -131,7 +125,6 @@ exports.createBrokerInstance = (runtime) => {
131
125
  const startTime = Date.now();
132
126
  await middlewareHandler.callHandlersAsync('starting', [runtime], true);
133
127
 
134
- // If transport is used, we connect the transport adapter.
135
128
  if (transport) {
136
129
  await transport.connect();
137
130
  }
@@ -146,10 +139,8 @@ exports.createBrokerInstance = (runtime) => {
146
139
 
147
140
  runtime.state.isStarted = true;
148
141
  eventBus.broadcastLocal('$broker.started');
149
- // refresh local node information
150
142
  registry.generateLocalNodeInfo(true);
151
143
 
152
- // If transport is used, we set the transport ready to inform the other nodes
153
144
  if (transport) {
154
145
  await transport.setReady();
155
146
  }
@@ -174,7 +165,6 @@ exports.createBrokerInstance = (runtime) => {
174
165
 
175
166
  await middlewareHandler.callHandlersAsync('stopping', [runtime], true);
176
167
 
177
- // Stop services
178
168
  try {
179
169
  await Promise.all(services.serviceList.map(service => service.stop()));
180
170
  } catch (error) {
@@ -182,33 +172,27 @@ exports.createBrokerInstance = (runtime) => {
182
172
  throw error;
183
173
  }
184
174
 
185
- // Disconnect transports
186
175
  if (transport) {
187
176
  await transport.disconnect();
188
177
  }
189
178
 
190
- // Stop cache
191
179
  if (runtime.cache) {
192
180
  log.debug('Stopping caching adapters.');
193
181
  await runtime.cache.stop();
194
182
  }
195
183
 
196
- // Stop metrics
197
184
  if (runtime.metrics) {
198
185
  log.debug('Stopping metrics.');
199
186
  await runtime.metrics.stop();
200
187
  }
201
188
 
202
- // Stop tracers
203
189
  if (runtime.tracer) {
204
190
  log.debug('Stopping tracing adapters.');
205
191
  await runtime.tracer.stop();
206
192
  }
207
193
 
208
- // Call "stopped" middleware method.
209
194
  await middlewareHandler.callHandlersAsync('stopped', [runtime], true);
210
195
 
211
- // Call "stopped" lifecycle hook
212
196
  if (!runtime.state.isStarted && isFunction(options.stopped)) {
213
197
  options.stopped.call(runtime);
214
198
  }
@@ -229,7 +213,7 @@ exports.createBrokerInstance = (runtime) => {
229
213
  * Ping other nodes
230
214
  * @param {string=} nodeId Node ID
231
215
  * @param {number} timeout Timeout
232
- * @returns {Object<string, number>} Result
216
+ * @returns {Promise<Object<string, number>>} Result
233
217
  */
234
218
  broker.ping = function (nodeId, timeout = 3000) {
235
219
  if (transport && transport.isConnected) {
@@ -250,7 +234,6 @@ exports.createBrokerInstance = (runtime) => {
250
234
  transport.sendPing(nodeId);
251
235
  });
252
236
  } else {
253
- // handle arrays
254
237
  const pongs = {};
255
238
 
256
239
  const nodes = registry.nodeCollection.list({})
@@ -289,7 +272,6 @@ exports.createBrokerInstance = (runtime) => {
289
272
  return Promise.resolve(nodeId ? null : {});
290
273
  };
291
274
 
292
- // Register internal broker events
293
275
  broker.bus.on('$node.disconnected', ({ nodeId }) => {
294
276
  runtime.transport.removePendingRequestsByNodeId(nodeId);
295
277
  services.serviceChanged(false);
@@ -301,101 +283,81 @@ exports.createBrokerInstance = (runtime) => {
301
283
  * @returns {void}
302
284
  */
303
285
  const registerMiddlewares = (customMiddlewares) => {
304
- // Register custom middlewares
305
286
  if (Array.isArray(customMiddlewares) && customMiddlewares.length > 0) {
306
287
  customMiddlewares.forEach(middleware => middlewareHandler.add(middleware));
307
288
  }
308
289
 
309
- // Add the built-in middlewares. (The order is important)
310
290
  if (options.loadInternalMiddlewares) {
311
291
  middlewareHandler.add(Middlewares.ActionHooks);
312
292
 
313
- // Validator middleware
314
293
  if (options.validateActionParams && validator) {
315
294
  middlewareHandler.add(Middlewares.Validator);
316
295
  }
317
296
 
318
- // Bulkhead
319
297
  if (options.bulkhead.enabled) {
320
298
  middlewareHandler.add(Middlewares.Bulkhead);
321
299
  }
322
300
 
323
- // Cache
324
301
  if (runtime.cache) {
325
302
  middlewareHandler.add(Middlewares.Cache);
326
303
  }
327
304
 
328
- // Context tracking
329
305
  if (options.contextTracking.enabled) {
330
306
  middlewareHandler.add(Middlewares.ContextTracker);
331
307
  }
332
308
 
333
- // Circuit breaker
334
309
  if (options.circuitBreaker.enabled) {
335
310
  middlewareHandler.add(Middlewares.CircuitBreaker);
336
311
  }
337
312
 
338
- // timeout middleware
339
313
  middlewareHandler.add(Middlewares.Timeout);
340
314
 
341
- // Retry policy
342
315
  if (options.retryPolicy.enabled) {
343
316
  middlewareHandler.add(Middlewares.Retry);
344
317
  }
345
318
 
346
- // Error handler
347
319
  middlewareHandler.add(Middlewares.ErrorHandler);
348
320
 
349
- // Tracing
350
321
  if (options.tracing.enabled) {
351
322
  middlewareHandler.add(Middlewares.Tracing);
352
323
  }
353
324
 
354
- // Metrics
355
325
  if (options.metrics.enabled) {
356
326
  middlewareHandler.add(Middlewares.Metrics);
357
327
  }
358
328
  }
359
329
 
360
- // Wrap runtime and broker methods for middlewares
361
330
  runtime.actionInvoker.call = middlewareHandler.wrapMethod('call', runtime.actionInvoker.call);
362
331
  runtime.actionInvoker.multiCall = middlewareHandler.wrapMethod('multiCall', broker.multiCall);
363
332
  runtime.eventBus.emit = middlewareHandler.wrapMethod('emit', runtime.eventBus.emit);
364
333
  runtime.eventBus.broadcast = middlewareHandler.wrapMethod('broadcast', runtime.eventBus.broadcast);
365
334
  runtime.eventBus.broadcastLocal = middlewareHandler.wrapMethod('broadcastLocal', runtime.eventBus.broadcastLocal);
366
335
 
367
- // Wrap broker methods
368
336
  broker.createService = middlewareHandler.wrapMethod('createService', broker.createService);
369
337
  broker.loadService = middlewareHandler.wrapMethod('loadService', broker.loadService);
370
338
  broker.loadServices = middlewareHandler.wrapMethod('loadServices', broker.loadServices);
371
339
  broker.ping = middlewareHandler.wrapMethod('ping', broker.ping);
372
340
  };
373
341
 
374
- // Run "beforeRegisterMiddlewares" hook
375
342
  if (isFunction(options.beforeRegisterMiddlewares)) {
376
343
  options.beforeRegisterMiddlewares.call(broker, { broker, runtime });
377
344
  }
378
345
 
379
- // Register middlewares
380
346
  registerMiddlewares(options.middlewares);
381
347
 
382
- // Stop the broker greaceful
383
348
  /* istanbul ignore next */
384
349
  const onClose = () => broker.stop()
385
350
  .catch(error => broker.log.error(error))
386
351
  .then(() => process.exit(0));
387
352
 
388
- // SIGTERM listener
389
353
  process.setMaxListeners(0);
390
354
  process.on('beforeExit', onClose);
391
355
  process.on('exit', onClose);
392
356
  process.on('SIGINT', onClose);
393
357
  process.on('SIGTERM', onClose);
394
358
 
395
- // Add broker reference to runtime
396
359
  Object.assign(runtime, { broker });
397
360
 
398
- // Call middleware hook for broker created.
399
361
  middlewareHandler.callHandlersSync('created', [runtime]);
400
362
 
401
363
  return broker;
@@ -37,7 +37,6 @@ exports.initRuntime = (options) => {
37
37
  maxListeners: 1000
38
38
  });
39
39
 
40
- // Create base runtime object
41
40
  const runtime = {
42
41
  nodeId: options.nodeId,
43
42
  version,
@@ -51,7 +50,6 @@ exports.initRuntime = (options) => {
51
50
  fatalError: (message, error, killProcess) => fatalErrorHandler(runtime, message, error, killProcess)
52
51
  };
53
52
 
54
- // Init modules
55
53
  initLogger(runtime);
56
54
  initUUIDFactory(runtime);
57
55
  initMiddlewareHandler(runtime);
@@ -39,7 +39,6 @@ const createCacheBase = (name, runtime, adapterOptions, options) => {
39
39
  ttl: null
40
40
  }, options),
41
41
  init () {
42
- // register metrics
43
42
  if (runtime.metrics) {
44
43
  this.metrics = runtime.metrics;
45
44
  registerCacheMetrics(runtime.metrics);
@@ -79,7 +78,6 @@ const createCacheBase = (name, runtime, adapterOptions, options) => {
79
78
  const prefix = actionName + '.';
80
79
 
81
80
  if (keys) {
82
- // fast path for single keys
83
81
  if (keys.length === 1) {
84
82
  const cacheKeyData = getPropertyFromDataOrMetadata(data, metadata, keys[0]);
85
83
  const key = getCacheKeyByObject(cacheKeyData);
@@ -87,7 +85,6 @@ const createCacheBase = (name, runtime, adapterOptions, options) => {
87
85
  return prefix + generateHash(value);
88
86
  }
89
87
 
90
- // Handle data cache keys
91
88
  if (keys.length > 0) {
92
89
  const valueString = keys.reduce((pre, property, index) => {
93
90
  let value = getPropertyFromDataOrMetadata(data, metadata, property);
@@ -36,7 +36,6 @@ const createInMemoryCache = (adapterOptions = {}) => (runtime, options = {}) =>
36
36
 
37
37
  ttlTimerHandle.unref();
38
38
 
39
- // if a new broker gets connected, we need to clear the cache
40
39
  runtime.bus.on('$transport.connected', () => {
41
40
  base.log.debug('Transport adapter connected. Cache will be cleared.');
42
41
  cache.clear();
@@ -95,7 +94,6 @@ const createInMemoryCache = (adapterOptions = {}) => (runtime, options = {}) =>
95
94
  base.metrics.increment(Constants.CACHE_SET_TOTAL);
96
95
  }
97
96
 
98
- // if ttl is not set in action cache settings, use options ttl
99
97
  if (ttl == null) {
100
98
  ttl = options.ttl;
101
99
  }
@@ -22,7 +22,6 @@ const createInMemoryLruCache = (adapterOptions) => (runtime, options = {}) => {
22
22
 
23
23
  timer.unref();
24
24
 
25
- // if a new broker gets connected, we need to clear the cache
26
25
  runtime.bus.on('$transport.connected', () => {
27
26
  base.log.debug('Transport adapter connected. Cache will be cleared.');
28
27
  cache.clear();
@@ -9,7 +9,6 @@ const getCacheKeyByObject = (value) => {
9
9
  if (Array.isArray(value)) {
10
10
  return '[' + value.map(object => getCacheKeyByObject(object)).join(',') + ']';
11
11
  } else if (isObject(value)) {
12
- // Handle date objects
13
12
  if (value instanceof Date) {
14
13
  return value.toISOString();
15
14
  }
@@ -8,9 +8,7 @@ const { dotGet } = require('@weave-js/utils');
8
8
  * @returns {any} Result
9
9
  */
10
10
  const getPropertyFromDataOrMetadata = (data, metadata, key) => {
11
- // if a key starts with ":", the property is picked from metadata
12
11
  if (key.startsWith(':')) {
13
- // remove ':' from key.
14
12
  key = key.replace(':', '');
15
13
  return dotGet(metadata, key);
16
14
  }
package/lib/errors.js CHANGED
@@ -7,8 +7,21 @@
7
7
  const { defaultsDeep } = require('@weave-js/utils');
8
8
  const { ExtendableError } = require('./ExtendableError');
9
9
 
10
+ /**
11
+ * @typedef {object} ErrorOptions
12
+ * @property {string} code Error code
13
+ * @property {boolean} retryable Retryable error
14
+ * @property {*} data Error data
15
+ * @property {string} name Error name
16
+ */
17
+
10
18
  class WeaveError extends ExtendableError {
11
- constructor (message, options) {
19
+ /**
20
+ * Create a new WeaveRetryableError
21
+ * @param {string} message Error message
22
+ * @param {ErrorOptions} options? Error options
23
+ */
24
+ constructor (message, options = {}) {
12
25
  options = defaultsDeep(
13
26
  options,
14
27
  {
@@ -26,6 +39,11 @@ class WeaveError extends ExtendableError {
26
39
  }
27
40
 
28
41
  class WeaveRetryableError extends WeaveError {
42
+ /**
43
+ * Create a new WeaveRetryableError
44
+ * @param {string} message Error message
45
+ * @param {ErrorOptions} options Error options
46
+ */
29
47
  constructor (message, options = { code: 'WEAVE_RETRYABLE_ERROR', retryable: true }) {
30
48
  super(message, options);
31
49
  this.retryable = true;
package/lib/index.js CHANGED
@@ -20,16 +20,12 @@ exports.defaultOptions = getDefaultOptions();
20
20
  * @return {Broker} Broker instance
21
21
  */
22
22
  exports.createBroker = (options) => {
23
- // get default options
24
23
  const defaultOptions = getDefaultOptions();
25
24
 
26
- // merge options with default options
27
25
  options = defaultsDeep(options, defaultOptions);
28
26
 
29
- // Init runtime
30
27
  const runtime = initRuntime(options);
31
28
 
32
- // Create broker instance
33
29
  return createBrokerInstance(runtime);
34
30
  };
35
31
 
@@ -40,12 +36,10 @@ exports.createBroker = (options) => {
40
36
  */
41
37
  exports.Weave = exports.createBroker;
42
38
 
43
- // Errors
44
39
  exports.Errors = require('./errors');
45
40
 
46
41
  exports.Constants = require('./constants');
47
42
 
48
- // Caching
49
43
  exports.Cache = require('./cache/adapters');
50
44
 
51
45
  /**
@@ -56,7 +50,6 @@ exports.TransportAdapters = require('./transport/adapters');
56
50
  exports.TracingAdapters = require('./tracing/collectors');
57
51
  exports.CacheAdapters = require('./cache/adapters');
58
52
 
59
- // Helper
60
53
  exports.defineBrokerOptions = require('./helper/defineBrokerOptions');
61
54
  exports.defineService = require('./helper/defineService');
62
55
  exports.defineAction = require('./helper/defineAction');
@@ -65,7 +65,7 @@ const wrapTracingLocalActionMiddleware = function (handler, action) {
65
65
  sampled: context.tracing
66
66
  });
67
67
 
68
- context.span = span;
68
+ context.tracing = span.sampled;
69
69
 
70
70
  return handler(context, serviceInjections)
71
71
  .then(result => {
@@ -110,7 +110,7 @@ const wrapTracingLocalEventMiddleware = function (handler, event) {
110
110
  sampled: context.tracing
111
111
  });
112
112
 
113
- context.span = span;
113
+ context.tracing = span.sampled;
114
114
 
115
115
  return handler(context)
116
116
  .then(result => {
@@ -20,24 +20,23 @@ exports.initActionInvoker = (runtime) => {
20
20
 
21
21
  const action = endpoint.action;
22
22
  const nodeId = endpoint.node.id;
23
- let context;
23
+ let contextToUse;
24
24
 
25
- // If a context is passed, we use this
26
25
  if (opts.context !== undefined) {
27
- context = opts.context;
28
- context.nodeId = nodeId;
26
+ contextToUse = opts.context;
27
+ contextToUse.nodeId = nodeId;
29
28
  } else {
30
- context = contextFactory.create(endpoint, data, opts);
29
+ contextToUse = contextFactory.create(endpoint, data, opts);
31
30
  }
32
31
 
33
32
  if (endpoint.isLocal) {
34
- log.debug({ action: context.action.name, requestId: context.requestId }, 'Call action local.');
33
+ log.debug({ action: contextToUse.action.name, requestId: contextToUse.requestId }, 'Call action local.');
35
34
  } else {
36
- log.debug({ action: context.action.name, nodeId, requestId: context.requestId }, 'Call action on remote node.');
35
+ log.debug({ action: contextToUse.action.name, nodeId, requestId: contextToUse.requestId }, 'Call action on remote node.');
37
36
  }
38
37
 
39
- const p = action.handler(context, { service: context.action.service, runtime, errors });
40
- p.context = context;
38
+ const p = action.handler(contextToUse, { service: contextToUse.action.service, runtime, errors });
39
+ p.context = contextToUse;
41
40
 
42
41
  return p;
43
42
  };
@@ -7,7 +7,6 @@ exports.initCache = (runtime) => {
7
7
  throw new WeaveError('Invalid cache adapter.');
8
8
  }
9
9
 
10
- // Init cache adapter
11
10
  const cache = runtime.options.cache.adapter(runtime, runtime.options.cache);
12
11
 
13
12
  runtime.log.info(`Cache: ${cache.name}`);
@@ -32,27 +32,28 @@ exports.initContextFactory = (runtime) => {
32
32
  create (endpoint, data, opts) {
33
33
  const context = createContext(runtime);
34
34
 
35
+ if (endpoint) {
36
+ context.setEndpoint(endpoint);
37
+ }
38
+
35
39
  opts = opts || {};
36
40
  context.setData(data);
37
41
  // context.timeout = opts.timeout || 0
38
42
  context.retryCount = opts.retryCount;
39
43
  context.options = opts;
40
44
 
41
- // get external request id from options
42
45
  if (opts.requestId) {
43
46
  context.requestId = opts.requestId;
44
47
  } else if (opts.parentContext && opts.parentContext.requestId) {
45
48
  context.requestId = opts.parentContext.requestId;
46
49
  }
47
50
 
48
- // meta data
49
51
  if (opts.parentContext && opts.parentContext.meta !== null) {
50
52
  context.meta = Object.assign({}, opts.parentContext.meta, opts.meta);
51
53
  } else if (opts.meta) {
52
54
  context.meta = opts.meta;
53
55
  }
54
56
 
55
- // Parent context
56
57
  if (opts.parentContext != null) {
57
58
  context.parentId = opts.parentContext.id;
58
59
  context.level = opts.parentContext.level + 1;
@@ -60,22 +61,19 @@ exports.initContextFactory = (runtime) => {
60
61
  context.span = opts.parentContext.span;
61
62
  }
62
63
 
63
- // handle local streams
64
64
  if (opts.stream) {
65
65
  context.setStream(opts.stream);
66
66
  }
67
67
 
68
- // set request ID for metrics
69
68
  if (context.metrics || context.nodeId !== runtime.nodeId) {
70
69
  if (!context.requestId) {
71
70
  context.requestId = context.id;
72
71
  }
73
72
  }
74
73
 
75
- if (endpoint) {
76
- context.setEndpoint(endpoint);
74
+ if (opts.parentSpan) {
75
+ context.tracing = opts.parentSpan.sampled;
77
76
  }
78
-
79
77
  return context;
80
78
  }
81
79
  }
@@ -15,7 +15,6 @@ exports.initEventbus = (runtime) => {
15
15
  options = {};
16
16
  }
17
17
 
18
- // Emit local events
19
18
  if (/^\$/.test(eventName)) {
20
19
  bus.emit(eventName, payload);
21
20
  }
@@ -34,7 +33,6 @@ exports.initEventbus = (runtime) => {
34
33
  endpoints.map(([endpoint, groupName]) => {
35
34
  if (endpoint) {
36
35
  if (endpoint.node.id === brokerOptions.nodeId) {
37
- // Local event. Call handler
38
36
  context.setEndpoint(endpoint);
39
37
  promises.push(endpoint.action.handler(context));
40
38
  } else {
@@ -51,7 +49,6 @@ exports.initEventbus = (runtime) => {
51
49
  }
52
50
  });
53
51
 
54
- // Send remote events
55
52
  if (runtime.transport) {
56
53
  Object.values(groupedEndpoints)
57
54
  .forEach(groupedEndpoint => {
@@ -73,7 +70,6 @@ exports.initEventbus = (runtime) => {
73
70
  * @returns {Promise<any>} Promise
74
71
  */
75
72
  const broadcastLocal = (eventName, payload, options) => {
76
- // If the given group is no array - wrap it.
77
73
  if (Array.isArray(options)) {
78
74
  options = { groups: options };
79
75
  } else if (options == null) {
@@ -84,7 +80,6 @@ exports.initEventbus = (runtime) => {
84
80
  context.eventType = 'broadcastLocal';
85
81
  context.eventName = eventName;
86
82
 
87
- // Emit the event on the internal event bus
88
83
  if (/^\$/.test(eventName)) {
89
84
  bus.emit(eventName, payload);
90
85
  }
@@ -109,14 +104,12 @@ exports.initEventbus = (runtime) => {
109
104
  const promises = [];
110
105
 
111
106
  if (runtime.transport) {
112
- // create context
113
107
  // todo: create an event context object
114
108
  const context = contextFactory.create(null, payload, options);
115
109
  context.eventType = 'broadcast';
116
110
  context.eventName = eventName;
117
111
  context.eventGroups = options.groups;
118
112
 
119
- // Avoid to broadcast internal events.
120
113
  if (!/^\$/.test(eventName)) {
121
114
  const endpoints = registry.eventCollection.getAllEndpointsUniqueNodes(eventName, options.groups);
122
115
 
@@ -20,12 +20,10 @@ exports.initLogger = (runtime) => {
20
20
  ...additional
21
21
  };
22
22
 
23
- // custom logger generator function.
24
23
  if (typeof runtime.options.logger === 'function') {
25
24
  return runtime.options.logger(bindings, runtime.options.logger.level);
26
25
  }
27
26
 
28
- // merge log options
29
27
  const loggerOptions = defaultsDeep({
30
28
  base: {
31
29
  ...bindings
@@ -37,7 +35,6 @@ exports.initLogger = (runtime) => {
37
35
 
38
36
  const createLogger = (moduleName, service) => loggerFactory(runtime, moduleName, service);
39
37
 
40
- // create weave default logger
41
38
  const log = createLogger('WEAVE');
42
39
 
43
40
  Object.assign(runtime, {
@@ -30,7 +30,6 @@ exports.initMetrics = (runtime) => {
30
30
  storage,
31
31
  log,
32
32
  init () {
33
- // Load adapters
34
33
  if (metricOptions.adapters) {
35
34
  if (!Array.isArray(metricOptions.adapters)) {
36
35
  runtime.handleError(new WeaveError('Metic adapter needs to be an Array.'));
@@ -128,11 +127,6 @@ exports.initMetrics = (runtime) => {
128
127
  }
129
128
  return duration;
130
129
  };
131
- // const item = this.storage.get(name)
132
- // if (item) {
133
-
134
- // }
135
- // item.observe(labels, value, timestamp)
136
130
  },
137
131
  getMetric (name) {
138
132
  const item = this.storage.get(name);
@@ -8,45 +8,13 @@ exports.initServiceManager = (runtime) => {
8
8
  const serviceList = [];
9
9
 
10
10
  const serviceChanged = (isLocalService = false) => {
11
- // Send local notification.
12
11
  eventBus.broadcastLocal('$services.changed', { isLocalService });
13
12
 
14
- // If the service is a local service - send current node information to other nodes
15
13
  if (state.isStarted && isLocalService && transport) {
16
14
  transport.sendNodeInfo();
17
15
  }
18
16
  };
19
17
 
20
- // const destroyService = (service) => Promise.resolve()
21
- // .then(() => service.stop())
22
- // .then(() => log.info(`Service "${service.name}" was stopped.`))
23
- // .then(() => {
24
- // registry.deregisterService(service.name, service.version)
25
- // log.info(`Service "${service.name}" was deregistered.`)
26
- // // Remove service from service store.
27
- // serviceList.splice(serviceList.indexOf(service), 1)
28
- // // Fire services changed event
29
- // serviceChanged(true)
30
- // return Promise.resolve()
31
- // })
32
- // .catch(error => log.error(error, `Unable to stop service "${service.name}"`))
33
-
34
- // `onServiceFileChanged` only triggered by the file watcher
35
- // const onServiceFileChanged = async (service) => {
36
- // const filename = service.filename;
37
-
38
- // // Clear the require cache
39
- // Object.keys(require.cache).forEach(key => {
40
- // if (key === filename) {
41
- // delete require.cache[key];
42
- // }
43
- // });
44
-
45
- // // Service has changed - 1. destroy the service, then reload it
46
- // await destroyService(service);
47
- // await runtime.broker.loadService(filename);
48
- // };
49
-
50
18
  Object.defineProperty(runtime, 'services', {
51
19
  value: {
52
20
  serviceList,
@@ -55,7 +23,6 @@ exports.initServiceManager = (runtime) => {
55
23
  try {
56
24
  const newService = createServiceFromSchema(runtime, schema);
57
25
 
58
- // if the broker is already started, we need to start the service.
59
26
  if (runtime.state.isStarted) {
60
27
  newService.start().catch(error => log.error(`Unable to start service ${newService.name}: ${error}`));
61
28
  }
@@ -114,11 +81,8 @@ exports.initServiceManager = (runtime) => {
114
81
  */
115
82
  async destroyService (service) {
116
83
  try {
117
- // Stop the service
118
84
  await service.stop();
119
- log.info(`Service "${service.name}" was stopped.`);
120
85
 
121
- // Deregister the service and remove it from the service list
122
86
  registry.deregisterService(service.name, service.version);
123
87
  serviceList.splice(serviceList.indexOf(service), 1);
124
88
  log.info(`Service "${service.name}" was deregistered.`);
@@ -127,24 +91,6 @@ exports.initServiceManager = (runtime) => {
127
91
  log.error(error, `Unable to stop service "${service.name}"`);
128
92
  }
129
93
  }
130
- /**
131
- * Watch a Service object for changes.
132
- * @param {Service} service Service object
133
- * @return {void}
134
- */
135
- // watchService: (service) => {
136
- // if (service.filename && onServiceFileChanged) {
137
- // // Create debounced service changed reference
138
- // const debouncedOnServiceChange = debounce(onServiceFileChanged, 500);
139
-
140
- // // Watch file changes
141
- // const watcher = fs.watch(service.filename, (eventType, filename) => {
142
- // log.info(`The Service ${service.name} has been changed. (${eventType}, ${filename})`);
143
- // watcher.close();
144
- // debouncedOnServiceChange(service);
145
- // });
146
- // }
147
- // }
148
94
  }
149
95
  });
150
96
  };
@@ -4,6 +4,7 @@ const { createSpan } = require('../tracing/span');
4
4
  exports.initTracer = (runtime) => {
5
5
  const options = runtime.options.tracing;
6
6
  const log = runtime.createLogger('TRACER');
7
+
7
8
  let collectors = [];
8
9
  let samplingCounter = 0;
9
10
 
@@ -18,7 +19,6 @@ exports.initTracer = (runtime) => {
18
19
  }
19
20
  },
20
21
  shouldSample (span) {
21
- // check span priority
22
22
  if (options.samplingRate === 0) {
23
23
  return false;
24
24
  }
@@ -38,6 +38,12 @@ exports.initTracer = (runtime) => {
38
38
  collectors.map(collector => collector[method].apply(collector, args));
39
39
  },
40
40
  startSpan (name, options) {
41
+ const parentOptions = {};
42
+ if (options.parentSpan) {
43
+ parentOptions.traceId = options.parentSpan.traceId;
44
+ parentOptions.parentId = options.parentSpan.id;
45
+ parentOptions.sampled = options.parentSpan.sampled;
46
+ }
41
47
  const span = createSpan(this, name, Object.assign({
42
48
  type: 'custom'
43
49
  }, options));
@@ -49,7 +55,6 @@ exports.initTracer = (runtime) => {
49
55
  }
50
56
  });
51
57
 
52
- // Init collectors
53
58
  if (options.enabled) {
54
59
  log.info('Tracer initialized.');
55
60
 
@@ -1,7 +1,6 @@
1
1
  const { isFunction } = require('@weave-js/utils');
2
2
 
3
3
  const collectors = {
4
- // Console: require('./console'),
5
4
  Event: require('./event'),
6
5
  BaseCollector: require('./base')
7
6
  };
@@ -27,7 +27,9 @@ exports.createSpan = (tracer, name, options) => {
27
27
 
28
28
  span.start = (time) => {
29
29
  span.startTime = time || hrTime();
30
- tracer.invokeCollectorMethod('startedSpan', [span]);
30
+ if (span.sampled) {
31
+ tracer.invokeCollectorMethod('startedSpan', [span]);
32
+ }
31
33
  return span;
32
34
  };
33
35
 
@@ -42,8 +44,12 @@ exports.createSpan = (tracer, name, options) => {
42
44
  span.finish = (time) => {
43
45
  span.finishTime = time || hrTime();
44
46
  span.duration = span.finishTime - span.startTime;
47
+
45
48
  tracer.log.debug(`Span "${span.id}" finished`);
46
- tracer.invokeCollectorMethod('finishedSpan', [span]);
49
+
50
+ if (span.sampled) {
51
+ tracer.invokeCollectorMethod('finishedSpan', [span]);
52
+ }
47
53
 
48
54
  return span;
49
55
  };
@@ -81,7 +81,6 @@ const createTransportBase = () => {
81
81
  },
82
82
  serialize (packet) {
83
83
  try {
84
- // Add the sender to each outgoing message
85
84
  packet.payload.sender = this.broker.nodeId;
86
85
  return Buffer.from(JSON.stringify(packet));
87
86
  } catch (error) {
@@ -1,5 +1,13 @@
1
1
  const MessageTypes = require('./messageTypes');
2
2
 
3
+ /**
4
+ * Create a message object
5
+ *
6
+ * @param {string} type - The message type
7
+ * @param {string} targetNodeId - The target node id
8
+ * @param {object} payload - The message payload
9
+ * @returns {object} - The message object
10
+ */
3
11
  const createMessage = (type, targetNodeId, payload) => {
4
12
  return {
5
13
  type: type || MessageTypes.MESSAGE_UNKNOWN,
@@ -70,7 +70,6 @@ exports.createTransport = (runtime, adapter) => {
70
70
 
71
71
  const doConnect = (isTryReconnect) => {
72
72
  const errorHandler = (error) => {
73
- // Skip reconnect, if the adapter is disconnecting or an reconnect is in progress.
74
73
  if (transport.isDisconnecting || transport.reconnectInProgress) {
75
74
  return;
76
75
  }
@@ -132,7 +131,6 @@ exports.createTransport = (runtime, adapter) => {
132
131
  return Promise.resolve();
133
132
  }
134
133
 
135
- // Collect own node info and send it to remote nodes
136
134
  const info = runtime.registry.getLocalNodeInfo();
137
135
  const message = createMessage(MessageTypes.MESSAGE_INFO, sender, {
138
136
  ...info,
@@ -194,6 +192,8 @@ exports.createTransport = (runtime, adapter) => {
194
192
  metrics: context.metrics,
195
193
  requestId: context.requestId,
196
194
  parentId: context.parentId,
195
+ callerNodeId: context.callerNodeId,
196
+ tracing: context.tracing,
197
197
  isBroadcast
198
198
  };
199
199
 
@@ -237,8 +237,10 @@ exports.createTransport = (runtime, adapter) => {
237
237
  };
238
238
 
239
239
  transport.sendRequest = (context) => {
240
- // If the queue size is set, check the queue size and reject the job when the limit is reached.
241
- if (runtime.options.transport.maxQueueSize && runtime.options.transport.maxQueueSize < pending.requests.size) {
240
+ const maxQueueSizeExceeded = runtime.options.transport.maxQueueSize &&
241
+ runtime.options.transport.maxQueueSize < pending.requests.size;
242
+
243
+ if (maxQueueSizeExceeded) {
242
244
  return Promise.reject(new WeaveQueueSizeExceededError({
243
245
  action: context.action.name,
244
246
  limit: runtime.options.transport.maxQueueSize,
@@ -273,6 +275,8 @@ exports.createTransport = (runtime, adapter) => {
273
275
  metrics: context.metrics,
274
276
  requestId: context.requestId,
275
277
  parentId: context.parentId,
278
+ callerNodeId: context.callerNodeId,
279
+ tracing: context.tracing,
276
280
  isStream
277
281
  };
278
282
 
@@ -383,10 +387,8 @@ exports.createTransport = (runtime, adapter) => {
383
387
  * @returns {Promise<void>} - Promise
384
388
  */
385
389
  transport.sendResponse = (target, contextId, data, meta, error) => {
386
- // Check if data is a stream
387
390
  const isStream = utils.isStream(data);
388
391
 
389
- // Build response payload
390
392
  const payload = {
391
393
  id: contextId,
392
394
  meta,
@@ -394,7 +396,6 @@ exports.createTransport = (runtime, adapter) => {
394
396
  success: error == null
395
397
  };
396
398
 
397
- // If an error is occurs, we attach the an error object to the payload.
398
399
  if (error) {
399
400
  payload.error = getErrorPayload(error);
400
401
  }
@@ -422,7 +423,6 @@ exports.createTransport = (runtime, adapter) => {
422
423
  stream.pause();
423
424
  const chunks = [];
424
425
 
425
- // chunk is larger than maxBufferSize
426
426
  if (data instanceof Buffer && runtime.options.transport.maxChunkSize > 0 && data.length > runtime.options.transport.maxChunkSize) {
427
427
  const length = data.length;
428
428
  let i = 0;
@@ -433,7 +433,6 @@ exports.createTransport = (runtime, adapter) => {
433
433
  chunks.push(data);
434
434
  }
435
435
 
436
- // Send chunks from chunk buffer
437
436
  for (const chunk of chunks) {
438
437
  const payloadCopy = Object.assign({}, payload);
439
438
  payloadCopy.sequence = ++payload.sequence;
@@ -445,7 +444,6 @@ exports.createTransport = (runtime, adapter) => {
445
444
  transport.send(message);
446
445
  }
447
446
 
448
- // resume stream
449
447
  stream.resume();
450
448
  return;
451
449
  });
@@ -541,7 +539,6 @@ exports.createTransport = (runtime, adapter) => {
541
539
 
542
540
  let messageHandler = createMessageHandler(runtime, transport);
543
541
 
544
- // Wrap message handler for middlewares
545
542
  messageHandler = middlewareHandler.wrapMethod('transportMessageHandler', messageHandler, transport);
546
543
 
547
544
  adapter.init(runtime, transport)
@@ -635,10 +632,11 @@ exports.createTransport = (runtime, adapter) => {
635
632
  });
636
633
  }
637
634
 
638
- // Removes the node after a given time from the registry.
639
635
  function checkOfflineNodes () {
640
636
  const now = Date.now();
641
- runtime.registry.nodeCollection.list({}).forEach(node => {
637
+ const nodeList = runtime.registry.nodeCollection.list({});
638
+
639
+ nodeList.forEach(node => {
642
640
  if (node.isLocal || node.isAvailable) {
643
641
  return;
644
642
  }
@@ -32,25 +32,20 @@ module.exports = (runtime, transport) => {
32
32
  const localRequestProxy = (context) => {
33
33
  const actionName = context.action.name;
34
34
 
35
- // Get available endpoints
36
- const endpointList = registry.getActionEndpoints(actionName);
35
+ const availableEndpointList = registry.getActionEndpoints(actionName);
37
36
 
38
- // Reject the request if no local endpoint can be found.
39
- if (endpointList == null || !endpointList.hasLocal()) {
37
+ if (availableEndpointList == null || !availableEndpointList.hasLocal()) {
40
38
  transport.log.warn(`Service ${actionName} not found localy.`);
41
39
  return Promise.reject('Service not found');
42
40
  }
43
41
 
44
- // From all available local endpoints - get one.
45
- const endpoint = endpointList.getNextLocalEndpoint();
42
+ const endpoint = availableEndpointList.getNextLocalEndpoint();
46
43
 
47
- // if there is no endpoint, reject
48
44
  if (!endpoint) {
49
45
  transport.log.warn(`Service ${actionName} is not available localy.`);
50
46
  return Promise.reject('Service not found');
51
47
  }
52
48
 
53
- // Call the local action handler with context
54
49
  const promise = endpoint.action.handler(context);
55
50
  promise.context = context;
56
51
 
@@ -58,7 +53,6 @@ module.exports = (runtime, transport) => {
58
53
  };
59
54
 
60
55
  const handleIncomingRequestStream = (payload) => {
61
- // check for open stream.
62
56
  let stream = transport.pending.requestStreams.get(payload.id);
63
57
  let isNew = false;
64
58
 
@@ -75,7 +69,6 @@ module.exports = (runtime, transport) => {
75
69
  }
76
70
  );
77
71
 
78
- // handle backpressure
79
72
  if (runtime.options.transport.streams.handleBackpressure) {
80
73
  stream.on('backpressure', async ({ sender, requestId }) => {
81
74
  const message = createMessage(MessageTypes.MESSAGE_REQUEST_STREAM_BACKPRESSURE, sender, { id: requestId });
@@ -107,7 +100,6 @@ module.exports = (runtime, transport) => {
107
100
 
108
101
  // Todo: Handle errors
109
102
 
110
- // end of stream
111
103
  stream.end();
112
104
  transport.pending.requestStreams.delete(payload.id);
113
105
  return null;
@@ -147,7 +139,6 @@ module.exports = (runtime, transport) => {
147
139
  }
148
140
  );
149
141
 
150
- // handle backpressure
151
142
  if (runtime.options.transport.streams.handleBackpressure) {
152
143
  stream.on('backpressure', async ({ sender, requestId }) => {
153
144
  const message = createMessage(MessageTypes.MESSAGE_RESPONSE_STREAM_BACKPRESSURE, sender, { id: requestId });
@@ -182,7 +173,6 @@ module.exports = (runtime, transport) => {
182
173
 
183
174
  // Todo: Handle errors
184
175
 
185
- // end of stream
186
176
  stream.end();
187
177
  transport.pending.responseStreams.delete(payload.id);
188
178
  return null;
@@ -229,9 +219,7 @@ module.exports = (runtime, transport) => {
229
219
  try {
230
220
  let stream;
231
221
 
232
- // Handle incomming stream
233
222
  if (payload.isStream !== undefined) {
234
- // check for open stream.
235
223
  stream = handleIncomingRequestStream(payload);
236
224
  if (!stream) {
237
225
  return Promise.resolve();
@@ -250,9 +238,9 @@ module.exports = (runtime, transport) => {
250
238
  context.metrics = payload.metrics;
251
239
  context.level = payload.level;
252
240
  context.callerNodeId = payload.sender;
241
+ context.tracing = payload.tracing;
253
242
  context.options.timeout = getRequestTimeout(payload);
254
243
 
255
- // If payload is a stream, attach stream to context
256
244
  if (payload.isStream) {
257
245
  context.stream = stream;
258
246
  }
@@ -278,10 +266,8 @@ module.exports = (runtime, transport) => {
278
266
  return Promise.resolve();
279
267
  }
280
268
 
281
- // Merge meta data from response
282
269
  Object.assign(request.context.meta, payload.meta);
283
270
 
284
- // Handle streams
285
271
  if (payload.isStream != null) {
286
272
  if (handleIncomingResponseStream(payload, request)) {
287
273
  return;
@@ -290,7 +276,6 @@ module.exports = (runtime, transport) => {
290
276
 
291
277
  transport.pending.requests.delete(payload.id);
292
278
 
293
- // Response is an error
294
279
  if (!payload.success) {
295
280
  const error = restoreError(payload.error);
296
281
 
@@ -357,12 +342,12 @@ module.exports = (runtime, transport) => {
357
342
  context.metrics = payload.metrics;
358
343
  context.level = payload.level;
359
344
  context.callerNodeId = payload.sender;
345
+ context.tracing = !!payload.tracing;
360
346
 
361
347
  if (payload.timeout) {
362
348
  context.options.timeout = payload.timeout;
363
349
  }
364
350
 
365
- // add event infos
366
351
  context.eventName = payload.eventName;
367
352
  context.eventType = payload.isBroadcast ? 'broadcast' : 'emit';
368
353
 
@@ -387,17 +372,14 @@ module.exports = (runtime, transport) => {
387
372
  transport.log.verbose(`Heartbeat from ${payload.sender}`);
388
373
  const node = registry.nodeCollection.get(payload.sender);
389
374
 
390
- // if node is unknown then request a node info message.
391
375
  if (node) {
392
376
  if (!node.isAvailable) {
393
377
  transport.log.debug('Known node. Propably reconnected.');
394
- // unknown node. request info message.
395
378
  transport.discoverNode(payload.sender);
396
379
  } else {
397
380
  node.heartbeat(payload);
398
381
  }
399
382
  } else {
400
- // unknown node. request info message.
401
383
  transport.discoverNode(payload.sender);
402
384
  }
403
385
  };
@@ -500,10 +482,7 @@ module.exports = (runtime, transport) => {
500
482
  return true;
501
483
  } catch (error) {
502
484
  transport.log.error(error, type, data);
503
-
504
- runtime.eventBus.broadcastLocal('$transport.error', {
505
- error
506
- });
485
+ runtime.eventBus.broadcastLocal('$transport.error', { error });
507
486
  }
508
487
  return false;
509
488
  };
package/lib/types.js CHANGED
@@ -6,8 +6,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
6
6
  * @typedef {import('stream').Writable} WritableStream
7
7
  */
8
8
 
9
- // Broker
10
-
11
9
  /**
12
10
  * @typedef {object} ActionOptions - Action options
13
11
  * @property {Context} [context] - Assign a existing context instead of creating a new one.
@@ -86,7 +84,7 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
86
84
  * @property {ServiceManager} [services] - Service manager
87
85
  * @property {ContextFactory} [contextFactory] - contextFactory
88
86
  * @property {Logger} log - Logger instance
89
- * @property {function(string, any):Logger} [createLogger] - Create a new logger instace.
87
+ * @property {function(string, any):Logger} [createLogger] - Create a new logger instance.
90
88
  * @property {Cache} [cache] - Cache
91
89
  * @property {string} [getUUID] - Create a new from the UUID factory.
92
90
  * @property {Registry} registry - Service registry reference
@@ -169,6 +167,7 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
169
167
  * @property {Boolean} enabled - Enable tracing middleware. (default = false)
170
168
  * @property {Number} samplingRate - Rate of traced actions. (default = 1.0)
171
169
  * @property {Array<String|Object>} collectors - Array of tracing collectors.
170
+ * @property {Array<String>} defaultTags - Default tags for spans.
172
171
  * @property {TracingErrorOptions} [errors] - Settings for tracing errors.
173
172
  */
174
173
 
@@ -323,6 +322,7 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
323
322
  * @property {number} [retryCount] retryCount
324
323
  * @property {Object} tracing tracing
325
324
  * @property {Object} span span
325
+ * @property {boolean} [isCachedResult] eventType
326
326
  * @property {Service} service service
327
327
  * @property {ServiceAction} [action] action
328
328
  * @property {number} [startHighResolutionTime] - High resolution start timestamp.
@@ -361,8 +361,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
361
361
  * @property {function():Promise<any>} stop stop
362
362
  */
363
363
 
364
- // Transport
365
-
366
364
  /**
367
365
  * Transport interface
368
366
  * @export
@@ -449,8 +447,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
449
447
  * @property {void} close close
450
448
  */
451
449
 
452
- // Registry
453
-
454
450
  /**
455
451
  * Registry init function
456
452
  * @typedef {function} RegistryInitFunctionDef
@@ -498,7 +494,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
498
494
 
499
495
  /**
500
496
  * Endpoint collection
501
- *
502
497
  * @typedef EndpointCollection
503
498
  * @property {string} name name
504
499
  * @property {string} groupName groupName
@@ -705,7 +700,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
705
700
  * @property {function():Promise<any>} stop stop
706
701
  */
707
702
 
708
- // Health handler
709
703
  /**
710
704
  * Health handler
711
705
  * @typedef HealthHandler
@@ -719,8 +713,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
719
713
  * @property {any} getNodeHealthInfo getNodeHealthInfo
720
714
  */
721
715
 
722
- // Metrics
723
-
724
716
  /**
725
717
  * Metric registry
726
718
  * @typedef MetricRegistry
@@ -737,8 +729,6 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
737
729
  * @property {function():Promise<any>} stop list
738
730
  */
739
731
 
740
- // Midlewares
741
-
742
732
  /**
743
733
  * Middleware handler
744
734
  * @export
@@ -779,15 +769,11 @@ const { EventEmitter2: EventEmitter } = require('eventemitter2');
779
769
  * @property {function(Broker):void} [transportMessageHandler] stopped
780
770
  */
781
771
 
782
- // Validator
783
-
784
772
  /**
785
773
  * Validator
786
774
  * @typedef {Object} Validator
787
775
  */
788
776
 
789
- // Tracer
790
-
791
777
  /**
792
778
  * Tracer instance
793
779
  * @typedef Tracer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weave-js/core",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "The core package of weave",
5
5
  "keywords": [
6
6
  "Weave",
@@ -49,5 +49,5 @@
49
49
  "lib": "lib",
50
50
  "test": "test"
51
51
  },
52
- "gitHead": "c2f63481b2a72ae03a35682c9a6d12800f22dbf2"
52
+ "gitHead": "84fd5e4af3af96e8c004bcfba40bb3cb35f23fa7"
53
53
  }