bazis 0.97.0 → 0.97.1

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.
@@ -150,8 +150,15 @@ export class HttpServer implements HostedService {
150
150
  if (this.options.securityHeaders !== false) {
151
151
  serverChain.push(securityHeaders(this.options.securityHeaders ?? {}));
152
152
  }
153
+ // Unexpected errors go to the application logger, like the access log.
154
+ const errorLogger = this.resolver.tryResolve(LOGGER);
153
155
  serverChain.push(
154
- errorHandler({ exposeDetails, ...this.options.errorHandler, onUnexpectedError: this.collectErrorHook() }),
156
+ errorHandler({
157
+ exposeDetails,
158
+ ...(errorLogger ? { logger: errorLogger } : {}),
159
+ ...this.options.errorHandler,
160
+ onUnexpectedError: this.collectErrorHook(),
161
+ }),
155
162
  );
156
163
  this.shortCircuitChain = [...serverChain];
157
164
  serverChain.push(...this.collectServerMiddleware());
@@ -1,4 +1,5 @@
1
- import { redactSensitive } from "../../../library/redaction";
1
+ import { REQUEST_ID_STATE_KEY, type Logger } from "../../kernel";
2
+ import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
2
3
  import type { HttpContext } from "../HttpContext/HttpContext";
3
4
  import { HttpError } from "../Errors/HttpError";
4
5
  import type { HttpMiddleware } from "./types";
@@ -9,10 +10,20 @@ export interface ErrorHandlerOptions {
9
10
  * never enable in production).
10
11
  */
11
12
  exposeDetails?: boolean;
12
- /** Called for non-{@link HttpError} failures (self-wired via `HTTP_ERROR_HOOK`, e.g. the logging module). */
13
+ /**
14
+ * Notified of every non-{@link HttpError} failure, for example to send it to
15
+ * an error tracker; `HTTP_ERROR_HOOK` registrations are merged in. It does not
16
+ * replace logging: the error is logged as well.
17
+ */
13
18
  onUnexpectedError?: (ctx: HttpContext, error: unknown) => void;
14
- /** Fallback when {@link onUnexpectedError} is omitted or does not handle the error. */
19
+ /** Replaces the built-in logging of unexpected errors. */
15
20
  logError?: (error: unknown) => void;
21
+ /**
22
+ * Logger for unexpected errors when {@link logError} is absent: one `error`
23
+ * line with the method, path, request id and the redacted error. The server
24
+ * passes the application `LOGGER`; without either, `console.error` is used.
25
+ */
26
+ logger?: Logger;
16
27
  }
17
28
 
18
29
  function jsonResponse(status: number, body: unknown, extraHeaders?: readonly [string, string][]): Response {
@@ -33,9 +44,30 @@ function safeJsonResponse(status: number, body: unknown, extraHeaders?: readonly
33
44
  }
34
45
  }
35
46
 
