web_plsql 1.0.0 → 1.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.
Files changed (51) hide show
  1. package/README.md +5 -5
  2. package/package.json +5 -5
  3. package/src/admin/client/charts.ts +303 -4
  4. package/src/admin/index.html +283 -69
  5. package/src/admin/js/api.ts +95 -20
  6. package/src/admin/js/app.ts +460 -152
  7. package/src/admin/js/schemas.ts +76 -14
  8. package/src/admin/js/templates/config.ts +10 -9
  9. package/src/admin/js/templates/errorRow.ts +8 -5
  10. package/src/admin/js/templates/index.ts +1 -0
  11. package/src/admin/js/templates/poolCard.ts +6 -6
  12. package/src/admin/js/templates/traceRow.ts +24 -0
  13. package/src/admin/js/types.ts +132 -19
  14. package/src/admin/js/ui/components.ts +44 -0
  15. package/src/admin/js/ui/table.ts +173 -0
  16. package/src/admin/js/ui/views.ts +311 -48
  17. package/src/admin/js/util/format.ts +22 -3
  18. package/src/admin/js/util/metrics.ts +24 -0
  19. package/src/admin/lib/chart.bundle.css +1 -0
  20. package/src/admin/lib/chart.bundle.js +54 -47
  21. package/src/admin/style.css +143 -27
  22. package/src/handler/handlerAdmin.js +121 -26
  23. package/src/handler/plsql/errorPage.js +0 -4
  24. package/src/handler/plsql/owaPageStream.js +93 -0
  25. package/src/handler/plsql/procedure.js +191 -121
  26. package/src/handler/plsql/procedureNamed.js +17 -3
  27. package/src/handler/plsql/procedureSanitize.js +24 -0
  28. package/src/handler/plsql/procedureVariable.js +2 -0
  29. package/src/handler/plsql/sendResponse.js +41 -3
  30. package/src/index.js +0 -1
  31. package/src/server/adminContext.js +23 -0
  32. package/src/server/server.js +83 -68
  33. package/src/types.js +1 -1
  34. package/src/util/statsManager.js +368 -0
  35. package/src/util/trace.js +2 -1
  36. package/src/util/traceManager.js +68 -0
  37. package/src/version.js +1 -1
  38. package/types/admin/js/schemas.d.ts +384 -0
  39. package/types/admin/js/types.d.ts +312 -0
  40. package/types/handler/handlerAdmin.d.ts +70 -0
  41. package/types/handler/plsql/owaPageStream.d.ts +28 -0
  42. package/types/handler/plsql/procedure.d.ts +1 -0
  43. package/types/index.d.ts +0 -1
  44. package/types/server/adminContext.d.ts +17 -0
  45. package/types/server/server.d.ts +2 -19
  46. package/types/types.d.ts +1 -1
  47. package/types/util/statsManager.d.ts +395 -0
  48. package/types/util/traceManager.d.ts +35 -0
  49. package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
  50. package/src/handler/handlerMetrics.js +0 -68
  51. package/types/handler/handlerMetrics.d.ts +0 -25
@@ -19,6 +19,12 @@ import {RequestError} from './requestError.js';
19
19
  import {inspect, getBlock} from '../../util/trace.js';
20
20
  import {errorToString} from '../../util/errorToString.js';
21
21
  import {sanitizeProcName} from './procedureSanitize.js';
22
+ import {OWAPageStream} from './owaPageStream.js';
23
+ import {traceManager} from '../../util/traceManager.js';
24
+
25
+ /**
26
+ * @typedef {import('../../admin/js/types.js').TraceEntry} TraceEntry
27
+ */
22
28
 
23
29
  /**
24
30
  * @typedef {import('express').Request} Request
@@ -48,7 +54,9 @@ import {sanitizeProcName} from './procedureSanitize.js';
48
54
  const getProcedure = async (req, procName, argObj, options, databaseConnection, procedureNameCache, argumentCache) => {
49
55
  // path alias
50
56
  if (options.pathAlias?.toLowerCase() === procName.toLowerCase()) {
57
+ /* v8 ignore start */
51
58
  debug(`getProcedure: path alias "${options.pathAlias}" redirects to "${options.pathAliasProcedure}"`);
