@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.
- package/lib/ExtendableError.js +33 -0
- package/lib/broker/context.js +25 -13
- package/lib/broker/{default-options.js → defaultOptions.js} +25 -6
- package/lib/broker/index.js +224 -194
- package/lib/buildRuntime.js +70 -0
- package/lib/cache/adapters/base.js +143 -0
- package/lib/cache/adapters/inMemory.js +154 -0
- package/lib/cache/adapters/inMemoryLru.js +140 -0
- package/lib/cache/adapters/index.js +46 -0
- package/lib/cache/base.js +49 -44
- package/lib/cache/lock.js +21 -4
- package/lib/constants.js +8 -5
- package/lib/errorHandler.js +19 -0
- package/lib/errors.js +17 -10
- package/lib/helper/{define-action.js → defineAction.js} +3 -1
- package/lib/helper/{define-broker-options.js → defineBrokerOptions.js} +2 -0
- package/lib/helper/{define-service.js → defineService.js} +4 -1
- package/lib/index.js +17 -92
- package/lib/logger/format/asHumanReadable.js +4 -3
- package/lib/logger/index.js +7 -1
- package/lib/logger/levels.js +8 -7
- package/lib/logger/tools.js +4 -0
- package/lib/logger/utils/format.js +18 -4
- package/lib/metrics/constants.js +8 -6
- package/lib/metrics/types/gauge.js +1 -1
- package/lib/middlewares/action-hooks/index.js +62 -14
- package/lib/middlewares/bulkhead/index.js +5 -4
- package/lib/middlewares/cache/index.js +106 -0
- package/lib/middlewares/circuit-breaker/index.js +41 -16
- package/lib/middlewares/context-tracker/index.js +3 -3
- package/lib/middlewares/error-handler/index.js +3 -3
- package/lib/middlewares/index.js +2 -1
- package/lib/middlewares/metrics/getMiddlewareWrapper.js +2 -2
- package/lib/middlewares/metrics/index.js +2 -2
- package/lib/middlewares/retry/index.js +11 -10
- package/lib/middlewares/timeout/index.js +5 -5
- package/lib/middlewares/tracing/index.js +27 -14
- package/lib/middlewares/tracing/tags.js +43 -0
- package/lib/middlewares/validator/index.js +6 -6
- package/lib/registry/{action-endpoint.js → actionEndpoint.js} +0 -0
- package/lib/registry/collections/{action-collection.js → actionCollection.js} +2 -2
- package/lib/registry/collections/{endpoint-collection.js → endpointCollection.js} +19 -3
- package/lib/registry/collections/{event-collection.js → eventCollection.js} +15 -9
- package/lib/registry/collections/{node-collection.js → nodeCollection.js} +1 -1
- package/lib/registry/collections/{service-collection.js → serviceCollection.js} +2 -2
- package/lib/registry/{event-endpoint.js → eventEndpoint.js} +1 -1
- package/lib/registry/node.js +1 -1
- package/lib/registry/registry.js +19 -38
- package/lib/registry/service/service.js +2 -2
- package/lib/registry/{service-item.js → serviceItem.js} +1 -1
- package/lib/runtime/{init-action-invoker.js → initActionInvoker.js} +3 -3
- package/lib/runtime/initCache.js +19 -0
- package/lib/runtime/{init-context-factory.js → initContextFactory.js} +2 -2
- package/lib/runtime/{init-eventbus.js → initEventbus.js} +11 -10
- package/lib/runtime/{init-logger.js → initLogger.js} +0 -0
- package/lib/runtime/{init-metrics.js → initMetrics.js} +11 -10
- package/lib/runtime/{init-middleware-manager.js → initMiddlewareManager.js} +1 -1
- package/lib/runtime/{init-registry.js → initRegistry.js} +1 -1
- package/lib/runtime/{init-service-manager.js → initServiceManager.js} +0 -0
- package/lib/runtime/{init-tracing.js → initTracing.js} +2 -1
- package/lib/runtime/{init-transport.js → initTransport.js} +0 -0
- package/lib/runtime/{init-uuid-factory.js → initUuidFactory.js} +0 -0
- package/lib/runtime/{init-validator.js → initValidator.js} +1 -1
- package/lib/tracing/collectors/base.js +11 -19
- package/lib/tracing/collectors/event.js +2 -2
- package/lib/tracing/collectors/index.js +2 -2
- package/lib/tracing/span.js +8 -6
- package/lib/transport/InboundTransformStream.js +75 -0
- package/lib/transport/adapters/{adapter-base.js → adapteBase.js} +16 -1
- package/lib/transport/adapters/adapters.js +1 -1
- package/lib/transport/adapters/dummy/index.js +3 -3
- package/lib/transport/adapters/index.js +1 -1
- package/lib/transport/adapters/tcp/discovery/index.js +45 -31
- package/lib/transport/adapters/tcp/index.js +20 -13
- package/lib/transport/adapters/tcp/tcpReader.js +1 -0
- package/lib/transport/adapters/tcp/tcpWriteStream.js +1 -1
- package/lib/transport/adapters/tcp/tcpWriter.js +2 -2
- package/lib/transport/createMessage.js +11 -0
- package/lib/transport/createTransport.js +215 -172
- package/lib/transport/errorPayloadFactory.js +18 -0
- package/lib/transport/{message-handlers.js → messageHandlers.js} +92 -23
- package/lib/transport/messageTypes.js +23 -0
- package/lib/types.js +86 -75
- package/lib/utils/index.js +1 -1
- package/lib/utils/options.js +4 -2
- package/lib/utils/{restore-error.js → restoreError.js} +1 -1
- package/package.json +6 -12
- package/CHANGELOG.md +0 -1317
- package/lib/cache/index.js +0 -48
- package/lib/cache/memory.js +0 -91
- package/lib/middlewares/tracing/buildTags.js +0 -17
- package/lib/new-errors.js +0 -25
- package/lib/runtime/init-cache.js +0 -10
- package/lib/tracing/collectors/console.js +0 -34
- package/lib/tracing/index.js +0 -3
- package/lib/tracing/tracer.js +0 -64
- package/lib/transport/message-types.js +0 -59
- package/lib/transport/message.js +0 -16
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./types.js').BrokerOptions} BrokerOptions
|
|
3
|
+
* @typedef {import('./types.js').Runtime} Runtime
|
|
4
|
+
* @typedef {import('./types.js').Broker} Broker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { initLogger } = require('./runtime/initLogger')
|
|
8
|
+
const { initMiddlewareHandler } = require('./runtime/initMiddlewareManager')
|
|
9
|
+
const { initRegistry } = require('./runtime/initRegistry')
|
|
10
|
+
const { initContextFactory } = require('./runtime/initContextFactory')
|
|
11
|
+
const { initEventbus } = require('./runtime/initEventbus')
|
|
12
|
+
const { initValidator } = require('./runtime/initValidator')
|
|
13
|
+
const { initTransport } = require('./runtime/initTransport')
|
|
14
|
+
const { initCache } = require('./runtime/initCache')
|
|
15
|
+
const { initActionInvoker } = require('./runtime/initActionInvoker')
|
|
16
|
+
const { initServiceManager } = require('./runtime/initServiceManager')
|
|
17
|
+
const { initMetrics } = require('./runtime/initMetrics')
|
|
18
|
+
const { initTracer } = require('./runtime/initTracing')
|
|
19
|
+
const { initUUIDFactory } = require('./runtime/initUuidFactory')
|
|
20
|
+
const { errorHandler, fatalErrorHandler } = require('./errorHandler')
|
|
21
|
+
const { uuid } = require('@weave-js/utils')
|
|
22
|
+
const { version } = require('../package.json')
|
|
23
|
+
const EventEmitter = require('eventemitter2')
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build runtime object
|
|
27
|
+
* @param {BrokerOptions} options Broker options
|
|
28
|
+
* @return {Runtime} Runtime
|
|
29
|
+
*/
|
|
30
|
+
exports.initRuntime = (options) => {
|
|
31
|
+
/**
|
|
32
|
+
* Event bus
|
|
33
|
+
* @returns {EventEmitter} Service object.
|
|
34
|
+
*/
|
|
35
|
+
const bus = new EventEmitter({
|
|
36
|
+
wildcard: true,
|
|
37
|
+
maxListeners: 1000
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Create base runtime object
|
|
41
|
+
const runtime = {
|
|
42
|
+
nodeId: options.nodeId,
|
|
43
|
+
version,
|
|
44
|
+
options,
|
|
45
|
+
bus,
|
|
46
|
+
state: {
|
|
47
|
+
instanceId: uuid(),
|
|
48
|
+
isStarted: false
|
|
49
|
+
},
|
|
50
|
+
handleError: (error) => errorHandler(runtime, error),
|
|
51
|
+
fatalError: (message, error, killProcess) => fatalErrorHandler(runtime, message, error, killProcess)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Init modules
|
|
55
|
+
initLogger(runtime)
|
|
56
|
+
initUUIDFactory(runtime)
|
|
57
|
+
initMiddlewareHandler(runtime)
|
|
58
|
+
initRegistry(runtime)
|
|
59
|
+
initContextFactory(runtime)
|
|
60
|
+
initEventbus(runtime)
|
|
61
|
+
initValidator(runtime)
|
|
62
|
+
initTransport(runtime)
|
|
63
|
+
initCache(runtime)
|
|
64
|
+
initActionInvoker(runtime)
|
|
65
|
+
initServiceManager(runtime)
|
|
66
|
+
initMetrics(runtime)
|
|
67
|
+
initTracer(runtime)
|
|
68
|
+
|
|
69
|
+
return runtime
|
|
70
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Author: Kevin Ries (kevin@fachw3rk.de)
|
|
3
|
+
* -----
|
|
4
|
+
* Copyright 2021 Fachwerk
|
|
5
|
+
*/
|
|
6
|
+
const crypto = require('crypto')
|
|
7
|
+
const { isObject, dotGet, isString } = require('@weave-js/utils')
|
|
8
|
+
const Constants = require('../../metrics/constants')
|
|
9
|
+
const { WeaveError } = require('../../errors')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get property from data or metadata object.
|
|
13
|
+
* @param {any} data data object
|
|
14
|
+
* @param {object} metadata metadata object
|
|
15
|
+
* @param {string} key key
|
|
16
|
+
* @returns {any} Result
|
|
17
|
+
*/
|
|
18
|
+
function getPropertyFromDataOrMetadata (data, metadata, key) {
|
|
19
|
+
// if a key starts with ":", the property is picked from metadata
|
|
20
|
+
if (key.startsWith(':')) {
|
|
21
|
+
// remove ':' from key.
|
|
22
|
+
key = key.replace(':', '')
|
|
23
|
+
return dotGet(metadata, key)
|
|
24
|
+
}
|
|
25
|
+
return dotGet(data, key)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getCacheKeyByObject (val) {
|
|
29
|
+
if (Array.isArray(val)) {
|
|
30
|
+
return val.map(object => getCacheKeyByObject(object)).join('/')
|
|
31
|
+
} else if (isObject(val)) {
|
|
32
|
+
return Object.keys(val).map(key => {
|
|
33
|
+
return [key, getCacheKeyByObject(val[key])].join('/')
|
|
34
|
+
}).join('/')
|
|
35
|
+
} else if (val !== null) {
|
|
36
|
+
return val.toString()
|
|
37
|
+
} else {
|
|
38
|
+
return 'null'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function generateHash (key) {
|
|
43
|
+
return crypto
|
|
44
|
+
.createHash('sha1')
|
|
45
|
+
.update(key)
|
|
46
|
+
.digest('base64')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function registerCacheMetrics (metrics) {
|
|
50
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_GET_TOTAL })
|
|
51
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_SET_TOTAL })
|
|
52
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_FOUND_TOTAL })
|
|
53
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_EXPIRED_TOTAL })
|
|
54
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_DELETED_TOTAL })
|
|
55
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_CLEANED_TOTAL })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const createCacheBase = (name, runtime, adapterOptions, options) => {
|
|
59
|
+
if (!isString(name)) {
|
|
60
|
+
throw new WeaveError('Name must be a string.')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cache = {
|
|
64
|
+
name,
|
|
65
|
+
isConnected: false,
|
|
66
|
+
runtime,
|
|
67
|
+
options: Object.assign({
|
|
68
|
+
ttl: null
|
|
69
|
+
}, options),
|
|
70
|
+
init () {
|
|
71
|
+
// register metrics
|
|
72
|
+
if (runtime.metrics) {
|
|
73
|
+
this.metrics = runtime.metrics
|
|
74
|
+
registerCacheMetrics(runtime.metrics)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
log: runtime.createLogger('CACHER'),
|
|
78
|
+
set (/* hashKey, result, ttl */) {
|
|
79
|
+
/* istanbul ignore next */
|
|
80
|
+
runtime.handleError(new Error('Method not implemented.'))
|
|
81
|
+
},
|
|
82
|
+
get (/* hashKey */) {
|
|
83
|
+
/* istanbul ignore next */
|
|
84
|
+
runtime.handleError(new Error('Method not implemented.'))
|
|
85
|
+
},
|
|
86
|
+
remove () {
|
|
87
|
+
/* istanbul ignore next */
|
|
88
|
+
runtime.handleError(new Error('Method not implemented.'))
|
|
89
|
+
},
|
|
90
|
+
clear () {
|
|
91
|
+
/* istanbul ignore next */
|
|
92
|
+
runtime.handleError(new Error('Method not implemented.'))
|
|
93
|
+
},
|
|
94
|
+
stop () {
|
|
95
|
+
/* istanbul ignore next */
|
|
96
|
+
return Promise.resolve()
|
|
97
|
+
},
|
|
98
|
+
getCachingKey (actionName, data, metadata, keys) {
|
|
99
|
+
if (data || metadata) {
|
|
100
|
+
const prefix = actionName + ':'
|
|
101
|
+
|
|
102
|
+
if (keys) {
|
|
103
|
+
// fast path for single keys
|
|
104
|
+
if (keys.length === 1) {
|
|
105
|
+
const value = getPropertyFromDataOrMetadata(data, metadata, keys[0])
|
|
106
|
+
const key = getCacheKeyByObject(value)
|
|
107
|
+
return prefix + (isObject(value) ? key : value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle data cache keys
|
|
111
|
+
if (keys.length > 0) {
|
|
112
|
+
const res = keys.reduce((pre, property, index) => {
|
|
113
|
+
const value = getPropertyFromDataOrMetadata(data, metadata, property)
|
|
114
|
+
let hash
|
|
115
|
+
if (isObject(value)) {
|
|
116
|
+
const key = getCacheKeyByObject(value)
|
|
117
|
+
hash = generateHash(key)
|
|
118
|
+
} else {
|
|
119
|
+
hash = value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return pre + (index > 0 ? '|' : '') + hash
|
|
123
|
+
}, prefix)
|
|
124
|
+
return res
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
return prefix + generateHash(getCacheKeyByObject(data))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return actionName
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Object.defineProperty(cache, 'adapterOptions', {
|
|
136
|
+
value: adapterOptions,
|
|
137
|
+
writable: false
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return cache
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { createCacheBase }
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Author: Kevin Ries (kevin@fachw3rk.de)
|
|
3
|
+
* -----
|
|
4
|
+
* Copyright 2021 Fachwerk
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { match, defaultsDeep } = require('@weave-js/utils')
|
|
8
|
+
const { createCacheBase } = require('./base')
|
|
9
|
+
const { createLock } = require('../lock')
|
|
10
|
+
const Constants = require('../../metrics/constants')
|
|
11
|
+
|
|
12
|
+
const defaultAdapterOptions = {
|
|
13
|
+
ttlCheckInterval: 3000
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} InMemoryAdapterOptions
|
|
18
|
+
* @property {number=} ttlCheckInterval TTL check interval
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create an in-memory cache adapter.
|
|
23
|
+
* @param {InMemoryAdapterOptions} adapterOptions Adapter options
|
|
24
|
+
* @returns {any} CacheFactory
|
|
25
|
+
*/
|
|
26
|
+
const createInMemoryCache = (adapterOptions = {}) => (runtime, options = {}) => {
|
|
27
|
+
adapterOptions = defaultsDeep(adapterOptions, defaultAdapterOptions)
|
|
28
|
+
const base = createCacheBase('In-Memory', runtime, adapterOptions, options)
|
|
29
|
+
const storage = new Map()
|
|
30
|
+
|
|
31
|
+
const lock = createLock()
|
|
32
|
+
|
|
33
|
+
const ttlTimerHandle = setInterval(() => {
|
|
34
|
+
checkTtl()
|
|
35
|
+
}, adapterOptions.ttlCheckInterval)
|
|
36
|
+
|
|
37
|
+
ttlTimerHandle.unref()
|
|
38
|
+
|
|
39
|
+
// if a new broker gets connected, we need to clear the cache
|
|
40
|
+
runtime.bus.on('$transport.connected', () => {
|
|
41
|
+
base.log.debug('Transport adapter connected. Cache will be cleared.')
|
|
42
|
+
cache.clear()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const checkTtl = () => {
|
|
46
|
+
const now = Date.now()
|
|
47
|
+
|
|
48
|
+
storage.forEach((item, hashKey) => {
|
|
49
|
+
if (item.expire && item.expire < now) {
|
|
50
|
+
cache.log.debug(`Delete ${hashKey}`)
|
|
51
|
+
storage.delete(hashKey)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const cache = Object.assign(
|
|
57
|
+
{},
|
|
58
|
+
base,
|
|
59
|
+
{
|
|
60
|
+
init () {
|
|
61
|
+
base.init()
|
|
62
|
+
cache.isConnected = true
|
|
63
|
+
},
|
|
64
|
+
get (cacheKey) {
|
|
65
|
+
base.log.debug(`Get ${cacheKey}`)
|
|
66
|
+
|
|
67
|
+
if (base.metrics) {
|
|
68
|
+
base.metrics.increment(Constants.CACHE_GET_TOTAL)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const item = storage.get(cacheKey)
|
|
72
|
+
|
|
73
|
+
if (item) {
|
|
74
|
+
cache.log.debug(`Found ${cacheKey}`)
|
|
75
|
+
|
|
76
|
+
if (base.metrics) {
|
|
77
|
+
base.metrics.increment(Constants.CACHE_FOUND_TOTAL)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (item.expire && item.expire < Date.now()) {
|
|
81
|
+
cache.log.debug(`Delete ${cacheKey}`)
|
|
82
|
+
storage.delete(cacheKey)
|
|
83
|
+
if (base.metrics) {
|
|
84
|
+
base.metrics.increment(Constants.CACHE_EXPIRED_TOTAL)
|
|
85
|
+
}
|
|
86
|
+
return Promise.resolve(null)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Promise.resolve(item.data)
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(null)
|
|
92
|
+
},
|
|
93
|
+
set (hashKey, data, ttl) {
|
|
94
|
+
if (base.metrics) {
|
|
95
|
+
base.metrics.increment(Constants.CACHE_SET_TOTAL)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// if ttl is not set in action cache settings, use options ttl
|
|
99
|
+
if (ttl == null) {
|
|
100
|
+
ttl = options.ttl
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
storage.set(hashKey, {
|
|
104
|
+
data,
|
|
105
|
+
expire: ttl ? Date.now() + ttl : null
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
base.log.debug(`Set ${hashKey}`)
|
|
109
|
+
|
|
110
|
+
return Promise.resolve(data)
|
|
111
|
+
},
|
|
112
|
+
remove (hashKey) {
|
|
113
|
+
if (base.metrics) {
|
|
114
|
+
base.metrics.increment(Constants.CACHE_DELETED_TOTAL)
|
|
115
|
+
}
|
|
116
|
+
storage.delete(hashKey)
|
|
117
|
+
base.log.debug(`Delete ${hashKey}`)
|
|
118
|
+
|
|
119
|
+
return Promise.resolve()
|
|
120
|
+
},
|
|
121
|
+
clear (pattern = '**') {
|
|
122
|
+
if (base.metrics) {
|
|
123
|
+
base.metrics.increment(Constants.CACHE_DELETED_TOTAL)
|
|
124
|
+
}
|
|
125
|
+
storage.forEach((_, key) => {
|
|
126
|
+
if (match(key, pattern)) {
|
|
127
|
+
base.log.debug(`Delete ${key}`)
|
|
128
|
+
this.remove(key)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
return Promise.resolve()
|
|
132
|
+
},
|
|
133
|
+
lock (key, ttl) {
|
|
134
|
+
return lock.acquire(key, ttl).then(() => {
|
|
135
|
+
return () => lock.release(key)
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
tryAcquireLock (key, ttl) {
|
|
139
|
+
if (lock.isLocked(key)) {
|
|
140
|
+
return Promise.reject(new Error('Locked'))
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return lock.acquire(key, ttl).then(() => {
|
|
144
|
+
return () => lock.release(key)
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
async stop () {
|
|
148
|
+
clearInterval(ttlTimerHandle)
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
return cache
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { createInMemoryCache }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Author: Kevin Ries (kevin@fachw3rk.de)
|
|
3
|
+
* -----
|
|
4
|
+
* Copyright 2021 Fachwerk
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { match } = require('@weave-js/utils')
|
|
8
|
+
const { createCacheBase } = require('./base')
|
|
9
|
+
const { createLock } = require('../lock')
|
|
10
|
+
const Constants = require('../../metrics/constants')
|
|
11
|
+
|
|
12
|
+
const createInMemoryLruCache = (adapterOptions) => (runtime, options = {}) => {
|
|
13
|
+
const name = 'In-Memory'
|
|
14
|
+
const base = createCacheBase(name, runtime, options)
|
|
15
|
+
const storage = new Map()
|
|
16
|
+
|
|
17
|
+
const lock = createLock()
|
|
18
|
+
|
|
19
|
+
const timer = setInterval(() => {
|
|
20
|
+
checkTtl()
|
|
21
|
+
}, 3000)
|
|
22
|
+
|
|
23
|
+
timer.unref()
|
|
24
|
+
|
|
25
|
+
// if a new broker gets connected, we need to clear the cache
|
|
26
|
+
runtime.bus.on('$transport.connected', () => {
|
|
27
|
+
base.log.debug('Transport adapter connected. Cache will be cleared.')
|
|
28
|
+
cache.clear()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const checkTtl = () => {
|
|
32
|
+
const now = Date.now()
|
|
33
|
+
|
|
34
|
+
storage.forEach((item, hashKey) => {
|
|
35
|
+
if (item.expire && item.expire < now) {
|
|
36
|
+
cache.log.debug(`Delete ${hashKey}`)
|
|
37
|
+
storage.delete(hashKey)
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cache = Object.assign(
|
|
43
|
+
{},
|
|
44
|
+
base,
|
|
45
|
+
{
|
|
46
|
+
name,
|
|
47
|
+
init () {
|
|
48
|
+
base.init()
|
|
49
|
+
cache.isConnected = true
|
|
50
|
+
},
|
|
51
|
+
get (cacheKey) {
|
|
52
|
+
base.log.debug(`Get ${cacheKey}`)
|
|
53
|
+
|
|
54
|
+
if (base.metrics) {
|
|
55
|
+
base.metrics.increment(Constants.CACHE_GET_TOTAL)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const item = storage.get(cacheKey)
|
|
59
|
+
|
|
60
|
+
if (item) {
|
|
61
|
+
cache.log.debug(`Found ${cacheKey}`)
|
|
62
|
+
|
|
63
|
+
if (base.metrics) {
|
|
64
|
+
base.metrics.increment(Constants.CACHE_FOUND_TOTAL)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (item.expire && item.expire < Date.now()) {
|
|
68
|
+
cache.log.debug(`Delete ${cacheKey}`)
|
|
69
|
+
storage.delete(cacheKey)
|
|
70
|
+
if (base.metrics) {
|
|
71
|
+
base.metrics.increment(Constants.CACHE_EXPIRED_TOTAL)
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve(null)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Promise.resolve(item.data)
|
|
77
|
+
}
|
|
78
|
+
return Promise.resolve(null)
|
|
79
|
+
},
|
|
80
|
+
set (hashKey, data, ttl) {
|
|
81
|
+
if (base.metrics) {
|
|
82
|
+
base.metrics.increment(Constants.CACHE_SET_TOTAL)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (ttl == null) {
|
|
86
|
+
ttl = options.ttl
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
storage.set(hashKey, {
|
|
90
|
+
data,
|
|
91
|
+
expire: ttl ? Date.now() + ttl : null
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
base.log.debug(`Set ${hashKey}`)
|
|
95
|
+
|
|
96
|
+
return Promise.resolve(data)
|
|
97
|
+
},
|
|
98
|
+
remove (hashKey) {
|
|
99
|
+
if (base.metrics) {
|
|
100
|
+
base.metrics.increment(Constants.CACHE_DELETED_TOTAL)
|
|
101
|
+
}
|
|
102
|
+
storage.delete(hashKey)
|
|
103
|
+
base.log.debug(`Delete ${hashKey}`)
|
|
104
|
+
|
|
105
|
+
return Promise.resolve()
|
|
106
|
+
},
|
|
107
|
+
clear (pattern = '**') {
|
|
108
|
+
if (base.metrics) {
|
|
109
|
+
base.metrics.increment(Constants.CACHE_DELETED_TOTAL)
|
|
110
|
+
}
|
|
111
|
+
storage.forEach((_, key) => {
|
|
112
|
+
if (match(key, pattern)) {
|
|
113
|
+
base.log.debug(`Delete ${key}`)
|
|
114
|
+
this.remove(key)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
return Promise.resolve()
|
|
118
|
+
},
|
|
119
|
+
lock (key, ttl) {
|
|
120
|
+
return lock.acquire(key, ttl).then(() => {
|
|
121
|
+
return () => lock.release(key)
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
tryAcquireLock (key, ttl) {
|
|
125
|
+
if (lock.isLocked(key)) {
|
|
126
|
+
return Promise.reject(new Error('Locked'))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return lock.acquire(key, ttl).then(() => {
|
|
130
|
+
return () => lock.release(key)
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
stop () {
|
|
134
|
+
clearInterval(timer)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
return cache
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { createInMemoryLruCache }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Author: Kevin Ries (kevin@fachw3rk.de)
|
|
3
|
+
* -----
|
|
4
|
+
* Copyright 2021 Fachwerk
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
...require('./base'),
|
|
9
|
+
...require('./inMemory'),
|
|
10
|
+
...require('./inMemoryLru')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// exports.resolve = (cacheOptions) => {
|
|
14
|
+
// const getByName = name => {
|
|
15
|
+
// if (!name) {
|
|
16
|
+
// return null
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
// const n = Object
|
|
20
|
+
// .keys(adapters)
|
|
21
|
+
// .find(n => n.toLowerCase() === name.toLowerCase())
|
|
22
|
+
|
|
23
|
+
// if (n) {
|
|
24
|
+
// return adapters[n]
|
|
25
|
+
// }
|
|
26
|
+
// }
|
|
27
|
+
|
|
28
|
+
// let cacheFactory
|
|
29
|
+
|
|
30
|
+
// if (cacheOptions === true) {
|
|
31
|
+
// cacheFactory = this.adapters.Memory
|
|
32
|
+
// } else if (isString(cacheOptions)) {
|
|
33
|
+
// const cache = getByName(cacheOptions)
|
|
34
|
+
|
|
35
|
+
// if (cache) {
|
|
36
|
+
// cacheFactory = cache()
|
|
37
|
+
// } else {
|
|
38
|
+
// throw new WeaveBrokerOptionsError(`Unknown cache type "${cacheOptions}"`)
|
|
39
|
+
// }
|
|
40
|
+
// } else if (isFunction(cacheOptions)) {
|
|
41
|
+
// cacheFactory = cacheOptions
|
|
42
|
+
// }
|
|
43
|
+
// if (cacheFactory) {
|
|
44
|
+
// return cacheFactory
|
|
45
|
+
// }
|
|
46
|
+
// }
|
package/lib/cache/base.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Author: Kevin Ries (kevin@fachw3rk.de)
|
|
3
3
|
* -----
|
|
4
|
-
* Copyright
|
|
4
|
+
* Copyright 2021 Fachwerk
|
|
5
5
|
*/
|
|
6
6
|
const crypto = require('crypto')
|
|
7
|
-
const { isObject } = require('@weave-js/utils')
|
|
7
|
+
const { isObject, dotGet, isString } = require('@weave-js/utils')
|
|
8
|
+
const Constants = require('../metrics/constants')
|
|
9
|
+
const { WeaveError } = require('../errors')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get property from data or metadata object.
|
|
13
|
+
* @param {any} data data object
|
|
14
|
+
* @param {object} metadata metadata object
|
|
15
|
+
* @param {string} key key
|
|
16
|
+
* @returns {any} Result
|
|
17
|
+
*/
|
|
18
|
+
function getPropertyFromDataOrMetadata (data, metadata, key) {
|
|
19
|
+
// if a key starts with ":", the property is picked from metadata
|
|
20
|
+
if (key.startsWith(':')) {
|
|
21
|
+
// remove ':' from key.
|
|
22
|
+
key = key.replace(':', '')
|
|
23
|
+
return dotGet(metadata, key)
|
|
24
|
+
}
|
|
25
|
+
return dotGet(data, key)
|
|
26
|
+
}
|
|
8
27
|
|
|
9
28
|
function getCacheKeyByObject (val) {
|
|
10
29
|
if (Array.isArray(val)) {
|
|
@@ -28,20 +47,31 @@ function generateHash (key) {
|
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
function registerCacheMetrics (metrics) {
|
|
31
|
-
|
|
50
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_GET_TOTAL })
|
|
51
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_SET_TOTAL })
|
|
52
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_FOUND_TOTAL })
|
|
53
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_EXPIRED_TOTAL })
|
|
54
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_DELETED_TOTAL })
|
|
55
|
+
metrics.register({ type: 'counter', name: Constants.CACHE_CLEANED_TOTAL })
|
|
32
56
|
}
|
|
33
57
|
|
|
34
|
-
exports.createCacheBase = (runtime, options) => {
|
|
58
|
+
exports.createCacheBase = (name, runtime, options) => {
|
|
59
|
+
if (!isString(name)) {
|
|
60
|
+
throw new WeaveError('Name needs to be a string.')
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
const cache = {
|
|
64
|
+
name,
|
|
65
|
+
isConnected: false,
|
|
66
|
+
runtime,
|
|
36
67
|
options: Object.assign({
|
|
37
68
|
ttl: null
|
|
38
69
|
}, options),
|
|
39
70
|
init () {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
registerCacheMetrics(this.metrics)
|
|
71
|
+
// register metrics
|
|
72
|
+
if (runtime.metrics) {
|
|
73
|
+
this.metrics = runtime.metrics
|
|
74
|
+
registerCacheMetrics(runtime.metrics)
|
|
45
75
|
}
|
|
46
76
|
},
|
|
47
77
|
log: runtime.createLogger('CACHER'),
|
|
@@ -65,20 +95,22 @@ exports.createCacheBase = (runtime, options) => {
|
|
|
65
95
|
/* istanbul ignore next */
|
|
66
96
|
return Promise.resolve()
|
|
67
97
|
},
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
const prefix =
|
|
98
|
+
getCachingKey (actionName, data, metadata, keys) {
|
|
99
|
+
if (data || metadata) {
|
|
100
|
+
const prefix = actionName + ':'
|
|
71
101
|
|
|
72
102
|
if (keys) {
|
|
103
|
+
// fast path for single keys
|
|
73
104
|
if (keys.length === 1) {
|
|
74
|
-
const value =
|
|
105
|
+
const value = getPropertyFromDataOrMetadata(data, metadata, keys[0])
|
|
75
106
|
const key = getCacheKeyByObject(value)
|
|
76
107
|
return prefix + (isObject(value) ? key : value)
|
|
77
108
|
}
|
|
78
109
|
|
|
110
|
+
// Handle data cache keys
|
|
79
111
|
if (keys.length > 0) {
|
|
80
|
-
const res = keys.reduce((pre, property,
|
|
81
|
-
const value =
|
|
112
|
+
const res = keys.reduce((pre, property, index) => {
|
|
113
|
+
const value = getPropertyFromDataOrMetadata(data, metadata, property)
|
|
82
114
|
let hash
|
|
83
115
|
if (isObject(value)) {
|
|
84
116
|
const key = getCacheKeyByObject(value)
|
|
@@ -87,12 +119,12 @@ exports.createCacheBase = (runtime, options) => {
|
|
|
87
119
|
hash = value
|
|
88
120
|
}
|
|
89
121
|
|
|
90
|
-
return pre + (
|
|
122
|
+
return pre + (index > 0 ? '|' : '') + hash
|
|
91
123
|
}, prefix)
|
|
92
124
|
return res
|
|
93
125
|
}
|
|
94
126
|
} else {
|
|
95
|
-
return prefix + generateHash(getCacheKeyByObject(
|
|
127
|
+
return prefix + generateHash(getCacheKeyByObject(data))
|
|
96
128
|
}
|
|
97
129
|
}
|
|
98
130
|
|
|
@@ -100,32 +132,5 @@ exports.createCacheBase = (runtime, options) => {
|
|
|
100
132
|
}
|
|
101
133
|
}
|
|
102
134
|
|
|
103
|
-
cache.middleware = () => {
|
|
104
|
-
return {
|
|
105
|
-
localAction: (handler, action) => {
|
|
106
|
-
const cacheOptions = Object.assign({ enabled: true }, isObject(action.cache) ? action.cache : { enabled: !!action.cache })
|
|
107
|
-
if (cacheOptions.enabled) {
|
|
108
|
-
return function cacheMiddleware (context) {
|
|
109
|
-
const cacheHashKey = cache.getCachingHash(action.name, context.data, context.meta, action.cache.keys)
|
|
110
|
-
context.isCachedResult = false
|
|
111
|
-
|
|
112
|
-
return cache.get(cacheHashKey).then((content) => {
|
|
113
|
-
if (content !== null) {
|
|
114
|
-
context.isCachedResult = true
|
|
115
|
-
return content
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return handler(context).then((result) => {
|
|
119
|
-
cache.set(cacheHashKey, result, action.cache.ttl)
|
|
120
|
-
return result
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return handler
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
135
|
return cache
|
|
131
136
|
}
|