@jsreport/jsreport-core 3.1.0 → 3.2.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.
@@ -6,7 +6,7 @@ exports.getDefaultTempDirectory = () => {
6
6
  }
7
7
 
8
8
  exports.getDefaultRootDirectory = () => {
9
- return path.join(__dirname, '../../../../')
9
+ return path.join(__dirname, '../../../../../')
10
10
  }
11
11
 
12
12
  exports.getDefaultLoadConfig = () => {
@@ -385,8 +385,8 @@ class MainReporter extends Reporter {
385
385
 
386
386
  if (
387
387
  this.options.enableRequestReportTimeout &&
388
- req.options &&
389
- req.options.timeout != null
388
+ req.options &&
389
+ req.options.timeout != null
390
390
  ) {
391
391
  reportTimeout = req
392
392
  }
@@ -435,10 +435,6 @@ class MainReporter extends Reporter {
435
435
  }
436
436
  }
437
437
 
438
- generateRequestId () {
439
- return generateRequestId()
440
- }
441
-
442
438
  registerWorkersManagerFactory (workersManagerFactory) {
443
439
  this._workersManagerFactory = workersManagerFactory
444
440
  }
@@ -459,12 +455,12 @@ class MainReporter extends Reporter {
459
455
  clearInterval(this._reaperTimerRef)
460
456
  }
461
457
 
458
+ await this.closeListeners.fire()
459
+
462
460
  if (this._workersManager) {
463
461
  await this._workersManager.close()
464
462
  }
465
463
 
466
- await this.closeListeners.fire()
467
-
468
464
  if (this.documentStore) {
469
465
  await this.documentStore.close()
470
466
  }
@@ -515,7 +511,6 @@ class MainReporter extends Reporter {
515
511
  options: req.options
516
512
  }
517
513
  }, {
518
- // TODO add worker timeout
519
514
  timeout,
520
515
  timeoutErrorMessage: options.timeoutErrorMessage || ('Timeout during worker action ' + actionName),
521
516
  executeMain: async (data) => {
@@ -6,6 +6,7 @@ const Folders = require('./folders')
6
6
  const createOrExtendError = require('./createError')
7
7
  const tempFilesHandler = require('./tempFilesHandler')
8
8
  const encryption = require('./encryption')
9
+ const generateRequestId = require('../shared/generateRequestId')
9
10
 
10
11
  class Reporter extends EventEmitter {
11
12
  constructor (options) {
@@ -46,6 +47,10 @@ class Reporter extends EventEmitter {
46
47
  return createOrExtendError(message, options)
47
48
  }
48
49
 
50
+ generateRequestId () {
51
+ return generateRequestId()
52
+ }
53
+
49
54
  /**
50
55
  * Ensures that the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory) exists by doing a mkdir call
51
56
  *
@@ -18,7 +18,7 @@ module.exports = (obj, parent) => {
18
18
  request.template = extend(true, {}, obj.template)
19
19
 
20
20
  if (parent) {
21
- request.context = Object.assign({}, request.context, omit(parent.context, ['id', 'logs', 'systemHelpers']))
21
+ request.context = Object.assign({}, request.context, omit(parent.context, ['id', 'logs']))
22
22
  request.context.isChildRequest = true
23
23
  request.options = Object.assign({}, request.options, parent.options)
24
24
 
@@ -10,32 +10,50 @@ const { nanoid } = require('nanoid')
10
10
 
11
11
  module.exports = (reporter) => {
12
12
  const cache = LRU(reporter.options.sandbox.cache || { max: 100 })
13
+
13
14
  reporter.templatingEngines = { cache }
15
+
14
16
  const executionFnParsedParamsMap = new Map()
15
17
 
18
+ const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
19
+ const engineImpl = reporter.extensionsManager.engines.find((e) => e.name === engine)
20
+
21
+ if (!engine) {
22
+ throw reporter.createError(`Engine '${engine}' not found. If this is a custom engine make sure it's properly installed from npm`, {
23
+ statusCode: 400
24
+ })
25
+ }
26
+
27
+ if (mainCall) {
28
+ executionFnParsedParamsMap.set(req.context.id, new Map())
29
+ }
30
+
31
+ try {
32
+ const res = await executeEngine({
33
+ engine: engineImpl,
34
+ content,
35
+ helpers,
36
+ data
37
+ }, { handleErrors: false, entity, entitySet }, req)
38
+
39
+ return res.content
40
+ } finally {
41
+ if (mainCall) {
42
+ executionFnParsedParamsMap.delete(req.context.id)
43
+ }
44
+ }
45
+ }
46
+
47
+ reporter.templatingEngines.evaluate = (executionInfo, entityInfo, req) => templatingEnginesEvaluate(true, executionInfo, entityInfo, req)
48
+
16
49
  reporter.extendProxy((proxy, req, {
17
50
  runInSandbox,
18
51
  context,
19
52
  getTopLevelFunctions
20
53
  }) => {
21
54
  proxy.templatingEngines = {
22
- evaluate: async ({ engine, content, helpers, data }, { entity, entitySet }) => {
23
- const engineImpl = reporter.extensionsManager.engines.find((e) => e.name === engine)
24
-
25
- if (!engine) {
26
- throw reporter.createError(`Engine '${engine}' not found. If this is a custom engine make sure it's properly installed from npm`, {
27
- statusCode: 400
28
- })
29
- }
30
-
31
- const res = await executeEngine({
32
- engine: engineImpl,
33
- content,
34
- helpers,
35
- systemHelpers: req.context.systemHelpers,
36
- data
37
- }, { handleErrors: false, entity, entitySet }, req)
38
- return res.content
55
+ evaluate: async (executionInfo, entityInfo) => {
56
+ return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
39
57
  }
40
58
  }
41
59
  })
@@ -51,7 +69,6 @@ module.exports = (reporter) => {
51
69
  engine,
52
70
  content: req.template.content,
53
71
  helpers: req.template.helpers,
54
- systemHelpers: req.context.systemHelpers,
55
72
  data: req.data
56
73
  }, {
57
74
  handleErrors: true,
@@ -63,14 +80,29 @@ module.exports = (reporter) => {
63
80
  }
64
81
  }
65
82
 
66
- async function executeEngine ({ engine, content, helpers, systemHelpers, data }, { handleErrors, entity, entitySet }, req) {
83
+ async function executeEngine ({ engine, content, helpers, data }, { handleErrors, entity, entitySet }, req) {
67
84
  let entityPath
85
+
68
86
  if (entity._id) {
69
87
  entityPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
70
88
  entityPath = entityPath.substring(0, entityPath.lastIndexOf('/'))
71
89
  }
72
90
 
73
- const joinedHelpers = systemHelpers + '\n' + helpers
91
+ const registerResults = await reporter.registerHelpersListeners.fire(req)
92
+ const systemHelpers = []
93
+
94
+ for (const result of registerResults) {
95
+ if (result == null) {
96
+ continue
97
+ }
98
+
99
+ if (typeof result === 'string') {
100
+ systemHelpers.push(result)
101
+ }
102
+ }
103
+
104
+ const systemHelpersStr = systemHelpers.join('\n')
105
+ const joinedHelpers = systemHelpersStr + '\n' + (helpers || '')
74
106
  const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${joinedHelpers}`
75
107
 
76
108
  const executionFn = async ({ require, console, topLevelFunctions }) => {
@@ -148,7 +180,7 @@ module.exports = (reporter) => {
148
180
  userCode: joinedHelpers,
149
181
  executionFn,
150
182
  currentPath: entityPath,
151
- errorLineNumberOffset: systemHelpers.split('\n').length,
183
+ errorLineNumberOffset: systemHelpersStr.split('\n').length,
152
184
  onRequire: (moduleName, { context }) => {
153
185
  if (engine.onRequire) {
154
186
  return engine.onRequire(moduleName, { context })
@@ -191,7 +223,7 @@ module.exports = (reporter) => {
191
223
 
192
224
  function wrapHelperForAsyncSupport (fn, asyncResultMap) {
193
225
  return function (...args) {
194
- // important to call the helper with the current this to preserve the same behaviour
226
+ // important to call the helper with the current this to preserve the same behavior
195
227
  const fnResult = fn.call(this, ...args)
196
228
 
197
229
  if (fnResult == null || typeof fnResult.then !== 'function') {
@@ -3,11 +3,13 @@ const path = require('path')
3
3
 
4
4
  module.exports = (reporter) => {
5
5
  let helpersScript
6
- reporter.beforeRenderListeners.add('core-helpers', async (req) => {
7
- if (!helpersScript) {
8
- helpersScript = await fs.readFile(path.join(__dirname, '../../static/helpers.js'), 'utf8')
9
- }
10
- req.context.systemHelpers += helpersScript + '\n'
6
+
7
+ reporter.registerHelpersListeners.add('core-helpers', (req) => {
8
+ return helpersScript
9
+ })
10
+
11
+ reporter.initializeListeners.add('core-helpers', async () => {
12
+ helpersScript = await fs.readFile(path.join(__dirname, '../../static/helpers.js'), 'utf8')
11
13
  })
12
14
 
13
15
  reporter.extendProxy((proxy, req, { safeRequire }) => {
@@ -11,19 +11,9 @@ const generateRequestId = require('../../shared/generateRequestId')
11
11
  const resolveReferences = require('./resolveReferences.js')
12
12
  const moduleHelper = require('./moduleHelper')
13
13
  let reportCounter = 0
14
- const fs = require('fs').promises
15
- const path = require('path')
16
14
 
17
15
  module.exports = (reporter) => {
18
- let helpersScript
19
- reporter.beforeRenderListeners.add('core-helpers', async (req) => {
20
- if (!helpersScript) {
21
- helpersScript = await fs.readFile(path.join(__dirname, '../../static/helpers.js'), 'utf8')
22
- }
23
- req.context.systemHelpers += helpersScript + '\n'
24
- })
25
16
  moduleHelper(reporter)
26
- reporter.addRequestContextMetaConfig('systemHelpers', { sandboxHidden: true })
27
17
 
28
18
  const executeEngine = ExecuteEngine(reporter)
29
19
  async function beforeRender (reporter, request, response) {
@@ -121,7 +111,6 @@ module.exports = (reporter) => {
121
111
 
122
112
  return async (req, parentReq) => {
123
113
  const request = Request(req, parentReq)
124
- request.context.systemHelpers = ''
125
114
  const response = { meta: {} }
126
115
  let renderStartProfilerEvent
127
116
  try {
@@ -141,7 +130,9 @@ module.exports = (reporter) => {
141
130
  request.context.reportCounter = ++reportCounter
142
131
  request.context.startTimestamp = new Date().getTime()
143
132
 
144
- reporter.requestModulesCache.set(request.context.id, Object.create(null))
133
+ if (parentReq == null) {
134
+ reporter.requestModulesCache.set(request.context.rootId, Object.create(null))
135
+ }
145
136
 
146
137
  reporter.logger.info(`Starting rendering request ${request.context.reportCounter} (user: ${(request.context.user ? request.context.user.username : 'null')})`, request)
147
138
 
@@ -203,7 +194,9 @@ module.exports = (reporter) => {
203
194
 
204
195
  throw e
205
196
  } finally {
206
- reporter.requestModulesCache.delete(request.context.id)
197
+ if (parentReq == null) {
198
+ reporter.requestModulesCache.delete(request.context.rootId)
199
+ }
207
200
  }
208
201
  }
209
202
  }
@@ -26,6 +26,7 @@ class WorkerReporter extends Reporter {
26
26
  this._workerActions = new Map()
27
27
  this._registerRenderAction()
28
28
 
29
+ this.registerHelpersListeners = this.createListenerCollection('registerHelpers')
29
30
  this.afterTemplatingEnginesExecutedListeners = this.createListenerCollection('afterTemplatingEnginesExecuted')
30
31
  this.validateRenderListeners = this.createListenerCollection('validateRender')
31
32
 
@@ -34,7 +34,7 @@ module.exports = (reporter) => {
34
34
  formatError: (error, moduleName) => {
35
35
  error.message += ` To be able to require custom modules you need to add to configuration { "allowLocalFilesAccess": true } or enable just specific module using { sandbox: { allowedModules": ["${moduleName}"] }`
36
36
  },
37
- modulesCache: reporter.requestModulesCache.get(req.context.id),
37
+ modulesCache: reporter.requestModulesCache.get(req.context.rootId),
38
38
  globalModules: reporter.options.sandbox.nativeModules || [],
39
39
  allowedModules: reporter.options.sandbox.allowedModules,
40
40
  propertiesConfig,
@@ -75,15 +75,22 @@ module.exports = (reporter) => {
75
75
  const functionNames = getTopLevelFunctions(userCode)
76
76
  const functionsCode = `return {${functionNames.map(h => `"${h}": ${h}`).join(',')}}`
77
77
  const executionCode = `;(async () => { ${userCode}; ${functionsCode} })()
78
- .then((topLevelFunctions) => ${executionFnName}({
79
- topLevelFunctions: {
80
- ...topLevelFunctions,
81
- ...__topLevelFunctions
82
- },
83
- require,
84
- console,
85
- context: this
86
- })).catch(__handleError);`
78
+ .then((topLevelFunctions) => {
79
+ const mergedTopLevelFunctions = { ...topLevelFunctions, ...__topLevelFunctions }
80
+
81
+ // expose top level functions to the sandbox context
82
+ // so helpers can call other helpers (from shared asset helpers, or .registerHelpers call from proxy)
83
+ for (const [topLevelFnName, topLevelFn] of Object.entries(mergedTopLevelFunctions)) {
84
+ this[topLevelFnName] = topLevelFn
85
+ }
86
+
87
+ return ${executionFnName}({
88
+ topLevelFunctions: mergedTopLevelFunctions,
89
+ require,
90
+ console,
91
+ context: this
92
+ })
93
+ }).catch(__handleError);`
87
94
 
88
95
  return run(executionCode, {
89
96
  filename: 'sandbox.js',
@@ -1,4 +1,5 @@
1
1
  const extend = require('node.extend.without.arrays')
2
+ const omit = require('lodash.omit')
2
3
 
3
4
  module.exports = (reporter) => {
4
5
  reporter.addRequestContextMetaConfig('currentFolderPath', { sandboxReadOnly: true })
@@ -35,8 +36,9 @@ module.exports = (reporter) => {
35
36
  })
36
37
  }
37
38
 
38
- // store a copy to prevent side-effects
39
- req.template = template ? extend(true, {}, template, req.template) : req.template
39
+ // store a copy to prevent side-effects, we ignore name from the req.template because it can be path "/path/to/template"
40
+ // and we want that req.template.name be always the real template name
41
+ req.template = template ? extend(true, {}, template, omit(req.template, ['name'])) : req.template
40
42
  req.template.content = req.template.content || ''
41
43
 
42
44
  reporter.logger.info(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsreport/jsreport-core",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "javascript based business reporting",
5
5
  "keywords": [
6
6
  "report",
@@ -32,7 +32,7 @@
32
32
  "@babel/code-frame": "7.12.13",
33
33
  "@babel/parser": "7.14.4",
34
34
  "@babel/traverse": "7.12.9",
35
- "@jsreport/advanced-workers": "1.1.0",
35
+ "@jsreport/advanced-workers": "1.2.0",
36
36
  "@jsreport/mingo": "2.4.1",
37
37
  "ajv": "6.12.6",
38
38
  "app-root-path": "2.0.1",
@@ -71,7 +71,7 @@
71
71
  "devDependencies": {
72
72
  "mocha": "8.2.1",
73
73
  "should": "13.2.3",
74
- "standard": "16.0.3",
74
+ "standard": "16.0.4",
75
75
  "std-mocks": "1.0.1",
76
76
  "winston-loggly-bulk": "3.2.1"
77
77
  },