newrelic 9.7.0 → 9.7.2
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/NEWS.md +42 -18
- package/THIRD_PARTY_NOTICES.md +203 -2
- package/api.js +155 -103
- package/lib/agent.js +29 -82
- package/lib/collector/api.js +234 -201
- package/lib/config/attribute-filter.js +35 -25
- package/lib/config/default.js +45 -19
- package/lib/config/index.js +260 -199
- package/lib/environment.js +16 -12
- package/lib/errors/error-collector.js +124 -55
- package/lib/errors/index.js +59 -49
- package/lib/instrumentation/@hapi/hapi.js +56 -50
- package/lib/instrumentation/@node-redis/client.js +50 -41
- package/lib/instrumentation/amqplib.js +116 -151
- package/lib/instrumentation/core/{async_hooks.js → async-hooks.js} +26 -11
- package/lib/instrumentation/core/globals.js +1 -1
- package/lib/instrumentation/core/http-outbound.js +193 -78
- package/lib/instrumentation/core/timers.js +106 -59
- package/lib/instrumentation/grpc-js/grpc.js +20 -23
- package/lib/instrumentation/mongodb/common.js +87 -85
- package/lib/instrumentation/redis.js +112 -90
- package/lib/instrumentation/undici.js +204 -192
- package/lib/instrumentation/{when.js → when/constants.js} +13 -10
- package/lib/instrumentation/when/contextualizer.js +168 -0
- package/lib/instrumentation/when/index.js +354 -0
- package/lib/instrumentation/when/nr-hooks.js +15 -0
- package/lib/instrumentations.js +1 -1
- package/lib/shim/shim.js +2 -0
- package/lib/shim/webframework-shim.js +19 -0
- package/lib/symbols.js +1 -0
- package/lib/system-info.js +240 -163
- package/lib/util/async-each-limit.js +30 -0
- package/lib/util/attributes.js +159 -0
- package/lib/util/code-level-metrics.js +58 -0
- package/lib/util/deep-equal.js +11 -144
- package/package.json +5 -4
- package/lib/instrumentation/promise.js +0 -572
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
const util = require('util')
|
|
9
|
+
const properties = require('../../util/properties')
|
|
10
|
+
const shimmer = require('../../shimmer')
|
|
11
|
+
const symbols = require('../../symbols')
|
|
12
|
+
const ANONYMOUS = '<anonymous>'
|
|
13
|
+
const { WHEN_SPEC } = require('./constants')
|
|
14
|
+
const Contextualizer = require('./contextualizer')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Instruments when.js
|
|
18
|
+
*
|
|
19
|
+
* @param {Shim} shim instance of shim
|
|
20
|
+
* @param {Function} when the exported when.js library.
|
|
21
|
+
*/
|
|
22
|
+
module.exports = function initialize(shim, when) {
|
|
23
|
+
const agent = shim.agent
|
|
24
|
+
const contextManager = agent._contextManager
|
|
25
|
+
const spec = WHEN_SPEC
|
|
26
|
+
|
|
27
|
+
// Wrap library-level methods.
|
|
28
|
+
wrapStaticMethods(when, spec.name, spec.$library)
|
|
29
|
+
|
|
30
|
+
// Wrap prototype methods.
|
|
31
|
+
const Promise = when[spec.constructor]
|
|
32
|
+
wrapPrototype(Promise.prototype)
|
|
33
|
+
wrapStaticMethods(Promise, spec.constructor, spec.$static)
|
|
34
|
+
|
|
35
|
+
// See if we are wrapping the class itself.
|
|
36
|
+
shimmer.wrapMethod(when, spec.name, spec.constructor, wrapPromise)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wraps every method of when.js and also defines properties on
|
|
40
|
+
* the $passThrough methods.
|
|
41
|
+
*
|
|
42
|
+
* @returns {Function} our wrapped promise
|
|
43
|
+
*/
|
|
44
|
+
function wrapPromise() {
|
|
45
|
+
spec.$static.$copy.forEach(function copyKeys(key) {
|
|
46
|
+
if (!wrappedPromise[key]) {
|
|
47
|
+
wrappedPromise[key] = Promise[key]
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
spec.$static.$passThrough.forEach(function assignProxy(proxyProp) {
|
|
52
|
+
if (!properties.hasOwn(wrappedPromise, proxyProp)) {
|
|
53
|
+
Object.defineProperty(wrappedPromise, proxyProp, {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
get: function getOriginal() {
|
|
57
|
+
return Promise[proxyProp]
|
|
58
|
+
},
|
|
59
|
+
set: function setOriginal(newValue) {
|
|
60
|
+
Promise[proxyProp] = newValue
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Inherit to pass `instanceof` checks.
|
|
67
|
+
util.inherits(wrappedPromise, Promise)
|
|
68
|
+
|
|
69
|
+
// Make the wrapper.
|
|
70
|
+
return wrappedPromise
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Wraps the promise handler and binds to the
|
|
75
|
+
* agent async context manager
|
|
76
|
+
*
|
|
77
|
+
* @param {Function} executor promise handler
|
|
78
|
+
* @returns {Function} wrapped handler
|
|
79
|
+
*/
|
|
80
|
+
function wrappedPromise(executor) {
|
|
81
|
+
if (!(this instanceof wrappedPromise)) {
|
|
82
|
+
return Promise(executor) // eslint-disable-line new-cap
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parent = contextManager.getContext()
|
|
86
|
+
let promise = null
|
|
87
|
+
if (
|
|
88
|
+
!parent ||
|
|
89
|
+
!parent.transaction.isActive() ||
|
|
90
|
+
typeof executor !== 'function' ||
|
|
91
|
+
arguments.length !== 1
|
|
92
|
+
) {
|
|
93
|
+
// We are expecting one function argument for executor, anything else is
|
|
94
|
+
// non-standard, do not attempt to wrap. Also do not attempt to wrap if we
|
|
95
|
+
// are not in a transaction.
|
|
96
|
+
const cnstrctArgs = agent.tracer.slice(arguments)
|
|
97
|
+
cnstrctArgs.unshift(Promise) // `unshift` === `push_front`
|
|
98
|
+
promise = new (Promise.bind.apply(Promise, cnstrctArgs))()
|
|
99
|
+
} else {
|
|
100
|
+
const segmentName = 'Promise ' + (executor.name || ANONYMOUS)
|
|
101
|
+
const context = {
|
|
102
|
+
promise: null,
|
|
103
|
+
self: null,
|
|
104
|
+
args: null
|
|
105
|
+
}
|
|
106
|
+
promise = new Promise(wrapExecutorContext(context))
|
|
107
|
+
context.promise = promise
|
|
108
|
+
const segment = _createSegment(segmentName)
|
|
109
|
+
Contextualizer.link(null, promise, segment)
|
|
110
|
+
|
|
111
|
+
segment.start()
|
|
112
|
+
try {
|
|
113
|
+
// Must run after promise is defined so that `__NR_wrapper` can be set.
|
|
114
|
+
contextManager.runInContext(segment, executor, context.self, context.args)
|
|
115
|
+
} catch (e) {
|
|
116
|
+
context.args[1](e)
|
|
117
|
+
} finally {
|
|
118
|
+
segment.touch()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The Promise must be created using the "real" Promise constructor (using
|
|
123
|
+
// normal Promise.apply(this) method does not work). But the prototype
|
|
124
|
+
// chain must include the wrappedPromise.prototype, V8's promise
|
|
125
|
+
// implementation uses promise.constructor to create new Promises for
|
|
126
|
+
// calls to `then`, `chain` and `catch` which allows these Promises to
|
|
127
|
+
// also be instrumented.
|
|
128
|
+
promise.__proto__ = wrappedPromise.prototype // eslint-disable-line no-proto
|
|
129
|
+
|
|
130
|
+
return promise
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Wraps then and catch on the when.js prototype
|
|
135
|
+
*
|
|
136
|
+
* @param {Function} PromiseProto when.js prototype
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*/
|
|
139
|
+
function wrapPrototype(PromiseProto) {
|
|
140
|
+
const name = spec.constructor + '.prototype'
|
|
141
|
+
|
|
142
|
+
// Wrap up instance methods.
|
|
143
|
+
_safeWrap(PromiseProto, name, spec.$proto.then, wrapThen)
|
|
144
|
+
_safeWrap(PromiseProto, name, spec.$proto.catch, wrapCatch)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Wraps all the static methods on when.js
|
|
149
|
+
* See: constants.STATIC_PROMISE_METHODS
|
|
150
|
+
*
|
|
151
|
+
* @param {Function} lib when.Promise
|
|
152
|
+
* @param {string} name `Promise`
|
|
153
|
+
* @param {object} staticSpec see WHEN_SPEC.$static
|
|
154
|
+
* @returns {void}
|
|
155
|
+
*/
|
|
156
|
+
function wrapStaticMethods(lib, name, staticSpec) {
|
|
157
|
+
_safeWrap(lib, name, staticSpec.cast, wrapCast)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Creates a function which will export the context and arguments of its
|
|
162
|
+
* execution.
|
|
163
|
+
*
|
|
164
|
+
* @param {object} context - The object to export the execution context with.
|
|
165
|
+
* @returns {Function} A function which, when executed, will add its context
|
|
166
|
+
* and arguments to the `context` parameter.
|
|
167
|
+
*/
|
|
168
|
+
function wrapExecutorContext(context) {
|
|
169
|
+
return function contextExporter(resolve, reject) {
|
|
170
|
+
context.self = this
|
|
171
|
+
context.args = [].slice.call(arguments)
|
|
172
|
+
context.args[0] = wrapResolver(context, resolve)
|
|
173
|
+
context.args[1] = wrapResolver(context, reject)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Wraps the resolve/reject functions of a when.js Promise
|
|
179
|
+
*
|
|
180
|
+
* @param {object} context object to update execution context
|
|
181
|
+
* @param {Function} fn function reference `resolve` or `rejct`
|
|
182
|
+
* @returns {Function} wrapped function
|
|
183
|
+
*/
|
|
184
|
+
function wrapResolver(context, fn) {
|
|
185
|
+
return function wrappedResolveReject(val) {
|
|
186
|
+
const promise = context.promise
|
|
187
|
+
if (promise && promise[symbols.context]) {
|
|
188
|
+
promise[symbols.context].getSegment().touch()
|
|
189
|
+
}
|
|
190
|
+
fn(val)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Creates a wrapper for `Promise#then` that extends the transaction context.
|
|
196
|
+
*
|
|
197
|
+
* @param {Function} then function reference to instrument
|
|
198
|
+
* @param {string} name `then`
|
|
199
|
+
* @returns {Function} A wrapped version of `Promise#then`.
|
|
200
|
+
*/
|
|
201
|
+
function wrapThen(then, name) {
|
|
202
|
+
return _wrapThen(then, name, true)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a wrapper for `Promise#catch` that extends the transaction context.
|
|
207
|
+
*
|
|
208
|
+
* @param {Function} catchMethod function reference to instrument
|
|
209
|
+
* @param {string} name `catch`
|
|
210
|
+
* @returns {Function} A wrapped version of `Promise#catch`.
|
|
211
|
+
*/
|
|
212
|
+
function wrapCatch(catchMethod, name) {
|
|
213
|
+
return _wrapThen(catchMethod, name, false)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Creates a wrapper for promise chain extending methods.
|
|
218
|
+
*
|
|
219
|
+
* @param {Function} then
|
|
220
|
+
* The function we are to wrap as a chain extender.
|
|
221
|
+
* @param {string} name name of function being wrapped
|
|
222
|
+
* @param {boolean} useAllParams
|
|
223
|
+
* When true, all parameters which are functions will be wrapped. Otherwise,
|
|
224
|
+
* only the last parameter will be wrapped.
|
|
225
|
+
* @returns {Function} A wrapped version of the function.
|
|
226
|
+
*/
|
|
227
|
+
function _wrapThen(then, name, useAllParams) {
|
|
228
|
+
// Don't wrap non-functions.
|
|
229
|
+
if (typeof then !== 'function' || then.name === '__NR_wrappedThen') {
|
|
230
|
+
return then
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// eslint-disable-next-line camelcase
|
|
234
|
+
return function __NR_wrappedThen() {
|
|
235
|
+
if (!(this instanceof Promise)) {
|
|
236
|
+
return then.apply(this, arguments)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const segmentNamePrefix = 'Promise#' + name + ' '
|
|
240
|
+
const thenSegment = agent.tracer.getSegment()
|
|
241
|
+
const promise = this
|
|
242
|
+
const ctx = { next: undefined, useAllParams, isWrapped: false, segmentNamePrefix }
|
|
243
|
+
|
|
244
|
+
// Wrap up the arguments and execute the real then.
|
|
245
|
+
const args = [].map.call(arguments, wrapHandler.bind(this, ctx))
|
|
246
|
+
ctx.next = then.apply(this, args)
|
|
247
|
+
|
|
248
|
+
// If we got a promise (which we should have), link the parent's context.
|
|
249
|
+
if (!ctx.isWrapped && ctx.next instanceof Promise && ctx.next !== promise) {
|
|
250
|
+
Contextualizer.link(promise, ctx.next, thenSegment)
|
|
251
|
+
}
|
|
252
|
+
return ctx.next
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Wraps every function passed to .then
|
|
258
|
+
*
|
|
259
|
+
* @param {object} ctx context to pass data from caller to callee and back
|
|
260
|
+
* @param {Function} fn function reference
|
|
261
|
+
* @param {number} i position of function in .then handler
|
|
262
|
+
* @param {Array} arr all args passed to .then
|
|
263
|
+
* @returns {Function} wraps every function pass to then
|
|
264
|
+
*/
|
|
265
|
+
function wrapHandler(ctx, fn, i, arr) {
|
|
266
|
+
if (
|
|
267
|
+
typeof fn !== 'function' || // Not a function
|
|
268
|
+
fn.name === '__NR_wrappedThenHandler' || // Already wrapped
|
|
269
|
+
(!ctx.useAllParams && i !== arr.length - 1) // Don't want all and not last
|
|
270
|
+
) {
|
|
271
|
+
ctx.isWrapped = fn && fn.name === '__NR_wrappedThenHandler'
|
|
272
|
+
return fn
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// eslint-disable-next-line camelcase
|
|
276
|
+
return function __NR_wrappedThenHandler() {
|
|
277
|
+
if (!ctx.next || !ctx.next[symbols.context]) {
|
|
278
|
+
return fn.apply(this, arguments)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let promSegment = ctx.next[symbols.context].getSegment()
|
|
282
|
+
const segmentName = ctx.segmentNamePrefix + (fn.name || ANONYMOUS)
|
|
283
|
+
const segment = _createSegment(segmentName, promSegment)
|
|
284
|
+
if (segment && segment !== promSegment) {
|
|
285
|
+
ctx.next[symbols.context].setSegment(segment)
|
|
286
|
+
promSegment = segment
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let ret = null
|
|
290
|
+
try {
|
|
291
|
+
ret = agent.tracer.bindFunction(fn, promSegment, true).apply(this, arguments)
|
|
292
|
+
} finally {
|
|
293
|
+
if (ret && typeof ret.then === 'function') {
|
|
294
|
+
ret = ctx.next[symbols.context].continue(ret)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return ret
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Creates a wrapper around the static `Promise` factory method.
|
|
303
|
+
*
|
|
304
|
+
* @param {Function} cast reference of function to wrap
|
|
305
|
+
* @param {string} name name of the function being wrapped
|
|
306
|
+
* @returns {Function} wrapped function
|
|
307
|
+
*/
|
|
308
|
+
function wrapCast(cast, name) {
|
|
309
|
+
if (typeof cast !== 'function' || cast.name === '__NR_wrappedCast') {
|
|
310
|
+
return cast
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const CAST_SEGMENT_NAME = 'Promise.' + name
|
|
314
|
+
// eslint-disable-next-line camelcase
|
|
315
|
+
return function __NR_wrappedCast() {
|
|
316
|
+
const segment = _createSegment(CAST_SEGMENT_NAME)
|
|
317
|
+
const prom = cast.apply(this, arguments)
|
|
318
|
+
if (segment) {
|
|
319
|
+
Contextualizer.link(null, prom, segment)
|
|
320
|
+
}
|
|
321
|
+
return prom
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Creates a segment for a given handler in promise chain
|
|
327
|
+
* if `config.feature_flag.promise_segments` is true
|
|
328
|
+
* Otherwise it just returns the current if existing or gets the current
|
|
329
|
+
*
|
|
330
|
+
* @param {string} name name of segment to create
|
|
331
|
+
* @param {object} parent current parent segment
|
|
332
|
+
* @returns {object} segment
|
|
333
|
+
*/
|
|
334
|
+
function _createSegment(name, parent) {
|
|
335
|
+
return agent.config.feature_flag.promise_segments === true
|
|
336
|
+
? agent.tracer.createSegment(name, null, parent)
|
|
337
|
+
: parent || agent.tracer.getSegment()
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Performs a `wrapMethod` if and only if `methods` is truthy and has a length
|
|
343
|
+
* greater than zero.
|
|
344
|
+
*
|
|
345
|
+
* @param {object} obj - The source of the methods to wrap.
|
|
346
|
+
* @param {string} name - The name of this source.
|
|
347
|
+
* @param {string | Array} methods - The names of the methods to wrap.
|
|
348
|
+
* @param {Function} wrapper - The function which wraps the methods.
|
|
349
|
+
*/
|
|
350
|
+
function _safeWrap(obj, name, methods, wrapper) {
|
|
351
|
+
if (methods && methods.length) {
|
|
352
|
+
shimmer.wrapMethod(obj, name, methods, wrapper)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const instrumentation = require('./index')
|
|
8
|
+
|
|
9
|
+
module.exports = [
|
|
10
|
+
{
|
|
11
|
+
type: null,
|
|
12
|
+
moduleName: 'when',
|
|
13
|
+
onRequire: instrumentation
|
|
14
|
+
}
|
|
15
|
+
]
|
package/lib/instrumentations.js
CHANGED
|
@@ -37,7 +37,7 @@ module.exports = function instrumentations() {
|
|
|
37
37
|
'superagent': { module: '@newrelic/superagent' },
|
|
38
38
|
'undici': { type: MODULE_TYPE.TRANSACTION },
|
|
39
39
|
'@hapi/vision': { type: MODULE_TYPE.WEB_FRAMEWORK },
|
|
40
|
-
'when': {
|
|
40
|
+
'when': { module: './instrumentation/when' },
|
|
41
41
|
'winston': { type: MODULE_TYPE.GENERIC }
|
|
42
42
|
}
|
|
43
43
|
}
|
package/lib/shim/shim.js
CHANGED
|
@@ -14,6 +14,7 @@ const path = require('path')
|
|
|
14
14
|
const specs = require('./specs')
|
|
15
15
|
const util = require('util')
|
|
16
16
|
const symbols = require('../symbols')
|
|
17
|
+
const maybeAddCLMAttributes = require('../util/code-level-metrics')
|
|
17
18
|
|
|
18
19
|
// Some modules do terrible things, like change the prototype of functions. To
|
|
19
20
|
// avoid crashing things we'll use a cached copy of apply everywhere.
|
|
@@ -863,6 +864,7 @@ function record(nodule, properties, recordNamer) {
|
|
|
863
864
|
)
|
|
864
865
|
|
|
865
866
|
const segment = shouldCreateSegment ? _rawCreateSegment(shim, segDesc) : parent
|
|
867
|
+
maybeAddCLMAttributes(fn, segment)
|
|
866
868
|
|
|
867
869
|
return _doRecord.call(this, segment, args, segDesc, shouldCreateSegment)
|
|
868
870
|
}
|
|
@@ -677,6 +677,23 @@ function _defaultResponsePredicate() {
|
|
|
677
677
|
return false
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
+
/**
|
|
681
|
+
* Attaches a flag on function indicating that
|
|
682
|
+
* CLM attributes need to be associated with the middleware
|
|
683
|
+
* span during record
|
|
684
|
+
*
|
|
685
|
+
* Note: director middleware instrumentation passes in a string as the "function" so we have to check if the middleware is actually a function
|
|
686
|
+
* to avoid crashing.
|
|
687
|
+
*
|
|
688
|
+
* @param {Shim} shim instance of shim
|
|
689
|
+
* @param {Function} middleware function to apply clm symbol
|
|
690
|
+
*/
|
|
691
|
+
function assignCLMSymbol(shim, middleware) {
|
|
692
|
+
if (shim.isFunction(middleware) && shim.agent.config.code_level_metrics.enabled) {
|
|
693
|
+
middleware[symbols.clm] = true
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
680
697
|
/**
|
|
681
698
|
* Wraps the given function in a middleware recorder function.
|
|
682
699
|
*
|
|
@@ -735,6 +752,8 @@ function _recordMiddleware(shim, middleware, spec) {
|
|
|
735
752
|
const isErrorWare = spec.type === MIDDLEWARE_TYPE_NAMES.ERRORWARE
|
|
736
753
|
const getReq = shim.isFunction(spec.req) ? spec.req : _makeGetReq(shim, spec.req)
|
|
737
754
|
|
|
755
|
+
assignCLMSymbol(shim, middleware)
|
|
756
|
+
|
|
738
757
|
return shim.record(
|
|
739
758
|
middleware,
|
|
740
759
|
spec.promise ? middlewareWithPromiseRecorder : middlewareWithCallbackRecorder
|
package/lib/symbols.js
CHANGED