@jsreport/jsreport-core 4.2.1 → 4.2.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/README.md CHANGED
@@ -282,6 +282,14 @@ jsreport.documentStore.collection('templates')
282
282
 
283
283
  ## Changelog
284
284
 
285
+ ### 4.2.2
286
+
287
+ - fix recursive component rendering
288
+
289
+ ### 4.2.1
290
+
291
+ - response.output.update now accepts Web ReadableStream (which is what new version of puppeteer returns)
292
+
285
293
  ### 4.2.0
286
294
 
287
295
  - the response object has new structure and api, the `response.output` is new addition and contains different methods to work with the response output. the `response.content`, `response.stream` are now soft deprecated (will be removed in future versions)
@@ -14,8 +14,10 @@ module.exports = (reporter) => {
14
14
 
15
15
  reporter.templatingEngines = { cache: templatesCache }
16
16
 
17
+ const contextExecutionChainMap = new WeakMap()
17
18
  const executionFnParsedParamsMap = new Map()
18
19
  const executionAsyncResultsMap = new Map()
20
+ const executionAsyncCallChainMap = new Map()
19
21
  const executionFinishListenersMap = new Map()
20
22
 
21
23
  const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
@@ -48,11 +50,14 @@ module.exports = (reporter) => {
48
50
  }
49
51
 
50
52
  executionAsyncResultsMap.delete(executionId)
53
+ executionAsyncCallChainMap.delete(executionId)
51
54
  executionFinishListenersMap.delete(executionId)
52
55
  }
53
56
  }
54
57
 
55
- reporter.templatingEngines.evaluate = (executionInfo, entityInfo, req) => templatingEnginesEvaluate(true, executionInfo, entityInfo, req)
58
+ reporter.templatingEngines.evaluate = (executionInfo, entityInfo, req) => {
59
+ return templatingEnginesEvaluate(true, executionInfo, entityInfo, req)
60
+ }
56
61
 
