@jsreport/jsreport-core 4.1.0 → 4.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.
- package/lib/main/reporter.js +13 -17
- package/lib/main/request.js +25 -16
- package/lib/shared/reporter.js +67 -1
- package/lib/shared/reqStorage.js +20 -0
- package/lib/shared/response.js +231 -0
- package/lib/shared/tempFilesHandler.js +95 -29
- package/lib/worker/defaultProxyExtend.js +2 -4
- package/lib/worker/render/engineStream.js +97 -0
- package/lib/worker/render/executeEngine.js +32 -6
- package/lib/worker/render/profiler.js +12 -4
- package/lib/worker/render/render.js +15 -8
- package/lib/worker/reporter.js +7 -13
- package/lib/worker/sandbox/isolatedRequire.js +25 -46
- package/lib/worker/sandbox/requireSandbox.js +69 -24
- package/lib/worker/sandbox/resolveFilename.js +30 -0
- package/lib/worker/sandbox/runInSandbox.js +16 -0
- package/package.json +3 -3
- package/test/extensions/validExtensions/listeners/main.js +16 -8
- package/test/extensions/validExtensions/listeners/worker.js +81 -81
package/lib/main/reporter.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Reporter main class including all methods jsreport-core exposes.
|
|
5
5
|
*/
|
|
6
6
|
const path = require('path')
|
|
7
|
-
const { Readable } = require('stream')
|
|
8
7
|
const Reaper = require('@jsreport/reap')
|
|
9
8
|
const pkg = require('../../package.json')
|
|
10
9
|
const optionsLoad = require('./optionsLoad')
|
|
@@ -27,7 +26,7 @@ const documentStoreActions = require('./store/mainActions')
|
|
|
27
26
|
const blobStorageActions = require('./blobStorage/mainActions')
|
|
28
27
|
const Reporter = require('../shared/reporter')
|
|
29
28
|
const Request = require('./request')
|
|
30
|
-
const
|
|
29
|
+
const Response = require('../shared/response')
|
|
31
30
|
const Profiler = require('./profiler')
|
|
32
31
|
const semver = require('semver')
|
|
33
32
|
let reportCounter = 0
|
|
@@ -370,7 +369,7 @@ class MainReporter extends Reporter {
|
|
|
370
369
|
|
|
371
370
|
req = Object.assign({}, req)
|
|
372
371
|
req.context = Object.assign({}, req.context)
|
|
373
|
-
req.context.rootId = req.context.rootId || generateRequestId()
|
|
372
|
+
req.context.rootId = req.context.rootId || this.generateRequestId()
|
|
374
373
|
req.context.id = req.context.rootId
|
|
375
374
|
req.context.reportCounter = ++reportCounter
|
|
376
375
|
req.context.startTimestamp = new Date().getTime()
|
|
@@ -379,7 +378,9 @@ class MainReporter extends Reporter {
|
|
|
379
378
|
let worker
|
|
380
379
|
let workerAborted
|
|
381
380
|
let dontCloseProcessing
|
|
382
|
-
|
|
381
|
+
|
|
382
|
+
const res = Response(this, req.context.id)
|
|
383
|
+
|
|
383
384
|
try {
|
|
384
385
|
await this.beforeRenderWorkerAllocatedListeners.fire(req)
|
|
385
386
|
|
|
@@ -442,7 +443,8 @@ class MainReporter extends Reporter {
|
|
|
442
443
|
worker
|
|
443
444
|
}, req)
|
|
444
445
|
|
|
445
|
-
|
|
446
|
+
await res.parse(responseResult)
|
|
447
|
+
|
|
446
448
|
await this.afterRenderListeners.fire(req, res)
|
|
447
449
|
} catch (err) {
|
|
448
450
|
await this._handleRenderError(req, res, err).catch((e) => {})
|
|
@@ -455,11 +457,11 @@ class MainReporter extends Reporter {
|
|
|
455
457
|
})
|
|
456
458
|
|
|
457
459
|
dontCloseProcessing = true
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
460
|
+
|
|
461
|
+
const r = req.context.clientNotification
|
|
462
|
+
|
|
462
463
|
delete req.context.clientNotification
|
|
464
|
+
|
|
463
465
|
return r
|
|
464
466
|
}
|
|
465
467
|
|
|
@@ -472,16 +474,10 @@ class MainReporter extends Reporter {
|
|
|
472
474
|
worker
|
|
473
475
|
}, req)
|
|
474
476
|
|
|
475
|
-
|
|
477
|
+
await res.parse(responseResult)
|
|
476
478
|
|
|
477
479
|
await this.afterRenderListeners.fire(req, res)
|
|
478
480
|
|
|
479
|
-
if (!res.content) {
|
|
480
|
-
this.logger.error('Worker didnt return render res.content, returned:' + JSON.stringify(responseResult), req)
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
res.stream = Readable.from(res.content)
|
|
484
|
-
|
|
485
481
|
this._cleanProfileInRequest(req)
|
|
486
482
|
|
|
487
483
|
return res
|
|
@@ -551,7 +547,7 @@ class MainReporter extends Reporter {
|
|
|
551
547
|
}
|
|
552
548
|
|
|
553
549
|
async executeWorkerAction (actionName, data, options = {}, req) {
|
|
554
|
-
req.context.rootId = req.context.rootId || generateRequestId()
|
|
550
|
+
req.context.rootId = req.context.rootId || this.generateRequestId()
|
|
555
551
|
const timeout = options.timeout || 60000
|
|
556
552
|
|
|
557
553
|
const worker = options.worker
|
package/lib/main/request.js
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Request = require('../shared/request')
|
|
2
2
|
|
|
3
3
|
module.exports = (obj) => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
const targetObj = { ...obj }
|
|
5
|
+
|
|
6
|
+
const originalData = targetObj.data
|
|
7
|
+
|
|
8
|
+
delete targetObj.data
|
|
9
|
+
|
|
10
|
+
const customOriginalInputDataIsEmptyResult = {
|
|
11
|
+
defined: false,
|
|
12
|
+
value: null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (targetObj?.context != null && Object.hasOwn(targetObj.context, 'originalInputDataIsEmpty')) {
|
|
16
|
+
customOriginalInputDataIsEmptyResult.defined = true
|
|
17
|
+
customOriginalInputDataIsEmptyResult.value = targetObj.context.originalInputDataIsEmpty
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const request = Request(targetObj)
|
|
21
|
+
request.data = originalData
|
|
22
|
+
|
|
23
|
+
if (customOriginalInputDataIsEmptyResult.defined) {
|
|
24
|
+
request.context.originalInputDataIsEmpty = customOriginalInputDataIsEmptyResult.value
|
|
25
|
+
} else {
|
|
26
|
+
delete request.context.originalInputDataIsEmpty
|
|
27
|
+
}
|
|
19
28
|
|
|
20
29
|
return request
|
|
21
30
|
}
|
package/lib/shared/reporter.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const EventEmitter = require('events')
|
|
2
2
|
const createListenerCollection = require('./listenerCollection')
|
|
3
3
|
const Request = require('./request')
|
|
4
|
+
const Response = require('./response')
|
|
4
5
|
const Templates = require('./templates')
|
|
5
6
|
const Folders = require('./folders')
|
|
6
7
|
const createOrExtendError = require('./createError')
|
|
@@ -8,6 +9,7 @@ const tempFilesHandler = require('./tempFilesHandler')
|
|
|
8
9
|
const encryption = require('./encryption')
|
|
9
10
|
const generateRequestId = require('./generateRequestId')
|
|
10
11
|
const adminRequest = require('./adminRequest')
|
|
12
|
+
const ReqStorage = require('./reqStorage')
|
|
11
13
|
|
|
12
14
|
class Reporter extends EventEmitter {
|
|
13
15
|
constructor (options) {
|
|
@@ -15,7 +17,9 @@ class Reporter extends EventEmitter {
|
|
|
15
17
|
|
|
16
18
|
this.options = options || {}
|
|
17
19
|
this.Request = Request
|
|
20
|
+
this.Response = (...args) => Response(this, ...args)
|
|
18
21
|
this.adminRequest = adminRequest
|
|
22
|
+
this.reqStorage = ReqStorage(this)
|
|
19
23
|
|
|
20
24
|
// since `reporter` instance will be used for other extensions,
|
|
21
25
|
// it will quickly reach the limit of `10` listeners,
|
|
@@ -82,6 +86,27 @@ class Reporter extends EventEmitter {
|
|
|
82
86
|
return tempFilesHandler.ensureTempDirectoryExists(this.options.tempAutoCleanupDirectory)
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
getTempFilePath (filename) {
|
|
90
|
+
if (this.options.tempAutoCleanupDirectory == null) {
|
|
91
|
+
throw new Error('Can not use getTempFilename when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return tempFilesHandler.getTempFilePath(this.options.tempAutoCleanupDirectory, filename)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Synchronously reads a file from the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
99
|
+
*
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
readTempFileSync (filename, opts) {
|
|
103
|
+
if (this.options.tempAutoCleanupDirectory == null) {
|
|
104
|
+
throw new Error('Can not use readTempFileSync when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return tempFilesHandler.readTempFileSync(this.options.tempAutoCleanupDirectory, filename, opts)
|
|
108
|
+
}
|
|
109
|
+
|
|
85
110
|
/**
|
|
86
111
|
* Reads a file from the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
87
112
|
*
|
|
@@ -95,6 +120,33 @@ class Reporter extends EventEmitter {
|
|
|
95
120
|
return tempFilesHandler.readTempFile(this.options.tempAutoCleanupDirectory, filename, opts)
|
|
96
121
|
}
|
|
97
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Open temp file in jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
125
|
+
*
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
async openTempFile (filename, flags) {
|
|
129
|
+
if (this.options.tempAutoCleanupDirectory == null) {
|
|
130
|
+
throw new Error('Can not use openTempFile when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return tempFilesHandler.openTempFile(this.options.tempAutoCleanupDirectory, filename, flags)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Synchronously creates a file into the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
138
|
+
* ensuring that the directory always exists
|
|
139
|
+
*
|
|
140
|
+
* @public
|
|
141
|
+
*/
|
|
142
|
+
writeTempFileSync (filenameFn, content, opts) {
|
|
143
|
+
if (this.options.tempAutoCleanupDirectory == null) {
|
|
144
|
+
throw new Error('Can not use writeTempFileSync when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return tempFilesHandler.writeTempFileSync(this.options.tempAutoCleanupDirectory, filenameFn, content, opts)
|
|
148
|
+
}
|
|
149
|
+
|
|
98
150
|
/**
|
|
99
151
|
* Creates a file into the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
100
152
|
* ensuring that the directory always exists
|
|
@@ -114,7 +166,7 @@ class Reporter extends EventEmitter {
|
|
|
114
166
|
*
|
|
115
167
|
* @public
|
|
116
168
|
*/
|
|
117
|
-
|
|
169
|
+
readTempFileStream (filename, opts) {
|
|
118
170
|
if (this.options.tempAutoCleanupDirectory == null) {
|
|
119
171
|
throw new Error('Can not use readTempFileStream when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
120
172
|
}
|
|
@@ -136,6 +188,20 @@ class Reporter extends EventEmitter {
|
|
|
136
188
|
return tempFilesHandler.writeTempFileStream(this.options.tempAutoCleanupDirectory, filenameFn, opts)
|
|
137
189
|
}
|
|
138
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Copies a file into the jsreport auto-cleanup temp directory (options.tempAutoCleanupDirectory)
|
|
193
|
+
* ensuring that the directory always exists
|
|
194
|
+
*
|
|
195
|
+
* @public
|
|
196
|
+
*/
|
|
197
|
+
async copyFileToTempFile (srcFilePath, destFilenameFn, mode) {
|
|
198
|
+
if (this.options.tempAutoCleanupDirectory == null) {
|
|
199
|
+
throw new Error('Can not use copyToTempFile when tempAutoCleanupDirectory option is not initialized, make sure to initialize jsreport first using jsreport.init()')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return tempFilesHandler.copyFileToTempFile(this.options.tempAutoCleanupDirectory, srcFilePath, destFilenameFn, mode)
|
|
203
|
+
}
|
|
204
|
+
|
|
139
205
|
async init () {
|
|
140
206
|
this.templates = Templates(this)
|
|
141
207
|
this.folders = Folders(this)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = (reporter) => {
|
|
2
|
+
const runningReqMap = new Map()
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
set: (key, val, req) => {
|
|
6
|
+
const keyValueMap = runningReqMap.get(req.context.rootId)
|
|
7
|
+
keyValueMap.set(key, val)
|
|
8
|
+
},
|
|
9
|
+
get: (key, req) => {
|
|
10
|
+
const keyValueMap = runningReqMap.get(req.context.rootId)
|
|
11
|
+
return keyValueMap.get(key)
|
|
12
|
+
},
|
|
13
|
+
registerReq: (req) => {
|
|
14
|
+
runningReqMap.set(req.context.rootId, new Map())
|
|
15
|
+
},
|
|
16
|
+
unregisterReq: (req) => {
|
|
17
|
+
runningReqMap.delete(req.context.rootId)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const extend = require('node.extend.without.arrays')
|
|
2
|
+
const fs = require('fs/promises')
|
|
3
|
+
const { Readable } = require('stream')
|
|
4
|
+
const { pipeline } = require('stream/promises')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const isArrayBufferView = require('util').types.isArrayBufferView
|
|
7
|
+
|
|
8
|
+
module.exports = (reporter, requestId, obj) => {
|
|
9
|
+
let outputImpl = new BufferOutput(reporter)
|
|
10
|
+
let cachedStream
|
|
11
|
+
|
|
12
|
+
const response = {
|
|
13
|
+
meta: extend(true, {}, (obj || {}).meta),
|
|
14
|
+
|
|
15
|
+
/** back compatibility methods **/
|
|
16
|
+
get content () {
|
|
17
|
+
return outputImpl.getBufferSync()
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
set content (v) {
|
|
21
|
+
outputImpl.setBufferSync(Buffer.from(v))
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
get stream () {
|
|
25
|
+
if (cachedStream == null) {
|
|
26
|
+
cachedStream = outputImpl.getStream()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return cachedStream
|
|
30
|
+
},
|
|
31
|
+
/** //// back compatibility methods **/
|
|
32
|
+
|
|
33
|
+
get isInStreamingMode () {
|
|
34
|
+
return outputImpl instanceof StreamOutput
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
get __isJsreportResponse__ () {
|
|
38
|
+
return true
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
output: {
|
|
42
|
+
async getBuffer () { return outputImpl.getBuffer() },
|
|
43
|
+
async getStream () { return outputImpl.getStream() },
|
|
44
|
+
async getSize () { return outputImpl.getSize() },
|
|
45
|
+
async writeToTempFile (...args) { return outputImpl.writeToTempFile(...args) },
|
|
46
|
+
async update (bufOrStreamOrPath) {
|
|
47
|
+
if (Buffer.isBuffer(bufOrStreamOrPath) || isArrayBufferView(bufOrStreamOrPath)) {
|
|
48
|
+
return outputImpl.setBuffer(bufOrStreamOrPath)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof bufOrStreamOrPath === 'string') {
|
|
52
|
+
if (!path.isAbsolute(bufOrStreamOrPath)) {
|
|
53
|
+
throw new Error('Invalid content passed to res.output.update, when content is string it must be an absolute path')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (outputImpl instanceof BufferOutput) {
|
|
57
|
+
outputImpl = new StreamOutput(reporter, requestId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await reporter.copyFileToTempFile(bufOrStreamOrPath, outputImpl.filePath)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (isReadableStream(bufOrStreamOrPath)) {
|
|
65
|
+
if (outputImpl instanceof BufferOutput) {
|
|
66
|
+
outputImpl = new StreamOutput(reporter, requestId)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return outputImpl.setStream(bufOrStreamOrPath)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error('Invalid content passed to res.output.update')
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
serialize () {
|
|
77
|
+
return {
|
|
78
|
+
meta: extend(true, {}, this.meta),
|
|
79
|
+
output: outputImpl.serialize()
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async parse (res) {
|
|
84
|
+
Object.assign(this.meta, res.meta)
|
|
85
|
+
|
|
86
|
+
if (res.output.type === 'buffer') {
|
|
87
|
+
outputImpl = await BufferOutput.parse(reporter, res.output)
|
|
88
|
+
} else {
|
|
89
|
+
outputImpl = await StreamOutput.parse(reporter, requestId, res.output)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return response
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class BufferOutput {
|
|
98
|
+
constructor (reporter) {
|
|
99
|
+
this.reporter = reporter
|
|
100
|
+
this.buffer = Buffer.from([])
|
|
101
|
+
|
|
102
|
+
this.getBufferSync = this.getBuffer
|
|
103
|
+
this.setBufferSync = this.setBuffer
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getBuffer () {
|
|
107
|
+
return this.buffer
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setBuffer (buf) {
|
|
111
|
+
// we need to ensure that the buffer is an actually buffer instance,
|
|
112
|
+
// so when receiving Uint8Array we convert it to a buffer
|
|
113
|
+
this.buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
writeToTempFile (tmpNameFn) {
|
|
117
|
+
return this.reporter.writeTempFile(tmpNameFn, this.buffer)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getSize () {
|
|
121
|
+
return this.buffer.length
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getStream () {
|
|
125
|
+
return Readable.from(this.buffer)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
serialize () {
|
|
129
|
+
const sharedBuf = new SharedArrayBuffer(this.buffer.byteLength)
|
|
130
|
+
const buf = Buffer.from(sharedBuf)
|
|
131
|
+
|
|
132
|
+
this.buffer.copy(buf)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
type: 'buffer',
|
|
136
|
+
content: buf
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static parse (reporter, output) {
|
|
141
|
+
const instance = new BufferOutput(reporter)
|
|
142
|
+
if (output?.content?.length) {
|
|
143
|
+
instance.setBufferSync(Buffer.from(output?.content))
|
|
144
|
+
}
|
|
145
|
+
return instance
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class StreamOutput {
|
|
150
|
+
constructor (reporter, requestId) {
|
|
151
|
+
this.reporter = reporter
|
|
152
|
+
this.filename = `response-${requestId}.raw-content`
|
|
153
|
+
const { pathToFile } = this.reporter.getTempFilePath(this.filename)
|
|
154
|
+
this.filePath = pathToFile
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getBuffer () {
|
|
158
|
+
const { content } = await this.reporter.readTempFile(this.filename)
|
|
159
|
+
return content
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setBuffer (buf) {
|
|
163
|
+
return this.reporter.writeTempFile(this.filename, buf)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getBufferSync () {
|
|
167
|
+
const { content } = this.reporter.readTempFileSync(this.filename)
|
|
168
|
+
return content
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
setBufferSync (buf) {
|
|
172
|
+
this.reporter.writeTempFileSync(this.filename, buf)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeToTempFile (tmpNameFn) {
|
|
176
|
+
return this.reporter.copyFileToTempFile(this.filePath, tmpNameFn)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getSize () {
|
|
180
|
+
const stat = await fs.stat(this.filePath)
|
|
181
|
+
return stat.size
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getStream () {
|
|
185
|
+
const reporter = this.reporter
|
|
186
|
+
const filename = this.filename
|
|
187
|
+
|
|
188
|
+
async function * generateResponseContent () {
|
|
189
|
+
const responseFileStream = reporter.readTempFileStream(filename).stream
|
|
190
|
+
|
|
191
|
+
for await (const chunk of responseFileStream) {
|
|
192
|
+
yield chunk
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// we produce a new Readable stream to avoid exposing the file stream directly
|
|
197
|
+
return Readable.from(generateResponseContent())
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async setStream (stream) {
|
|
201
|
+
const { stream: responseFileStream } = await this.reporter.writeTempFileStream(this.filename)
|
|
202
|
+
await pipeline(stream, responseFileStream)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
serialize () {
|
|
206
|
+
return {
|
|
207
|
+
type: 'stream',
|
|
208
|
+
filePath: this.filePath
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static async parse (reporter, requestId, output) {
|
|
213
|
+
const instance = new StreamOutput(reporter, requestId)
|
|
214
|
+
|
|
215
|
+
if (output.filePath !== instance.filePath) {
|
|
216
|
+
await reporter.copyFileToTempFile(output.filePath, instance.filePath)
|
|
217
|
+
}
|
|
218
|
+
return instance
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// from https://github.com/sindresorhus/is-stream/blob/main/index.js
|
|
223
|
+
function isReadableStream (stream) {
|
|
224
|
+
return (
|
|
225
|
+
stream !== null &&
|
|
226
|
+
typeof stream === 'object' &&
|
|
227
|
+
typeof stream.pipe === 'function' &&
|
|
228
|
+
stream.readable !== false && typeof stream._read === 'function' &&
|
|
229
|
+
typeof stream._readableState === 'object'
|
|
230
|
+
)
|
|
231
|
+
}
|
|
@@ -13,8 +13,34 @@ module.exports.ensureTempDirectoryExists = async function (tempDirectory) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
module.exports.getTempFilePath = getTempFilePath
|
|
17
|
+
|
|
18
|
+
module.exports.readTempFileSync = function readTempFileSync (tempDirectory, filename, opts = {}) {
|
|
19
|
+
const { pathToFile } = getTempFilePath(tempDirectory, filename)
|
|
20
|
+
|
|
21
|
+
const content = fs.readFileSync(pathToFile, opts)
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
pathToFile,
|
|
25
|
+
filename,
|
|
26
|
+
content
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports.openTempFile = async function (tempDirectory, filenameFn, flags) {
|
|
31
|
+
const { pathToFile, filename } = getTempFilePath(tempDirectory, filenameFn)
|
|
32
|
+
|
|
33
|
+
const fileHandle = await fsAsync.open(pathToFile, flags)
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
pathToFile,
|
|
37
|
+
filename,
|
|
38
|
+
fileHandle
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
module.exports.readTempFile = async function readTempFile (tempDirectory, filename, opts = {}) {
|
|
17
|
-
const pathToFile =
|
|
43
|
+
const { pathToFile } = getTempFilePath(tempDirectory, filename)
|
|
18
44
|
|
|
19
45
|
const content = await fsAsync.readFile(pathToFile, opts)
|
|
20
46
|
|
|
@@ -25,51 +51,91 @@ module.exports.readTempFile = async function readTempFile (tempDirectory, filena
|
|
|
25
51
|
}
|
|
26
52
|
}
|
|
27
53
|
|
|
28
|
-
module.exports.readTempFileStream =
|
|
29
|
-
const pathToFile =
|
|
54
|
+
module.exports.readTempFileStream = function readTempFileStream (tempDirectory, filename, opts = {}) {
|
|
55
|
+
const { pathToFile } = getTempFilePath(tempDirectory, filename)
|
|
30
56
|
|
|
31
|
-
|
|
32
|
-
const stream = fs.createReadStream(pathToFile, opts)
|
|
57
|
+
const stream = fs.createReadStream(pathToFile, opts)
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
return {
|
|
60
|
+
pathToFile,
|
|
61
|
+
filename,
|
|
62
|
+
stream
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports.writeTempFileSync = function writeTempFileSync (tempDirectory, filenameOrFn, content, opts = {}) {
|
|
67
|
+
return writeFileSync(tempDirectory, filenameOrFn, content, opts)
|
|
40
68
|
}
|
|
41
69
|
|
|
42
|
-
module.exports.writeTempFile = async function writeTempFile (tempDirectory,
|
|
43
|
-
return writeFile(tempDirectory,
|
|
70
|
+
module.exports.writeTempFile = async function writeTempFile (tempDirectory, filenameOrFn, content, opts = {}) {
|
|
71
|
+
return writeFile(tempDirectory, filenameOrFn, content, opts)
|
|
44
72
|
}
|
|
45
73
|
|
|
46
|
-
module.exports.writeTempFileStream = async function writeTempFileStream (tempDirectory,
|
|
47
|
-
return writeFile(tempDirectory,
|
|
74
|
+
module.exports.writeTempFileStream = async function writeTempFileStream (tempDirectory, filenameOrFn, opts = {}) {
|
|
75
|
+
return writeFile(tempDirectory, filenameOrFn, undefined, opts, true)
|
|
48
76
|
}
|
|
49
77
|
|
|
50
|
-
async function
|
|
51
|
-
const filename =
|
|
78
|
+
module.exports.copyFileToTempFile = async function copyFileToTempFile (tempDirectory, srcFilePath, destFilenameOrFn, mode) {
|
|
79
|
+
const { pathToFile, filename } = getTempFilePath(tempDirectory, destFilenameOrFn)
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
await fsAsync.mkdir(tempDirectory, {
|
|
82
|
+
recursive: true
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
await fsAsync.copyFile(srcFilePath, pathToFile, mode)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
pathToFile,
|
|
89
|
+
filename
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getTempFilePath (tempDirectory, filenameOrFn) {
|
|
94
|
+
const filenameResult = typeof filenameOrFn === 'function' ? filenameOrFn(uuidv4()) : filenameOrFn
|
|
95
|
+
|
|
96
|
+
if (filenameResult == null || filenameResult === '') {
|
|
97
|
+
throw new Error('No valid filename')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const pathToFile = path.isAbsolute(filenameResult) ? filenameResult : path.join(tempDirectory, filenameResult)
|
|
101
|
+
const filename = path.basename(pathToFile)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
pathToFile,
|
|
105
|
+
filename
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeFileSync (tempDirectory, filenameOrFn, content, opts) {
|
|
110
|
+
const { pathToFile, filename } = getTempFilePath(tempDirectory, filenameOrFn)
|
|
111
|
+
|
|
112
|
+
fs.mkdirSync(tempDirectory, {
|
|
113
|
+
recursive: true
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
fs.writeFileSync(pathToFile, content, opts)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
pathToFile,
|
|
120
|
+
filename
|
|
55
121
|
}
|
|
122
|
+
}
|
|
56
123
|
|
|
57
|
-
|
|
124
|
+
async function writeFile (tempDirectory, filenameOrFn, content, opts, asStream = false) {
|
|
125
|
+
const { pathToFile, filename } = getTempFilePath(tempDirectory, filenameOrFn)
|
|
58
126
|
|
|
59
127
|
await fsAsync.mkdir(tempDirectory, {
|
|
60
128
|
recursive: true
|
|
61
129
|
})
|
|
62
130
|
|
|
63
131
|
if (asStream === true) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
})
|
|
132
|
+
const stream = fs.createWriteStream(pathToFile, opts)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
pathToFile,
|
|
136
|
+
filename,
|
|
137
|
+
stream
|
|
138
|
+
}
|
|
73
139
|
} else {
|
|
74
140
|
await fsAsync.writeFile(pathToFile, content, opts)
|
|
75
141
|
|