@jsreport/jsreport-core 4.4.0 → 4.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.
- package/README.md +13 -0
- package/lib/main/blobStorage/mainActions.js +11 -2
- package/lib/main/createDefaultLoggerFormat.js +11 -2
- package/lib/main/createNormalizeMetaLoggerFormat.js +21 -8
- package/lib/main/logger.js +1 -1
- package/lib/main/optionsSchema.js +1 -1
- package/lib/shared/normalizeMetaFromLogs.js +6 -4
- package/lib/shared/response.js +1 -2
- package/lib/worker/blobStorage.js +21 -7
- package/lib/worker/logger.js +1 -1
- package/lib/worker/render/executeEngine.js +39 -22
- package/lib/worker/reporter.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -282,11 +282,24 @@ jsreport.documentStore.collection('templates')
|
|
|
282
282
|
|
|
283
283
|
## Changelog
|
|
284
284
|
|
|
285
|
+
### 4.5.0
|
|
286
|
+
|
|
287
|
+
- fix blobStorage failing to save reports bigger than 1gb
|
|
288
|
+
- decrease default value of `options.profile.maxDiffSize` to `10mb`
|
|
289
|
+
- fix logs metadata for main logs
|
|
290
|
+
- fix support for using of async helper that returns a value other than string in template engines
|
|
291
|
+
- improve support for `jsreport.templateEngines.waitForAsyncHelpers` when used in async helper
|
|
292
|
+
|
|
293
|
+
### 4.4.1
|
|
294
|
+
|
|
295
|
+
- fix timestamp shown in logging
|
|
296
|
+
|
|
285
297
|
### 4.4.0
|
|
286
298
|
|
|
287
299
|
- trigger async report using header to avoid allocating worker
|
|
288
300
|
- fix require('..') and require('.') in sandbox
|
|
289
301
|
- fix component rendering in loop with async work on helpers
|
|
302
|
+
- update @jsreport/ses to 1.1.0
|
|
290
303
|
|
|
291
304
|
### 4.3.1
|
|
292
305
|
|
|
@@ -3,13 +3,22 @@ module.exports = (reporter) => {
|
|
|
3
3
|
const localReq = reporter.Request(originalReq)
|
|
4
4
|
const res = await reporter.blobStorage.read(spec.blobName, localReq)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
if (res.length < 1000 * 1000 * 10) {
|
|
7
|
+
return { content: res.toString('base64') }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { pathToFile } = await reporter.writeTempFile((uuid) => `${uuid}.blob`, res)
|
|
11
|
+
return { pathToFile }
|
|
7
12
|
})
|
|
8
13
|
|
|
9
14
|
reporter.registerMainAction('blobStorage.write', async (spec, originalReq) => {
|
|
10
15
|
const localReq = reporter.Request(originalReq)
|
|
16
|
+
if (spec.content) {
|
|
17
|
+
return await reporter.blobStorage.write(spec.blobName, Buffer.from(spec.content, 'base64'), localReq)
|
|
18
|
+
}
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
const { content } = await reporter.readTempFile(spec.pathToFile)
|
|
21
|
+
return await reporter.blobStorage.write(spec.blobName, content, localReq)
|
|
13
22
|
})
|
|
14
23
|
|
|
15
24
|
reporter.registerMainAction('blobStorage.remove', async (spec, originalReq) => {
|
|
@@ -4,8 +4,17 @@ const winston = require('winston')
|
|
|
4
4
|
|
|
5
5
|
module.exports = (options = {}) => {
|
|
6
6
|
return winston.format((info) => {
|
|
7
|
-
const { level, message, ...meta } = info
|
|
8
|
-
|
|
7
|
+
const { level, message, timestamp, ...meta } = info
|
|
8
|
+
let logDate
|
|
9
|
+
|
|
10
|
+
if (timestamp == null) {
|
|
11
|
+
logDate = new Date()
|
|
12
|
+
info.timestamp = logDate.getTime()
|
|
13
|
+
} else {
|
|
14
|
+
logDate = new Date(timestamp)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
info[MESSAGE] = `${options.timestamp === true ? `${logDate.toISOString()} - ` : ''}${level}: ${info.userLevel === true ? colors.cyan(message) : message}`
|
|
9
18
|
|
|
10
19
|
const metaKeys = Object.keys(meta)
|
|
11
20
|
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
const omit = require('lodash.omit')
|
|
1
2
|
const winston = require('winston')
|
|
3
|
+
const { LEVEL, MESSAGE, SPLAT } = require('triple-beam')
|
|
2
4
|
const normalizeMetaFromLogs = require('../shared/normalizeMetaFromLogs')
|
|
3
5
|
|
|
4
6
|
module.exports = () => {
|
|
5
7
|
return winston.format((info) => {
|
|
6
|
-
const { level, message, ...meta } = info
|
|
7
|
-
const newMeta = normalizeMetaFromLogs(level, message, meta)
|
|
8
|
+
const { level, message, timestamp: _timestamp, ...meta } = info
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const symbolProps = [LEVEL, MESSAGE, SPLAT]
|
|
11
|
+
const originalSymbolProps = {}
|
|
12
|
+
|
|
13
|
+
for (const symbolProp of symbolProps) {
|
|
14
|
+
if (info[symbolProp] != null) {
|
|
15
|
+
originalSymbolProps[symbolProp] = info[symbolProp]
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
const timestamp = _timestamp ?? new Date().getTime()
|
|
20
|
+
|
|
21
|
+
const targetMeta = omit(meta, symbolProps)
|
|
22
|
+
const newMeta = normalizeMetaFromLogs(level, message, timestamp, targetMeta)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
level,
|
|
26
|
+
message,
|
|
27
|
+
timestamp,
|
|
28
|
+
...originalSymbolProps,
|
|
29
|
+
...newMeta
|
|
30
|
+
}
|
|
18
31
|
})
|
|
19
32
|
}
|
package/lib/main/logger.js
CHANGED
|
@@ -171,6 +171,7 @@ function configureLogger (logger, _transports) {
|
|
|
171
171
|
if (options.silent) {
|
|
172
172
|
continue
|
|
173
173
|
}
|
|
174
|
+
|
|
174
175
|
const transportInstance = new TransportClass(options)
|
|
175
176
|
|
|
176
177
|
const existingTransport = logger.transports.find((t) => t.name === transportInstance.name)
|
|
@@ -238,7 +239,6 @@ class DebugTransport extends Transport {
|
|
|
238
239
|
|
|
239
240
|
this.format = options.format || winston.format.combine(
|
|
240
241
|
winston.format.colorize(),
|
|
241
|
-
normalizeMetaLoggerFormat(),
|
|
242
242
|
defaultLoggerFormat()
|
|
243
243
|
)
|
|
244
244
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const omit = require('lodash.omit')
|
|
2
2
|
|
|
3
|
-
module.exports = (level, msg, meta) => {
|
|
3
|
+
module.exports = (level, msg, timestamp, meta) => {
|
|
4
|
+
let result = meta
|
|
5
|
+
|
|
4
6
|
// detecting if meta is jsreport request object
|
|
5
7
|
if (meta != null && meta.context) {
|
|
6
8
|
meta.context.logs = meta.context.logs || []
|
|
@@ -8,7 +10,7 @@ module.exports = (level, msg, meta) => {
|
|
|
8
10
|
meta.context.logs.push({
|
|
9
11
|
level: level,
|
|
10
12
|
message: msg,
|
|
11
|
-
timestamp
|
|
13
|
+
timestamp
|
|
12
14
|
})
|
|
13
15
|
|
|
14
16
|
// TODO adding cancel looks bad, its before script is adding req.cancel()
|
|
@@ -23,8 +25,8 @@ module.exports = (level, msg, meta) => {
|
|
|
23
25
|
newMeta.id = meta.context.id
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
result = newMeta
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
return
|
|
31
|
+
return result != null && Object.keys(result).length > 0 ? result : null
|
|
30
32
|
}
|
package/lib/shared/response.js
CHANGED
|
@@ -3,7 +3,6 @@ const fs = require('fs/promises')
|
|
|
3
3
|
const { Readable } = require('stream')
|
|
4
4
|
const { pipeline } = require('stream/promises')
|
|
5
5
|
const path = require('path')
|
|
6
|
-
const isArrayBufferView = require('util').types.isArrayBufferView
|
|
7
6
|
|
|
8
7
|
module.exports = (reporter, requestId, obj) => {
|
|
9
8
|
let outputImpl = new BufferOutput(reporter)
|
|
@@ -44,7 +43,7 @@ module.exports = (reporter, requestId, obj) => {
|
|
|
44
43
|
async getSize () { return outputImpl.getSize() },
|
|
45
44
|
async writeToTempFile (...args) { return outputImpl.writeToTempFile(...args) },
|
|
46
45
|
async update (bufOrStreamOrPath) {
|
|
47
|
-
if (Buffer.isBuffer(bufOrStreamOrPath) ||
|
|
46
|
+
if (Buffer.isBuffer(bufOrStreamOrPath) || ArrayBuffer.isView(bufOrStreamOrPath)) {
|
|
48
47
|
return outputImpl.setBuffer(bufOrStreamOrPath)
|
|
49
48
|
}
|
|
50
49
|
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
module.exports = (executeMainAction) => {
|
|
1
|
+
module.exports = (executeMainAction, { writeTempFile, readTempFile }) => {
|
|
2
2
|
return {
|
|
3
3
|
async read (blobName, req) {
|
|
4
4
|
const r = await executeMainAction('blobStorage.read', {
|
|
5
5
|
blobName
|
|
6
6
|
}, req)
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
if (r.content) {
|
|
9
|
+
return Buffer.from(r.content, 'base64')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { content } = await readTempFile(r.pathToFile)
|
|
13
|
+
return content
|
|
8
14
|
},
|
|
9
15
|
|
|
10
|
-
write (blobName, content, req) {
|
|
11
|
-
|
|
12
|
-
blobName
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
async write (blobName, content, req) {
|
|
17
|
+
const message = {
|
|
18
|
+
blobName
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (content.length < 1000 * 1000 * 10) {
|
|
22
|
+
message.content = Buffer.from(content).toString('base64')
|
|
23
|
+
} else {
|
|
24
|
+
const { pathToFile } = await writeTempFile((uuid) => `${uuid}.blob`, content)
|
|
25
|
+
message.pathToFile = pathToFile
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return executeMainAction('blobStorage.write', message, req)
|
|
15
29
|
},
|
|
16
30
|
|
|
17
31
|
remove (blobName, req) {
|
package/lib/worker/logger.js
CHANGED
|
@@ -35,7 +35,7 @@ function logFn (level, profiler, ...args) {
|
|
|
35
35
|
message: util.format.apply(util, msgArgs)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const meta = normalizeMetaFromLogs(level, log.message, lastArg)
|
|
38
|
+
const meta = normalizeMetaFromLogs(level, log.message, log.timestamp, lastArg)
|
|
39
39
|
|
|
40
40
|
if (meta != null) {
|
|
41
41
|
log.meta = meta
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
const LRU = require('lru-cache')
|
|
9
9
|
const { nanoid } = require('nanoid')
|
|
10
|
+
const { AsyncLocalStorage } = require('node:async_hooks')
|
|
10
11
|
|
|
11
12
|
module.exports = (reporter) => {
|
|
13
|
+
const helperCallerAsyncLocalStorage = new AsyncLocalStorage()
|
|
12
14
|
const templatesCache = LRU(reporter.options.sandbox.cache)
|
|
13
15
|
let systemHelpersCache
|
|
14
16
|
|
|
@@ -17,7 +19,6 @@ module.exports = (reporter) => {
|
|
|
17
19
|
const contextExecutionChainMap = new Map()
|
|
18
20
|
const executionFnParsedParamsMap = new Map()
|
|
19
21
|
const executionAsyncResultsMap = new Map()
|
|
20
|
-
const executionAsyncCallChainMap = new Map()
|
|
21
22
|
const executionFinishListenersMap = new Map()
|
|
22
23
|
|
|
23
24
|
const templatingEnginesEvaluate = async (mainCall, { engine, content, helpers, data }, { entity, entitySet }, req) => {
|
|
@@ -50,7 +51,6 @@ module.exports = (reporter) => {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
executionAsyncResultsMap.delete(executionId)
|
|
53
|
-
executionAsyncCallChainMap.delete(executionId)
|
|
54
54
|
executionFinishListenersMap.delete(executionId)
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -59,6 +59,8 @@ module.exports = (reporter) => {
|
|
|
59
59
|
return templatingEnginesEvaluate(true, executionInfo, entityInfo, req)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
reporter.closeListeners.add('engineHelperCaller', () => helperCallerAsyncLocalStorage.disable())
|
|
63
|
+
|
|
62
64
|
reporter.extendProxy((proxy, req, {
|
|
63
65
|
runInSandbox,
|
|
64
66
|
context,
|
|
@@ -90,10 +92,23 @@ module.exports = (reporter) => {
|
|
|
90
92
|
const matchedPart = matchResult[0]
|
|
91
93
|
const asyncResultId = matchResult[1]
|
|
92
94
|
const result = await asyncResultMap.get(asyncResultId)
|
|
93
|
-
|
|
95
|
+
const isFullMatch = content === matchedPart
|
|
96
|
+
|
|
97
|
+
if (typeof result !== 'string' && isFullMatch) {
|
|
98
|
+
// this allows consuming async helper that returns a value other than string
|
|
99
|
+
// like an async helper that returns object and it is received as
|
|
100
|
+
// parameter of another helper
|
|
101
|
+
content = result
|
|
102
|
+
} else {
|
|
103
|
+
content = `${content.slice(0, matchResult.index)}${result}${content.slice(matchResult.index + matchedPart.length)}`
|
|
104
|
+
}
|
|
94
105
|
}
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
if (typeof content === 'string') {
|
|
108
|
+
matchResult = content.match(asyncHelperResultRegExp)
|
|
109
|
+
} else {
|
|
110
|
+
matchResult = null
|
|
111
|
+
}
|
|
97
112
|
} while (matchResult != null)
|
|
98
113
|
|
|
99
114
|
return content
|
|
@@ -103,13 +118,14 @@ module.exports = (reporter) => {
|
|
|
103
118
|
const executionId = executionChain[executionChain.length - 1]
|
|
104
119
|
|
|
105
120
|
if (executionId != null && executionAsyncResultsMap.has(executionId)) {
|
|
106
|
-
const asyncCallChainSet = executionAsyncCallChainMap.get(executionId)
|
|
107
|
-
const lastAsyncCall = [...asyncCallChainSet].pop()
|
|
108
|
-
|
|
109
121
|
const asyncResultMap = executionAsyncResultsMap.get(executionId)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
|
|
123
|
+
const callerId = helperCallerAsyncLocalStorage.getStore()
|
|
124
|
+
|
|
125
|
+
// we must exclude the caller helper because if it exists it represents some parent
|
|
126
|
+
// sync/async call that called .waitForAsyncHelpers, it is not going to be resolved at this point
|
|
127
|
+
// so we should skip it in order for the execution to not hang
|
|
128
|
+
const targetAsyncResultKeys = [...asyncResultMap.keys()].filter((key) => key !== callerId)
|
|
113
129
|
|
|
114
130
|
return Promise.all(targetAsyncResultKeys.map((k) => asyncResultMap.get(k)))
|
|
115
131
|
}
|
|
@@ -157,7 +173,6 @@ module.exports = (reporter) => {
|
|
|
157
173
|
} finally {
|
|
158
174
|
executionFnParsedParamsMap.delete(req.context.id)
|
|
159
175
|
executionAsyncResultsMap.delete(executionId)
|
|
160
|
-
executionAsyncCallChainMap.delete(executionId)
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
|
|
@@ -216,7 +231,6 @@ module.exports = (reporter) => {
|
|
|
216
231
|
const executionFn = async ({ require, console, topLevelFunctions, context }) => {
|
|
217
232
|
sandboxId = context.__sandboxId
|
|
218
233
|
const asyncResultMap = new Map()
|
|
219
|
-
const asyncCallChainSet = new Set()
|
|
220
234
|
|
|
221
235
|
if (!contextExecutionChainMap.has(sandboxId)) {
|
|
222
236
|
contextExecutionChainMap.set(sandboxId, [])
|
|
@@ -225,7 +239,6 @@ module.exports = (reporter) => {
|
|
|
225
239
|
contextExecutionChainMap.get(sandboxId).push(executionId)
|
|
226
240
|
|
|
227
241
|
executionAsyncResultsMap.set(executionId, asyncResultMap)
|
|
228
|
-
executionAsyncCallChainMap.set(executionId, asyncCallChainSet)
|
|
229
242
|
executionFinishListenersMap.set(executionId, reporter.createListenerCollection())
|
|
230
243
|
executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
|
|
231
244
|
|
|
@@ -253,7 +266,7 @@ module.exports = (reporter) => {
|
|
|
253
266
|
if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
|
|
254
267
|
wrappedTopLevelFunctions[h] = engine.wrapHelper(wrappedTopLevelFunctions[h], { context })
|
|
255
268
|
} else {
|
|
256
|
-
wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h],
|
|
269
|
+
wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], h, asyncResultMap)
|
|
257
270
|
}
|
|
258
271
|
}
|
|
259
272
|
|
|
@@ -269,7 +282,6 @@ module.exports = (reporter) => {
|
|
|
269
282
|
|
|
270
283
|
await Promise.all(keysEvaluated.map(async (k) => {
|
|
271
284
|
const result = await clonedMap.get(k)
|
|
272
|
-
asyncCallChainSet.delete(k)
|
|
273
285
|
resolvedResultsMap.set(k, `${result}`)
|
|
274
286
|
clonedMap.delete(k)
|
|
275
287
|
}))
|
|
@@ -401,20 +413,25 @@ module.exports = (reporter) => {
|
|
|
401
413
|
}
|
|
402
414
|
}
|
|
403
415
|
|
|
404
|
-
function wrapHelperForAsyncSupport (fn,
|
|
416
|
+
function wrapHelperForAsyncSupport (fn, helperName, asyncResultMap) {
|
|
405
417
|
return function (...args) {
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
const resultId = nanoid(7)
|
|
419
|
+
|
|
420
|
+
let fnResult
|
|
421
|
+
|
|
422
|
+
// make the result id available for all calls inside the helper
|
|
423
|
+
helperCallerAsyncLocalStorage.run(resultId, () => {
|
|
424
|
+
// important to call the helper with the current this to preserve the same behavior
|
|
425
|
+
fnResult = fn.call(this, ...args)
|
|
426
|
+
})
|
|
408
427
|
|
|
409
428
|
if (fnResult == null || typeof fnResult.then !== 'function') {
|
|
410
429
|
return fnResult
|
|
411
430
|
}
|
|
412
431
|
|
|
413
|
-
|
|
414
|
-
asyncResultMap.set(asyncResultId, fnResult)
|
|
415
|
-
asyncCallChainSet.add(asyncResultId)
|
|
432
|
+
asyncResultMap.set(resultId, fnResult)
|
|
416
433
|
|
|
417
|
-
return `{#asyncHelperResult ${
|
|
434
|
+
return `{#asyncHelperResult ${resultId}}`
|
|
418
435
|
}
|
|
419
436
|
}
|
|
420
437
|
|
package/lib/worker/reporter.js
CHANGED
|
@@ -54,7 +54,7 @@ class WorkerReporter extends Reporter {
|
|
|
54
54
|
await this.extensionsManager.init()
|
|
55
55
|
|
|
56
56
|
this.documentStore = DocumentStore(this._documentStoreData, this.executeMainAction.bind(this))
|
|
57
|
-
this.blobStorage = BlobStorage(this.executeMainAction.bind(this))
|
|
57
|
+
this.blobStorage = BlobStorage(this.executeMainAction.bind(this), { writeTempFile: this.writeTempFile.bind(this), readTempFile: this.readTempFile.bind(this) })
|
|
58
58
|
|
|
59
59
|
this.addRequestContextMetaConfig('rootId', { sandboxReadOnly: true })
|
|
60
60
|
this.addRequestContextMetaConfig('id', { sandboxReadOnly: true })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsreport/jsreport-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "javascript based business reporting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"report",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@node-rs/jsonwebtoken": "0.2.0",
|
|
81
|
-
"jsdom": "
|
|
81
|
+
"jsdom": "24.1.0",
|
|
82
82
|
"mocha": "10.1.0",
|
|
83
83
|
"should": "13.2.3",
|
|
84
84
|
"standard": "16.0.4",
|