@jsreport/jsreport-core 3.4.0 → 3.5.0

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.
@@ -25,13 +25,13 @@ const setupValidateShortid = require('./store/setupValidateShortid')
25
25
  const documentStoreActions = require('./store/mainActions')
26
26
  const blobStorageActions = require('./blobStorage/mainActions')
27
27
  const Reporter = require('../shared/reporter')
28
- const Request = require('../shared/request')
28
+ const Request = require('./request')
29
29
  const generateRequestId = require('../shared/generateRequestId')
30
30
  const Profiler = require('./profiler')
31
- const Monitoring = require('./monitoring')
32
31
  const migrateXlsxTemplatesToAssets = require('./migration/xlsxTemplatesToAssets')
33
32
  const migrateResourcesToAssets = require('./migration/resourcesToAssets')
34
33
  const semver = require('semver')
34
+ let reportCounter = 0
35
35
 
36
36
  class MainReporter extends Reporter {
37
37
  constructor (options, defaults) {
@@ -69,6 +69,7 @@ class MainReporter extends Reporter {
69
69
 
70
70
  this.logger = createLogger()
71
71
  this.beforeMainActionListeners = this.createListenerCollection('beforeMainAction')
72
+ this.beforeRenderWorkerAllocatedListeners = this.createListenerCollection('beforeRenderWorkerAllocated')
72
73
  }
73
74
 
74
75
  discover () {
@@ -165,9 +166,11 @@ class MainReporter extends Reporter {
165
166
  this._initializing = true
166
167
 
167
168
  if (this.compilation) {
168
- this.compilation.resource('sandbox.js', require.resolve('vm2/lib/sandbox.js'))
169
- this.compilation.resource('contextify.js', require.resolve('vm2/lib/contextify.js'))
170
- this.compilation.resource('fixasync.js', require.resolve('vm2/lib/fixasync.js'))
169
+ this.compilation.resource('vm2-events.js', require.resolve('vm2/lib/events.js'))
170
+ this.compilation.resource('vm2-resolver-compat.js', require.resolve('vm2/lib/resolver-compat.js'))
171
+ this.compilation.resource('vm2-resolver.js', require.resolve('vm2/lib/resolver.js'))
172
+ this.compilation.resource('vm2-setup-node-sandbox.js', require.resolve('vm2/lib/setup-node-sandbox.js'))
173
+ this.compilation.resource('vm2-setup-sandbox.js', require.resolve('vm2/lib/setup-sandbox.js'))
171
174
  }
172
175
 
173
176
  try {
@@ -180,7 +183,6 @@ class MainReporter extends Reporter {
180
183
  blobStorageActions(this)
181
184
  Templates(this)
182
185
  Profiler(this)
183
- Monitoring(this)
184
186
 
185
187
  this.folders = Object.assign(this.folders, Folders(this))
186
188
 
@@ -267,8 +269,6 @@ class MainReporter extends Reporter {
267
269
  name: 'none'
268
270
  })
269
271
 
270
- this.monitoring.init()
271
-
272
272
  this.logger.info('reporter initialized')
273
273
  this._initialized = true
274
274
  this._initExecution.resolve()
@@ -306,6 +306,34 @@ class MainReporter extends Reporter {
306
306
  await validateReservedName(this, c, doc)
307
307
  }
308
308
 
309
+ async _handleRenderError (req, res, err) {
310
+ if (err.code === 'WORKER_TIMEOUT') {
311
+ err.message = 'Report timeout'
312
+ if (req.context.profiling?.lastOperation != null && req.context.profiling?.entity != null) {
313
+ err.message += `. Last profiler operation: (${req.context.profiling.lastOperation.subtype}) ${req.context.profiling.lastOperation.name}`
314
+ }
315
+
316
+ if (req.context.http != null) {
317
+ const profileUrl = `${req.context.http.baseUrl}/studio/profiles/${req.context.profiling.entity._id}`
318
+ err.message += `. You can inspect and find more details here: ${profileUrl}`
319
+ }
320
+
321
+ err.weak = true
322
+ }
323
+
324
+ if (err.code === 'WORKER_ABORTED') {
325
+ err.message = 'Report cancelled'
326
+ err.weak = true
327
+ }
328
+
329
+ if (!err.logged) {
330
+ const logFn = err.weak ? this.logger.warn : this.logger.error
331
+ logFn(`Report render failed: ${err.message}${err.stack != null ? ' ' + err.stack : ''}`, req)
332
+ }
333
+ await this.renderErrorListeners.fire(req, res, err)
334
+ throw err
335
+ }
336
+
309
337
  /**
310
338
  * Main method for invoking rendering
311
339
  * render({ template: { content: 'foo', engine: 'none', recipe: 'html' }, data: { foo: 'hello' } })
@@ -315,63 +343,55 @@ class MainReporter extends Reporter {
315
343
  *
316
344
  * @public
317
345
  */
318
- async render (req, parentReq) {
346
+ async render (req, options = {}) {
319
347
  if (!this._initialized) {
320
348
  throw new Error('Not initialized, you need to call jsreport.init().then before rendering')
321
349
  }
322
350
 
323
- let options = {}
324
- if (parentReq && !parentReq.__isJsreportRequest__) {
325
- options = parentReq
326
- parentReq = null
327
- }
328
-
329
351
  req = Object.assign({}, req)
330
352
  req.context = Object.assign({}, req.context)
331
353
  req.context.rootId = req.context.rootId || generateRequestId()
332
354
  req.context.id = req.context.rootId
355
+ req.context.reportCounter = ++reportCounter
356
+ req.context.startTimestamp = new Date().getTime()
333
357
 
334
- const worker = options.worker || await this._workersManager.allocate(req, {
335
- timeout: this.options.reportTimeout
336
- })
337
-
338
- let keepWorker
358
+ let worker
339
359
  let workerAborted
360
+ let dontCloseProcessing
361
+ const res = { meta: {} }
362
+ try {
363
+ await this.beforeRenderWorkerAllocatedListeners.fire(req)
340
364
 
341
- if (options.abortEmitter) {
342
- options.abortEmitter.once('abort', () => {
343
- workerAborted = true
344
- worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
365
+ worker = await this._workersManager.allocate(req, {
366
+ timeout: this.getReportTimeout(req)
345
367
  })
346
- }
347
368
 
348
- const res = { meta: {} }
349
- try {
369
+ if (options.abortEmitter) {
370
+ options.abortEmitter.once('abort', () => {
371
+ if (workerAborted) {
372
+ return
373
+ }
374
+ workerAborted = true
375
+ worker.release(req).catch((e) => this.logger.error('Failed to release worker ' + e))
376
+ })
377
+ }
378
+
350
379
  if (workerAborted) {
351
380
  throw this.createError('Request aborted by client')
352
381
  }
353
382
 
354
- let isDataStoredInWorker = false
355
-
356
383
  if (req.rawContent) {
357
- isDataStoredInWorker = true
358
384
  const result = await worker.execute({
359
385
  actionName: 'parse',
360
386
  req,
361
387
  data: {}
362
388
  }, {
363
- timeout: this.options.reportTimeout
389
+ timeout: this.getReportTimeout(req)
364
390
  })
365
391
  req = result
366
392
  }
367
393
 
368
- req = Request(req, parentReq)
369
-
370
- if (isDataStoredInWorker) {
371
- // we unset this because we want the Request() call in worker to evaluate the data
372
- // and determine if the original was empty or not
373
- delete req.context.originalInputDataIsEmpty
374
- }
394
+ req = Request(req)
375
395
 
376
396
  // TODO: we will probably validate in the thread
377
397
  if (this.entityTypeValidator.getSchema('TemplateType') != null) {
@@ -384,29 +404,40 @@ class MainReporter extends Reporter {
384
404
  }
385
405
  }
386
406
 
387
- let reportTimeout = this.options.reportTimeout
407
+ const reportTimeout = this.getReportTimeout(req)
388
408
 
389
- if (
390
- this.options.enableRequestReportTimeout &&
391
- req.options &&
392
- req.options.timeout != null
393
- ) {
394
- reportTimeout = req.options.timeout
409
+ await this.beforeRenderListeners.fire(req, res, { worker })
410
+
411
+ if (workerAborted) {
412
+ throw this.createError('Request aborted by client')
395
413
  }
396
414
 
397
- await this.beforeRenderListeners.fire(req, res, { worker })
415
+ if (req.context.clientNotification) {
416
+ process.nextTick(async () => {
417
+ try {
418
+ const responseResult = await this.executeWorkerAction('render', {}, {
419
+ timeout: reportTimeout + this.options.reportTimeoutMargin,
420
+ worker
421
+ }, req)
422
+
423
+ Object.assign(res, responseResult)
424
+ await this.afterRenderListeners.fire(req, res)
425
+ } catch (err) {
426
+ await this._handleRenderError(req, res, err).catch((e) => {})
427
+ } finally {
428
+ if (!workerAborted) {
429
+ await worker.release(req)
430
+ }
431
+ }
432
+ })
398
433
 
399
- // this is used so far just in the reports extension
400
- // it wants to send to the client immediate response with link to the report status
401
- // but the previous steps already allocated worker which has the parsed input request
402
- // so we need to keep the worker active and let the subsequent real render call use it
403
- // we cant move the main beforeRenderListener before the worker allocation, because at that point
404
- // the request isn't parsed and we don't know the template and options
405
- if (req.context.returnResponseAndKeepWorker) {
406
- keepWorker = true
407
- res.stream = Readable.from(res.content)
408
- await this.afterRenderListeners.fire(req, res)
409
- return res
434
+ dontCloseProcessing = true
435
+ const r = {
436
+ ...req.context.clientNotification,
437
+ stream: Readable.from(req.context.clientNotification.content)
438
+ }
439
+ delete req.context.clientNotification
440
+ return r
410
441
  }
411
442
 
412
443
  if (workerAborted) {
@@ -423,33 +454,10 @@ class MainReporter extends Reporter {
423
454
  res.stream = Readable.from(res.content)
424
455
  return res
425
456
  } catch (err) {
426
- if (err.code === 'WORKER_TIMEOUT') {
427
- err.message = 'Report timeout'
428
- if (req.context.profiling?.lastOperation != null && req.context.profiling?.entity != null) {
429
- err.message += `. Last profiler operation: (${req.context.profiling.lastOperation.subtype}) ${req.context.profiling.lastOperation.name}`
430
- }
431
-
432
- if (req.context.http != null) {
433
- const profileUrl = `${req.context.http.baseUrl}/studio/profiles/${req.context.profiling.entity._id}`
434
- err.message += `. You can inspect and find more details here: ${profileUrl}`
435
- }
436
-
437
- err.weak = true
438
- }
439
-
440
- if (err.code === 'WORKER_ABORTED') {
441
- err.message = 'Report cancelled'
442
- err.weak = true
443
- }
444
-
445
- if (!err.logged) {
446
- const logFn = err.weak ? this.logger.warn : this.logger.error
447
- logFn(`Report render failed: ${err.message}${err.stack != null ? ' ' + err.stack : ''}`, req)
448
- }
449
- await this.renderErrorListeners.fire(req, res, err)
457
+ await this._handleRenderError(req, res, err)
450
458
  throw err
451
459
  } finally {
452
- if (!workerAborted && !keepWorker) {
460
+ if (worker && !workerAborted && !dontCloseProcessing) {
453
461
  await worker.release(req)
454
462
  }
455
463
  }
@@ -0,0 +1,21 @@
1
+ const extend = require('node.extend.without.arrays')
2
+
3
+ module.exports = (obj) => {
4
+ const request = Object.create({}, {
5
+ __isJsreportRequest__: {
6
+ value: true,
7
+ writable: false,
8
+ configurable: false,
9
+ enumerable: false
10
+ }
11
+ })
12
+
13
+ request.template = extend(true, {}, obj.template)
14
+
15
+ request.options = extend(true, {}, request.options, obj.options)
16
+ request.context = extend(true, {}, request.context, obj.context)
17
+ request.context.shared = extend(true, {}, request.context.shared)
18
+ request.data = obj.data
19
+
20
+ return request
21
+ }
@@ -3,6 +3,7 @@ const set = require('lodash.set')
3
3
  const hasOwn = require('has-own-deep')
4
4
  const unsetValue = require('unset-value')
5
5
  const ms = require('ms')
6
+ const bytes = require('bytes')
6
7
  const Ajv = require('ajv')
7
8
 
8
9
  const validatorCollection = new WeakMap()
@@ -126,6 +127,35 @@ class SchemaValidator {
126
127
  }
127
128
  })
128
129
 
130
+ validator.addKeyword('$jsreport-acceptsSize', {
131
+ modifying: true,
132
+ compile: (sch) => {
133
+ if (sch !== true) {
134
+ return () => true
135
+ }
136
+
137
+ return (data, dataPath, parentData, parentDataProperty) => {
138
+ if (typeof data !== 'string' && typeof data !== 'number') {
139
+ return false
140
+ }
141
+
142
+ if (typeof data === 'number') {
143
+ return true
144
+ }
145
+
146
+ const newData = bytes(data)
147
+
148
+ if (newData == null) {
149
+ return false
150
+ }
151
+
152
+ parentData[parentDataProperty] = newData
153
+
154
+ return true
155
+ }
156
+ }
157
+ })
158
+
129
159
  let rootValidate
130
160
 
131
161
  if (options.rootSchema != null) {
@@ -25,7 +25,7 @@ Settings.prototype.get = function (key) {
25
25
  }
26
26
 
27
27
  Settings.prototype.findValue = async function (key, req) {
28
- const res = await this.documentStore.collection('settings').find({ key: key }, req)
28
+ const res = await this.documentStore.collection('settings').find({ key: key }, localReqWithoutAuthorization(req))
29
29
  if (res.length !== 1) {
30
30
  return null
31
31
  }
@@ -49,7 +49,6 @@ Settings.prototype.addOrSet = async function (key, avalue, req) {
49
49
  const value = typeof avalue !== 'string' ? JSON.stringify(avalue) : avalue
50
50
 
51
51
  const updateCount = await this.documentStore.collection('settings').update({ key }, { $set: { key: key, value: value } }, localReqWithoutAuthorization(req))
52
-
53
52
  if (updateCount === 0) {
54
53
  await this.documentStore.collection('settings').insert({ key: key, value: value }, localReqWithoutAuthorization(req))
55
54
  return 1
@@ -86,16 +86,18 @@ module.exports = (entitySet, provider, model, validator, encryption, transaction
86
86
  validateEntityName(data.name)
87
87
  }
88
88
 
89
- const entityType = model.entitySets[entitySet] ? model.entitySets[entitySet].normalizedEntityTypeName : null
89
+ if (req == null || req.context.skipValidationFor !== data) {
90
+ const entityType = model.entitySets[entitySet] ? model.entitySets[entitySet].normalizedEntityTypeName : null
90
91
 
91
- if (entityType != null && validator.getSchema(entityType) != null) {
92
- const validationResult = validator.validate(entityType, data)
92
+ if (entityType != null && validator.getSchema(entityType) != null) {
93
+ const validationResult = validator.validate(entityType, data)
93
94
 
94
- if (!validationResult.valid) {
95
- throw createError(`Error when trying to insert into "${entitySet}" collection. input contain values that does not match the schema. ${validationResult.fullErrorMessage}`, {
96
- weak: true,
97
- statusCode: 400
98
- })
95
+ if (!validationResult.valid) {
96
+ throw createError(`Error when trying to insert into "${entitySet}" collection. input contain values that does not match the schema. ${validationResult.fullErrorMessage}`, {
97
+ weak: true,
98
+ statusCode: 400
99
+ })
100
+ }
99
101
  }
100
102
  }
101
103
 
@@ -372,6 +372,10 @@ const DocumentStore = (options, validator, encryption) => {
372
372
  },
373
373
 
374
374
  async beginTransaction (req) {
375
+ if (this.options.store?.transactions?.enabled === false) {
376
+ return
377
+ }
378
+
375
379
  if (req.context.storeTransaction && transactions.has(req.context.storeTransaction)) {
376
380
  throw new Error('Can not call store.beginTransaction when an active transaction already exists, make sure you are not calling store.beginTransaction more than once')
377
381
  }
@@ -386,6 +390,10 @@ const DocumentStore = (options, validator, encryption) => {
386
390
  },
387
391
 
388
392
  async commitTransaction (req) {
393
+ if (this.options.store?.transactions?.enabled === false) {
394
+ return
395
+ }
396
+
389
397
  const tranId = req.context.storeTransaction
390
398
  const tran = transactions.get(tranId)
391
399
 
@@ -400,6 +408,10 @@ const DocumentStore = (options, validator, encryption) => {
400
408
  },
401
409
 
402
410
  async rollbackTransaction (req) {
411
+ if (this.options.store?.transactions?.enabled === false) {
412
+ return
413
+ }
414
+
403
415
  const tranId = req.context.storeTransaction
404
416
  const tran = transactions.get(tranId)
405
417
 
@@ -411,6 +423,13 @@ const DocumentStore = (options, validator, encryption) => {
411
423
 
412
424
  transactions.delete(tranId)
413
425
  delete req.context.storeTransaction
426
+ },
427
+
428
+ generateId () {
429
+ if (this.provider.generateId) {
430
+ return this.provider.generateId()
431
+ }
432
+ return uuidv4()
414
433
  }
415
434
  }
416
435
 
@@ -3,10 +3,16 @@ module.exports = (reporter) => {
3
3
  reporter.initializeListeners.add('core-validate-id', () => {
4
4
  for (const c of Object.keys(reporter.documentStore.collections)) {
5
5
  reporter.documentStore.collection(c).beforeInsertListeners.add('validate-id', (doc, req) => {
6
- return validateIdForStoreChange(reporter, c, doc._id, undefined, req)
6
+ if (req == null || req.context.skipValidationFor !== doc) {
7
+ return validateIdForStoreChange(reporter, c, doc._id, undefined, req)
8
+ }
7
9
  })
8
10
 
9
11
  reporter.documentStore.collection(c).beforeUpdateListeners.add('validate-id', async (q, update, opts, req) => {
12
+ if (req != null && req.context.skipValidationFor === update) {
13
+ return
14
+ }
15
+
10
16
  if (update.$set && opts && opts.upsert === true) {
11
17
  await validateIdForStoreChange(reporter, c, update.$set._id, undefined, req)
12
18
  }
@@ -31,6 +31,10 @@ module.exports = (reporter) => {
31
31
  }
32
32
 
33
33
  async function validateShortid (reporter, collectionName, doc, originalIdValue, req) {
34
+ if (req != null && req.context.skipValidationFor === doc) {
35
+ return
36
+ }
37
+
34
38
  const shortid = doc.shortid
35
39
 
36
40
  if (!shortid) {
@@ -13,7 +13,7 @@ module.exports = (level, msg, meta) => {
13
13
 
14
14
  // TODO adding cancel looks bad, its before script is adding req.cancel()
15
15
  // excluding non relevant properties for the log
16
- const newMeta = Object.assign({}, omit(meta, ['template', 'options', 'data', 'context', 'timestamp', 'cancel']))
16
+ const newMeta = Object.assign({}, omit(meta, ['rawContent', 'template', 'options', 'data', 'context', 'timestamp', 'cancel']))
17
17
 
18
18
  if (newMeta.rootId == null && meta.context.rootId != null) {
19
19
  newMeta.rootId = meta.context.rootId
@@ -51,6 +51,22 @@ class Reporter extends EventEmitter {
51
51
  return generateRequestId()
52
52
  }
53
53
 
54
+ /**
55
+ * @public Ensures that we get the proper report timeout in case when custom timeout per request was enabled
56
+ */
57
+ getReportTimeout (req) {
58
+ const elapsedTime = req.context.startTimestamp ? (new Date().getTime() - req.context.startTimestamp) : 0
59
+ if (
60
+ this.options.enableRequestReportTimeout &&
61
+ req.options != null &&
62
+ req.options.timeout != null
63
+ ) {
64
+ return Math.max(0, req.options.timeout - elapsedTime)
65
+ }
66
+
67
+ return Math.max(0, this.options.reportTimeout - elapsedTime)
68
+ }
69
+
54
70
  /**
55
71
  * Ensures that the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory) exists by doing a mkdir call
56
72
  *
@@ -14,6 +14,7 @@ module.exports = (reporter) => {
14
14
  reporter.templatingEngines = { cache }
15
15
 
16
16
  const executionFnParsedParamsMap = new Map()
17
+ const executionAsyncResultsMap = new Map()
17
18
 
18
19
  const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
19
20
  const engineImpl = reporter.extensionsManager.engines.find((e) => e.name === engine)
@@ -28,19 +29,23 @@ module.exports = (reporter) => {
28
29
  executionFnParsedParamsMap.set(req.context.id, new Map())
29
30
  }
30
31
 
32
+ const executionId = nanoid(7)
33
+
31
34
  try {
32
35
  const res = await executeEngine({
33
36
  engine: engineImpl,
34
37
  content,
35
38
  helpers,
36
39
  data
37
- }, { handleErrors: false, entity, entitySet }, req)
40
+ }, { executionId, handleErrors: false, entity, entitySet }, req)
38
41
 
39
42
  return res.content
40
43
  } finally {
41
44
  if (mainCall) {
42
45
  executionFnParsedParamsMap.delete(req.context.id)
43
46
  }
47
+
48
+ executionAsyncResultsMap.delete(executionId)
44
49
  }
45
50
  }
46
51
 
@@ -54,6 +59,12 @@ module.exports = (reporter) => {
54
59
  proxy.templatingEngines = {
55
60
  evaluate: async (executionInfo, entityInfo) => {
56
61
  return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
62
+ },
63
+ waitForAsyncHelpers: async () => {
64
+ if (context.__executionId != null && executionAsyncResultsMap.has(context.__executionId)) {
65
+ const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
66
+ return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
67
+ }
57
68
  }
58
69
  }
59
70
  })
@@ -64,6 +75,8 @@ module.exports = (reporter) => {
64
75
  req.data.__rootDirectory = reporter.options.rootDirectory
65
76
  req.data.__parentModuleDirectory = reporter.options.parentModuleDirectory
66
77
 
78
+ const executionId = nanoid(7)
79
+
67
80
  try {
68
81
  return await executeEngine({
69
82
  engine,
@@ -71,16 +84,18 @@ module.exports = (reporter) => {
71
84
  helpers: req.template.helpers,
72
85
  data: req.data
73
86
  }, {
87
+ executionId,
74
88
  handleErrors: true,
75
89
  entity: req.template,
76
90
  entitySet: 'templates'
77
91
  }, req)
78
92
  } finally {
79
93
  executionFnParsedParamsMap.delete(req.context.id)
94
+ executionAsyncResultsMap.delete(executionId)
80
95
  }
81
96
  }
82
97
 
83
- async function executeEngine ({ engine, content, helpers, data }, { handleErrors, entity, entitySet }, req) {
98
+ async function executeEngine ({ engine, content, helpers, data }, { executionId, handleErrors, entity, entitySet }, req) {
84
99
  let entityPath
85
100
 
86
101
  if (entity._id) {
@@ -104,9 +119,14 @@ module.exports = (reporter) => {
104
119
  const joinedHelpers = systemHelpersStr + '\n' + (helpers || '')
105
120
  const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${joinedHelpers}`
106
121
 
107
- const executionFn = async ({ require, console, topLevelFunctions }) => {
122
+ const executionFn = async ({ require, console, topLevelFunctions, context }) => {
108
123
  const asyncResultMap = new Map()
109
- executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions })
124
+
125
+ context.__executionId = executionId
126
+
127
+ executionAsyncResultsMap.set(executionId, asyncResultMap)
128
+ executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
129
+
110
130
  const key = `template:${content}:${engine.name}`
111
131
 
112
132
  if (!cache.has(key)) {
@@ -143,16 +163,19 @@ module.exports = (reporter) => {
143
163
  }
144
164
 
145
165
  return {
146
- content: contentResult
166
+ // handlebars escapes single brackets before execution to prevent errors on {#asset}
167
+ // we need to unescape them later here, because at the moment the engine.execute finishes
168
+ // the async helpers aren't executed yet
169
+ content: engine.unescape ? engine.unescape(contentResult) : contentResult
147
170
  }
148
171
  }
149
172
 
150
173
  // executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
151
174
  // we store there for the particular request and component a promise and only the first component gets compiled
152
175
  if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
153
- const { require, console, topLevelFunctions } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
176
+ const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
154
177
 
155
- return executionFn({ require, console, topLevelFunctions })
178
+ return executionFn({ require, console, topLevelFunctions, context })
156
179
  } else {
157
180
  const awaiter = {}
158
181
  awaiter.promise = new Promise((resolve) => {
@@ -57,9 +57,9 @@ class Profiler {
57
57
 
58
58
  if (m.type !== 'log') {
59
59
  req.context.profiling.lastEventId = m.id
60
+ m.operationId = m.operationId || generateRequestId()
60
61
  }
61
62
 
62
- m.operationId = m.operationId || generateRequestId()
63
63
  if (m.previousOperationId == null && req.context.profiling.lastOperationId) {
64
64
  m.previousOperationId = req.context.profiling.lastOperationId
65
65
  }
@@ -72,15 +72,21 @@ class Profiler {
72
72
  let content = res.content
73
73
 
74
74
  if (content != null) {
75
- if (isbinaryfile(content)) {
75
+ if (content.length > this.reporter.options.profiler.maxResponseSize) {
76
76
  content = {
77
- content: res.content.toString('base64'),
78
- encoding: 'base64'
77
+ tooLarge: true
79
78
  }
80
79
  } else {
81
- content = {
82
- content: createPatch('res', req.context.profiling.resLastVal ? req.context.profiling.resLastVal.toString() : '', res.content.toString(), 0),
83
- encoding: 'diff'
80
+ if (isbinaryfile(content)) {
81
+ content = {
82
+ content: res.content.toString('base64'),
83
+ encoding: 'base64'
84
+ }
85
+ } else {
86
+ content = {
87
+ content: createPatch('res', req.context.profiling.resLastVal ? req.context.profiling.resLastVal.toString() : '', res.content.toString(), 0),
88
+ encoding: 'diff'
89
+ }
84
90
  }
85
91
  }
86
92
  }
@@ -93,7 +99,7 @@ class Profiler {
93
99
 
94
100
  m.req = { diff: createPatch('req', req.context.profiling.reqLastVal || '', stringifiedReq, 0) }
95
101
 
96
- req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content)) ? null : res.content.toString()
102
+ req.context.profiling.resLastVal = (res.content == null || isbinaryfile(res.content) || content.tooLarge) ? null : res.content.toString()
97
103
  req.context.profiling.resMetaLastVal = stringifiedResMeta
98
104
  req.context.profiling.reqLastVal = stringifiedReq
99
105
  }
@@ -129,10 +135,6 @@ class Profiler {
129
135
  previousOperationId: parentReq ? parentReq.context.profiling.lastOperationId : null
130
136
  }
131
137
 
132
- if (!req.context.isChildRequest) {
133
- profilerEvent.profileId = req.context.profiling.entity._id
134
- }
135
-
136
138
  return this.emit(profilerEvent, req, res)
137
139
  }
138
140