fix(validations): route source-priority parse breadcrumb via observer

`@/lib/validations/source-priority` imported `annotate` from
`@/lib/logging/context`, which pulls `node:async_hooks` via
`AsyncLocalStorage`. Any client component touching a type-only export
(e.g. `DEFAULT_SOURCE_PRIORITY` in `components/settings/sources-section`)
dragged async_hooks into the browser bundle and broke the Turbopack
build.

The validations module now exposes `registerSourcePriorityParseObserver`
with a no-op default. `@/lib/logging/context` registers the real
`annotate` callback on first server-side import, so the breadcrumb
still surfaces in ops dashboards while the client bundle stays clean.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 18:57:59 +02:00
1 parent 7bede83d22
commit 87853eb2ac
2 files changed
+41 -2

No files matched your search

+10
View File
@@ -1,6 +1,7 @@
import { AsyncLocalStorage } from "node:async_hooks";
import type { WideEvent } from "./types";
import { WideEventBuilder } from "./event-builder";
import { registerSourcePriorityParseObserver } from "@/lib/validations/source-priority";
/** AsyncLocalStorage fuer den aktuellen Request-Kontext */
export const eventStorage = new AsyncLocalStorage<WideEventBuilder>();
@@ -27,3 +28,12 @@ export function annotate(fields: {
}
}
}
// v1.4.25 Fix-G — wire the schema-parser breadcrumb into the
// AsyncLocalStorage-backed `annotate()` only on the server. The
// validations module stays bundle-safe for client imports; this side
// effect runs the first time any server module imports the logging
// context.
registerSourcePriorityParseObserver((fields) => {
annotate(fields);
});
+31 -2
View File
@@ -34,12 +34,41 @@
*/
import { z } from "zod/v4";
import { annotate } from "@/lib/logging/context";
import {
measurementSourceEnum,
measurementTypeEnum,
} from "@/lib/validations/measurement";
// v1.4.25 Fix-G — `annotate` from `@/lib/logging/context` was the
// canonical breadcrumb for a parse failure, but the helper pulls
// `node:async_hooks` via `AsyncLocalStorage`. Any client component that
// reaches for a type-only export here (e.g.
// `src/components/settings/sources-section.tsx` importing
// `DEFAULT_SOURCE_PRIORITY`) used to drag `node:async_hooks` into the
// browser bundle and break the Turbopack build. The parser stays in the
// shared module — splitting it out would ripple through every callsite —
// so the breadcrumb path now goes through a runtime-resolved callback.
// The server logging module registers the real `annotate` on first import
// (via `registerSourcePriorityParseObserver`); the browser leaves it as
// the no-op default and never traces `node:async_hooks`.
type ParseFailureObserver = (entry: {
meta: Record<string, unknown>;
}) => void;
let parseFailureObserver: ParseFailureObserver = () => {};
/**
* Server-side opt-in for the parse-failure breadcrumb. Called once from
* `@/lib/logging/context` so a buggy schema-tightening that silently
* wipes a saved ladder still surfaces in ops dashboards. The client
* bundle never imports the logging module, so the observer stays the
* no-op default in the browser.
*/
export function registerSourcePriorityParseObserver(
observer: ParseFailureObserver,
): void {
parseFailureObserver = observer;
}
/**
* Device-type tag attached to a `Measurement` row. Mirrors
* open-wearables (`watch | band | ring | phone | scale | other`) plus
@@ -280,7 +309,7 @@ export function parseSourcePriority(raw: unknown): ResolvedSourcePriority {
// priorities reset?" user report. `annotate()` is a no-op outside a
// request context, so the static settings-page render and tests
// remain side-effect-free.
annotate({
parseFailureObserver({
meta: {
sourcePriority: {
parse: "failed",