47
+ type ErrorLog = (ctx: HttpContext, error: unknown) => void;
48
+
49
+ function resolveErrorLog(options: ErrorHandlerOptions): ErrorLog {
50
+ const { logError, logger } = options;
51
+ if (logError) {
52
+ return (_ctx, error) => logError(error);
53
+ }
54
+ if (logger) {
55
+ return (ctx, error) => {
56
+ const requestId = ctx.state.get(REQUEST_ID_STATE_KEY);
57
+ logger.error(`${ctx.method} ${ctx.path} failed`, {
58
+ method: ctx.method,
59
+ path: ctx.path,
60
+ ...(typeof requestId === "string" ? { requestId } : {}),
61
+ error: redactSensitive(error),
62
+ });
63
+ };
64
+ }
65
+ return (_ctx, error) => console.error("[http] unhandled error:", redactSensitive(error));
66
+ }
67
+
36
68
  function safelyReportUnexpected(
37
69
  options: ErrorHandlerOptions,
38
- fallback: (error: unknown) => void,
70
+ log: ErrorLog,
39
71
  ctx: HttpContext,
40
72
  error: unknown,
41
73
  ): void {
@@ -43,13 +75,13 @@ function safelyReportUnexpected(
43
75
  options.onUnexpectedError?.(ctx, error);
44
76
  } catch (hookError) {
45
77
  try {
46
- fallback(hookError);
78
+ log(ctx, hookError);
47
79
  } catch {
48
80
  // Logging hooks are best-effort; response safety is primary.
49
81
  }
50
82
  }
51
83
  try {
52
- fallback(error);
84
+ log(ctx, error);
53
85
  } catch {
54
86
  // A broken sink must not escape the HTTP error boundary.
55
87
  }
@@ -61,7 +93,7 @@ function safelyReportUnexpected(
61
93
  * else becomes 500 Internal Server Error.
62
94
  */
63
95
  export function errorHandler(options: ErrorHandlerOptions = {}): HttpMiddleware {
64
- const fallback = options.logError ?? ((error: unknown) => console.error("[http] unhandled error:", redactSensitive(error)));
96
+ const log = resolveErrorLog(options);
65
97
  return async (ctx, next) => {
66
98
  try {
67
99
  await next();
@@ -87,12 +119,14 @@ export function errorHandler(options: ErrorHandlerOptions = {}): HttpMiddleware
87
119
  );
88
120
  return;
89
121
  }
90
- safelyReportUnexpected(options, fallback, ctx, error);
122
+ safelyReportUnexpected(options, log, ctx, error);
91
123
  ctx.response = options.exposeDetails
92
124
  ? safeJsonResponse(500, {
93
125
  error: "Internal Server Error",
94
- message: error instanceof Error ? error.message : String(error),
95
- stack: error instanceof Error ? error.stack : undefined,
126
+ // Development only, but redacted like the log: a secret in an error
127
+ // message must not reach a browser or a shared screenshot.
128
+ message: redactSensitiveText(error instanceof Error ? error.message : String(error)),
129
+ stack: error instanceof Error && error.stack !== undefined ? redactSensitiveText(error.stack) : undefined,
96
130
  })
97
131
  : safeJsonResponse(500, { error: "Internal Server Error" });
98
132
  }
package/core/http/SPEC.md CHANGED
@@ -405,9 +405,10 @@ Exported from `bazis/core/http`. They are connected through `@Middleware(...)`,
405
405
 
406
406
  | `ErrorHandlerOptions` field | Type | Default | Description |
407
407
  | --- | --- | --- | --- |
408
- | `exposeDetails` | `boolean` | `httpModule({ exposeErrorDetails })` / environment | message/stack in 500 responses; never enable in production |
409
- | `onUnexpectedError` | `(ctx, error) => void` | DI `HTTP_ERROR_HOOK` hooks | called for non-`HttpError` failures |
410
- | `logError` | `(error) => void` | `console.error` | fallback when no hook handles the error |
408
+ | `exposeDetails` | `boolean` | `httpModule({ exposeErrorDetails })` / environment | redacted message/stack in 500 responses; never enable in production |
409
+ | `onUnexpectedError` | `(ctx, error) => void` | DI `HTTP_ERROR_HOOK` hooks | notification for non-`HttpError` failures (e.g. an error tracker); the error is still logged |
410
+ | `logError` | `(error) => void` | — | replaces the built-in logging of unexpected errors |
411
+ | `logger` | `Logger` | the application `LOGGER` | one `error` line: `"<METHOD> <path> failed"` with `method`, `path`, `requestId` and the redacted `error`; `console.error` when there is no logger |
411
412
 
412
413
  `HttpError` → JSON with its status; anything else → 500.
413
414
 
@@ -31,8 +31,8 @@ export const ROUTE_MIDDLEWARE_COMPOSER = createToken<RouteMiddlewareComposerRegi
31
31
 
32
32
  /**
33
33
  * Hook invoked by the built-in error boundary on a non-{@link HttpError}
34
- * failure. Self-wiring: e.g. the logging module registers one to record the
35
- * error, keeping `@/core/http` free of logging imports. Collected via `resolveAll`.
34
+ * failure, for example to report it to an error tracker. It is a notification:
35
+ * the error is still logged and answered with 500. Collected via `resolveAll`.
36
36
  */
37
37
  export type HttpErrorHook = (ctx: HttpContext, error: unknown) => void;
38
38
 
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
232
232
  if (method === "account/updated") this.modelCache = undefined;
233
233
  }, () => { this.clearLogin(); this.modelCache = undefined; });
234
234
  const initialized = await client.request("initialize", {
235
- clientInfo: { name: "bazis", title: "Bazis", version: "0.97.0" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.97.1" }, capabilities: { experimentalApi: true },
236
236
  }, 15_000, signal);
237
237
  if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
238
238
  client.notify("initialized");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.97.0",
3
+ "version": "0.97.1",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",