web_plsql 1.7.0 → 1.8.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.
@@ -6,7 +6,7 @@
6
6
  <title>web_plsql Admin Console</title>
7
7
  <link rel="icon" type="image/svg+xml" href="/admin/assets/favicon-mQAM4tVu.svg" />
8
8
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
9
- <script type="module" crossorigin src="/admin/assets/main-DurwHchn.js"></script>
9
+ <script type="module" crossorigin src="/admin/assets/main-Co9Kke3j.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/admin/assets/main-CoERzZAr.css">
11
11
  </head>
12
12
  <body class="dark">
@@ -128,8 +128,7 @@
128
128
  </div>
129
129
  <p id="cache-hit-rate-val" title="Percentage of requests served from cache">0%</p>
130
130
  <small
131
- ><span id="cache-hits-val" title="Total cache hits">0</span> hits,
132
- <span id="cache-misses-val" title="Total cache misses">0</span> misses</small
131
+ ><span id="cache-hits-val" title="Total cache hits">0</span> hits, <span id="cache-misses-val" title="Total cache misses">0</span> misses</small
133
132
  >
134
133
  </div>
135
134
  </div>
@@ -184,9 +183,7 @@
184
183
  <section id="view-errors" class="view" style="display: none">
185
184
  <div class="header-toolbar mb-4" style="justify-content: flex-start; gap: 12px">
186
185
  <div class="search-group" style="flex: 1; max-width: 400px">
187
- <span
188
- class="material-symbols-rounded"
189
- style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
186
+ <span class="material-symbols-rounded" style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
190
187
  >search</span
191
188
  >
192
189
  <input
@@ -217,9 +214,7 @@
217
214
  <section id="view-trace" class="view" style="display: none">
218
215
  <div class="header-toolbar mb-4" style="justify-content: flex-start; gap: 12px">
219
216
  <div class="search-group" style="flex: 1; max-width: 300px">
220
- <span
221
- class="material-symbols-rounded"
222
- style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
217
+ <span class="material-symbols-rounded" style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
223
218
  >search</span
224
219
  >
225
220
  <input type="text" id="trace-filter" placeholder="Filter traces..." class="input-sm" style="width: 100%; padding-left: 32px" />
@@ -254,9 +249,7 @@
254
249
  <section id="view-access" class="view" style="display: none">
255
250
  <div class="header-toolbar mb-4" style="justify-content: flex-start; gap: 12px">
256
251
  <div class="search-group" style="flex: 1; max-width: 400px">
257
- <span
258
- class="material-symbols-rounded"
259
- style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
252
+ <span class="material-symbols-rounded" style="position: absolute; left: 8px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5"
260
253
  >search</span
261
254
  >
262
255
  <input type="text" id="access-filter" placeholder="Filter logs..." class="input-sm" style="width: 100%; padding-left: 32px" />
Binary file
Binary file
package/dist/index.d.mts CHANGED
@@ -179,8 +179,14 @@ declare const z$configType: z$1.ZodObject<{
179
179
  adminPassword: z$1.ZodOptional<z$1.ZodString>; /** Developer mode (skips frontend build check, enables CORS) */
180
180
  devMode: z$1.ZodOptional<z$1.ZodBoolean>; /** Callback function to setup custom Express extensions */
181
181
  setupExtensions: z$1.ZodOptional<z$1.ZodCustom<(app: Express, pools: Pool[]) => void | Promise<void>, (app: Express, pools: Pool[]) => void | Promise<void>>>;
182
+ oracle: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodObject<{
183
+ poolMin: z$1.ZodDefault<z$1.ZodInt>;
184
+ poolMax: z$1.ZodDefault<z$1.ZodInt>;
185
+ poolIncrement: z$1.ZodDefault<z$1.ZodInt>;
186
+ }, z$1.core.$strict>>>;
182
187
  }, z$1.core.$strict>;
183
- type configType = z$1.infer<typeof z$configType>;
188
+ type configInputType = z$1.input<typeof z$configType>;
189
+ type configType = z$1.output<typeof z$configType>;
184
190
  /**
185
191
  * Mapping of PL/SQL procedure argument names to their database types
186
192
  */
