@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 +8 -0
- package/lib/worker/render/executeEngine.js +54 -40
- package/package.json +1 -1
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) =>
|
|
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
|
-
|
|
69
|
-
!executionAsyncResultsMap.has(
|
|
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(
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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
|
}
|