59
+ /* v8 ignore stop */
52
60
  return {
53
61
  sql: `${options.pathAliasProcedure}(p_path=>:p_path)`,
54
62
  bind: {
@@ -131,47 +139,6 @@ const procedureExecute = async (para, databaseConnection) => {
131
139
  }
132
140
  };
133
141
 
134
- /**
135
- * Get page from procedure
136
- *
137
- * @param {boolean} _test - Test.
138
- * @param {Connection} databaseConnection - Database connection.
139
- * @returns {Promise<string>} Promise resolving to the returned page content.
140
- */
141
- const procedureGetPage = async (_test, databaseConnection) => {
142
- const MAX_IROWS = 100000;
143
-
144
- /** @type {BindParameterConfig} */
145
- const bindParameter = {
146
- lines: {dir: oracledb.BIND_OUT, type: oracledb.STRING, maxArraySize: MAX_IROWS},
147
- irows: {dir: oracledb.BIND_INOUT, type: oracledb.NUMBER, val: MAX_IROWS},
148
- };
149
-
150
- const sqlStatement = 'BEGIN owa.get_page(thepage=>:lines, irows=>:irows); END;';
151
-
152
- /** @type {Result} */
153
- let result = {};
154
- try {
155
- result = await databaseConnection.execute(sqlStatement, bindParameter);
156
- } catch (err) {
157
- if (debug.enabled) {
158
- debug(getBlock('procedureGetPage: results', inspect(result)));
159
- }
160
-
161
- throw new ProcedureError(`procedureGetPage: error when getting page returned by procedure\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
162
- }
163
-
164
- const {lines, irows} = z.object({irows: z.number(), lines: z.array(z.string())}).parse(result.outBinds);
165
-
166
- // Make sure that we have retrieved all the rows
167
- if (irows > MAX_IROWS) {
168
- /* v8 ignore next - defensive check for row limit */
169
- throw new ProcedureError(`procedureGetPage: error when retrieving rows. irows="${irows}"`, {}, sqlStatement, bindParameter);
170
- }
171
-
172
- return lines.join('');
173
- };
174
-
175
142
  /**
176
143
  * Download files from procedure
177
144
  *
@@ -209,9 +176,11 @@ END;
209
176
  try {
210
177
  result = await databaseConnection.execute(sqlStatement, bindParameter);
211
178
  } catch (err) {
179
+ /* v8 ignore start */
212
180
  if (debug.enabled) {
213
181
  debug(getBlock('procedureDownloadFiles: results', inspect(result)));
214
182
  }
183
+ /* v8 ignore stop */
215
184
 
216
185
  throw new ProcedureError(`procedureDownloadFiles: error when downloading files\n${errorToString(err)}`, {}, sqlStatement, bindParameter);
217
186
  }
@@ -248,108 +217,209 @@ END;
248
217
  export const invokeProcedure = async (req, res, argObj, cgiObj, filesToUpload, options, databaseConnection, procedureNameCache, argumentCache) => {
249
218
  debug('invokeProcedure: begin');
250
219
 
251
- // 1) upload files
252
- debug(`invokeProcedure: upload "${filesToUpload.length}" files`);
253
- if (filesToUpload.length > 0) {
254
- if (typeof options.documentTable === 'string' && options.documentTable.length > 0) {
255
- const {documentTable} = options;
220
+ const startTime = Date.now();
221
+ /** @type {TraceEntry | null} */
222
+ let traceData = null;
223
+
224
+ if (traceManager.isEnabled()) {
225
+ traceData = {
226
+ id: Math.random().toString(36).substring(2, 15),
227
+ timestamp: new Date().toISOString(),
228
+ source: cgiObj.REMOTE_ADDR ?? '',
229
+ url: req.originalUrl,
230
+ method: req.method,
231
+ status: 'pending',
232
+ duration: 0,
233
+ cgi: cgiObj,
234
+ headers: /** @type {Record<string, string>} */ (req.headers),
235
+ cookies: req.cookies,
236
+ uploads: filesToUpload.map((f) => ({
237
+ originalname: f.originalname,
238
+ mimetype: f.mimetype,
239
+ size: f.size,
240
+ })),
241
+ };
242
+ }
256
243
 
257
- await Promise.all(filesToUpload.map((file) => uploadFile(file, documentTable, databaseConnection)));
258
- } else {
259
- console.warn(`Unable to upload "${filesToUpload.length}" files because the option ""doctable" has not been defined`);
244
+ try {
245
+ // 1) upload files
246
+ debug(`invokeProcedure: upload "${filesToUpload.length}" files`);
247
+ if (filesToUpload.length > 0) {
248
+ if (typeof options.documentTable === 'string' && options.documentTable.length > 0) {
249
+ const {documentTable} = options;
250
+
251
+ await Promise.all(filesToUpload.map((file) => uploadFile(file, documentTable, databaseConnection)));
252
+ } else {
253
+ console.warn(`Unable to upload "${filesToUpload.length}" files because the option ""doctable" has not been defined`);
254
+ }
260
255
  }
261
- }
262
256
 
263
- // 2) get procedure to execute and the arguments
257
+ // 2) get procedure to execute and the arguments
264
258
 
265
- debug('invokeProcedure: get procedure to execute and the arguments');
266
- // Extract the raw procedure name from params
267
- const rawProcName = Array.isArray(req.params.name) ? req.params.name[0] : req.params.name;
268
- if (!rawProcName) {
269
- throw new RequestError('No procedure name provided');
270
- }
271
- const para = await getProcedure(req, rawProcName, argObj, options, databaseConnection, procedureNameCache, argumentCache);
259
+ debug('invokeProcedure: get procedure to execute and the arguments');
260
+ // Extract the raw procedure name from params
261
+ const rawProcName = Array.isArray(req.params.name) ? req.params.name[0] : req.params.name;
262
+ if (!rawProcName) {
263
+ throw new RequestError('No procedure name provided');
264
+ }
265
+ const para = await getProcedure(req, rawProcName, argObj, options, databaseConnection, procedureNameCache, argumentCache);
272
266
 
273
- // 3) prepare the session
267
+ if (traceData) {
268
+ traceData.procedure = para.resolvedName;
269
+ traceData.parameters = para.bind;
270
+ }
274
271
 
275
- debug('invokeProcedure: prepare the session');
276
- await procedurePrepare(cgiObj, databaseConnection);
272
+ // 3) prepare the session
273
+
274
+ debug('invokeProcedure: prepare the session');
275
+ await procedurePrepare(cgiObj, databaseConnection);
276
+
277
+ // 4) execute the procedure
278
+
279
+ debug('invokeProcedure: execute the session');
280
+ try {
281
+ await procedureExecute(para, databaseConnection);
282
+ } catch (err) {
283
+ // Invalidation Logic
284
+ if (err instanceof ProcedureError) {
285
+ const errorString = err.toString();
286
+ // Check for ORA-04068, ORA-04061, ORA-04065, ORA-06550
287
+ if (
288
+ errorString.includes('ORA-04068') ||
289
+ errorString.includes('ORA-04061') ||
290
+ errorString.includes('ORA-04065') ||
291
+ errorString.includes('ORA-06550')
292
+ ) {
293
+ debug(`invokeProcedure: detected invalidation error (${errorString}). Clearing caches.`);
294
+
295
+ // Clear name resolution cache for the input name
296
+ if (rawProcName) {
297
+ procedureNameCache.delete(rawProcName);
298
+ }
299
+
300
+ // Clear argument cache for the resolved name
301
+ if (para.resolvedName) {
302
+ argumentCache.delete(para.resolvedName.toUpperCase());
303
+ }
304
+ }
305
+ }
306
+ throw err;
307
+ }
277
308
 
278
- // 4) execute the procedure
309
+ // 5) get the page returned from the procedure
310
+ debug('invokeProcedure: get the page returned from the procedure');
279
311
 
280
- debug('invokeProcedure: execute the session');
281
- try {
282
- await procedureExecute(para, databaseConnection);
283
- } catch (err) {
284
- // Invalidation Logic
285
- if (err instanceof ProcedureError) {
286
- const errorString = err.toString();
287
- // Check for ORA-04068, ORA-04061, ORA-04065, ORA-06550
288
- if (
289
- errorString.includes('ORA-04068') ||
290
- errorString.includes('ORA-04061') ||
291
- errorString.includes('ORA-04065') ||
292
- errorString.includes('ORA-06550')
293
- ) {
294
- debug(`invokeProcedure: detected invalidation error (${errorString}). Clearing caches.`);
295
-
296
- // Clear name resolution cache for the input name
297
- if (rawProcName) {
298
- procedureNameCache.delete(rawProcName);
299
- }
312
+ const streamInstance = new OWAPageStream(databaseConnection);
313
+ const lines = await streamInstance.fetchChunk();
300
314
 
301
- // Clear argument cache for the resolved name
302
- if (para.resolvedName) {
303
- argumentCache.delete(para.resolvedName.toUpperCase());
304
- }
305
- }
315
+ /* v8 ignore start */
316
+ if (debug.enabled) {
317
+ debug(getBlock('data', lines.join('')));
306
318
  }
307
- throw err;
308
- }
319
+ /* v8 ignore stop */
309
320
 
310
- // 5) get the page returned from the procedure
321
+ // 6) download files
311
322
 
312
- debug('invokeProcedure: get the page returned from the procedure');
313
- const lines = await procedureGetPage(true, databaseConnection);
314
- if (debug.enabled) {
315
- debug(getBlock('data', lines));
316
- }
323
+ debug('invokeProcedure: download files');
324
+ const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
317
325
 
318
- // 6) download files
326
+ try {
327
+ const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
328
+ /* v8 ignore start */
329
+ if (debug.enabled) {
330
+ debug(getBlock('fileDownload', inspect({fileType: fileDownload.fileType, fileSize: fileDownload.fileSize})));
331
+ }
332
+ /* v8 ignore stop */
319
333
 
320
- debug('invokeProcedure: download files');
321
- const fileBlob = await databaseConnection.createLob(oracledb.BLOB);
334
+ // 7) parse the page
322
335
 
323
- try {
324
- const fileDownload = await procedureDownloadFiles(fileBlob, databaseConnection);
325
- if (debug.enabled) {
326
- debug(getBlock('fileDownload', inspect({fileType: fileDownload.fileType, fileSize: fileDownload.fileSize})));
327
- }
336
+ debug('invokeProcedure: parse the page');
337
+ // We parse the headers from the first chunk
338
+ const pageComponents = parsePage(lines.join(''));
328
339
 
329
- // 7) parse the page
340
+ // add "Server" header
341
+ pageComponents.head.server = cgiObj.SERVER_SOFTWARE ?? '';
330
342
 
331
- debug('invokeProcedure: parse the page');
332
- const pageComponents = parsePage(lines);
343
+ // add file download information
344
+ if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
345
+ pageComponents.file.fileType = fileDownload.fileType;
346
+ pageComponents.file.fileSize = fileDownload.fileSize;
347
+ pageComponents.file.fileBlob = fileDownload.fileBlob;
333
348
 
334
- // add "Server" header
335
- pageComponents.head.server = cgiObj.SERVER_SOFTWARE ?? '';
349
+ if (traceData) {
350
+ traceData.downloads = {
351
+ fileType: fileDownload.fileType,
352
+ fileSize: fileDownload.fileSize,
353
+ };
354
+ }
355
+ } else {
356
+ // For normal pages, we use the stream.
357
+ // Prepend the initial body (parsed by parsePage) to the stream
358
+ if (typeof pageComponents.body === 'string' && pageComponents.body.length > 0) {
359
+ streamInstance.addBody(pageComponents.body);
360
+ }
361
+ // Use the stream as the body
362
+ pageComponents.body = streamInstance;
363
+
364
+ if (traceData) {
365
+ // Buffer HTML if tracing enabled
366
+ let htmlBuffer = typeof pageComponents.body === 'string' ? pageComponents.body : lines.join('');
367
+ const MAX_HTML_SIZE = 1024 * 1024; // 1MB
368
+
369
+ const originalPush = streamInstance.push.bind(streamInstance);
370
+ streamInstance.push = (chunk) => {
371
+ if (chunk !== null && htmlBuffer.length < MAX_HTML_SIZE) {
372
+ const str = String(chunk);
373
+ htmlBuffer += str;
374
+ if (htmlBuffer.length > MAX_HTML_SIZE) {
375
+ htmlBuffer = htmlBuffer.substring(0, MAX_HTML_SIZE) + '... [truncated]';
376
+ }
377
+ }
378
+ return originalPush(chunk);
379
+ };
380
+
381
+ // Since we can't easily wait for the stream to finish here without blocking,
382
+ // we'll have to finalize traceData when the stream ends.
383
+ const currentTrace = traceData;
384
+ streamInstance.on('end', () => {
385
+ currentTrace.html = htmlBuffer;
386
+ currentTrace.status = 'success';
387
+ currentTrace.duration = Date.now() - startTime;
388
+ traceManager.addTrace(currentTrace);
389
+ });
390
+ streamInstance.on('error', (err) => {
391
+ currentTrace.status = 'fail';
392
+ currentTrace.error = err instanceof Error ? err.message : String(err);
393
+ currentTrace.duration = Date.now() - startTime;
394
+ traceManager.addTrace(currentTrace);
395
+ });
396
+ }
397
+ }
336
398
 
337
- // add file download information
338
- if (fileDownload.fileType !== '' && fileDownload.fileSize > 0 && fileDownload.fileBlob !== null) {
339
- pageComponents.file.fileType = fileDownload.fileType;
340
- pageComponents.file.fileSize = fileDownload.fileSize;
341
- pageComponents.file.fileBlob = fileDownload.fileBlob;
342
- }
399
+ // 8) send the page to browser
343
400
 
344
- // 8) send the page to browser
401
+ debug('invokeProcedure: send the page to browser');
402
+ await sendResponse(req, res, pageComponents);
345
403
 
346
- debug('invokeProcedure: send the page to browser');
347
- await sendResponse(req, res, pageComponents);
348
- } finally {
349
- // 9) cleanup
404
+ if (traceData?.downloads) {
405
+ traceData.status = 'success';
406
+ traceData.duration = Date.now() - startTime;
407
+ traceManager.addTrace(traceData);
408
+ }
409
+ } finally {
410
+ // 9) cleanup
350
411
 
351
- debug('invokeProcedure: cleanup');
352
- fileBlob.destroy();
412
+ debug('invokeProcedure: cleanup');
413
+ fileBlob.destroy();
414
+ }
415
+ } catch (err) {
416
+ if (traceData) {
417
+ traceData.status = err instanceof ProcedureError ? 'error' : 'fail';
418
+ traceData.error = err instanceof Error ? err.message : String(err);
419
+ traceData.duration = Date.now() - startTime;
420
+ traceManager.addTrace(traceData);
421
+ }
422
+ throw err;
353
423
  }
354
424
 
355
425
  debug('invokeProcedure: end');
@@ -83,22 +83,28 @@ const loadArguments = async (procedure, databaseConnection) => {
83
83
  try {
84
84
  result = await databaseConnection.execute(SQL_GET_ARGUMENT, bind);
85
85
  } catch (err) {
86
+ /* v8 ignore start */
86
87
  debug('result', result);
88
+ /* v8 ignore stop */
89
+
87
90
  const message = `Error when retrieving arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
88
91
  throw new RequestError(message);
89
92
  }
90
93
 
91
- /** @type {{names: string[], types: string[]}} */
94
+ /** @type {{names: (string | null)[], types: (string | null)[]}} */
92
95
  let data;
93
96
  try {
94
97
  data = z
95
98
  .object({
96
- names: z.array(z.string()),
97
- types: z.array(z.string()),
99
+ names: z.array(z.string().nullable()),
100
+ types: z.array(z.string().nullable()),
98
101
  })
99
102
  .parse(result.outBinds);
100
103
  } catch (err) {
104
+ /* v8 ignore start */
101
105
  debug('result.outBinds', result.outBinds);
106
+ /* v8 ignore stop */
107
+
102
108
  const message = `Error when decoding arguments\n${SQL_GET_ARGUMENT}\n${errorToString(err)}`;
103
109
  throw new RequestError(message);
104
110
  }
@@ -136,16 +142,22 @@ const findArguments = async (procedure, databaseConnection, argumentCache) => {
136
142
 
137
143
  // if we found the procedure in the cache, we return it
138
144
  if (cachedArgs) {
145
+ /* v8 ignore start */
139
146
  if (debug.enabled) {
140
147
  debug(`findArguments: procedure "${procedure}" found in cache`);
141
148
  }
149
+ /* v8 ignore stop */
150
+
142
151
  return cachedArgs;
143
152
  }
144
153
 
145
154
  // load from database
155
+ /* v8 ignore start */
146
156
  if (debug.enabled) {
147
157
  debug(`findArguments: procedure "${procedure}" not found in cache and must be loaded`);
148
158
  }
159
+ /* v8 ignore stop */
160
+
149
161
  const args = await loadArguments(procedure, databaseConnection);
150
162
 
151
163
  // add to the cache
@@ -259,10 +271,12 @@ export const getProcedureNamed = async (req, procName, argObj, databaseConnectio
259
271
  // select statement
260
272
  const sql = `${procName}(${sqlParameter.join(', ')})`;
261
273
 
274
+ /* v8 ignore start */
262
275
  if (debug.enabled) {
263
276
  debug(sql);
264
277
  debug(inspectBindings(argObj, argTypes));
265
278
  }
279
+ /* v8 ignore stop */
266
280
 
267
281
  return {sql, bind: bindings};
268
282
  };
@@ -95,7 +95,10 @@ const resolveProcedureName = async (procName, databaseConnection, procedureNameC
95
95
 
96
96
  return resolved;
97
97
  } catch (err) {
98
+ /* v8 ignore start */
98
99
  debug(`resolveProcedureName: Error resolving "${procName}"`, err);
100
+ /* v8 ignore stop */
101
+
99
102
  // Rethrow as RequestError to indicate 404/403
100
103
  throw new RequestError(`Procedure "${procName}" not found or not accessible.\n${errorToString(err)}`);
101
104
  }
@@ -123,7 +126,10 @@ export const sanitizeProcName = async (procName, databaseConnection, options, pr
123
126
  for (const i of DEFAULT_EXCLUSION_LIST) {
124
127
  if (finalProcName.startsWith(i)) {
125
128
  const error = `Procedure name "${procName}" is in default exclusion list "${DEFAULT_EXCLUSION_LIST.join(',')}"`;
129
+ /* v8 ignore start */
126
130
  debug(error);
131
+ /* v8 ignore stop */
132
+
127
133
  throw new RequestError(error);
128
134
  }
129
135
  }
@@ -133,7 +139,10 @@ export const sanitizeProcName = async (procName, databaseConnection, options, pr
133
139
  for (const i of options.exclusionList) {
134
140
  if (finalProcName.startsWith(i)) {
135
141
  const error = `Procedure name "${procName}" is in custom exclusion list "${options.exclusionList.join(',')}"`;
142
+ /* v8 ignore start */
136
143
  debug(error);
144
+ /* v8 ignore stop */
145
+
137
146
  throw new RequestError(error);
138
147
  }
139
148
  }
@@ -148,7 +157,10 @@ export const sanitizeProcName = async (procName, databaseConnection, options, pr
148
157
  const valid = await requestValidationFunction(finalProcName, options.requestValidationFunction, databaseConnection);
149
158
  if (!valid) {
150
159
  const error = `Procedure name "${procName}" is not valid according to the request validation function "${options.requestValidationFunction}"`;
160
+ /* v8 ignore start */
151
161
  debug(error);
162
+ /* v8 ignore stop */
163
+
152
164
  throw new RequestError(error);
153
165
  }
154
166
  }
@@ -209,7 +221,10 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
209
221
  try {
210
222
  result = await databaseConnection.execute(SQL, bind);
211
223
  } catch (err) {
224
+ /* v8 ignore start */
212
225
  debug('result', result);
226
+ /* v8 ignore stop */
227
+
213
228
  const message = `Error when validating procedure name "${procName}"\n${SQL}\n${errorToString(err)}`;
214
229
  throw new RequestError(message);
215
230
  }
@@ -218,7 +233,10 @@ const loadRequestValid = async (procName, requestValidationFunction, databaseCon
218
233
  const data = z.strictObject({valid: z.number()}).parse(result.outBinds);
219
234
  return data.valid === 1;
220
235
  } catch (err) {
236
+ /* v8 ignore start */
221
237
  debug('result', result.outBinds);
238
+ /* v8 ignore stop */
239
+
222
240
  const message = `Internal error when parsing ${result.outBinds}\n${errorToString(err)}`;
223
241
  throw new Error(message);
224
242
  }
@@ -243,16 +261,22 @@ const requestValidationFunction = async (procName, requestValidationFunction, da
243
261
 
244
262
  // if we found the procedure in the cache, we return it (hitCount already incremented by get)
245
263
  if (cacheEntry !== undefined) {
264
+ /* v8 ignore start */
246
265
  if (debug.enabled) {
247
266
  debug(`requestValidationFunction: procedure "${procName}" found in cache`);
248
267
  }
268
+ /* v8 ignore stop */
269
+
249
270
  return cacheEntry.valid;
250
271
  }
251
272
 
252
273
  // load from database
274
+ /* v8 ignore start */
253
275
  if (debug.enabled) {
254
276
  debug(`requestValidationFunction: procedure "${procName}" not found in cache and must be loaded`);
255
277
  }
278
+ /* v8 ignore stop */
279
+
256
280
  const valid = await loadRequestValid(procName, requestValidationFunction, databaseConnection);
257
281
 
258
282
  // add to the cache
@@ -24,9 +24,11 @@ import oracledb from 'oracledb';
24
24
  * @returns {{sql: string; bind: BindParameterConfig}} - The SQL statement and bindings for the procedure to execute
25
25
  */
26
26
  export const getProcedureVariable = (_req, procName, argObj) => {
27
+ /* v8 ignore start */
27
28
  if (debug.enabled) {
28
29
  debug(`getProcedureVariable: ${procName} arguments=`, argObj);
29
30
  }
31
+ /* v8 ignore stop */
30
32
 
31
33
  const names = [];
32
34
  const values = [];
@@ -29,19 +29,23 @@ export const sendResponse = async (_req, res, page) => {
29
29
 
30
30
  // Send the "cookies"
31
31
  page.head.cookies.forEach((cookie) => {
32
+ /* v8 ignore start */
32
33
  if (debug.enabled) {
33
34
  debugText.push(`res.cookie: name="${cookie.name}" value="${cookie.value}" options=${JSON.stringify(cookie.options)}`);
34
35
  }
36
+ /* v8 ignore stop */
35
37
 
36
38
  res.cookie(cookie.name, cookie.value, cookie.options);
37
39
  });
38
40
 
39
41
  // If there is a "redirectLocation" header, we immediately redirect and return
40
42
  if (typeof page.head.redirectLocation === 'string' && page.head.redirectLocation.length > 0) {
43
+ /* v8 ignore start */
41
44
  if (debug.enabled) {
42
45
  debugText.push(`res.redirect(302, "${page.head.redirectLocation}")`);
43
46
  debug(getBlock('RESPONSE', debugText.join('\n')));
44
47
  }
48
+ /* v8 ignore stop */
45
49
 
46
50
  res.redirect(302, page.head.redirectLocation);
47
51
  return;
@@ -49,9 +53,11 @@ export const sendResponse = async (_req, res, page) => {
49
53
 
50
54
  // Send all the "otherHeaders"
51
55
  for (const key in page.head.otherHeaders) {
56
+ /* v8 ignore start */
52
57
  if (debug.enabled) {
53
58
  debugText.push(`res.set("${key}", "${page.head.otherHeaders[key]}")`);
54
59
  }
60
+ /* v8 ignore stop */
55
61
 
56
62
  res.set(key, page.head.otherHeaders[key]);
57
63
  }
@@ -70,18 +76,23 @@ export const sendResponse = async (_req, res, page) => {
70
76
  }
71
77
 
72
78
  if (Object.keys(headers).length > 0) {
79
+ /* v8 ignore start */
73
80
  if (debug.enabled) {
74
81
  debugText.push(`res.writeHead(200, ${JSON.stringify(headers)})`);
75
82
  }
83
+ /* v8 ignore stop */
84
+
76
85
  res.writeHead(200, headers);
77
86
  }
78
87
 
79
88
  // Check if fileBlob is a stream
80
89
  if (page.file.fileBlob instanceof stream.Readable) {
90
+ /* v8 ignore start */
81
91
  if (debug.enabled) {
82
92
  debugText.push(`res.pipe("${page.file.fileType}") - streaming`);
83
93
  debug(getBlock('RESPONSE', debugText.join('\n')));
84
94
  }
95
+ /* v8 ignore stop */
85
96
 
86
97
  /** @type {Promise<void>} */
87
98
  const streamComplete = new Promise((resolve, reject) => {
@@ -96,10 +107,12 @@ export const sendResponse = async (_req, res, page) => {
96
107
  });
97
108
  await streamComplete;
98
109
  } else {
110
+ /* v8 ignore start */
99
111
  if (debug.enabled) {
100
112
  debugText.push(`res.end("${page.file.fileType}") - buffer`);
101
113
  debug(getBlock('RESPONSE', debugText.join('\n')));
102
114
  }
115
+ /* v8 ignore stop */
103
116
 
104
117
  res.end(page.file.fileBlob, 'binary');
105
118
  }
@@ -108,29 +121,54 @@ export const sendResponse = async (_req, res, page) => {
108
121
 
109
122
  // Is the a "contentType" header
110
123
  if (typeof page.head.contentType === 'string' && page.head.contentType.length > 0) {
124
+ /* v8 ignore start */
111
125
  if (debug.enabled) {
112
126
  debugText.push(`res.set("Content-Type", "${page.head.contentType}")`);
113
127
  }
128
+ /* v8 ignore stop */
114
129
 
115
130
  res.set('Content-Type', page.head.contentType);
116
131
  }
117
132
 
118
133
  // If we have a "Status" header, we send the header and then return.
119
134
  if (typeof page.head.statusCode === 'number') {
135
+ /* v8 ignore start */
120
136
  if (debug.enabled) {
121
137
  debugText.push(`res.status(page.head.statusCode).send("${page.head.statusDescription}")`);
122
138
  debug(getBlock('RESPONSE', debugText.join('\n')));
123
139
  }
140
+ /* v8 ignore stop */
124
141
 
125
142
  res.status(page.head.statusCode).send(page.head.statusDescription);
126
143
  return;
127
144
  }
128
145
 
129
146
  // Send the body
147
+ /* v8 ignore start */
130
148
  if (debug.enabled) {
131
- debugText.push(`${'-'.repeat(60)}\n${page.body}`);
149
+ if (page.body instanceof stream.Readable) {
150
+ debugText.push(`${'-'.repeat(60)}\n[Stream Body]`);
151
+ } else {
152
+ debugText.push(`${'-'.repeat(60)}\n${page.body}`);
153
+ }
132
154
  debug(getBlock('RESPONSE', debugText.join('\n')));
133
155
  }
134
-
135
- res.send(page.body);
156
+ /* v8 ignore stop */
157
+
158
+ if (page.body instanceof stream.Readable) {
159
+ /** @type {Promise<void>} */
160
+ const streamComplete = new Promise((resolve, reject) => {
161
+ if (page.body instanceof stream.Readable) {
162
+ page.body.pipe(res);
163
+ page.body.on('end', () => resolve());
164
+ /* v8 ignore next - error handler */
165
+ page.body.on('error', (/** @type {Error} */ err) => reject(err));
166
+ /* v8 ignore next - error handler */
167
+ res.on('close', () => resolve());
168
+ }
169
+ });
170
+ await streamComplete;
171
+ } else {
172
+ res.send(page.body);
173
+ }
136
174
  };
package/src/index.js CHANGED
@@ -11,7 +11,6 @@ export * from './util/shutdown.js';
11
11
  export {handlerWebPlSql} from './handler/plsql/handlerPlSql.js';
12
12
  export {handlerLogger} from './handler/handlerLogger.js';
13
13
  export {handlerUpload} from './handler/handlerUpload.js';
14
- export {handlerMetrics} from './handler/handlerMetrics.js';
15
14
 
16
15
  // oracle
17
16
  export * from './util/oracle.js';