web_plsql 0.17.0 → 0.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web_plsql",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "author": "Dieter Oberkofler <dieter.oberkofler@gmail.com>",
5
5
  "license": "MIT",
6
6
  "description": "The Express Middleware for Oracle PL/SQL",
@@ -44,10 +44,12 @@
44
44
  "type": "module",
45
45
  "scripts": {
46
46
  "lint": "prettier --check --experimental-cli . && eslint . && tsc --noEmit",
47
- "test": "node --test --test-concurrency=1 \"./tests/**/*.test.js\"",
47
+ "test": "vitest run",
48
+ "test:coverage": "vitest run --coverage",
48
49
  "types": "rm -rf types && tsc --noEmit false --declaration true --declarationDir types --emitDeclarationOnly true --allowJs true --target es2020 --module es2020 --moduleResolution node --allowSyntheticDefaultImports true src/*.js",
49
50
  "clean": "shx rm -f *.tgz && shx rm -f *.log",
50
- "ci": "npm run clean && npm run lint && npm run test && npm run types",
51
+ "ci": "npm run clean && npm run version && npm run types && npm run lint && npm run test:coverage",
52
+ "version": "node update-version.js",
51
53
  "create-package": "shx rm -f *.tgz && npm pack",
52
54
  "image-build": "docker build --no-cache --progress=plain --tag=web_plsql .",
53
55
  "image-save": "docker save web_plsql | gzip > web_plsql.tar.gz"
@@ -56,7 +58,6 @@
56
58
  "oracledb": "6.10.0"
57
59
  },
58
60
  "dependencies": {
59
- "basic-auth": "2.0.1",
60
61
  "compression": "1.8.1",
61
62
  "cookie-parser": "1.4.7",
62
63
  "debug": "4.4.3",
@@ -64,25 +65,27 @@
64
65
  "http-parser-js": "0.5.10",
65
66
  "morgan": "1.10.1",
66
67
  "multer": "2.0.2",
67
- "rotating-file-stream": "3.2.7",
68
+ "rotating-file-stream": "3.2.8",
68
69
  "zod": "4.3.6"
69
70
  },
70
71
  "devDependencies": {
71
- "@types/basic-auth": "1.1.8",
72
72
  "@types/compression": "1.8.1",
73
73
  "@types/cookie-parser": "1.4.10",
74
74
  "@types/debug": "4.1.12",
75
75
  "@types/morgan": "1.9.10",
76
76
  "@types/multer": "2.0.0",
77
- "@types/node": "25.0.10",
77
+ "@types/node": "25.2.1",
78
78
  "@types/oracledb": "6.10.1",
79
79
  "@types/supertest": "6.0.3",
80
+ "@vitest/coverage-v8": "^4.0.18",
80
81
  "eslint": "9.39.2",
81
- "eslint-plugin-jsdoc": "62.4.1",
82
+ "eslint-plugin-jsdoc": "62.5.2",
83
+ "oracledb": "6.10.0",
82
84
  "prettier": "3.8.1",
83
85
  "shx": "0.4.0",
84
86
  "supertest": "7.2.2",
85
87
  "typescript": "5.9.3",
86
- "typescript-eslint": "8.53.1"
88
+ "typescript-eslint": "8.54.0",
89
+ "vitest": "^4.0.18"
87
90
  }
88
91
  }