57
62
  reporter.extendProxy((proxy, req, {
58
63
  runInSandbox,
@@ -64,15 +69,18 @@ module.exports = (reporter) => {
64
69
  return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
65
70
  },
66
71
  waitForAsyncHelper: async (maybeAsyncContent) => {
72
+ const executionChain = contextExecutionChainMap.get(context) || []
73
+ const executionId = executionChain[executionChain.length - 1]
74
+
67
75
  if (
68
- context.__executionId == null ||
69
- !executionAsyncResultsMap.has(context.__executionId) ||
76
+ executionId == null ||
77
+ !executionAsyncResultsMap.has(executionId) ||
70
78
  typeof maybeAsyncContent !== 'string'
71
79
  ) {
72
80
  return maybeAsyncContent
73
81
  }
74
82
 
75
- const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
83
+ const asyncResultMap = executionAsyncResultsMap.get(executionId)
76
84
  const asyncHelperResultRegExp = /{#asyncHelperResult ([^{}]+)}/
77
85
  let content = maybeAsyncContent
78
86
  let matchResult
@@ -91,18 +99,34 @@ module.exports = (reporter) => {
91
99
  return content
92
100
  },
93
101
  waitForAsyncHelpers: async () => {
94
- if (context.__executionId != null && executionAsyncResultsMap.has(context.__executionId)) {
95
- const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
96
- return Promise.all([...asyncResultMap.keys()].map((k) => asyncResultMap.get(k)))
102
+ const executionChain = contextExecutionChainMap.get(context) || []
103
+ const executionId = executionChain[executionChain.length - 1]
104
+
105
+ if (executionId != null && executionAsyncResultsMap.has(executionId)) {
106
+ const asyncCallChainSet = executionAsyncCallChainMap.get(executionId)
107
+ const lastAsyncCall = [...asyncCallChainSet].pop()
108
+
109
+ const asyncResultMap = executionAsyncResultsMap.get(executionId)
110
+ // we should exclude the last async call because if it exists it represents the parent
111
+ // async call that called .waitForAsyncHelpers, it is not going to be resolved at this point
112
+ const targetAsyncResultKeys = [...asyncResultMap.keys()].filter((key) => key !== lastAsyncCall)
113
+
114
+ return Promise.all(targetAsyncResultKeys.map((k) => asyncResultMap.get(k)))
97
115
  }
98
116
  },
99
117
  addFinishListener: (fn) => {
100
- if (executionFinishListenersMap.has(context.__executionId)) {
101
- executionFinishListenersMap.get(context.__executionId).add('finish', fn)
118
+ const executionChain = contextExecutionChainMap.get(context) || []
119
+ const executionId = executionChain[executionChain.length - 1]
120
+
121
+ if (executionId && executionFinishListenersMap.has(executionId)) {
122
+ executionFinishListenersMap.get(executionId).add('finish', fn)
102
123
  }
103
124
  },
104
125
  createAsyncHelperResult: (v) => {
105
- const asyncResultMap = executionAsyncResultsMap.get(context.__executionId)
126
+ const executionChain = contextExecutionChainMap.get(context) || []
127
+ const executionId = executionChain[executionChain.length - 1]
128
+
129
+ const asyncResultMap = executionAsyncResultsMap.get(executionId)
106
130
  const asyncResultId = nanoid(7)
107
131
  asyncResultMap.set(asyncResultId, v)
108
132
  return `{#asyncHelperResult ${asyncResultId}}`
@@ -133,6 +157,7 @@ module.exports = (reporter) => {
133
157
  } finally {
134
158
  executionFnParsedParamsMap.delete(req.context.id)
135
159
  executionAsyncResultsMap.delete(executionId)
160
+ executionAsyncCallChainMap.delete(executionId)
136
161
  }
137
162
  }
138
163
 
@@ -147,10 +172,6 @@ module.exports = (reporter) => {
147
172
  const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${normalizedHelpers}`
148
173
 
149
174
  const initFn = async (getTopLevelFunctions, compileScript) => {
150
- if (reporter.options.trustUserCode === false) {
151
- return null
152
- }
153
-
154
175
  if (systemHelpersCache != null) {
155
176
  return systemHelpersCache
156
177
  }
@@ -193,10 +214,16 @@ module.exports = (reporter) => {
193
214
 
194
215
  const executionFn = async ({ require, console, topLevelFunctions, context }) => {
195
216
  const asyncResultMap = new Map()
217
+ const asyncCallChainSet = new Set()
218
+
219
+ if (!contextExecutionChainMap.has(context)) {
220
+ contextExecutionChainMap.set(context, [])
221
+ }
196
222
 
197
- context.__executionId = executionId
223
+ contextExecutionChainMap.get(context).push(executionId)
198
224
 
199
225
  executionAsyncResultsMap.set(executionId, asyncResultMap)
226
+ executionAsyncCallChainMap.set(executionId, asyncCallChainSet)
200
227
  executionFinishListenersMap.set(executionId, reporter.createListenerCollection())
201
228
  executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
202
229
 
@@ -223,7 +250,7 @@ module.exports = (reporter) => {
223
250
  if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
224
251
  wrappedTopLevelFunctions[h] = engine.wrapHelper(wrappedTopLevelFunctions[h], { context })
225
252
  } else {
226
- wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], asyncResultMap)
253
+ wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], asyncResultMap, asyncCallChainSet)
227
254
  }
228
255
  }
229
256
 
@@ -231,11 +258,13 @@ module.exports = (reporter) => {
231
258
 
232
259
  const resolvedResultsMap = new Map()
233
260
 
234
- // we need to use the cloned map, becuase there can be a waitForAsyncHelper pending that needs the asyncResultMap values
261
+ // we need to use the cloned map, because there can be a waitForAsyncHelper pending that needs the asyncResultMap values
235
262
  const clonedMap = new Map(asyncResultMap)
236
263
  while (clonedMap.size > 0) {
237
264
  await Promise.all([...clonedMap.keys()].map(async (k) => {
238
- resolvedResultsMap.set(k, `${await clonedMap.get(k)}`)
265
+ const result = await clonedMap.get(k)
266
+ asyncCallChainSet.delete(k)
267
+ resolvedResultsMap.set(k, `${result}`)
239
268
  clonedMap.delete(k)
240
269
  }))
241
270
  }
@@ -245,7 +274,7 @@ module.exports = (reporter) => {
245
274
  contentResult = contentResult.replace(/{#asyncHelperResult ([^{}]+)}/g, (str, p1) => {
246
275
  const asyncResultId = p1
247
276
  // this can happen if a child jsreport.templatingEngines.evaluate receives an async value from outer scope
248
- // because every evaluate uses a unique map of async resuts
277
+ // because every evaluate uses a unique map of async results
249
278
  // example is the case when component receives as a value async thing
250
279
  // instead of returning "undefined" we let the outer eval to do the replace
251
280
  if (!resolvedResultsMap.has(asyncResultId)) {
@@ -257,7 +286,9 @@ module.exports = (reporter) => {
257
286
  }
258
287
  contentResult = contentResult.replace(/asyncUnresolvedHelperResult/g, 'asyncHelperResult')
259
288
 
260
- await executionFinishListenersMap.get(context.__executionId).fire()
289
+ await executionFinishListenersMap.get(executionId).fire()
290
+
291
+ contextExecutionChainMap.set(context, contextExecutionChainMap.get(context).filter((id) => id !== executionId))
261
292
 
262
293
  return {
263
294
  // handlebars escapes single brackets before execution to prevent errors on {#asset}
@@ -287,30 +318,12 @@ module.exports = (reporter) => {
287
318
  templatesCache.reset()
288
319
  }
289
320
 
290
- let helpersStr = normalizedHelpers
291
- if (reporter.options.trustUserCode === false) {
292
- const registerResults = await reporter.registerHelpersListeners.fire()
293
- const systemHelpers = []
294
-
295
- for (const result of registerResults) {
296
- if (result == null) {
297
- continue
298
- }
299
-
300
- if (typeof result === 'string') {
301
- systemHelpers.push(result)
302
- }
303
- }
304
- const systemHelpersStr = systemHelpers.join('\n')
305
- helpersStr = normalizedHelpers + '\n' + systemHelpersStr
306
- }
307
-
308
321
  try {
309
322
  return await reporter.runInSandbox({
310
323
  context: {
311
324
  ...(engine.createContext ? engine.createContext(req) : {})
312
325
  },
313
- userCode: helpersStr,
326
+ userCode: normalizedHelpers,
314
327
  initFn,
315
328
  executionFn,
316
329
  currentPath: entityPath,
@@ -363,7 +376,7 @@ module.exports = (reporter) => {
363
376
  }
364
377
  }
365
378
 
366
- function wrapHelperForAsyncSupport (fn, asyncResultMap) {
379
+ function wrapHelperForAsyncSupport (fn, asyncResultMap, asyncCallChainSet) {
367
380
  return function (...args) {
368
381
  // important to call the helper with the current this to preserve the same behavior
369
382
  const fnResult = fn.call(this, ...args)
@@ -374,6 +387,7 @@ module.exports = (reporter) => {
374
387
 
375
388
  const asyncResultId = nanoid(7)
376
389
  asyncResultMap.set(asyncResultId, fnResult)
390
+ asyncCallChainSet.add(asyncResultId)
377
391
 
378
392
  return `{#asyncHelperResult ${asyncResultId}}`
379
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsreport/jsreport-core",
3
- "version": "4.2.1",
3
+ "version": "4.2.2",
4
4
  "description": "javascript based business reporting",
5
5
  "keywords": [
6
6
  "report",