fix(cli): reuse shared normalizeBootError helper in instrumentation.ts

The outermost instrumentation-hook boot boundary (#10171) was inlining its
own err-instanceof-Error normalization instead of reusing the existing
normalizeBootError() helper already defined in instrumentation-node.ts for
the same purpose (#6560/#7773). Extract it into a dependency-free
src/lib/instrumentationBootError.ts so both instrumentation.ts (which also
loads under the Edge runtime) and instrumentation-node.ts can import it
statically without risking a second failing dynamic import of
instrumentation-node.ts from within the catch block.
This commit is contained in:
adevwithpurpose
2026-08-17 22:38:55 -03:00
parent a3236a013f
commit d917e93974
3 changed files with 42 additions and 18 deletions

View File

@@ -7,6 +7,7 @@
*/
import { markServerReady, markServerStarting } from "@/lib/serverLifecycle";
import { normalizeBootError } from "@/lib/instrumentationBootError";
function getRandomBytes(byteLength: number): Uint8Array {
const bytes = new Uint8Array(byteLength);
@@ -37,23 +38,13 @@ export function renameProcessTitle(currentTitle: string): string {
return `omniroute${currentTitle.slice("next-server".length)}`;
}
/**
* Normalize any thrown/rejected value into a real `Error` instance.
*
* Next.js's own `registerInstrumentation()` wrapper (see
* `node_modules/next/dist/server/lib/router-utils/instrumentation-globals.external.js`)
* unconditionally does `err.message = \`...${err.message}\`` on whatever our
* `register()` export rejects with, assuming it is always an `Error`. If a raw
* non-Error primitive bubbles up instead (e.g. sql.js's WASM adapter throws the
* bare string `"Database closed"` — see `./lib/db/adapters/sqljsAdapter.ts`),
* that assignment throws `TypeError: Cannot create property 'message' on
* string '...'` in strict mode, masking the original error and crashing the
* whole server on every boot (#6560). Normalizing before it leaves our code
* guarantees Next always receives something `.message`-assignable.
*/
export function normalizeBootError(err: unknown): Error {
return err instanceof Error ? err : new Error(String(err));
}
// `normalizeBootError` now lives in `@/lib/instrumentationBootError` (imported
// above) — shared, dependency-free, and reused by `src/instrumentation.ts`'s
// outermost boot boundary (#10171) so both boot-failure logging sites agree
// on the same normalization instead of maintaining two copies of the same
// one-liner. Re-exported here so existing callers/tests importing it from
// this module keep working unchanged.
export { normalizeBootError };
// Matches sql.js's raw `throw "Database closed"` (and similarly-worded
// variants) thrown when a query runs against an already-closed WASM handle —

View File

@@ -8,6 +8,8 @@
* @see https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
*/
import { normalizeBootError } from "@/lib/instrumentationBootError";
/**
* `registerNodejsFn` is only for tests to inject a fake without module-mocking
* (`node:test` does not support `mock.module` reliably here) — mirrors the
@@ -34,7 +36,12 @@ export async function register(registerNodejsFn?: () => Promise<void>) {
// up while every DB-touching route 500s forever with a permanently
// empty app.log. This guarantees stdout/app.log is never silently
// empty on a failed boot, regardless of platform or which step threw.
const normalizedError = err instanceof Error ? err : new Error(String(err));
// Reuses the same normalizeBootError() helper as ensureDbReadyForBoot
// (./instrumentation-node) — imported from a dependency-free module so
// this file, which also loads under the Edge runtime, never has to pull
// in Node-only code (or risk a second failing dynamic import of
// ./instrumentation-node itself) just to normalize the caught value.
const normalizedError = normalizeBootError(err);
const message = normalizedError.message;
console.error("[STARTUP] Fatal: instrumentation hook failed during boot:", message);
throw normalizedError;

View File

@@ -0,0 +1,26 @@
/**
* Normalize any thrown/rejected value into a real `Error` instance.
*
* Next.js's own `registerInstrumentation()` wrapper (see
* `node_modules/next/dist/server/lib/router-utils/instrumentation-globals.external.js`)
* unconditionally does `err.message = \`...${err.message}\`` on whatever our
* `register()` export (`src/instrumentation.ts`) rejects with, assuming it is
* always an `Error`. If a raw non-Error primitive bubbles up instead (e.g.
* sql.js's WASM adapter throws the bare string `"Database closed"` — see
* `./db/adapters/sqljsAdapter.ts`), that assignment throws `TypeError: Cannot
* create property 'message' on string '...'` in strict mode, masking the
* original error and crashing the whole server on every boot (#6560).
* Normalizing before it leaves our code guarantees Next always receives
* something `.message`-assignable.
*
* Deliberately dependency-free (no imports) so both `src/instrumentation.ts`
* (the shared Edge+Node instrumentation entry point) and
* `src/instrumentation-node.ts` (the Node-only boot sequence) can import it
* statically without pulling Node-specific modules (fs/path/os/better-sqlite3
* etc.) into the Edge runtime bundle, and without relying on a dynamic
* `import("./instrumentation-node")` that could itself be the thing that
* failed to load (#10171).
*/
export function normalizeBootError(err: unknown): Error {
return err instanceof Error ? err : new Error(String(err));
}