@@ -109,7 +109,7 @@ export const getCGI = (req, doctable, cgi) => {
109
109
  const CGI = {
110
110
  SERVER_PORT: typeof req.socket.localPort === 'number' ? req.socket.localPort.toString() : '',
111
111
  REQUEST_METHOD: req.method,
112
- PATH_INFO: req.params.name,
112
+ PATH_INFO: Array.isArray(req.params.name) ? req.params.name[0] : req.params.name,
113
113
  SCRIPT_NAME: PATH.script,
114
114
  REMOTE_ADDR: (req.ip ?? '').replace('::ffff:', ''),
115
115
  SERVER_PROTOCOL: `${PROTOCOL}/${req.httpVersion}`,
@@ -36,30 +36,26 @@ const getError = (req, error) => {
36
36
  // what type of Error did we receive
37
37
  if (error instanceof ProcedureError) {
38
38
  timestamp = error.timestamp;
39
- /* istanbul ignore next */
40
39
  message = error.stack ?? '';
41
40
  environment = error.environment;
42
41
  sql = error.sql;
43
42
  bind = error.bind;
44
43
  } else if (error instanceof RequestError) {
45
44
  timestamp = error.timestamp;
46
- /* istanbul ignore next */
47
45
  message = error.stack ?? '';
48
46
  } else if (error instanceof Error) {
49
- /* istanbul ignore next */
50
47
  message = errorToString(error);
51
48
  } else {
52
49
  if (typeof error === 'string') {
53
- /* istanbul ignore next */
54
50
  message = `${error}\n`;
55
51
  }
52
+ /* v8 ignore start - unreachable code: creating Error without throwing */
56
53
  try {
57
- /* istanbul ignore next */
58
54
  new Error();
59
55
  } catch (err) {
60
- /* istanbul ignore next */
61
56
  message += errorToString(err);
62
57
  }
58
+ /* v8 ignore stop */
63
59
  }
64
60
 
65
61
  return getFormattedMessage({type: 'error', timestamp, message, req, environment, sql, bind});
@@ -55,7 +55,7 @@ export const parsePage = (text) => {
55
55
  {
56
56
  const cookie = parseCookie(header.value);
57
57
  debug(`oracle header "set-cookie" with value "${header.value}" was received has been parsed to ${JSON.stringify(cookie)}`);
58
- /* istanbul ignore else */
58
+ /* v8 ignore else - parseCookie validation ensures non-null */
59
59
  if (cookie !== null) {
60
60
  page.head.cookies.push(cookie);
61
61
  } else {
@@ -72,7 +72,7 @@ export const parsePage = (text) => {
72
72
  case 'x-db-content-length':
73
73
  {
74
74
  const contentLength = parseInt(header.value, 10);
75
- /* istanbul ignore else */
75
+ /* v8 ignore else - parseInt validation */
76
76
  if (!Number.isNaN(contentLength)) {
77
77
  page.head.contentLength = contentLength;
78
78
  debug(`oracle header "x-db-content-length" with value "${page.head.contentLength}" was parsed`);
@@ -85,12 +85,12 @@ export const parsePage = (text) => {
85
85
  case 'status':
86
86
  {
87
87
  const statusCode = parseInt(header.value, 10);
88
- /* istanbul ignore else */
88
+ /* v8 ignore else - parseInt validation */
89
89
  if (!Number.isNaN(statusCode)) {
90
90
  page.head.statusCode = statusCode;
91
91
  debug(`oracle header "status" with value "${page.head.statusCode}" was parsed`);
92
92
  const index = header.value.indexOf(' ');
93
- /* istanbul ignore else */
93
+ /* v8 ignore else - status code may not have description */
94
94
  if (index !== -1) {
95
95
  page.head.statusDescription = header.value.substring(index + 1);
96
96
  }
@@ -143,7 +143,7 @@ const getHeader = (line) => {
143
143
  */
144
144
  const parseCookie = (text) => {
145
145
  // validate
146
- /* istanbul ignore next */
146
+ /* v8 ignore next - input validation */
147
147
  if (typeof text !== 'string' || text.trim().length === 0) {
148
148
  return null;
149
149
  }
@@ -156,7 +156,7 @@ const parseCookie = (text) => {
156
156
 
157
157
  // get name and value
158
158
  const index = cookieElements[0].indexOf('=');
159
- /* istanbul ignore next */
159
+ /* v8 ignore next - cookie format validation */
160
160
  if (index <= 0) {
161
161
  // if the index is -1, there is no equal sign and if it's 0 the name is empty
162
162
  return null;
@@ -179,7 +179,7 @@ const parseCookie = (text) => {
179
179
  } else if (element.toLowerCase().startsWith('domain=')) {
180
180
  cookie.options.domain = element.substring(7);
181
181
  } else if (element.toLowerCase().startsWith('secure=')) {
182
- /* istanbul ignore next */
182
+ /* v8 ignore next - secure cookie attribute */
183
183
  cookie.options.secure = true;
184
184
  } else if (element.toLowerCase().startsWith('expires=')) {
185
185
  const date = tryDecodeDate(element.substring(8));
@@ -203,7 +203,7 @@ const tryDecodeDate = (value) => {
203
203
  try {
204
204
  return new Date(value);
205
205
  } catch (err) {
206
- /* istanbul ignore next */
206
+ /* v8 ignore next - invalid date format */
207
207
  return null;
208
208
  }
209
209
  };
@@ -9,7 +9,6 @@ import oracledb from 'oracledb';
9
9
  import stream from 'node:stream';
10
10
  import z from 'zod';
11
11
 
12
- import {streamToBuffer} from './stream.js';
13
12
  import {uploadFile} from './upload.js';
14
13
  import {getProcedureVariable} from './procedureVariable.js';
15
14
  import {getProcedureNamed} from './procedureNamed.js';
@@ -82,7 +81,7 @@ const procedurePrepare = async (cgiObj, databaseConnection) => {
82
81
  try {
83
82
  await databaseConnection.execute(sqlStatement);
84
83
  } catch (err) {
85
- throw new ProcedureError(`Error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, {});
84
+ throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, {});
86
85
  }
87
86
 
88
87
  // htbuf_len: reduce this limit based on your worst-case character size.
@@ -99,7 +98,7 @@ const procedurePrepare = async (cgiObj, databaseConnection) => {
99
98
  try {
100
99
  await databaseConnection.execute(sqlStatement, bindParameter);
101
100
  } catch (err) {
102
- throw new ProcedureError(`Error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, bindParameter);
101
+ throw new ProcedureError(`procedurePrepare: error when preparing procedure\n${errorToString(err)}`, cgiObj, sqlStatement, bindParameter);
103
102
  }
104
103
  };
105
104
 
@@ -116,7 +115,7 @@ const procedureExecute = async (para, databaseConnection) => {
116
115
  try {
117
116
  await databaseConnection.execute(sqlStatement, para.bind);
118
117
  } catch (err) {
119
- throw new ProcedureError(`Error when executing procedure:\n${sqlStatement}\n${errorToString(err)}`, {}, para.sql, para.bind);
118
+ throw new ProcedureError(`procedureExecute: error when executing procedure:\n${sqlStatement}\n${errorToString(err)}`, {}, para.sql, para.bind);
120
119
  }
121
120
  };
122
121
 
@@ -144,18 +143,18 @@ const procedureGetPage = async (test, databaseConnection) => {
144
143
  result = await databaseConnection.execute(sqlStatement, bindParameter);
145
144
  } catch (err) {
146
145
  if (debug.enabled) {
147
- debug(getBlock('results', inspect(result)));
146
+ debug(getBlock('procedureGetPage: results', inspect(result)));
148
147
  }
149
148
 
150
- throw new ProcedureError(`Error when getting page returned by procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
149
+ throw new ProcedureError(`procedureGetPage: error when getting page returned by procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
151
150
  }
152
151
 
153
152
  const {lines, irows} = z.object({irows: z.number(), lines: z.array(z.string())}).parse(result.outBinds);
154
153
 
155
154
  // Make sure that we have retrieved all the rows
156
155
  if (irows > MAX_IROWS) {
157
- /* istanbul ignore next */
158
- throw new ProcedureError(`Error when retrieving rows. irows="${irows}"`, {}, sqlStatement, bindParameter);
156
+ /* v8 ignore next - defensive check for row limit */
157
+ throw new ProcedureError(`procedureGetPage: error when retrieving rows. irows="${irows}"`, {}, sqlStatement, bindParameter);
159
158
  }
160
159
 
161
160
  return lines.join('');
@@ -199,10 +198,10 @@ END;
199
198
  result = await databaseConnection.execute(sqlStatement, bindParameter);
200
199
  } catch (err) {
201
200
  if (debug.enabled) {
202
- debug(getBlock('results', inspect(result)));
201
+ debug(getBlock('procedureDownloadFiles: results', inspect(result)));
203
202
  }
204
203
 
205
- throw new ProcedureError(`Error when downloading files of procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
204
+ throw new ProcedureError(`procedureDownloadFiles: error when downloading files\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
206
205
  }
207
206
 
208
207
  return z
@@ -233,12 +232,9 @@ END;
233
232
  * @returns {Promise<void>} Promise resolving to the page content generated by the executed procedure
234
233
  */
235
234
  export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, options, databaseConnection) => {
236
- debug('invokeProcedure: ENTER');
237
-
238
- //
239
- // UPLOAD FILES
240
- //
235
+ debug('invokeProcedure: begin');
241
236
 
237
+ // 1) upload files
242
238
  debug(`invokeProcedure: upload "${filesToUpload.length}" files`);
243
239
  if (filesToUpload.length > 0) {
244
240
  if (typeof options.documentTable === 'string' && options.documentTable.length > 0) {
@@ -250,71 +246,65 @@ export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, o
250
246
  }
251
247
  }
252
248
 
253
- //
254
- // GET SQL STATEMENT AND ARGUMENTS
255
- //
249
+ // 2) get procedure to execute and the arguments
256
250
 
257
- const para = await getProcedure(req, req.params.name, argObj, options, databaseConnection);
251
+ debug('invokeProcedure: get procedure to execute and the arguments');
252
+ const para = await getProcedure(req, Array.isArray(req.params.name) ? req.params.name[0] : req.params.name, argObj, options, databaseConnection);
258
253
 
259
- //
260
- // PROCEDURE PREPARE
261
- //
254
+ // 3) prepare the session
262
255
 
256
+ debug('invokeProcedure: prepare the session');
263
257
  await procedurePrepare(cgiObj, databaseConnection);
264
258
 
265
- //
266
- // PROCEDURE EXECUTE
267
- //
259
+ // 4) execute the procedure
268
260
 
261
+ debug('invokeProcedure: execute the session');
269
262
  await procedureExecute(para, databaseConnection);
270
263
 
271
- //
272
- // PROCEDURE GET PAGE
273
- //
264
+ // 5) get the page returned from the procedure
274
265
 
266
+ debug('invokeProcedure: get the page returned from the procedure');
275
267
  const lines = await procedureGetPage(true, databaseConnection);
276
268
  if (debug.enabled) {
277
269
  debug(getBlock('data', lines));
278
270
  }
279
271
 
280
- //
281
- // PROCEDURE DOWNLOAD FILE
282
- //
272
+ // 6) download files
283
273
 
274
+ debug('invokeProcedure: download files');
284
275
  const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
285
- const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
286
- if (debug.enabled) {
287
- debug(getBlock('data', inspect(fileDownload)));
288
- }
289
276
 
290
- //
291
- // PARSE PAGE
292
- //
277
+ try {
278
+ const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
279
+ if (debug.enabled) {
280
+ debug(getBlock('fileDownload', inspect({fileType: fileDownload.fileType, fileSize: fileDownload.fileSize})));
281
+ }
293
282
 
294
- // parse what we received from PL/SQL
295
- const pageComponents = parsePage(lines);
283
+ // 7) parse the page
296
284
 
297
- // add "Server" header
298
- pageComponents.head.server = cgiObj.SERVER_SOFTWARE;
285
+ debug('invokeProcedure: parse the page');
286
+ const pageComponents = parsePage(lines);
299
287
 
300
- // add file download information
301
- if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
302
- pageComponents.file.fileType = fileDownload.fileType;
303
- pageComponents.file.fileSize = fileDownload.fileSize;
304
- pageComponents.file.fileBlob = await streamToBuffer(fileDownload.fileBlob);
305
- }
288
+ // add "Server" header
289
+ pageComponents.head.server = cgiObj.SERVER_SOFTWARE;
306
290
 
307
- //
308
- // SEND THE RESPONSE
309
- //
291
+ // add file download information
292
+ if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
293
+ pageComponents.file.fileType = fileDownload.fileType;
294
+ pageComponents.file.fileSize = fileDownload.fileSize;
295
+ pageComponents.file.fileBlob = fileDownload.fileBlob;
296
+ }
310
297
 
311
- sendResponse(req, res, pageComponents);
298
+ // 8) send the page to browser
312
299
 
313
- //
314
- // CLEANUP
315
- //
300
+ debug('invokeProcedure: send the page to browser');
301
+ await sendResponse(req, res, pageComponents);
302
+ } finally {
303
+ // 9) cleanup
316
304
 
317
- fileBlob.destroy();
305
+ debug('invokeProcedure: cleanup');
306
+ fileBlob.destroy();
307
+ }
318
308
 
319
- debug('invokeProcedure: EXIT');
309
+ debug('invokeProcedure: end');
320
310
  };
@@ -89,9 +89,7 @@ const loadArguments = async (procedure, databaseConnection) => {
89
89
  result = await databaseConnection.execute(SQL_GET_ARGUMENT, bind);
90
90
  } catch (err) {
91
91
  debug('result', result);
92
- /* istanbul ignore next */
93
92
  const message = `Error when retrieving arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
94
- /* istanbul ignore next */
95
93
  throw new RequestError(message);
96
94
  }
97
95
 
@@ -106,14 +104,11 @@ const loadArguments = async (procedure, databaseConnection) => {
106
104
  .parse(result.outBinds);
107
105
  } catch (err) {
108
106
  debug('result.outBinds', result.outBinds);
109
- /* istanbul ignore next */
110
107
  const message = `Error when decoding arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
111
- /* istanbul ignore next */
112
108
  throw new RequestError(message);
113
109
  }
114
110
 
115
111
  if (data.names.length !== data.types.length) {
116
- /* istanbul ignore next */
117
112
  throw new RequestError('Error when decoding arguments. The number of names and types does not match');
118
113
  }
119
114
 
@@ -128,9 +128,7 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
128
128
  result = await databaseConnection.execute(SQL, bind);
129
129
  } catch (err) {
130
130
  debug('result', result);
131
- /* istanbul ignore next */
132
131
  const message = `Error when validating procedure name "${procName}"\n${SQL}\n${errorToString(err)}`;
133
- /* istanbul ignore next */
134
132
  throw new RequestError(message);
135
133
  }
136
134
 
@@ -139,9 +137,7 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
139
137
  return data.valid === 1;
140
138
  } catch (err) {
141
139
  debug('result', result.outBinds);
142
- /* istanbul ignore next */
143
140
  const message = `Internal error when parsing ${result.outBinds}\n${errorToString(err)}`;
144
- /* istanbul ignore next */
145
141
  throw new Error(message);
146
142
  }
147
143
  };
@@ -33,6 +33,11 @@ import {isStringOrArrayOfString} from '../../util/type.js';
33
33
  export const processRequest = async (req, res, options, connectionPool) => {
34
34
  debug('processRequest: ENTER');
35
35
 
36
+ //
37
+ if (Array.isArray(req.params.name)) {
38
+ console.warn(`processRequest: WARNING: the req.params.name is not a string but an array of string: ${req.params.name.join(',')}`);
39
+ }
40
+
36
41
  // open database connection
37
42
  const connection = await connectionPool.getConnection();
38
43
 
@@ -68,7 +73,7 @@ export const processRequest = async (req, res, options, connectionPool) => {
68
73
  await connection.rollback();
69
74
  } else if (typeof options.transactionMode === 'function') {
70
75
  debug('transactionMode: callback');
71
- const result = options.transactionMode(connection, req.params.name);
76
+ const result = options.transactionMode(connection, Array.isArray(req.params.name) ? req.params.name[0] : req.params.name);
72
77
  debug('transactionMode: callback restult', result);
73
78
  if (result && typeof result.then === 'function') {
74
79
  await result;
@@ -93,16 +98,16 @@ const normalizeBody = (req) => {
93
98
  /** @type {Record<string, string | string[]>} */
94
99
  const args = {};
95
100
 
96
- /* istanbul ignore else */
101
+ /* v8 ignore else - body validation */
97
102
  if (typeof req.body === 'object' && req.body !== null) {
98
103
  for (const key in req.body) {
99
104
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
100
105
  const value = req.body[key];
101
- /* istanbul ignore else */
106
+ /* v8 ignore else - type validation */
102
107
  if (isStringOrArrayOfString(value)) {
103
108
  args[key] = value;
104
109
  } else {
105
- /* istanbul ignore next */
110
+ /* v8 ignore next - invalid body type */
106
111
  throw new RequestError(
107
112
  `The element "${key}" in the body is not a string or an array of strings!\n${util.inspect(req.body, {showHidden: false, depth: null, colors: false})}`,
108
113
  );
@@ -5,6 +5,7 @@
5
5
  import debugModule from 'debug';
6
6
  const debug = debugModule('webplsql:sendResponse');
7
7
 
8
+ import stream from 'node:stream';
8
9
  import {getBlock} from '../../util/trace.js';
9
10
 
10
11
  /**
@@ -20,9 +21,9 @@ import {getBlock} from '../../util/trace.js';
20
21
  * @param {Request} req - The req object represents the HTTP request.
21
22
  * @param {Response} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
22
23
  * @param {pageType} page - The page to send.
23
- * @returns {void}
24
+ * @returns {Promise<void>}
24
25
  */
25
- export const sendResponse = (req, res, page) => {
26
+ export const sendResponse = async (req, res, page) => {
26
27
  /** @type {string[]} */
27
28
  const debugText = [];
28
29
 
@@ -57,20 +58,51 @@ export const sendResponse = (req, res, page) => {
57
58
 
58
59
  // If this is a file download, we eventually set the "Content-Type" and the file content and then return.
59
60
  if (page.file.fileType === 'B' || page.file.fileType === 'F') {
61
+ /** @type {Record<string, string>} */
62
+ const headers = {};
63
+
60
64
  if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
61
- if (debug.enabled) {
62
- debugText.push(`res.writeHead("Content-Type", "${page.head.contentType}")`);
63
- }
65
+ headers['Content-Type'] = page.head.contentType;
66
+ }
64
67
 
65
- res.writeHead(200, {'Content-Type': page.head.contentType});
68
+ if (typeof page.file.fileSize === 'number' && page.file.fileSize > 0) {
69
+ headers['Content-Length'] = page.file.fileSize.toString();
66
70
  }
67
71
 
68
- if (debug.enabled) {
69
- debugText.push(`res.end("${page.file.fileType}")`);
70
- debug(getBlock('RESPONSE', debugText.join('\n')));
72
+ if (Object.keys(headers).length > 0) {
73
+ if (debug.enabled) {
74
+ debugText.push(`res.writeHead(200, ${JSON.stringify(headers)})`);
75
+ }
76
+ res.writeHead(200, headers);
71
77
  }
72
78
 
73
- res.end(page.file.fileBlob, 'binary');
79
+ // Check if fileBlob is a stream
80
+ if (page.file.fileBlob instanceof stream.Readable) {
81
+ if (debug.enabled) {
82
+ debugText.push(`res.pipe("${page.file.fileType}") - streaming`);
83
+ debug(getBlock('RESPONSE', debugText.join('\n')));
84
+ }
85
+
86
+ /** @type {Promise<void>} */
87
+ const streamComplete = new Promise((resolve, reject) => {
88
+ if (page.file.fileBlob instanceof stream.Readable) {
89
+ page.file.fileBlob.pipe(res);
90
+ page.file.fileBlob.on('end', () => resolve());
91
+ /* v8 ignore next - error handler */
92
+ page.file.fileBlob.on('error', (/** @type {Error} */ err) => reject(err));
93
+ /* v8 ignore next - error handler */
94
+ res.on('close', () => resolve());
95
+ }
96
+ });
97
+ await streamComplete;
98
+ } else {
99
+ if (debug.enabled) {
100
+ debugText.push(`res.end("${page.file.fileType}") - buffer`);
101
+ debug(getBlock('RESPONSE', debugText.join('\n')));
102
+ }
103
+
104
+ res.end(page.file.fileBlob, 'binary');
105
+ }
74
106
  return;
75
107
  }
76
108
 
@@ -21,7 +21,7 @@ export const streamToBuffer = async (readable) => {
21
21
  });
22
22
 
23
23
  readable.on('error', (err) => {
24
- // istanbul ignore next
24
+ /* v8 ignore next - error handler */
25
25
  reject(err);
26
26
  });
27
27
  });
@@ -70,7 +70,7 @@ export const getFiles = (req) => {
70
70
  export const uploadFile = async (file, doctable, databaseConnection) => {
71
71
  debug(`uploadFile`, file, doctable);
72
72
 
73
- /* istanbul ignore next */
73
+ /* v8 ignore next - defensive validation */
74
74
  if (typeof doctable !== 'string' || doctable.length === 0) {
75
75
  throw new Error(`Unable to upload file "${file.filename}" because the option ""doctable" has not been defined`);
76
76
  }
package/src/index.js CHANGED
@@ -1,5 +1,9 @@
1
+ // libraries
2
+ import oracledb from 'oracledb';
3
+ export {oracledb};
4
+
1
5
  // server
2
- export {getVersion} from './server/version.js';
6
+ export {getVersion} from './version.js';
3
7
  export {createServer, startServer, loadConfig, startServerConfig} from './server/server.js';
4
8
  export * from './util/shutdown.js';
5
9
 
@@ -1,4 +1,4 @@
1
- import {getVersion} from './version.js';
1
+ import {getVersion} from '../version.js';
2
2
 
3
3
  /**
4
4
  * @typedef {import('../types.js').configType} configType
package/src/types.js CHANGED
@@ -140,5 +140,5 @@ export const z$configType = z.strictObject({
140
140
  * @property {object} file - The file.
141
141
  * @property {string | null} file.fileType - The file type.
142
142
  * @property {number | null} file.fileSize - The file size.
143
- * @property {Buffer | null} file.fileBlob - The file blob.
143
+ * @property {import('node:stream').Readable | Buffer | null} file.fileBlob - The file blob.
144
144
  */
package/src/util/file.js CHANGED
@@ -10,7 +10,6 @@ export const readFileSyncUtf8 = (filePath) => {
10
10
  try {
11
11
  return readFileSync(filePath, 'utf8');
12
12
  } catch (err) {
13
- /* istanbul ignore next */
14
13
  throw new Error(`Unable to read file "${filePath}"`);
15
14
  }
16
15
  };
@@ -21,11 +20,10 @@ export const readFileSyncUtf8 = (filePath) => {
21
20
  * @param {string} filePath - File name.
22
21
  * @returns {Promise<Buffer>} The buffer.
23
22
  */
24
- export const readFile = (filePath) => {
23
+ export const readFile = async (filePath) => {
25
24
  try {
26
- return fs.readFile(filePath);
25
+ return await fs.readFile(filePath);
27
26
  } catch (err) {
28
- /* istanbul ignore next */
29
27
  throw new Error(`Unable to read file "${filePath}"`);
30
28
  }
31
29
  };
@@ -36,11 +34,10 @@ export const readFile = (filePath) => {
36
34
  * @param {string} filePath - File name.
37
35
  * @returns {Promise<void>}.
38
36
  */
39
- export const removeFile = (filePath) => {
37
+ export const removeFile = async (filePath) => {
40
38
  try {
41
- return fs.unlink(filePath);
39
+ return await fs.unlink(filePath);
42
40
  } catch (err) {
43
- /* istanbul ignore next */
44
41
  throw new Error(`Unable to remove file "${filePath}"`);
45
42
  }
46
43
  };
@@ -52,9 +49,8 @@ export const removeFile = (filePath) => {
52
49
  * @returns {unknown} The json object.
53
50
  */
54
51
  export const getJsonFile = (filePath) => {
55
- const fileContent = readFileSync(filePath, 'utf8');
56
-
57
52
  try {
53
+ const fileContent = readFileSync(filePath, 'utf8');
58
54
  return JSON.parse(fileContent);
59
55
  } catch (err) {
60
56
  throw new Error(`Unable to load file "${filePath}"`);
@@ -2,4 +2,4 @@
2
2
  * Returns the current library version
3
3
  * @returns {string} - Version.
4
4
  */
5
- export const getVersion = () => '0.17.0';
5
+ export const getVersion = () => '0.18.0';
@@ -1,4 +1,4 @@
1
- export function sendResponse(req: Request, res: Response, page: pageType): void;
1
+ export function sendResponse(req: Request, res: Response, page: pageType): Promise<void>;
2
2
  export type Request = import("express").Request;
3
3
  export type Response = import("express").Response;
4
4
  export type CookieOptions = import("express").CookieOptions;
package/types/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { getVersion } from "./server/version.js";
1
+ export { oracledb };
2
+ export { getVersion } from "./version.js";
2
3
  export * from "./util/shutdown.js";
3
4
  export * from "./util/oracle.js";
4
5
  export * from "./util/file.js";
@@ -6,4 +7,5 @@ export { handlerWebPlSql } from "./handler/plsql/handlerPlSql.js";
6
7
  export { handlerLogger } from "./handler/handlerLogger.js";
7
8
  export { handlerUpload } from "./handler/handlerUpload.js";
8
9
  export { handlerMetrics } from "./handler/handlerMetrics.js";
10
+ import oracledb from 'oracledb';
9
11
  export { createServer, startServer, loadConfig, startServerConfig } from "./server/server.js";
package/types/types.d.ts CHANGED
@@ -304,7 +304,7 @@ export type pageType = {
304
304
  file: {
305
305
  fileType: string | null;
306
306
  fileSize: number | null;
307
- fileBlob: Buffer | null;
307
+ fileBlob: import("node:stream").Readable | Buffer | null;
308
308
  };
309
309
  };
310
310
  import z from 'zod';
@@ -1,2 +0,0 @@
1
- export function streamToBuffer(readable: stream.Readable): Promise<Buffer>;
2
- import stream from 'node:stream';
File without changes