@@ -297,12 +303,12 @@ declare global {
297
303
  */
298
304
  declare const getVersion: () => string;
299
305
  //#endregion
300
- //#region src/backend/server/config.d.ts
306
+ //#region src/backend/server/printBanner.d.ts
301
307
  /**
302
- * Show configuration.
303
- * @param config - The config.
308
+ * Renders the server startup banner to stdout.
309
+ * @param cfg - Server configuration.
304
310
  */
305
- declare const showConfig: (config: configType) => void;
311
+ declare const printBanner: (cfg: configType) => void;
306
312
  //#endregion
307
313
  //#region src/backend/server/server.d.ts
308
314
  type webServer = {
@@ -330,7 +336,7 @@ declare const createServer: (app: Express, ssl?: sslConfig) => http.Server | htt
330
336
  * @param ssl - ssl configuration.
331
337
  * @returns Promise resolving to the web server object.
332
338
  */
333
- declare const startServer: (config: configType, ssl?: sslConfig) => Promise<webServer>;
339
+ declare const startServer: (config: configInputType, ssl?: sslConfig) => Promise<webServer>;
334
340
  /**
335
341
  * Load configuration.
336
342
  * @param filename - The configuration filename.
@@ -614,6 +620,14 @@ declare const handlerAdminConsole: (config: AdminConsoleConfig, adminContext: Ad
614
620
  */
615
621
  declare const createSpaFallback: (directoryPath: string, _route: string) => RequestHandler;
616
622
  //#endregion
623
+ //#region src/backend/util/logError.d.ts
624
+ /**
625
+ * Log an error.
626
+ *
627
+ * @param error - The error.
628
+ */
629
+ declare const logError: (error: unknown) => void;
630
+ //#endregion
617
631
  //#region src/backend/util/file.d.ts
618
632
  /**
619
633
  * Read file.
@@ -655,5 +669,5 @@ declare const isDirectory: (directoryPath: unknown) => Promise<boolean>;
655
669
  */
656
670
  declare const isFile: (filePath: unknown) => Promise<boolean>;
657
671
  //#endregion
658
- export { type AdminConsoleConfig, AdminContext, type configPlSqlType, type configType, createAdminRouter, createServer, createSpaFallback, forceShutdown, getJsonFile, getVersion, handlerAdminConsole, handlerLogger, handlerUpload, handlerWebPlSql, installShutdown, isDirectory, isFile, loadConfig, oracledb_provider_d_exports as oracledb, readFile, readFileSyncUtf8, removeFile, showConfig, startServer, startServerConfig, z$configType };
672
+ export { type AdminConsoleConfig, AdminContext, type configInputType, type configPlSqlType, type configType, createAdminRouter, createServer, createSpaFallback, forceShutdown, getJsonFile, getVersion, handlerAdminConsole, handlerLogger, handlerUpload, handlerWebPlSql, installShutdown, isDirectory, isFile, loadConfig, logError, oracledb_provider_d_exports as oracledb, printBanner, readFile, readFileSyncUtf8, removeFile, startServer, startServerConfig, z$configType };
659
673
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -2,6 +2,9 @@ import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
2
  import { n as setExecuteCallback } from "./oracledb-mock-Cya43xKB.mjs";
3
3
  import z$1, { z } from "zod";
4
4
  import oracledb from "oracledb";
5
+ import chalk from "chalk";
6
+ import stringWidth from "string-width";
7
+ import sliceAnsi from "slice-ansi";
5
8
  import debugModule from "debug";
6
9
  import http from "node:http";
7
10
  import https from "node:https";
@@ -11,15 +14,16 @@ import cookieParser from "cookie-parser";
11
14
  import compression from "compression";
12
15
  import expressStaticGzip from "express-static-gzip";
13
16
  import os from "node:os";
14
- import url, { URL, fileURLToPath } from "node:url";
15
17
  import util from "node:util";
16
18
  import stream, { Readable } from "node:stream";
17
19
  import fs, { existsSync, promises, readFileSync } from "node:fs";
18
20
  import * as rotatingFileStream from "rotating-file-stream";
19
21
  import path from "node:path";
22
+ import { URL as URL$1 } from "node:url";
20
23
  import morgan from "morgan";
21
24
  import multer from "multer";
22
25
  import readline from "node:readline";
26
+ import process$1 from "node:process";
23
27
  //#region src/common/configStaticSchema.ts
24
28
  /**
25
29
  * Configuration for serving static files
@@ -156,7 +160,16 @@ const z$configType = z$1.strictObject({
156
160
  adminUser: z$1.string().optional(),
157
161
  adminPassword: z$1.string().optional(),
158
162
  devMode: z$1.boolean().optional(),
159
- setupExtensions: z$1.custom((val) => typeof val === "function", { message: "Invalid setupExtensions callback" }).optional()
163
+ setupExtensions: z$1.custom((val) => typeof val === "function", { message: "Invalid setupExtensions callback" }).optional(),
164
+ oracle: z$1.strictObject({
165
+ poolMin: z$1.int().min(10).default(10),
166
+ poolMax: z$1.int().min(10).default(100),
167
+ poolIncrement: z$1.int().min(1).default(10)
168
+ }).optional().default({
169
+ poolMin: 10,
170
+ poolMax: 100,
171
+ poolIncrement: 10
172
+ })
160
173
  });
161
174
  //#endregion
162
175
  //#region src/backend/util/oracledb-provider.ts
@@ -209,58 +222,107 @@ globalThis.__VERSION__ ??= "**development**";
209
222
  * Returns the current library version
210
223
  * @returns {string} - Version.
211
224
  */
212
- const getVersion = () => "1.7.0";
225
+ const getVersion = () => "1.8.0";
213
226
  //#endregion
214
- //#region src/backend/server/config.ts
215
- const paddedLine = (title, value) => {
216
- console.log(`${(title + ":").padEnd(30)} ${value}`);
217
- };
218
- /**
219
- * Show configuration.
220
- * @param config - The config.
221
- */
222
- const showConfig = (config) => {
223
- const LINE = "-".repeat(80);
224
- console.log(LINE);
225
- console.log(`NODE PL/SQL SERVER version ${getVersion()}`);
226
- console.log(LINE);
227
- paddedLine("Server port", config.port);
228
- paddedLine("Admin route", `${config.adminRoute ?? "/admin"}${config.adminUser ? " (authenticated)" : ""}`);
229
- paddedLine("Access log", config.loggerFilename.length > 0 ? config.loggerFilename : "-");
230
- paddedLine("Upload file size limit", typeof config.uploadFileSizeLimit === "number" ? `${config.uploadFileSizeLimit} bytes` : "-");
231
- if (config.routeStatic.length > 0) config.routeStatic.forEach((e, i) => {
232
- console.log(`Static route #${i + 1}:`);
233
- paddedLine(" Route", e.route);
234
- paddedLine(" Path", e.directoryPath);
235
- });
236
- if (config.routePlSql.length > 0) config.routePlSql.forEach((e, i) => {
237
- console.log(`PL/SQL route #${i + 1}:`);
238
- let transactionMode = "";
239
- if (typeof e.transactionMode === "string") transactionMode = e.transactionMode;
240
- else if (typeof e.transactionMode === "function") transactionMode = "custom callback";
241
- paddedLine(" Route", `http://localhost:${config.port}${e.route}`);
242
- paddedLine(" Oracle user", e.user);
243
- paddedLine(" Oracle server", e.connectString);
244
- paddedLine(" Oracle document table", e.documentTable);
245
- paddedLine(" Default page", e.defaultPage.length > 0 ? e.defaultPage : "-");
246
- paddedLine(" Path alias", e.pathAlias ?? "-");
247
- paddedLine(" Path alias procedure", e.pathAliasProcedure ?? "-");
248
- paddedLine(" Exclution list", e.exclusionList ? e.exclusionList.join(", ") : "-");
249
- paddedLine(" Validation function", e.requestValidationFunction ?? "-");
250
- paddedLine(" After request handler", transactionMode.length > 0 ? transactionMode : "-");
251
- paddedLine(" Error style", e.errorStyle);
252
- });
253
- console.log(LINE);
254
- const baseUrl = `http://localhost:${config.port}`;
255
- paddedLine("🏠 Admin Console", `${baseUrl}${config.adminRoute ?? "/admin"}`);
256
- if (config.routePlSql.length > 0) {
257
- console.log("");
258
- console.log("⚙️ PL/SQL Gateways:");
259
- config.routePlSql.forEach((e) => {
260
- console.log(` ${e.route.padEnd(28)} ${baseUrl}${e.route}`);
227
+ //#region src/backend/server/printBanner.ts
228
+ const ICON_GLOBE = "🌐";
229
+ const ICON_KEY = "🔑";
230
+ const ICON_DOC = "📄";
231
+ const ICON_PACKAGE = "📦";
232
+ const ICON_LINK = "🔗";
233
+ const ICON_FOLDER = "📁";
234
+ const ICON_GEAR = "🔧";
235
+ const ICON_HOME = "🏠";
236
+ const W = 80;
237
+ const LABEL_W = 35;
238
+ const VALUE_W = W - LABEL_W - 5;
239
+ const BOX = {
240
+ h: "─",
241
+ v: "",
242
+ tl: "",
243
+ tr: "",
244
+ bl: "╰",
245
+ br: "╯",
246
+ ml: "├",
247
+ mr: "┤"
248
+ };
249
+ const displayWidth = (s) => stringWidth(s, { ambiguousIsNarrow: true });
250
+ const padTo = (s, w) => {
251
+ const delta = w - displayWidth(s);
252
+ return delta > 0 ? s + " ".repeat(delta) : s;
253
+ };
254
+ const truncateTo = (s, w) => w > 0 ? sliceAnsi(s, 0, w) : "";
255
+ const divider = () => chalk.dim(BOX.ml + BOX.h.repeat(W - 2) + BOX.mr);
256
+ const bottom = () => chalk.dim(BOX.bl + BOX.h.repeat(W - 2) + BOX.br);
257
+ /**
258
+ * Single key/value row.
259
+ * @param key - Label text.
260
+ * @param value - Display value; null/undefined/empty renders as a dim dash.
261
+ * @param icon - Optional leading icon (must be a single-cell or double-cell glyph).
262
+ * @returns Formatted string.
263
+ */
264
+ const row = (key, value, icon) => {
265
+ const label = padTo(icon ? `${icon} ${key}` : ` ${key}`, LABEL_W);
266
+ const hasValue = value !== null && value !== void 0 && value !== "";
267
+ const valueText = hasValue ? String(value) : "—";
268
+ const colorFn = hasValue ? chalk.white : chalk.dim;
269
+ const valueCell = padTo(truncateTo(valueText, VALUE_W), VALUE_W);
270
+ return chalk.dim(BOX.v) + " " + chalk.dim(label) + " " + colorFn(valueCell) + " " + chalk.dim(BOX.v);
271
+ };
272
+ /**
273
+ * Renders the server startup banner to stdout.
274
+ * @param cfg - Server configuration.
275
+ */
276
+ const printBanner = (cfg) => {
277
+ const adminRoute = cfg.adminRoute ?? "/admin";
278
+ const baseUrl = `http://localhost:${cfg.port}`;
279
+ const lines = [];
280
+ const title = `NODE PL/SQL SERVER ${getVersion()}`;
281
+ const pad = W - 2 - displayWidth(title);
282
+ lines.push(chalk.dim(BOX.tl + BOX.h.repeat(W - 2) + BOX.tr));
283
+ lines.push(chalk.dim(BOX.v) + " ".repeat(Math.floor(pad / 2)) + chalk.bold.cyan(title) + " ".repeat(Math.ceil(pad / 2)) + chalk.dim(BOX.v));
284
+ lines.push(divider());
285
+ lines.push(row("Port", cfg.port, ICON_GLOBE));
286
+ lines.push(row("Admin route", `${adminRoute}${cfg.adminUser ? " (authenticated)" : ""}`, ICON_KEY));
287
+ lines.push(row("Access log", cfg.loggerFilename, ICON_DOC));
288
+ lines.push(row("Upload limit", typeof cfg.uploadFileSizeLimit === "number" ? `${cfg.uploadFileSizeLimit} bytes` : null, ICON_PACKAGE));
289
+ lines.push(divider());
290
+ lines.push(row("Oracle pool min", cfg.oracle.poolMin, ICON_LINK));
291
+ lines.push(row("Oracle pool max", cfg.oracle.poolMax, ICON_LINK));
292
+ lines.push(row("Oracle pool increment", cfg.oracle.poolIncrement, ICON_LINK));
293
+ if (cfg.routeStatic.length > 0) {
294
+ lines.push(divider());
295
+ cfg.routeStatic.forEach((r, i) => {
296
+ lines.push(row(`Static route #${i + 1} route`, r.route, ICON_FOLDER));
297
+ lines.push(row(`Static route #${i + 1} path`, r.directoryPath));
298
+ });
299
+ }
300
+ if (cfg.routePlSql.length > 0) {
301
+ lines.push(divider());
302
+ cfg.routePlSql.forEach((r, i) => {
303
+ const txMode = typeof r.transactionMode === "string" ? r.transactionMode : r.transactionMode ? "custom" : "";
304
+ const n = `PL/SQL route #${i + 1}`;
305
+ lines.push(row(`${n} route`, r.route, ICON_GEAR));
306
+ lines.push(row(`${n} Oracle user`, r.user));
307
+ lines.push(row(`${n} Oracle server`, r.connectString));
308
+ lines.push(row(`${n} document table`, r.documentTable));
309
+ lines.push(row(`${n} default page`, r.defaultPage));
310
+ lines.push(row(`${n} path alias`, r.pathAlias ?? ""));
311
+ lines.push(row(`${n} path alias proc`, r.pathAliasProcedure ?? ""));
312
+ lines.push(row(`${n} exclusion list`, r.exclusionList?.join(", ") ?? ""));
313
+ lines.push(row(`${n} validation fn`, r.requestValidationFunction ?? ""));
314
+ lines.push(row(`${n} session mode`, txMode));
315
+ lines.push(row(`${n} auth`, typeof r.auth === "string" ? r.auth : ""));
316
+ lines.push(row(`${n} error style`, r.errorStyle));
261
317
  });
262
318
  }
263
- console.log(LINE);
319
+ lines.push(divider());
320
+ lines.push(row("Admin console", `${baseUrl}${adminRoute}`, ICON_HOME));
321
+ cfg.routePlSql.forEach((r) => {
322
+ lines.push(row(r.route, `${baseUrl}${r.route}`, ICON_GEAR));
323
+ });
324
+ lines.push(bottom());
325
+ console.log(lines.join("\n"));
264
326
  };
265
327
  //#endregion
266
328
  //#region src/backend/server/server.ts
@@ -297,7 +359,7 @@ const createServer = (app, ssl) => {
297
359
  const startServer = async (config, ssl) => {
298
360
  debug$14("startServer: BEGIN", config, ssl);
299
361
  const internalConfig = z$configType.parse(config);
300
- showConfig(internalConfig);
362
+ printBanner(internalConfig);
301
363
  const app = express();
302
364
  if (internalConfig.devMode) app.use(cors({
303
365
  origin: "http://localhost:5173",
@@ -322,7 +384,10 @@ const startServer = async (config, ssl) => {
322
384
  const handler = handlerWebPlSql(await createPool({
323
385
  user: i.user,
324
386
  password: i.password,
325
- connectString: i.connectString
387
+ connectString: i.connectString,
388
+ poolMin: internalConfig.oracle.poolMin,
389
+ poolMax: internalConfig.oracle.poolMax,
390
+ poolIncrement: internalConfig.oracle.poolIncrement
326
391
  }), i, adminContext);
327
392
  app.use([`${i.route}/:name`, i.route], (req, res, next) => {
328
393
  const start = process.hrtime();
@@ -1819,6 +1884,68 @@ const tryDecodeDate = (value) => {
1819
1884
  //#region src/backend/handler/plsql/sendResponse.ts
1820
1885
  const debug$7 = debugModule("webplsql:sendResponse");
1821
1886
  /**
1887
+ * Pipe a readable to the response and await completion.
1888
+ * Listeners are attached before piping to avoid races.
1889
+ *
1890
+ * @param readable - The source readable stream.
1891
+ * @param res - The HTTP response stream.
1892
+ */
1893
+ const pipeReadableToResponse = async (readable, res) => {
1894
+ await new Promise((resolve, reject) => {
1895
+ let settled = false;
1896
+ const canAddResponseListener = typeof res.on === "function";
1897
+ const canRemoveResponseListener = typeof res.removeListener === "function";
1898
+ const cleanup = () => {
1899
+ readable.removeListener("end", onReadableEnd);
1900
+ readable.removeListener("close", onReadableClose);
1901
+ readable.removeListener("error", onReadableError);
1902
+ if (canRemoveResponseListener) {
1903
+ res.removeListener("finish", onResponseFinish);
1904
+ res.removeListener("close", onResponseClose);
1905
+ }
1906
+ };
1907
+ const resolveOnce = () => {
1908
+ if (settled) return;
1909
+ settled = true;
1910
+ cleanup();
1911
+ resolve();
1912
+ };
1913
+ const rejectOnce = (error) => {
1914
+ if (settled) return;
1915
+ settled = true;
1916
+ cleanup();
1917
+ reject(error);
1918
+ };
1919
+ const onReadableEnd = () => {
1920
+ resolveOnce();
1921
+ };
1922
+ const onReadableClose = () => {
1923
+ resolveOnce();
1924
+ };
1925
+ const onReadableError = (error) => {
1926
+ rejectOnce(error);
1927
+ };
1928
+ const onResponseFinish = () => {
1929
+ resolveOnce();
1930
+ };
1931
+ const onResponseClose = () => {
1932
+ resolveOnce();
1933
+ };
1934
+ readable.on("end", onReadableEnd);
1935
+ readable.on("close", onReadableClose);
1936
+ readable.on("error", onReadableError);
1937
+ if (canAddResponseListener) {
1938
+ res.on("finish", onResponseFinish);
1939
+ res.on("close", onResponseClose);
1940
+ }
1941
+ try {
1942
+ readable.pipe(res);
1943
+ } catch (error) {
1944
+ rejectOnce(error instanceof Error ? error : new Error(String(error)));
1945
+ }
1946
+ });
1947
+ };
1948
+ /**
1822
1949
  * Send "default" response to the browser
1823
1950
  * @param _req - The req object represents the HTTP request.
1824
1951
  * @param res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.
@@ -1864,16 +1991,8 @@ const sendResponse = async (_req, res, page) => {
1864
1991
  debugText.push(`res.pipe("${page.file.fileType ?? ""}") - streaming`);
1865
1992
  debug$7(getBlock("RESPONSE", debugText.join("\n")));
1866
1993
  }
1867
- await new Promise((resolve, reject) => {
1868
- if (page.file.fileBlob instanceof stream.Readable) {
1869
- page.file.fileBlob.pipe(res);
1870
- page.file.fileBlob.on("end", () => resolve());
1871
- /* v8 ignore next - error handler */
1872
- page.file.fileBlob.on("error", (err) => reject(err));
1873
- /* v8 ignore next - error handler */
1874
- res.on("close", () => resolve());
1875
- }
1876
- });
1994
+ /* v8 ignore stop */
1995
+ await pipeReadableToResponse(page.file.fileBlob, res);
1877
1996
  } else {
1878
1997
  /* v8 ignore start */
1879
1998
  if (debug$7.enabled) {
@@ -1908,16 +2027,7 @@ const sendResponse = async (_req, res, page) => {
1908
2027
  debug$7(getBlock("RESPONSE", debugText.join("\n")));
1909
2028
  }
1910
2029
  /* v8 ignore stop */
1911
- if (page.body instanceof stream.Readable) await new Promise((resolve, reject) => {
1912
- if (page.body instanceof stream.Readable) {
1913
- page.body.pipe(res);
1914
- page.body.on("end", () => resolve());
1915
- /* v8 ignore next - error handler */
1916
- page.body.on("error", (err) => reject(err));
1917
- /* v8 ignore next - error handler */
1918
- res.on("close", () => resolve());
1919
- }
1920
- });
2030
+ if (page.body instanceof stream.Readable) await pipeReadableToResponse(page.body, res);
1921
2031
  else res.send(page.body);
1922
2032
  };
1923
2033
  //#endregion
@@ -2720,7 +2830,7 @@ const getCookieString = (req) => {
2720
2830
  * @returns The DAD structure.
2721
2831
  */
2722
2832
  const getPath = (req) => {
2723
- const pathname = new URL(`${req.protocol}://${os.hostname()}${req.originalUrl}`).pathname;
2833
+ const pathname = new URL$1(`${req.protocol}://${os.hostname()}${req.originalUrl}`).pathname;
2724
2834
  const tmp = trimPath(pathname.slice(0, Math.max(0, pathname.lastIndexOf("/") + 1)));
2725
2835
  return {
2726
2836
  script: `/${tmp}`,
@@ -2998,7 +3108,8 @@ const requestHandler = async (req, res, _next, connectionPool, options, procedur
2998
3108
  }
2999
3109
  }
3000
3110
  if (typeof req.params.name !== "string" || req.params.name.length === 0) if (typeof options.defaultPage === "string" && options.defaultPage.length > 0) {
3001
- const newUrl = url.resolve(`${req.originalUrl}/${options.defaultPage}`, "");
3111
+ const currentUrl = new URL(req.originalUrl, "http://localhost");
3112
+ const newUrl = `${currentUrl.pathname.endsWith("/") ? currentUrl.pathname : `${currentUrl.pathname}/`}${options.defaultPage.replace(/^\/+/, "")}${currentUrl.search}`;
3002
3113
  debug$1(`Redirect to the url "${newUrl}"`);
3003
3114
  res.redirect(newUrl);
3004
3115
  } else errorPage(req, res, options, new RequestError("No procedure name given and no default page has been specified"));
@@ -3301,8 +3412,7 @@ const createAdminRouter = (adminContext) => {
3301
3412
  * @throws {Error} if project root cannot be found
3302
3413
  */
3303
3414
  const resolveAdminStaticDir = () => {
3304
- const __filename = fileURLToPath(import.meta.url);
3305
- let projectRoot = path.dirname(__filename);
3415
+ let projectRoot = import.meta.dirname;
3306
3416
  while (!existsSync(path.join(projectRoot, "package.json")) && projectRoot !== "/") projectRoot = path.dirname(projectRoot);
3307
3417
  if (projectRoot === "/") throw new Error("Could not find project root (package.json). Please provide explicit staticDir in AdminConsoleConfig.");
3308
3418
  return path.join(projectRoot, "dist", "frontend");
@@ -3426,6 +3536,58 @@ const createSpaFallback = (directoryPath, _route) => {
3426
3536
  };
3427
3537
  };
3428
3538
  //#endregion
3429
- export { AdminContext, createAdminRouter, createServer, createSpaFallback, forceShutdown, getJsonFile, getVersion, handlerAdminConsole, handlerLogger, handlerUpload, handlerWebPlSql, installShutdown, isDirectory, isFile, loadConfig, oracledb_provider_exports as oracledb, readFile, readFileSyncUtf8, removeFile, showConfig, startServer, startServerConfig, z$configType };
3539
+ //#region src/backend/util/printError.ts
3540
+ const C = {
3541
+ red: chalk.ansi256(196),
3542
+ gray: chalk.gray,
3543
+ dimGray: chalk.blackBright,
3544
+ white: chalk.whiteBright
3545
+ };
3546
+ /**
3547
+ * Prints a formatted CLI error block to stderr with timestamp.
3548
+ *
3549
+ * @param message - Primary error description
3550
+ * @param meta - Optional metadata
3551
+ */
3552
+ const printError = (message, meta) => {
3553
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace(/(\.\d{3})Z$/, ".$1 UTC");
3554
+ const sep = C.dimGray("─".repeat(48));
3555
+ const header = `${C.red("✖ ERROR")} ${C.dimGray(ts)}`;
3556
+ const body = C.white(message);
3557
+ const defaultMeta = {
3558
+ pid: String(process$1.pid),
3559
+ node: process$1.version,
3560
+ ...meta
3561
+ };
3562
+ const footer = Object.entries(defaultMeta).map(([k, v]) => `${C.dimGray(k)} ${C.gray(v)}`).join(" ");
3563
+ console.error([
3564
+ sep,
3565
+ header,
3566
+ body,
3567
+ sep,
3568
+ footer
3569
+ ].join("\n"));
3570
+ };
3571
+ //#endregion
3572
+ //#region src/backend/util/logError.ts
3573
+ /**
3574
+ * Log an error.
3575
+ *
3576
+ * @param error - The error.
3577
+ */
3578
+ const logError = (error) => {
3579
+ let message = "";
3580
+ if (error instanceof Error) message = errorToString(error);
3581
+ else if (typeof error === "string") message = error;
3582
+ logToFile(message);
3583
+ jsonLogger.log({
3584
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3585
+ type: "error",
3586
+ message
3587
+ });
3588
+ printError(message);
3589
+ };
3590
+ //#endregion
3591
+ export { AdminContext, createAdminRouter, createServer, createSpaFallback, forceShutdown, getJsonFile, getVersion, handlerAdminConsole, handlerLogger, handlerUpload, handlerWebPlSql, installShutdown, isDirectory, isFile, loadConfig, logError, oracledb_provider_exports as oracledb, printBanner, readFile, readFileSyncUtf8, removeFile, startServer, startServerConfig, z$configType };
3430
3592
 
3431
3593
  //# sourceMappingURL=index.mjs.map