From 15007513163e00f5c880f20743d463bda37568c6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 18:21:18 +0200 Subject: [PATCH 001/104] fix(config): negatively cache deterministic hosted config rejections and stop reporting 4xx errors A hosted veryfront.config that the declarative evaluator deterministically rejects (e.g. forbidden-capability: unsupported-call) was re-read, re-hashed and re-sent to the evaluator worker on every request, because only successful evaluations were cached. Each per-request rethrow at the renderer.request boundary also produced a Sentry event even though CONFIG_PARSE_ERROR is a status-400 tenant-content failure. - Add a negative cache next to the positive hosted config cache, keyed by the same source-digest/policy/fingerprint cache key so a corrected config or a new release self-invalidates the entry. Worker-phase and retryable failures are never cached. - Treat client-class (4xx) VeryfrontErrors as expected in captureApplicationError so tenant-content failures no longer flood the error tracker; they remain logged at their throw sites. Fixes VERYFRONT-SERVER-E --- src/config/loader.test.ts | 179 +++++++++++++++++++ src/config/loader.ts | 53 +++++- src/observability/application-errors.test.ts | 35 ++++ src/observability/application-errors.ts | 9 +- 4 files changed, 274 insertions(+), 2 deletions(-) diff --git a/src/config/loader.test.ts b/src/config/loader.test.ts index 1868c82999..fbd8476940 100644 --- a/src/config/loader.test.ts +++ b/src/config/loader.test.ts @@ -2209,6 +2209,185 @@ export default config as const; assertEquals(reads, 4); }); + describe("hosted config negative caching", () => { + const productionSourceContext = { + productionMode: true, + releaseId: "release-negative-cache", + environmentName: "Production", + } as const; + type PreparedContext = Awaited< + ReturnType + >; + type TestAdapter = ReturnType; + + function createHostedAdapter( + readSource: () => string = () => 'export default { title: "source" };', + ): TestAdapter { + const adapter = setup(); + Object.assign(adapter.fs, { + getUnderlyingAdapter: () => adapter.fs, + isMultiProjectMode: () => true, + isVeryfrontAdapter: () => true, + exists: async (path: string) => path === "/veryfront.config.ts", + readFile: async (path: string) => { + if (path !== "/veryfront.config.ts") throw configCandidateNotFound(path); + return readSource(); + }, + }); + return adapter; + } + + function loadProductionHostedConfig( + adapter: TestAdapter, + preparedContext: PreparedContext, + ) { + const projectId = "project-negative-cache"; + return runWithRequestContext( + { + projectSlug: projectId, + projectId, + token: "token", + productionMode: true, + releaseId: productionSourceContext.releaseId, + environmentName: productionSourceContext.environmentName, + }, + () => + getHostedConfig(`/hosted/${projectId}`, adapter, { + cacheKey: projectId, + sourceContext: productionSourceContext, + preparedContext, + }), + ); + } + + function prepareProductionContext(): Promise { + return prepareDeclarativeConfigContext({ + environmentName: "Production", + environment: { TENANT: "tenant" }, + }); + } + + it("does not re-evaluate a deterministically rejected hosted config on later requests", async () => { + const adapter = createHostedAdapter(); + const preparedContext = await prepareProductionContext(); + let evaluations = 0; + __setHostedConfigEvaluatorForTests(async () => { + evaluations += 1; + throw new DeclarativeConfigEvaluationError({ + code: "forbidden-capability", + phase: "validate", + reason: "unsupported-call", + }); + }); + + const first = await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ) as VeryfrontError; + const second = await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ) as VeryfrontError; + + assertEquals(first.slug, "config-parse-error"); + assertEquals(second.slug, "config-parse-error"); + assertStringIncludes( + first.detail ?? "", + "Hosted configuration rejected (forbidden-capability: unsupported-call)", + ); + assertStringIncludes( + second.detail ?? "", + "Hosted configuration rejected (forbidden-capability: unsupported-call)", + ); + assertEquals( + evaluations, + 1, + "a deterministic rejection must be negatively cached, not re-evaluated per request", + ); + }); + + it("re-evaluates a rejected hosted config after the source changes", async () => { + let source = "const forbidden = process.env;\nexport default { title: 'source' };"; + const adapter = createHostedAdapter(() => source); + const preparedContext = await prepareProductionContext(); + let evaluations = 0; + __setHostedConfigEvaluatorForTests(async () => { + evaluations += 1; + if (evaluations === 1) { + throw new DeclarativeConfigEvaluationError({ + code: "forbidden-capability", + phase: "validate", + reason: "unsupported-call", + }); + } + return { title: "corrected" }; + }); + + await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ); + source = 'export default { title: "corrected" };'; + const corrected = await loadProductionHostedConfig(adapter, preparedContext); + + assertEquals(corrected.title, "corrected"); + assertEquals(evaluations, 2); + }); + + it("re-evaluates a rejected hosted config after clearConfigCache", async () => { + const adapter = createHostedAdapter(); + const preparedContext = await prepareProductionContext(); + let evaluations = 0; + __setHostedConfigEvaluatorForTests(async () => { + evaluations += 1; + throw new DeclarativeConfigEvaluationError({ + code: "forbidden-capability", + phase: "validate", + reason: "unsupported-call", + }); + }); + + await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ); + clearConfigCache(); + await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ); + + assertEquals(evaluations, 2); + }); + + it("never negatively caches retryable infrastructure failures", async () => { + const adapter = createHostedAdapter(); + const preparedContext = await prepareProductionContext(); + let evaluations = 0; + __setHostedConfigEvaluatorForTests(async () => { + evaluations += 1; + if (evaluations === 1) { + throw new DeclarativeConfigEvaluationError({ + code: "evaluator-unavailable", + phase: "worker", + reason: "worker-timeout", + retryable: true, + }); + } + return { title: "recovered" }; + }); + + await assertRejects( + () => loadProductionHostedConfig(adapter, preparedContext), + VeryfrontError, + ); + const recovered = await loadProductionHostedConfig(adapter, preparedContext); + + assertEquals(recovered.title, "recovered"); + assertEquals(evaluations, 2); + }); + }); + describe("hosted config single-flight", () => { const productionSourceContext = { productionMode: true, diff --git a/src/config/loader.ts b/src/config/loader.ts index 0b57ed1aec..3f68218958 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -567,6 +567,26 @@ const configCacheByProject = new LRUCache({ maxEntries: DEFAULT_CONFIG_CACHE_MAX_ENTRIES, }); +interface HostedConfigFailureCacheEntry { + readonly revision: number; + readonly error: DeclarativeConfigEvaluationError; +} + +/** + * Negative cache for deterministic hosted config rejections. + * + * The hosted cache key already folds in the exact source digest, policy + * version and environment fingerprint, so a rejected source stays rejected + * until the tenant ships different content; re-sending it to the evaluator + * worker on every request only repeats the same failure. + */ +const hostedConfigFailureCacheByProject = new LRUCache< + string, + HostedConfigFailureCacheEntry +>({ + maxEntries: DEFAULT_CONFIG_CACHE_MAX_ENTRIES, +}); + type HostedConfigEvaluator = typeof evaluatePreparedDeclarativeConfigInWorker; interface HostedConfigSourceSelection { @@ -632,8 +652,9 @@ const trustedConfigFlights = new IntrinsicMap(); const trustedVirtualFilesystemIds = new IntrinsicWeakMap(); let nextTrustedVirtualFilesystemId = 1; -// Register cache for monitoring +// Register caches for monitoring registerLRUCache("config-cache", configCacheByProject); +registerLRUCache("config-failure-cache", hostedConfigFailureCacheByProject); let cacheRevision = 0; @@ -1069,6 +1090,19 @@ function buildHostedConfigFlightKey(hostedCacheKey: string, revision: number): s return `${revision}:${hostedCacheKey}`; } +/** + * Whether a hosted evaluation failure is guaranteed to repeat for the same + * cache key. Worker-phase and retryable failures are infrastructure + * conditions that can succeed on retry, so they must never be cached. + */ +function isDeterministicHostedConfigRejection( + error: unknown, +): error is DeclarativeConfigEvaluationError { + return error instanceof DeclarativeConfigEvaluationError && + !error.retryable && + error.phase !== "worker"; +} + function createHostedConfigFlight( flightKey: string, hostedCacheKey: string, @@ -1117,6 +1151,15 @@ function createHostedConfigFlight( }, (error: unknown) => { finish(); + if ( + usePersistentCache && cacheRevision === revisionAtStart && + isDeterministicHostedConfigRejection(error) + ) { + hostedConfigFailureCacheByProject.set(hostedCacheKey, { + revision: revisionAtStart, + error, + }); + } result.reject(error); }, ); @@ -1739,6 +1782,13 @@ function loadHostedConfigFromSource( return cached.config; } + const cachedFailure = usePersistentCache + ? hostedConfigFailureCacheByProject.get(hostedCacheKey) + : undefined; + if (cachedFailure?.revision === revisionAtStart) { + throw cachedFailure.error; + } + const flight = getOrCreateHostedConfigFlight( hostedCacheKey, payload, @@ -2492,6 +2542,7 @@ export function __getTrustedConfigFlightStateForTests(): Readonly<{ export function clearConfigCache(): void { configCacheByProject.clear(); + hostedConfigFailureCacheByProject.clear(); cacheRevision++; } diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index e285dfb57d..ba7e18cf21 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -13,6 +13,7 @@ import { setApplicationErrorReporter, } from "./application-errors.ts"; import type { ApplicationErrorContext as SharedApplicationErrorContext } from "./application-error-contract.ts"; +import { CONFIG_PARSE_ERROR, INITIALIZATION_ERROR } from "#veryfront/errors"; it("application error reporter is optional", async () => { setApplicationErrorReporter(undefined); @@ -96,6 +97,40 @@ it("application error reporter ignores expected cancellation", () => { assertEquals(captured, false); }); +it("application error reporter ignores client-class veryfront errors", () => { + const captures: unknown[] = []; + setApplicationErrorReporter({ + capture(error) { + captures.push(error); + return "event-id"; + }, + flush: () => Promise.resolve(true), + }); + + const clientError = CONFIG_PARSE_ERROR.create({ + detail: "Hosted configuration rejected (forbidden-capability: unsupported-call)", + }); + assertEquals( + captureApplicationError(clientError, { boundary: "renderer.request" }), + undefined, + ); + assertEquals(captures, []); + + const serverError = INITIALIZATION_ERROR.create({ + detail: "renderer failed to initialize", + }); + assertEquals( + captureApplicationError(serverError, { boundary: "renderer.request" }), + "event-id", + ); + const plainError = new Error("render failed"); + assertEquals( + captureApplicationError(plainError, { boundary: "renderer.request" }), + "event-id", + ); + assertEquals(captures, [serverError, plainError]); +}); + it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { getPrototypeOf() { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 4fb8c2888d..d6be4129c0 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -1,3 +1,4 @@ +import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/timer.ts"; import { sanitizeTelemetryAttributes, sanitizeTelemetryText } from "./telemetry-error.ts"; import { MAX_APPLICATION_ERROR_CONTEXT_VALUE_LENGTH } from "./limits.ts"; @@ -265,7 +266,13 @@ export async function flushApplicationErrors(timeoutMs = 2_000): Promise= 400 && snapshot.status < 500; } catch { return false; } From aff43ab06c33487d150fa48f33820a233300d52c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 18:39:05 +0200 Subject: [PATCH 002/104] fix(observability): downgrade tenant build errors to tagged warning-level captures Every SSR render failure was captured per request at level=error through captureApplicationError (ssr.service.ts boundary ssr.render), even when the error object already discriminated a tenant-content build failure. Tenant mistakes (a page that does not compile, MDX that does not parse) therefore surfaced as our own error-level Sentry issues and drove sustained noise. Classify tenant build errors centrally in captureApplicationError using the discriminators the error already carries: the module loader's build-failure tag, toError(createError({ type: "build" })) structured data, VeryfrontError BUILD-category registry errors, and the render pipeline's buildFailure error context. Classified captures gain errorClass="tenant-build" and level="warning" on the shared ApplicationErrorContext; the Sentry policy maps these to a veryfront.error_class tag and scope.setLevel("warning"). The events are still captured, so tenant failures remain visible for escalation analysis, while genuine framework errors keep their error-level capture. Builds on the client-class (4xx) suppression from fix/sentry-veryfront-server-e. Refs VERYFRONT-SERVER-2 VERYFRONT-SERVER-3 VERYFRONT-SERVER-S VERYFRONT-SERVER-Q VERYFRONT-SERVER-R --- .../src/policy.test.ts | 35 ++++++++++ .../ext-observability-sentry/src/policy.ts | 3 + .../application-error-contract.ts | 4 ++ src/observability/application-errors.test.ts | 54 ++++++++++++++- src/observability/application-errors.ts | 65 ++++++++++++++++++- 5 files changed, 158 insertions(+), 3 deletions(-) diff --git a/extensions/ext-observability-sentry/src/policy.test.ts b/extensions/ext-observability-sentry/src/policy.test.ts index 320463880d..63763e18e0 100644 --- a/extensions/ext-observability-sentry/src/policy.test.ts +++ b/extensions/ext-observability-sentry/src/policy.test.ts @@ -17,6 +17,7 @@ function createSentrySdk(options: { contexts: [] as Array<[string, Record]>, fingerprints: [] as string[][], flushTimeouts: [] as Array, + levels: [] as string[], tags: [] as Array<[string, string]>, }; const scope = { @@ -26,6 +27,9 @@ function createSentrySdk(options: { setFingerprint(fingerprint: string[]) { state.fingerprints.push(fingerprint); }, + setLevel(level: "error" | "warning") { + state.levels.push(level); + }, setTag(key: string, value: string) { state.tags.push([key, value]); }, @@ -155,6 +159,37 @@ it("policy preserves process_role as a native Sentry tag", () => { ); }); +it("policy tags classified errors and applies their downgraded level", () => { + const { sdk, state } = createSentrySdk(); + + const eventId = captureWithSentryPolicy(sdk, "renderer", new Error("page failed to compile"), { + boundary: "ssr.render", + errorClass: "tenant-build", + level: "warning", + }); + + assertEquals(eventId, "event-id"); + assertEquals( + state.tags.some(([key, value]) => key === "veryfront.error_class" && value === "tenant-build"), + true, + ); + assertEquals(state.levels, ["warning"]); +}); + +it("policy leaves the event level alone for unclassified errors", () => { + const { sdk, state } = createSentrySdk(); + + captureWithSentryPolicy(sdk, "renderer", new Error("request failed"), { + boundary: "ssr.render", + }); + + assertEquals(state.levels, []); + assertEquals( + state.tags.some(([key]) => key === "veryfront.error_class"), + false, + ); +}); + it("policy redacts application error attribute keys and credential-shaped values", () => { assertEquals( sanitizeApplicationErrorAttributes({ diff --git a/extensions/ext-observability-sentry/src/policy.ts b/extensions/ext-observability-sentry/src/policy.ts index e32c589fad..d5adb0eb65 100644 --- a/extensions/ext-observability-sentry/src/policy.ts +++ b/extensions/ext-observability-sentry/src/policy.ts @@ -16,6 +16,7 @@ const SENSITIVE_ATTRIBUTE_KEY_PATTERN = export type SentryPolicyScope = { setContext(name: string, context: Record): void; setFingerprint(fingerprint: string[]): void; + setLevel(level: "error" | "warning"): void; setTag(key: string, value: string): void; }; @@ -87,6 +88,8 @@ export function applySentryScopePolicy( scope.setTag("service.name", serviceName); if (context.processRole) scope.setTag("process_role", context.processRole); scope.setTag("veryfront.boundary", context.boundary); + if (context.errorClass) scope.setTag("veryfront.error_class", context.errorClass); + if (context.level) scope.setLevel(context.level); if (context.method) scope.setTag("http.request.method", context.method); if (context.requestId) scope.setTag("veryfront.request_id", context.requestId); if (context.traceId) { diff --git a/src/observability/application-error-contract.ts b/src/observability/application-error-contract.ts index 971da7d7d9..f171c61dd0 100644 --- a/src/observability/application-error-contract.ts +++ b/src/observability/application-error-contract.ts @@ -15,6 +15,10 @@ export type ApplicationErrorContext = { spanId?: string; /** OpenTelemetry trace correlation identifier. */ traceId?: string; + /** Stable failure classification (e.g. "tenant-build") tagged on the event. */ + errorClass?: string; + /** Severity of the captured event; reporters default to "error" when unset. */ + level?: "error" | "warning"; /** Sanitized scalar metadata for the failure boundary. */ attributes?: Record; }; diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index ba7e18cf21..524b7fac6d 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -13,7 +13,13 @@ import { setApplicationErrorReporter, } from "./application-errors.ts"; import type { ApplicationErrorContext as SharedApplicationErrorContext } from "./application-error-contract.ts"; -import { CONFIG_PARSE_ERROR, INITIALIZATION_ERROR } from "#veryfront/errors"; +import { + CONFIG_PARSE_ERROR, + createError, + INITIALIZATION_ERROR, + RENDER_ERROR, + toError, +} from "#veryfront/errors"; it("application error reporter is optional", async () => { setApplicationErrorReporter(undefined); @@ -131,6 +137,52 @@ it("application error reporter ignores client-class veryfront errors", () => { assertEquals(captures, [serverError, plainError]); }); +it("application error reporter downgrades tenant build errors to tagged warnings", () => { + const captures: Array<{ error: unknown; context: SharedApplicationErrorContext }> = []; + setApplicationErrorReporter({ + capture(error, context) { + captures.push({ error, context }); + return "event-id"; + }, + flush: () => Promise.resolve(true), + }); + + const compileError = toError( + createError({ type: "build", message: "MDX compilation failed" }), + ); + const pipelineError = RENDER_ERROR.create({ + detail: "Critical page module(s) failed to load:\n/pages/index.mdx: bad syntax", + context: { buildFailure: true }, + }); + const frameworkError = INITIALIZATION_ERROR.create({ + detail: "renderer failed to initialize", + }); + + assertEquals( + captureApplicationError(compileError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals( + captureApplicationError(pipelineError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals( + captureApplicationError(frameworkError, { boundary: "ssr.render" }), + "event-id", + ); + + assertEquals(captures.length, 3); + // Tenant build/content failures stay visible for escalation analysis, but + // are tagged and downgraded so they stop surfacing as error-level issues. + assertEquals(captures[0]?.context.errorClass, "tenant-build"); + assertEquals(captures[0]?.context.level, "warning"); + assertEquals(captures[1]?.context.errorClass, "tenant-build"); + assertEquals(captures[1]?.context.level, "warning"); + // Genuine framework failures keep their default error-level capture. + assertEquals(captures[2]?.context.errorClass, undefined); + assertEquals(captures[2]?.context.level, undefined); +}); + it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { getPrototypeOf() { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index d6be4129c0..fc73cfb076 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -221,6 +221,52 @@ export function initializeApplicationErrorReporter(options: { }); } +const TENANT_BUILD_ERROR_CLASS = "tenant-build"; + +/** + * Tag applied by the module loader at the point of a compilation failure. + * + * The tag is read through the shared symbol registry instead of importing the + * rendering layer; see src/rendering/orchestrator/module-loader/build-failure.ts. + */ +const BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.build-failure"); + +/** + * Whether `error` describes tenant build/content failing to compile (a page + * that does not build, MDX that does not parse) rather than a framework fault. + * + * Recognizes the existing discriminators at their capture seam: + * - the module loader's build-failure tag, + * - `toError(createError({ type: "build" }))` structured error data, + * - `VeryfrontError` instances in the BUILD category, and + * - the render pipeline's `buildFailure` error context. + */ +function isTenantBuildError(error: unknown): boolean { + try { + if (error instanceof Error) { + if ((error as { [BUILD_FAILURE_TAG]?: unknown })[BUILD_FAILURE_TAG] === true) { + return true; + } + const descriptor = Object.getOwnPropertyDescriptor(error, "context"); + const data = descriptor && "value" in descriptor ? descriptor.value : undefined; + if ( + typeof data === "object" && data !== null && + (data as { type?: unknown }).type === "build" + ) { + return true; + } + } + const snapshot = snapshotVeryfrontError(error); + if (!snapshot) return false; + if (snapshot.category === "BUILD") return true; + const errorContext = snapshot.context; + return typeof errorContext === "object" && errorContext !== null && + (errorContext as { buildFailure?: unknown }).buildFailure === true; + } catch { + return false; + } +} + export function captureApplicationError( error: unknown, context: ApplicationErrorContext, @@ -230,7 +276,17 @@ export function captureApplicationError( if (!currentReporter) return undefined; try { - const snapshot = snapshotApplicationErrorContext(context); + // Tenant build/content failures stay captured for escalation analysis, + // but are tagged and downgraded so per-request tenant mistakes stop + // surfacing as error-level framework issues. + const classifiedContext = isTenantBuildError(error) + ? { + ...context, + errorClass: context.errorClass ?? TENANT_BUILD_ERROR_CLASS, + level: context.level ?? "warning" as const, + } + : context; + const snapshot = snapshotApplicationErrorContext(classifiedContext); return snapshot ? currentReporter.capture(error, snapshot) : undefined; } catch { // Error reporting is diagnostic and must never replace the application @@ -286,12 +342,17 @@ function snapshotApplicationErrorContext( if (!boundary) return null; const snapshot: ApplicationErrorContext = { boundary }; - for (const key of ["method", "processRole", "requestId", "spanId", "traceId"] as const) { + for ( + const key of ["method", "processRole", "requestId", "spanId", "traceId", "errorClass"] as const + ) { const value = context[key]; if (value === undefined) continue; const normalized = normalizeContextValue(value); if (normalized) snapshot[key] = normalized; } + if (context.level === "error" || context.level === "warning") { + snapshot.level = context.level; + } const attributes = sanitizeTelemetryAttributes(context.attributes); if (attributes && Object.keys(attributes).length > 0) { snapshot.attributes = Object.freeze(attributes); From 07482ba59f6ab83f7ddd4be1b99d4288f1bd87da Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 18:58:57 +0200 Subject: [PATCH 003/104] fix(transforms): keep @/ alias imports off the page origin and stop blaming esm.sh for foreign HTML responses An MDX page's "@/" alias import that escaped the loader's alias rewrite fell through the ESM specifier resolver: with a project import map mapping the "@/" prefix it was resolved as a relative URL against the page's own public origin (https:///@/components/...), whose HTML fallback then failed the HTTP module cache with an error that wrongly blamed esm.sh. - resolveSpecifier now pins "@/" specifiers to the project-module transport (/_vf_modules/.js), matching the MDX loader's alias rewrite, instead of routing them to esm.sh or the page origin. - The HTML-response diagnostic only mentions esm.sh for esm.sh hosts; other origins get an unresolved-import explanation, with an explicit hint when the path is an /@/ alias form. Fixes VERYFRONT-SERVER-G --- src/transforms/esm/http-cache-helpers.ts | 31 +++++++++++ src/transforms/esm/http-cache.test.ts | 55 +++++++++++++++++++ src/transforms/esm/http-cache.ts | 6 +- src/transforms/esm/specifier-resolver.test.ts | 42 ++++++++++++++ src/transforms/esm/specifier-resolver.ts | 18 ++++++ 5 files changed, 149 insertions(+), 3 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index efcf8a9205..b9c05c4420 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -528,6 +528,37 @@ export function isCanonicalReactEsmUrl(rawUrl: string): boolean { return getCanonicalReactEsmVersion(rawUrl) !== null; } +/** + * Diagnostic for an HTTP module fetch that answered with HTML. + * + * Only esm.sh gets the "package failed to build" explanation. Any other host + * answering HTML is almost always an unresolved import that fell through to + * the site's own origin — and a path starting with "/@/" is the "@/" project + * alias in absolute form, i.e. an alias import that failed to resolve. + */ +export function describeHtmlModuleResponse(rawUrl: string): string { + let hostname = ""; + let pathname = ""; + try { + const url = new IntrinsicURL(rawUrl); + hostname = getURLHostname(url); + pathname = getURLPathname(url); + } catch (_) { + /* expected: URL may be malformed */ + } + + const received = `Received HTML instead of JavaScript from ${rawUrl}.`; + if (hostname === "esm.sh") { + return `${received} The package may not exist or failed to build on esm.sh.`; + } + + const aliasHint = stringStartsWith(pathname, "/@/") + ? ' The path looks like an "@/" alias import that failed to resolve to a project module.' + : ""; + return `${received} The URL returned an HTML page — likely an unresolved import ` + + `that fell through to the site origin.${aliasHint}`; +} + export function isExternalScheme(specifier: string): boolean { return stringStartsWith(specifier, "node:") || stringStartsWith(specifier, "data:") || diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 7eb1e8ec58..5527470b52 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -1368,6 +1368,61 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, }); }); + it("does not blame esm.sh when a non-esm.sh origin returns HTML", async () => { + // VERYFRONT-SERVER-G: an unresolved import that fell through to the + // tenant's own site origin returned the HTML fallback page, and the + // diagnostic wrongly claimed the package "failed to build on esm.sh". + const siteUrl = "https://example.com/some/page"; + const mockFetch = (() => + Promise.resolve( + new Response("site fallback", { + headers: { "content-type": "text/html;charset=utf-8" }, + }), + )) as typeof fetch; + + await withIsolatedHttpCache("vf-esm-site-html-", mockFetch, async (tempDir) => { + const error = await assertRejects( + () => cacheModuleToLocal(siteUrl, tempDir), + Error, + ); + + assertInstanceOf(error, Error); + assert(!error.message.includes("esm.sh"), `must not blame esm.sh: ${error.message}`); + assert( + error.message.includes("Received HTML instead of JavaScript from " + siteUrl), + `must name the URL: ${error.message}`, + ); + assert( + error.message.includes("unresolved import"), + `must point at an unresolved import falling through to the site origin: ${error.message}`, + ); + }); + }); + + it("hints at a failed alias import when an HTML response comes from an /@/ path", async () => { + const aliasUrl = "https://example.com/@/components/ResponsiveImage"; + const mockFetch = (() => + Promise.resolve( + new Response("site fallback", { + headers: { "content-type": "text/html;charset=utf-8" }, + }), + )) as typeof fetch; + + await withIsolatedHttpCache("vf-esm-alias-html-", mockFetch, async (tempDir) => { + const error = await assertRejects( + () => cacheModuleToLocal(aliasUrl, tempDir), + Error, + ); + + assertInstanceOf(error, Error); + assert(!error.message.includes("esm.sh"), `must not blame esm.sh: ${error.message}`); + assert( + error.message.includes('"@/" alias import'), + `must hint that an alias import failed to resolve: ${error.message}`, + ); + }); + }); + it("bounds transient failure attempts and cancels every response body", async () => { let fetchCount = 0; let cancelledBodies = 0; diff --git a/src/transforms/esm/http-cache.ts b/src/transforms/esm/http-cache.ts index 4d26ce140e..447948eddb 100644 --- a/src/transforms/esm/http-cache.ts +++ b/src/transforms/esm/http-cache.ts @@ -50,6 +50,7 @@ import { buildHttpCacheIdentityMetadata, type CacheOptions, deriveHttpCacheRequestOptions, + describeHtmlModuleResponse, ensureAbsoluteDir, ensurePreparedHttpCacheRequestOptions, getEffectiveHttpCacheRequest, @@ -568,15 +569,14 @@ async function cacheHttpModuleInternal(url: string, options: CacheOptions): Prom if (isHtmlContent) { logger.error( - "[HTTP-CACHE] Received HTML instead of JavaScript, likely an esm.sh error page", + "[HTTP-CACHE] Received HTML instead of JavaScript", { url: safeUrl, contentType, }, ); throw BUNDLE_ERROR.create({ - detail: - `Received HTML instead of JavaScript from ${safeUrl}. The package may not exist or failed to build on esm.sh.`, + detail: describeHtmlModuleResponse(safeUrl), }); } diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index 7a24cabad6..32034eb8c7 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -191,6 +191,48 @@ describe("transforms/esm/specifier-resolver", () => { assertEquals(cacheCalls, []); }); + it("rewrites @/ alias imports to the project-module form without fetching", async () => { + // The "@/" project alias is framework-supported (the default import map + // maps "@/" -> "/_vf_modules/"). If one escapes the MDX loader's alias + // rewrite and reaches this resolver, it must land on the project-module + // transport — never on esm.sh as a bogus scoped package. + const code = `import ResponsiveImage from "@/components/ResponsiveImage";`; + const cacheCalls: string[] = []; + const result = await buildReplacements(code, undefined, defaultOptions, async (url) => { + cacheCalls.push(url); + return "/tmp/cache/http-alias.mjs"; + }); + + assertEquals(cacheCalls, []); + assertEquals( + result.replacements.get("@/components/ResponsiveImage"), + "/_vf_modules/components/ResponsiveImage.js", + ); + }); + + it("never resolves an @/ alias against the page origin via an import-map prefix", async () => { + // A project import map commonly maps "@/" to "./". Resolving that mapped + // relative path against the page origin fetches the tenant's own public + // site, which answers with HTML (VERYFRONT-SERVER-G). + const code = `import ResponsiveImage from "@/components/ResponsiveImage";`; + const cacheCalls: string[] = []; + const result = await buildReplacements( + code, + "https://responsive-image.example.com/foo", + { ...defaultOptions, importMap: { imports: { "@/": "./" } } }, + async (url) => { + cacheCalls.push(url); + return "/tmp/cache/http-origin.mjs"; + }, + ); + + assertEquals(cacheCalls, []); + assertEquals( + result.replacements.get("@/components/ResponsiveImage"), + "/_vf_modules/components/ResponsiveImage.js", + ); + }); + it("uses relative path when parent is an HTTP module", async () => { const code = `import lodash from "https://esm.sh/lodash@4";`; const mockCache: CacheHttpModuleFn = async () => "/tmp/cache/http-99999.mjs"; diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 52ad253005..cd5ce68348 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -27,6 +27,7 @@ import { } from "./http-cache-helpers.ts"; const ReflectApply = Reflect.apply; +const StringEndsWith = String.prototype.endsWith; const StringSlice = String.prototype.slice; const StringStartsWith = String.prototype.startsWith; @@ -34,6 +35,10 @@ function stringSlice(value: string, start: number, end?: number): string { return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; } +function stringEndsWith(value: string, search: string): boolean { + return ReflectApply(StringEndsWith, value, [search]) as boolean; +} + function stringStartsWith(value: string, search: string): boolean { return ReflectApply(StringStartsWith, value, [search]) as boolean; } @@ -99,6 +104,19 @@ async function resolveSpecifier( ); if (isExternalScheme(specifier)) return null; + // The "@/" project alias always denotes the project's own module transport: + // the framework's default import map pins "@/" to "/_vf_modules/". An alias + // that escaped an upstream rewrite must land there too — treating it as a + // bare specifier would route it to esm.sh as a bogus scoped package, and a + // project import map that maps "@/" to a relative prefix would resolve it + // against the page's public origin, which answers with HTML + // (VERYFRONT-SERVER-G). + if (stringStartsWith(specifier, "@/")) { + const aliasPath = stringSlice(specifier, 2); + const jsPath = stringEndsWith(aliasPath, ".js") ? aliasPath : `${aliasPath}.js`; + return `/_vf_modules/${jsPath}`; + } + // Server-only packages (`redis`, `pg`, …), including their explicit `npm:` // form, must never be routed through esm.sh. esm.sh either 500s building them // or emits a browser bundle with Node built-ins stubbed that can never From 67f20d1e961d5f8001f10a753f3d6b239cfd7b1c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 20:16:51 +0200 Subject: [PATCH 004/104] Keep framework build errors at error severity The tenant build classifier was using the broad BUILD category as a downgrade signal, which also caught framework-side asset optimization and source map failures. The classifier now keeps the existing explicit tenant build discriminators and limits direct BUILD registry matching to tenant-facing slugs. Constraint: PR #3723 must keep tenant content compile failures captured as warning-level tenant-build events while genuine framework errors remain error-level. Rejected: Treat every BUILD-category VeryfrontError as tenant content | asset optimization and source map errors can be framework faults. Confidence: high Scope-risk: narrow Directive: Do not broaden tenant-build classification by category without explicit framework-error regression coverage. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/observability/application-errors.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all extensions/ext-observability-sentry/src/policy.test.ts Tested: deno fmt --check src/observability/application-errors.ts src/observability/application-errors.test.ts src/observability/application-error-contract.ts extensions/ext-observability-sentry/src/policy.ts extensions/ext-observability-sentry/src/policy.test.ts Tested: deno lint src/observability/application-errors.ts src/observability/application-errors.test.ts src/observability/application-error-contract.ts extensions/ext-observability-sentry/src/policy.ts extensions/ext-observability-sentry/src/policy.test.ts Tested: deno check src/observability/application-errors.ts src/observability/application-errors.test.ts src/observability/application-error-contract.ts extensions/ext-observability-sentry/src/policy.ts extensions/ext-observability-sentry/src/policy.test.ts Not-tested: full repository test suite --- src/observability/application-errors.test.ts | 36 ++++++++++++++++++-- src/observability/application-errors.ts | 22 +++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 524b7fac6d..811d8dabad 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -14,10 +14,13 @@ import { } from "./application-errors.ts"; import type { ApplicationErrorContext as SharedApplicationErrorContext } from "./application-error-contract.ts"; import { + ASSET_OPTIMIZATION_ERROR, CONFIG_PARSE_ERROR, createError, INITIALIZATION_ERROR, + MDX_COMPILE_ERROR, RENDER_ERROR, + SOURCEMAP_ERROR, toError, } from "#veryfront/errors"; @@ -154,9 +157,18 @@ it("application error reporter downgrades tenant build errors to tagged warnings detail: "Critical page module(s) failed to load:\n/pages/index.mdx: bad syntax", context: { buildFailure: true }, }); + const mdxRegistryError = MDX_COMPILE_ERROR.create({ + detail: "MDX compilation failed in /pages/index.mdx", + }); const frameworkError = INITIALIZATION_ERROR.create({ detail: "renderer failed to initialize", }); + const assetOptimizationError = ASSET_OPTIMIZATION_ERROR.create({ + detail: "framework image optimization failed", + }); + const sourcemapError = SOURCEMAP_ERROR.create({ + detail: "framework source map generation failed", + }); assertEquals( captureApplicationError(compileError, { boundary: "ssr.render" }), @@ -166,21 +178,39 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(pipelineError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(mdxRegistryError, { boundary: "ssr.render" }), + "event-id", + ); assertEquals( captureApplicationError(frameworkError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(assetOptimizationError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals( + captureApplicationError(sourcemapError, { boundary: "ssr.render" }), + "event-id", + ); - assertEquals(captures.length, 3); + assertEquals(captures.length, 6); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); assertEquals(captures[0]?.context.level, "warning"); assertEquals(captures[1]?.context.errorClass, "tenant-build"); assertEquals(captures[1]?.context.level, "warning"); + assertEquals(captures[2]?.context.errorClass, "tenant-build"); + assertEquals(captures[2]?.context.level, "warning"); // Genuine framework failures keep their default error-level capture. - assertEquals(captures[2]?.context.errorClass, undefined); - assertEquals(captures[2]?.context.level, undefined); + assertEquals(captures[3]?.context.errorClass, undefined); + assertEquals(captures[3]?.context.level, undefined); + assertEquals(captures[4]?.context.errorClass, undefined); + assertEquals(captures[4]?.context.level, undefined); + assertEquals(captures[5]?.context.errorClass, undefined); + assertEquals(captures[5]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index fc73cfb076..30e57df03e 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -230,6 +230,14 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; * rendering layer; see src/rendering/orchestrator/module-loader/build-failure.ts. */ const BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.build-failure"); +const TENANT_BUILD_ERROR_SLUGS = new Set([ + "build-failed", + "bundle-error", + "typescript-error", + "mdx-compile-error", + "ssg-generation-error", + "compilation-error", +]); /** * Whether `error` describes tenant build/content failing to compile (a page @@ -238,8 +246,8 @@ const BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.build-failure"); * Recognizes the existing discriminators at their capture seam: * - the module loader's build-failure tag, * - `toError(createError({ type: "build" }))` structured error data, - * - `VeryfrontError` instances in the BUILD category, and - * - the render pipeline's `buildFailure` error context. + * - the render pipeline's `buildFailure` error context, and + * - tenant-facing BUILD registry slugs. */ function isTenantBuildError(error: unknown): boolean { try { @@ -258,10 +266,14 @@ function isTenantBuildError(error: unknown): boolean { } const snapshot = snapshotVeryfrontError(error); if (!snapshot) return false; - if (snapshot.category === "BUILD") return true; const errorContext = snapshot.context; - return typeof errorContext === "object" && errorContext !== null && - (errorContext as { buildFailure?: unknown }).buildFailure === true; + if ( + typeof errorContext === "object" && errorContext !== null && + (errorContext as { buildFailure?: unknown }).buildFailure === true + ) { + return true; + } + return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); } catch { return false; } From 2dc472d71e2f95166f43e1ccc02ec209ac40862b Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 20:16:54 +0200 Subject: [PATCH 005/104] Keep escaped aliases on module transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Escaped @/ imports in the HTTP module resolver now reuse the existing import-rewriter extension normalizer, so explicit source extensions land on the same /_vf_modules/*.js shape as the unified alias strategy. The HTML diagnostic copy also avoids punctuation that violates the public-copy rules. Constraint: Root AGENTS.md forbids em dashes in public copy and asks for small reversible diffs. Rejected: Add a new alias helper module | existing normalizeExtension already removes the divergent source-extension behavior without a broad refactor. Confidence: high Scope-risk: narrow Directive: Keep escaped @/ resolver output aligned with AliasStrategy SSR/moduleServerUrl extension normalization. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/esm/specifier-resolver.test.ts src/transforms/esm/http-cache-helpers.test.ts Tested: deno fmt --check src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Tested: deno lint src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Tested: deno check --config deno.json src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts src/transforms/esm/http-cache-helpers.ts Tested: git diff --check && ! rg -n '—|–' src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Not-tested: Full repository test suite. --- src/transforms/esm/http-cache-helpers.ts | 4 ++-- src/transforms/esm/specifier-resolver.test.ts | 19 +++++++++++++++++-- src/transforms/esm/specifier-resolver.ts | 19 ++++++++++--------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index b9c05c4420..2a5940f9dd 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -533,7 +533,7 @@ export function isCanonicalReactEsmUrl(rawUrl: string): boolean { * * Only esm.sh gets the "package failed to build" explanation. Any other host * answering HTML is almost always an unresolved import that fell through to - * the site's own origin — and a path starting with "/@/" is the "@/" project + * the site's own origin, and a path starting with "/@/" is the "@/" project * alias in absolute form, i.e. an alias import that failed to resolve. */ export function describeHtmlModuleResponse(rawUrl: string): string { @@ -555,7 +555,7 @@ export function describeHtmlModuleResponse(rawUrl: string): string { const aliasHint = stringStartsWith(pathname, "/@/") ? ' The path looks like an "@/" alias import that failed to resolve to a project module.' : ""; - return `${received} The URL returned an HTML page — likely an unresolved import ` + + return `${received} The URL returned an HTML page, likely an unresolved import ` + `that fell through to the site origin.${aliasHint}`; } diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index 32034eb8c7..aa65247e40 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -195,7 +195,7 @@ describe("transforms/esm/specifier-resolver", () => { // The "@/" project alias is framework-supported (the default import map // maps "@/" -> "/_vf_modules/"). If one escapes the MDX loader's alias // rewrite and reaches this resolver, it must land on the project-module - // transport — never on esm.sh as a bogus scoped package. + // transport, never on esm.sh as a bogus scoped package. const code = `import ResponsiveImage from "@/components/ResponsiveImage";`; const cacheCalls: string[] = []; const result = await buildReplacements(code, undefined, defaultOptions, async (url) => { @@ -210,6 +210,21 @@ describe("transforms/esm/specifier-resolver", () => { ); }); + it("normalizes explicit source extensions in escaped @/ alias imports", async () => { + const code = `import Card from "@/components/Card.tsx";`; + const cacheCalls: string[] = []; + const result = await buildReplacements(code, undefined, defaultOptions, async (url) => { + cacheCalls.push(url); + return "/tmp/cache/http-alias.mjs"; + }); + + assertEquals(cacheCalls, []); + assertEquals( + result.replacements.get("@/components/Card.tsx"), + "/_vf_modules/components/Card.js", + ); + }); + it("never resolves an @/ alias against the page origin via an import-map prefix", async () => { // A project import map commonly maps "@/" to "./". Resolving that mapped // relative path against the page origin fetches the tenant's own public @@ -366,7 +381,7 @@ describe("transforms/esm/specifier-resolver", () => { it("leaves a server-only package external instead of routing it to esm.sh", async () => { // `redis` and its explicit npm: form only run server-side. They must be // left in place for the runtime to resolve (node_modules / npm:), never - // fetched from esm.sh — so the cache function is never called and nothing + // fetched from esm.sh, so the cache function is never called and nothing // is degraded or aborted. for (const specifier of ["redis", "npm:redis", "npm:redis@5.11.0"]) { const code = `export const load = () => import(${JSON.stringify(specifier)});`; diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index cd5ce68348..1abab3eae5 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -10,7 +10,10 @@ import { basename } from "#veryfront/compat/path/index.ts"; import { resolveImport } from "#veryfront/modules/import-map/resolver.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; -import { appendSameOriginSSRDependencyPinningKey } from "#veryfront/transforms/import-rewriter/url-builder.ts"; +import { + appendSameOriginSSRDependencyPinningKey, + normalizeExtension, +} from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { parseBarePackageSpecifier } from "../shared/package-specifier.ts"; import { isServerOnlyPackage } from "../shared/server-only-packages.ts"; import { parseImports, replaceSpecifiers } from "./lexer.ts"; @@ -27,7 +30,6 @@ import { } from "./http-cache-helpers.ts"; const ReflectApply = Reflect.apply; -const StringEndsWith = String.prototype.endsWith; const StringSlice = String.prototype.slice; const StringStartsWith = String.prototype.startsWith; @@ -35,10 +37,6 @@ function stringSlice(value: string, start: number, end?: number): string { return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; } -function stringEndsWith(value: string, search: string): boolean { - return ReflectApply(StringEndsWith, value, [search]) as boolean; -} - function stringStartsWith(value: string, search: string): boolean { return ReflectApply(StringStartsWith, value, [search]) as boolean; } @@ -106,14 +104,17 @@ async function resolveSpecifier( // The "@/" project alias always denotes the project's own module transport: // the framework's default import map pins "@/" to "/_vf_modules/". An alias - // that escaped an upstream rewrite must land there too — treating it as a + // that escaped an upstream rewrite must land there too. Treating it as a // bare specifier would route it to esm.sh as a bogus scoped package, and a // project import map that maps "@/" to a relative prefix would resolve it // against the page's public origin, which answers with HTML // (VERYFRONT-SERVER-G). if (stringStartsWith(specifier, "@/")) { const aliasPath = stringSlice(specifier, 2); - const jsPath = stringEndsWith(aliasPath, ".js") ? aliasPath : `${aliasPath}.js`; + const normalizedPath = normalizeExtension(aliasPath); + const jsPath = /\.(js|mjs|cjs|css)$/.test(normalizedPath) + ? normalizedPath + : `${normalizedPath}.js`; return `/_vf_modules/${jsPath}`; } @@ -123,7 +124,7 @@ async function resolveSpecifier( // connect. The framework's adapters only `import()` them behind a lazy, // configured code path, so leaving the specifier external lets the runtime // resolve the real package (node_modules on Node, npm: on Deno) if and when - // the backend is actually used — and costs nothing when it is not. + // the backend is actually used, and costs nothing when it is not. const serverOnlyCandidate = stringStartsWith(specifier, "npm:") ? stringSlice(specifier, 4) : specifier; From 91e0eb2aefb99f4b415b4ad7c60218e9798e546a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 20:18:36 +0200 Subject: [PATCH 006/104] Keep observability API reference aligned with capture changes The application error capture change shifted exported observability source anchors, and CI checks generated API reference files with the pinned Deno 2.7.7 toolchain. Regenerating only the stale observability reference keeps the stacked PR narrow while clearing the failing docs check. Constraint: PR #3723 is stacked on this branch, so the fix must avoid broad generated churn. Rejected: Commit docs generated by local Deno 2.7.12 | it rewrote anchors across 42 files and did not match CI's pinned generator output. Confidence: high Scope-risk: narrow Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno task lint:ci Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno test --preload=src/testing/preload.ts --no-check --allow-all --unstable-worker-options --unstable-net src/config/loader.test.ts src/observability/application-errors.test.ts Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno fmt --check docs/api-reference/veryfront/observability.md src/config/loader.ts src/config/loader.test.ts src/observability/application-errors.ts src/observability/application-errors.test.ts Tested: git diff --check --- docs/api-reference/veryfront/observability.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index b46bec055f..3bf86d2395 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L223) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L224) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L241) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L242) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -58,7 +58,7 @@ const result = await withSpan("load-data", async () => { | `getMetricsState` | State for get metrics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L38) | | `getTraceContext` | Context for get trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L500) | | `initAutoInstrumentation` | Initialize automatic instrumentation wrappers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/orchestrator.ts#L15) | -| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L148) | +| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L149) | | `initializeOTLP` | Initialize OTLP tracing export. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L113) | | `initMetrics` | Initialize metrics collection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L20) | | `initTracing` | Initialize tracing for the current runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L18) | @@ -134,7 +134,7 @@ const result = await withSpan("load-data", async () => { | `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L23) | | `ApplicationErrorReporterInitializationContext` | Runtime context passed to an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L9) | | `ApplicationErrorReporterInitializer` | Application-composition contract for an error-reporting implementation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L20) | -| `ApplicationErrorReporterLifecycle` | Active application-error reporter ownership. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L27) | +| `ApplicationErrorReporterLifecycle` | Active application-error reporter ownership. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L28) | | `ApplicationErrorReporterSession` | Reporter and cleanup ownership returned by an application-selected initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L14) | | `AttributeValue` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L33) | | `AutoInstrumentConfig` | Configuration used by auto instrument. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/types.ts#L24) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L223) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L241) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L224) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L242) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | From 26467889a28f873b188a82339034697a1a85a460 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 20:53:44 +0200 Subject: [PATCH 007/104] Keep generic build infrastructure failures error-level Generic BUILD_FAILED and BUNDLE_ERROR errors are used by framework cache and bundle infrastructure paths, so slug-only tenant classification was still too broad. The classifier now relies on explicit tenant discriminators or tenant-facing registry slugs that do not also identify framework infrastructure failures. Constraint: CodeRabbit PRRT_kwDOQaPiP86ZYi4U identified generic build and bundle slugs as framework-owned in some throw paths. Constraint: The CI-pinned API reference generator requires both observability and extensions source anchors to be current. Rejected: Keep build-failed and bundle-error in the slug allowlist | misclassifies framework cache and bundle infrastructure errors as tenant-build warnings. Rejected: Commit only observability.md docs output | the pinned docs:api-reference:check still reported extensions.md stale. Confidence: high Scope-risk: narrow Directive: Do not classify generic BUILD_FAILED or BUNDLE_ERROR as tenant-owned without an explicit tenant discriminator at the capture seam. Tested: Red regression in src/observability/application-errors.test.ts failed before implementation for framework BUILD_FAILED false positive. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/observability/application-errors.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all extensions/ext-observability-sentry/src/policy.test.ts Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task docs:api-reference:check Not-tested: External GitHub CI after push. --- docs/api-reference/veryfront/extensions.md | 2 +- docs/api-reference/veryfront/observability.md | 12 +++++----- src/observability/application-errors.test.ts | 22 ++++++++++++++++++- src/observability/application-errors.ts | 5 ++--- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/api-reference/veryfront/extensions.md b/docs/api-reference/veryfront/extensions.md index b042f3f19c..aeed77a6d5 100644 --- a/docs/api-reference/veryfront/extensions.md +++ b/docs/api-reference/veryfront/extensions.md @@ -784,7 +784,7 @@ import { | Name | Description | Source | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `ApplicationErrorContext` | Sanitized context attached when a runtime reports an application error. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L5) | -| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L23) | +| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L27) | | `ApplicationErrorReporterInitializationContext` | Runtime context passed to an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L9) | | `ApplicationErrorReporterInitializer` | Application-composition contract for an error-reporting implementation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L20) | | `ApplicationErrorReporterSession` | Reporter and cleanup ownership returned by an application-selected initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L14) | diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 3bf86d2395..e2a65f4f3d 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L224) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L281) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L242) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L309) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -131,7 +131,7 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ----------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `ApplicationErrorContext` | Sanitized context attached when a runtime reports an application error. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L5) | -| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L23) | +| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L27) | | `ApplicationErrorReporterInitializationContext` | Runtime context passed to an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L9) | | `ApplicationErrorReporterInitializer` | Application-composition contract for an error-reporting implementation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L20) | | `ApplicationErrorReporterLifecycle` | Active application-error reporter ownership. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L28) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L224) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L242) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L281) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L309) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | @@ -250,5 +250,5 @@ import { | Name | Description | Source | | -------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `ApplicationErrorContext` | Sanitized context attached when a runtime reports an application error. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L5) | -| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L23) | +| `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L27) | | `SentryConfig` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L18) | diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 811d8dabad..e7a2f66ec0 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -15,6 +15,8 @@ import { import type { ApplicationErrorContext as SharedApplicationErrorContext } from "./application-error-contract.ts"; import { ASSET_OPTIMIZATION_ERROR, + BUILD_FAILED, + BUNDLE_ERROR, CONFIG_PARSE_ERROR, createError, INITIALIZATION_ERROR, @@ -169,6 +171,12 @@ it("application error reporter downgrades tenant build errors to tagged warnings const sourcemapError = SOURCEMAP_ERROR.create({ detail: "framework source map generation failed", }); + const frameworkCacheWriteError = BUILD_FAILED.create({ + detail: "Failed to write MDX module cache file: ", + }); + const frameworkBundleError = BUNDLE_ERROR.create({ + detail: "Failed to regenerate framework bundle cache entry: ", + }); assertEquals( captureApplicationError(compileError, { boundary: "ssr.render" }), @@ -194,8 +202,16 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(sourcemapError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(frameworkCacheWriteError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals( + captureApplicationError(frameworkBundleError, { boundary: "ssr.render" }), + "event-id", + ); - assertEquals(captures.length, 6); + assertEquals(captures.length, 8); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -211,6 +227,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[4]?.context.level, undefined); assertEquals(captures[5]?.context.errorClass, undefined); assertEquals(captures[5]?.context.level, undefined); + assertEquals(captures[6]?.context.errorClass, undefined); + assertEquals(captures[6]?.context.level, undefined); + assertEquals(captures[7]?.context.errorClass, undefined); + assertEquals(captures[7]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 30e57df03e..f4836af7e4 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -231,8 +231,6 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; */ const BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.build-failure"); const TENANT_BUILD_ERROR_SLUGS = new Set([ - "build-failed", - "bundle-error", "typescript-error", "mdx-compile-error", "ssg-generation-error", @@ -247,7 +245,8 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ * - the module loader's build-failure tag, * - `toError(createError({ type: "build" }))` structured error data, * - the render pipeline's `buildFailure` error context, and - * - tenant-facing BUILD registry slugs. + * - tenant-facing BUILD registry slugs that do not also describe framework + * cache or bundle infrastructure failures. */ function isTenantBuildError(error: unknown): boolean { try { From 595040ab22176ccf9dc792a17daa2633d716a59b Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 20:56:27 +0200 Subject: [PATCH 008/104] Keep alias extension normalization on captured strings normalizeExtension is shared by the alias rewrite path, so it now uses a captured String.prototype.replace through captured Reflect.apply. This keeps escaped @/ source-extension normalization stable even if project code mutates the shared string prototype. Constraint: CodeRabbit thread PRRT_kwDOQaPiP86ZYXsJ requires alias normalization not to call mutable String.prototype.replace through the shared helper. Rejected: Harden the ESM lexer in this PR | the unresolved thread is about the shared extension helper, and parser replace usage is outside this branch's review scope. Confidence: high Scope-risk: narrow Directive: Keep normalizeExtension on captured string intrinsics because alias rewriting imports this helper in long-lived runtimes. Tested: red test first, deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/import-rewriter/url-builder.test.ts failed on poisoned String.prototype.replace before the fix. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/import-rewriter/url-builder.test.ts src/transforms/esm/specifier-resolver.test.ts Tested: deno fmt --check src/transforms/import-rewriter/url-builder.ts src/transforms/import-rewriter/url-builder.test.ts src/transforms/esm/specifier-resolver.test.ts Tested: deno lint src/transforms/import-rewriter/url-builder.ts src/transforms/import-rewriter/url-builder.test.ts src/transforms/esm/specifier-resolver.test.ts Tested: deno check --config deno.json src/transforms/import-rewriter/url-builder.ts src/transforms/import-rewriter/url-builder.test.ts src/transforms/esm/specifier-resolver.test.ts Tested: git diff --check Not-tested: Full repository test suite before commit; pre-push hook will run it before remote update. --- .../import-rewriter/url-builder.test.ts | 23 +++++++++++++++++++ src/transforms/import-rewriter/url-builder.ts | 14 +++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/transforms/import-rewriter/url-builder.test.ts b/src/transforms/import-rewriter/url-builder.test.ts index 69061067c8..018164df9d 100644 --- a/src/transforms/import-rewriter/url-builder.test.ts +++ b/src/transforms/import-rewriter/url-builder.test.ts @@ -417,6 +417,29 @@ describe("transforms/import-rewriter/url-builder", () => { assertEquals(normalizeExtension("file.tsx", { removeExtension: true }), "file"); }); + it("should use captured replace after String.replace poisoning", () => { + const originalReplace = Object.getOwnPropertyDescriptor(String.prototype, "replace")!; + let poisonCalls = 0; + try { + Object.defineProperty(String.prototype, "replace", { + ...originalReplace, + value() { + poisonCalls += 1; + throw new Error("poisoned String.prototype.replace"); + }, + }); + + assertEquals(normalizeExtension("components/Card.tsx"), "components/Card.js"); + assertEquals( + normalizeExtension("components/Card.tsx", { removeExtension: true }), + "components/Card", + ); + assertEquals(poisonCalls, 0); + } finally { + Object.defineProperty(String.prototype, "replace", originalReplace); + } + }); + it("should keep .js unchanged", () => { assertEquals(normalizeExtension("file.js"), "file.js"); }); diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index 39ea179775..c3e59d1aff 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -33,6 +33,16 @@ type EsmShOptions = { }; const ObjectEntries = Object.entries; +const ReflectApply = Reflect.apply; +const StringReplace = String.prototype.replace; + +function stringReplace( + value: string, + search: string | RegExp, + replacement: string, +): string { + return ReflectApply(StringReplace, value, [search, replacement]) as string; +} function buildEsmShParams(options?: EsmShOptions): string[] { const params: string[] = []; @@ -452,8 +462,8 @@ export function buildVeryfrontModuleUrl(path: string): string { * Normalize file extension for JavaScript output. */ export function normalizeExtension(path: string, options?: { removeExtension?: boolean }): string { - if (options?.removeExtension) return path.replace(/\.(tsx?|jsx|mdx)$/, ""); - return path.replace(/\.(tsx?|jsx|mdx)$/, ".js"); + if (options?.removeExtension) return stringReplace(path, /\.(tsx?|jsx|mdx)$/, ""); + return stringReplace(path, /\.(tsx?|jsx|mdx)$/, ".js"); } /** From 0e94107e05ce4a51063aba2cba5f2e3550f56534 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 21:01:36 +0200 Subject: [PATCH 009/104] Regenerate bundles for hardened alias paths The alias extension helper now captures String.prototype.replace, and the generated runtime bundles need to carry that captured intrinsic so CI and local source snapshots stay aligned. Constraint: Generated bundle snapshots changed after the full pre-push verification run. Rejected: Leave generated files dirty after push | a clean branch is required for reproducible CI and review confidence. Confidence: high Scope-risk: narrow Directive: Regenerate these bundles whenever shared import rewriting helpers change bundled runtime code. Tested: deno fmt --check src/build/production-build/templates.ts src/server/services/rsc/endpoints/rsc-bundles.generated.ts Tested: git diff --check Not-tested: Full pre-push after this generated-only commit before this commit; it will run before the next push. --- src/build/production-build/templates.ts | 2 +- src/server/services/rsc/endpoints/rsc-bundles.generated.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/build/production-build/templates.ts b/src/build/production-build/templates.ts index 189e417e93..fbeb8c0e2e 100644 --- a/src/build/production-build/templates.ts +++ b/src/build/production-build/templates.ts @@ -14,4 +14,4 @@ export const CLIENT_ROUTER_BUNDLE: string | undefined = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar rendererLogger = logger;\nvar PREFETCH_MAX_SIZE_BYTES = 200 * 1024;\n\n// src/rendering/client/navigation-store.ts\nvar STORE_KEY = /* @__PURE__ */ Symbol.for("veryfront.navigation.store.v1");\nfunction getNavigationStore() {\n const holder = globalThis;\n const existing = holder[STORE_KEY];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? `${loc.pathname}${loc.search}${loc.hash}` : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n holder[STORE_KEY] = store;\n return store;\n}\n\n// src/rendering/client/router.ts\nimport ReactDOM from "react-dom/client";\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_ENTRIES = 128;\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar REACT_HEAD_ATTRIBUTE_NAMES = {\n charSet: "charset",\n className: "class",\n crossOrigin: "crossorigin",\n fetchPriority: "fetchpriority",\n htmlFor: "for",\n httpEquiv: "http-equiv",\n imageSizes: "imagesizes",\n imageSrcSet: "imagesrcset",\n noModule: "nomodule",\n referrerPolicy: "referrerpolicy"\n};\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar SUPPORTED_MANAGED_HEAD_TAGS = /* @__PURE__ */ new Set([\n "title",\n "meta",\n "link",\n "style",\n "script"\n]);\nvar HEAD_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_.:-]*$/;\nvar MAX_HEAD_PROP_ENTRIES = 128;\nvar MAX_HEAD_ATTRIBUTE_NAME_BYTES = 256;\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\nfunction normalizeManagedHeadString(value) {\n return value.replace(/\\r\\n?/g, "\\n");\n}\nfunction inspectHeadProps(value) {\n if (typeof value !== "object" || value === null || Array.isArray(value)) return null;\n let prototype;\n let keys;\n try {\n prototype = Object.getPrototypeOf(value);\n keys = Reflect.ownKeys(value);\n } catch {\n return null;\n }\n if (prototype !== Object.prototype && prototype !== null) return null;\n const inspected = /* @__PURE__ */ new Map();\n let entries = 0;\n for (const key of keys) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(value, key);\n } catch {\n return null;\n }\n if (!descriptor) return null;\n if (!descriptor.enumerable) continue;\n if (typeof key !== "string" || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return null;\n }\n entries++;\n if (entries > MAX_HEAD_PROP_ENTRIES) return null;\n inspected.set(key, descriptor.value);\n }\n return inspected;\n}\nfunction normalizeContentPrimitive(value) {\n if (value === null || value === void 0 || typeof value === "boolean") return void 0;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n return null;\n }\n const content = normalizeManagedHeadString(String(value));\n return headTextEncoder.encode(content).byteLength <= MAX_HEAD_CONTENT_BYTES ? content : null;\n}\nfunction normalizeManagedHeadAttributesFromProps(tagName, props, ambientNonce, excludedKeys = /* @__PURE__ */ new Set()) {\n const attributeMap = /* @__PURE__ */ new Map();\n for (const [key, value] of props) {\n if (key === "children" || key === "dangerouslySetInnerHTML" || excludedKeys.has(key)) {\n continue;\n }\n if (/^on/i.test(key) || typeof value === "function" || typeof value === "symbol" || typeof value === "object") {\n continue;\n }\n const name = (REACT_HEAD_ATTRIBUTE_NAMES[key] ?? key).toLowerCase();\n if (isHeadFrameworkAttribute(name) || !HEAD_ATTRIBUTE_NAME_PATTERN.test(name) || headTextEncoder.encode(name).byteLength > MAX_HEAD_ATTRIBUTE_NAME_BYTES) {\n continue;\n }\n if (BOOLEAN_HEAD_ATTRIBUTES.has(name)) {\n if (value !== false && value !== void 0) attributeMap.set(name, "");\n continue;\n }\n if (typeof value === "boolean") {\n if (name.startsWith("data-") || name.startsWith("aria-")) {\n attributeMap.set(name, String(value));\n }\n continue;\n }\n if (value === void 0) continue;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n continue;\n }\n const normalizedValue = normalizeManagedHeadString(String(value));\n if (headTextEncoder.encode(normalizedValue).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) {\n return null;\n }\n attributeMap.set(name, normalizedValue);\n }\n if (tagName === "script" || tagName === "style") {\n attributeMap.delete("nonce");\n }\n const acceptsAmbientNonce = tagName === "style" || tagName === "script" && !attributeMap.has("src");\n if (acceptsAmbientNonce && ambientNonce) {\n const nonce = normalizeManagedHeadString(ambientNonce);\n if (headTextEncoder.encode(nonce).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) return null;\n attributeMap.set("nonce", nonce);\n }\n if (tagName === "link" && attributeMap.get("rel")?.trim().toLowerCase() === "preload" && attributeMap.get("as")?.trim().toLowerCase() === "font" && !attributeMap.has("crossorigin")) {\n attributeMap.set("crossorigin", "anonymous");\n }\n if (attributeMap.size > MAX_HEAD_PROP_ENTRIES) return null;\n let totalBytes = 0;\n for (const [name, value] of attributeMap) {\n totalBytes += headTextEncoder.encode(name).byteLength + headTextEncoder.encode(value).byteLength;\n if (totalBytes > MAX_HEAD_ATTRIBUTE_BYTES) return null;\n }\n return [...attributeMap.entries()].sort(([left], [right]) => left.localeCompare(right));\n}\nfunction singletonKey(tagName, attributes) {\n if (tagName === "title") return "title";\n const record = Object.fromEntries(attributes);\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(record);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(record);\n return void 0;\n}\nfunction scriptKeys(tagName, attributes) {\n if (tagName !== "script") return [];\n const keys = [];\n const id = attributes.get("id");\n const src = attributes.get("src");\n if (id) keys.push(`script:id:${id}`);\n if (src) keys.push(`script:src:${src}`);\n return keys;\n}\nfunction declaresDocumentEncoding(attributes) {\n return attributes.has("charset") || attributes.get("http-equiv")?.trim().toLowerCase() === "content-type";\n}\nfunction createManagedHeadDescriptor(tagName, attributes, content, contentMode) {\n const attributeMap = new Map(attributes);\n return {\n tagName,\n attributes,\n ...content !== void 0 && { content },\n contentMode,\n signature: JSON.stringify([\n tagName,\n attributes,\n contentMode,\n content ?? null\n ]),\n singletonKey: singletonKey(tagName, attributeMap),\n scriptKeys: scriptKeys(tagName, attributeMap)\n };\n}\nfunction descriptorFromManagedHeadRecord(rawTagName, record, options = {}) {\n const tagName = rawTagName.toLowerCase();\n if (!SUPPORTED_MANAGED_HEAD_TAGS.has(tagName)) return null;\n const inspected = inspectHeadProps(record);\n if (!inspected) return null;\n const excludedKeys = options.contentProperty ? /* @__PURE__ */ new Set([options.contentProperty]) : /* @__PURE__ */ new Set();\n const attributes = normalizeManagedHeadAttributesFromProps(\n tagName,\n inspected,\n options.ambientNonce,\n excludedKeys\n );\n if (!attributes) return null;\n const attributeMap = new Map(attributes);\n if (tagName === "meta" && declaresDocumentEncoding(attributeMap)) return null;\n if ((tagName === "meta" || tagName === "link") && attributes.length === 0) return null;\n let content;\n if (options.contentProperty) {\n const normalized = normalizeContentPrimitive(inspected.get(options.contentProperty));\n if (normalized === null) return null;\n content = normalized;\n }\n return createManagedHeadDescriptor(tagName, attributes, content, "text");\n}\nfunction headScriptKeysIntersect(left, right) {\n if (left.length === 0 || right.length === 0) return false;\n const rightKeys = new Set(right);\n return left.some((key) => rightKeys.has(key));\n}\nfunction aggregateManagedHeadDescriptors(descriptors) {\n const aggregated = [];\n const singletonIndexes = /* @__PURE__ */ new Map();\n const scriptKeysSeen = /* @__PURE__ */ new Set();\n for (const descriptor of descriptors) {\n if (descriptor.singletonKey) {\n const index = singletonIndexes.get(descriptor.singletonKey);\n if (index !== void 0) {\n aggregated[index] = descriptor;\n continue;\n }\n singletonIndexes.set(descriptor.singletonKey, aggregated.length);\n } else if (descriptor.scriptKeys.length > 0) {\n if (descriptor.scriptKeys.some((key) => scriptKeysSeen.has(key))) continue;\n for (const key of descriptor.scriptKeys) scriptKeysSeen.add(key);\n }\n aggregated.push(descriptor);\n }\n return aggregated;\n}\nfunction managedHeadDescriptorBytes(descriptor) {\n let bytes = headTextEncoder.encode(descriptor.tagName).byteLength;\n for (const [name, value] of descriptor.attributes) {\n bytes += headTextEncoder.encode(name).byteLength;\n bytes += headTextEncoder.encode(value).byteLength;\n }\n if (descriptor.content !== void 0) {\n bytes += headTextEncoder.encode(descriptor.content).byteLength;\n }\n return bytes;\n}\nfunction assertManagedHeadDescriptorBudget(descriptors) {\n if (descriptors.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_ENTRIES}-entry request limit`\n );\n }\n let bytes = 0;\n for (const descriptor of descriptors) {\n bytes += managedHeadDescriptorBytes(descriptor);\n if (bytes > MAX_MANAGED_HEAD_BYTES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_BYTES}-byte request limit`\n );\n }\n }\n}\nfunction managedHeadDescriptorToTransportEntry(descriptor) {\n const attributes = descriptor.attributes.filter(([name]) => name !== "nonce");\n return {\n tagName: descriptor.tagName,\n attributes: attributes.map(([name, value]) => [name, value]),\n ...descriptor.content !== void 0 && { content: descriptor.content }\n };\n}\nfunction ownTransportValue(record, key) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n } catch {\n return void 0;\n }\n if (!descriptor || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return void 0;\n }\n return descriptor.value;\n}\nfunction descriptorFromManagedHeadTransportEntry(entry, ambientNonce) {\n if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n let prototype;\n try {\n prototype = Object.getPrototypeOf(entry);\n } catch {\n throw new TypeError("Managed-head transport entry cannot be inspected");\n }\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n const tagName = ownTransportValue(entry, "tagName");\n const rawAttributes = ownTransportValue(entry, "attributes");\n const content = ownTransportValue(entry, "content");\n if (typeof tagName !== "string" || tagName !== tagName.toLowerCase() || !Array.isArray(rawAttributes)) {\n throw new TypeError("Managed-head transport entry is not canonical");\n }\n if (rawAttributes.length > MAX_HEAD_PROP_ENTRIES) {\n throw new TypeError("Managed-head transport entry exceeds the attribute limit");\n }\n if (content !== void 0 && typeof content !== "string") {\n throw new TypeError("Managed-head transport content must be a string");\n }\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (!supportsText && content !== void 0) {\n throw new TypeError("Managed-head transport content is invalid for this tag");\n }\n const record = /* @__PURE__ */ Object.create(null);\n const inputAttributes = [];\n const names = /* @__PURE__ */ new Set();\n for (let index = 0; index < rawAttributes.length; index += 1) {\n const pair = ownTransportValue(rawAttributes, String(index));\n if (!Array.isArray(pair) || pair.length !== 2) {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const name = ownTransportValue(pair, "0");\n const value = ownTransportValue(pair, "1");\n if (typeof name !== "string" || typeof value !== "string") {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const normalizedName = name.toLowerCase();\n if (name !== normalizedName || normalizedName === "nonce" || names.has(normalizedName)) {\n throw new TypeError("Managed-head transport attributes are not canonical");\n }\n names.add(normalizedName);\n inputAttributes.push([normalizedName, value]);\n Object.defineProperty(record, normalizedName, {\n enumerable: true,\n value\n });\n }\n if (content !== void 0) {\n Object.defineProperty(record, "__veryfront_transport_content", {\n enumerable: true,\n value: content\n });\n }\n const descriptor = descriptorFromManagedHeadRecord(tagName, record, {\n ...supportsText && { contentProperty: "__veryfront_transport_content" },\n ...(tagName === "script" || tagName === "style") && ambientNonce ? { ambientNonce } : {}\n });\n const normalizedInput = inputAttributes.sort(([left], [right]) => left.localeCompare(right));\n const normalizedOutput = descriptor?.attributes.filter(([name]) => name !== "nonce");\n if (!descriptor || JSON.stringify(normalizedOutput) !== JSON.stringify(normalizedInput) || supportsText && (descriptor.content ?? "") !== (content ?? "")) {\n throw new TypeError("Managed-head transport entry failed validation");\n }\n return descriptor;\n}\nvar BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";\nfunction decodeBase64Url(value) {\n if (value.length % 4 === 1 || !/^[A-Za-z0-9_-]*$/.test(value)) {\n throw new TypeError("Managed-head payload is not valid base64url");\n }\n const estimatedBytes = Math.floor(value.length * 3 / 4);\n if (estimatedBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n const bytes = new Uint8Array(estimatedBytes);\n let outputIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const character of value) {\n const decoded = BASE64URL_ALPHABET.indexOf(character);\n if (decoded < 0) throw new TypeError("Managed-head payload is not valid base64url");\n buffer = buffer << 6 | decoded;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes[outputIndex++] = buffer >> bits & 255;\n buffer &= bits === 0 ? 0 : (1 << bits) - 1;\n }\n }\n if (bits > 0 && buffer !== 0) {\n throw new TypeError("Managed-head payload has non-canonical trailing bits");\n }\n return bytes.subarray(0, outputIndex);\n}\nfunction inspectManagedHeadPayload(payload, ambientNonce) {\n if (typeof payload !== "string") throw new TypeError("Managed-head payload must be a string");\n const payloadBytes = headTextEncoder.encode(payload).byteLength;\n if (payloadBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n let decoded;\n try {\n decoded = new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(payload));\n } catch (error) {\n if (error instanceof TypeError) throw error;\n throw new TypeError("Managed-head payload is not valid UTF-8", { cause: error });\n }\n let entries;\n try {\n entries = JSON.parse(decoded);\n } catch (error) {\n throw new TypeError("Managed-head payload is not valid JSON", { cause: error });\n }\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Managed-head payload exceeds the entry limit");\n }\n const rawDescriptors = entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, ambientNonce)\n );\n assertManagedHeadDescriptorBudget(rawDescriptors);\n return {\n descriptors: aggregateManagedHeadDescriptors(rawDescriptors),\n entryCount: rawDescriptors.length,\n descriptorBytes: rawDescriptors.reduce(\n (total, descriptor) => total + managedHeadDescriptorBytes(descriptor),\n 0\n ),\n payloadBytes\n };\n}\nfunction deserializeManagedHeadPayload(payload, ambientNonce) {\n return inspectManagedHeadPayload(payload, ambientNonce).descriptors;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction getManagedHeadNonce(targetDocument) {\n if (typeof targetDocument.querySelector !== "function") return void 0;\n const element = targetDocument.querySelector(\n "script[nonce], style[nonce], link[nonce]"\n );\n if (!element) return void 0;\n const nonce = element.nonce || element.getAttribute("nonce") || "";\n return nonce || void 0;\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey2 = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey2 !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey2);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nvar ROUTE_HEAD_CONTENT_PROPERTY = "__veryfront_route_head_content";\nfunction descriptorFromHeadElement(element) {\n const record = /* @__PURE__ */ Object.create(null);\n for (const { name, value } of element.attributes) {\n if (!isHeadFrameworkAttribute(name)) record[name] = value;\n }\n const tagName = element.tagName.toLowerCase();\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (supportsText) record[ROUTE_HEAD_CONTENT_PROPERTY] = element.textContent ?? "";\n return descriptorFromManagedHeadRecord(\n tagName,\n record,\n supportsText ? { contentProperty: ROUTE_HEAD_CONTENT_PROPERTY } : void 0\n );\n}\nfunction writeRouteDescriptor(element, descriptor) {\n for (const attribute of [...element.attributes]) element.removeAttribute(attribute.name);\n for (const [name, value] of descriptor.attributes) element.setAttribute(name, value);\n element.textContent = descriptor.content ?? "";\n element.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n element.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n}\nfunction prepareClientRouteHeadEntries(entries, targetDocument = document) {\n if (entries === void 0) return [];\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Route head payload exceeds the entry limit");\n }\n const descriptors = aggregateManagedHeadDescriptors(\n entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, getManagedHeadNonce(targetDocument))\n )\n );\n assertManagedHeadDescriptorBudget(descriptors);\n return descriptors;\n}\nfunction applyPreparedClientRouteHeadDescriptors(descriptors, targetDocument = document) {\n for (const descriptor of descriptors) {\n const described = [...targetDocument.head.children].flatMap((element2) => {\n const current = descriptorFromHeadElement(element2);\n return current ? [{ element: element2, descriptor: current }] : [];\n });\n if (descriptor.singletonKey) {\n const matches = described.filter(\n ({ descriptor: current }) => current.singletonKey === descriptor.singletonKey\n );\n const directive = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1"\n );\n if (directive) {\n continue;\n }\n const reusable = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element2.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true"\n );\n if (reusable) {\n writeRouteDescriptor(reusable.element, descriptor);\n continue;\n }\n }\n if (described.some(\n ({ descriptor: current }) => current.signature === descriptor.signature || headScriptKeysIntersect(current.scriptKeys, descriptor.scriptKeys)\n )) {\n continue;\n }\n const element = targetDocument.createElement(descriptor.tagName);\n writeRouteDescriptor(element, descriptor);\n targetDocument.head.appendChild(element);\n }\n}\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/routing/client/dom-utils.ts\nvar logger2 = rendererLogger.component("veryfront");\nfunction isInternalLink(target) {\n const href = target.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return false;\n const linkTarget = target.getAttribute("target");\n if (linkTarget === "_blank" || target.hasAttribute("download")) return false;\n return true;\n}\nfunction findAnchorElement(element) {\n let current = element;\n while (current && current.tagName !== "A") {\n current = current.parentElement;\n }\n return current instanceof HTMLAnchorElement ? current : null;\n}\nfunction applyHeadDirectives(container) {\n const targetDocument = container.ownerDocument ?? document;\n const nodes = [...container.querySelectorAll(\'[data-veryfront-head="1"], vf-head\')].filter(\n (node) => typeof node.getAttribute !== "function" || node.getAttribute(HEAD_REACT_OWNER_ATTRIBUTE) !== "1"\n );\n if (!nodes.length) return;\n retireClientHeadOwnership(targetDocument);\n cleanManagedHeadTags(targetDocument);\n for (const wrapper of nodes) {\n const TemplateElement = targetDocument.defaultView?.HTMLTemplateElement ?? globalThis.HTMLTemplateElement;\n const contentSource = TemplateElement && wrapper instanceof TemplateElement ? wrapper.content : wrapper;\n processHeadWrapper(contentSource, targetDocument);\n wrapper.parentElement?.removeChild(wrapper);\n }\n}\nfunction cleanManagedHeadTags(targetDocument) {\n for (const element of targetDocument.head.querySelectorAll(\n `[${HEAD_LEGACY_MANAGED_ATTRIBUTE}="1"]`\n )) {\n element.parentElement?.removeChild(element);\n }\n}\nfunction processHeadWrapper(wrapper, targetDocument) {\n const ElementConstructor = targetDocument.defaultView?.Element ?? globalThis.Element;\n const activeNonce = getManagedHeadNonce(targetDocument);\n for (const node of wrapper.childNodes) {\n if (!ElementConstructor || !(node instanceof ElementConstructor)) continue;\n const tagName = node.tagName.toLowerCase();\n if (headSingletonKey(node) === "meta:charset") continue;\n const clone = targetDocument.createElement(tagName);\n for (const { name, value } of node.attributes) {\n if (name.toLowerCase() !== "nonce") clone.setAttribute(name, value);\n }\n if (activeNonce && (tagName === "script" || tagName === "style" || tagName === "link")) {\n clone.setAttribute("nonce", activeNonce);\n }\n if (node.textContent && !clone.hasAttribute("src")) {\n clone.textContent = node.textContent;\n }\n replaceExistingHeadSingleton(targetDocument, clone);\n clone.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n clone.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(clone);\n }\n}\nfunction headSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n if (tagName !== "meta" && tagName !== "link") return void 0;\n const attributes = /* @__PURE__ */ Object.create(null);\n if (!element.attributes) return void 0;\n for (const { name, value } of element.attributes) attributes[name.toLowerCase()] = value;\n if (tagName === "meta" && attributes["http-equiv"]?.trim().toLowerCase() === "content-type") {\n return "meta:charset";\n }\n return tagName === "meta" ? headMetaSingletonKeyFromRecord(attributes) : headLinkSingletonKeyFromRecord(attributes);\n}\nfunction replaceExistingHeadSingleton(targetDocument, replacement) {\n const singletonKey2 = headSingletonKey(replacement);\n if (!singletonKey2 || singletonKey2 === "meta:charset") return;\n for (const existing of [...targetDocument.head?.children ?? []]) {\n if (headSingletonKey(existing) === singletonKey2) existing.remove();\n }\n}\nfunction manageFocus(container) {\n try {\n const focusElement = container.querySelector("[data-router-focus]") || container.querySelector("main") || container.querySelector("h1");\n focusElement?.focus?.({ preventScroll: true });\n } catch (error) {\n logger2.warn("focus management failed", error);\n }\n}\nfunction extractPageDataFromScript() {\n const pageDataScript = document.querySelector("script[data-veryfront-page]");\n if (!pageDataScript) return null;\n try {\n const content = pageDataScript.textContent;\n if (!content) {\n logger2.warn("Page data script has no content");\n return {};\n }\n return JSON.parse(content);\n } catch (error) {\n logger2.error("Failed to parse page data:", error);\n return null;\n }\n}\nfunction snapshotClientRouteHead(targetDocument = document) {\n const hydrationDataScript = findServerHydrationDataElement(targetDocument);\n if (!hydrationDataScript?.textContent) return [];\n try {\n const hydrationData = JSON.parse(hydrationDataScript.textContent);\n if (typeof hydrationData.managedHeadPayload !== "string") return [];\n const descriptors = deserializeManagedHeadPayload(\n hydrationData.managedHeadPayload\n );\n const aggregated = aggregateManagedHeadDescriptors(descriptors);\n assertManagedHeadDescriptorBudget(aggregated);\n return aggregated.map(managedHeadDescriptorToTransportEntry);\n } catch {\n return [];\n }\n}\nfunction routeRequiresDocumentNavigation(data) {\n return Boolean(\n data.requiresFullDocumentNavigation || data.managedHead?.some((entry) => entry.tagName === "script") || typeof data.html === "string" && / entry.tagName === "script") || typeof root.querySelector === "function" && root.querySelector("script")\n ) {\n pageData = { ...pageData, requiresFullDocumentNavigation: true };\n }\n return { content, pageData, managedHead, dependencyPinningCacheKey };\n}\n\n// src/rendering/client/browser-stubs/config.ts\nvar DEFAULT_PREFETCH_DELAY_MS = 100;\nvar PAGE_TRANSITION_DELAY_MS = 150;\n\n// src/routing/client/navigation-handlers.ts\nvar logger3 = rendererLogger.component("veryfront");\nvar MAX_SCROLL_POSITIONS = 100;\nvar NavigationHandlers = class {\n constructor(prefetchDelay = DEFAULT_PREFETCH_DELAY_MS, prefetchOptions = {}) {\n __publicField(this, "prefetchQueue", /* @__PURE__ */ new Set());\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "scrollPositions", /* @__PURE__ */ new Map());\n __publicField(this, "isPopStateNav", false);\n __publicField(this, "prefetchDelay");\n __publicField(this, "prefetchOptions");\n this.prefetchDelay = prefetchDelay;\n this.prefetchOptions = prefetchOptions;\n }\n createClickHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n const anchor = findAnchorElement(event.target);\n if (!anchor || !isInternalLink(anchor)) return;\n const href = anchor.getAttribute("href");\n if (!href) return;\n event.preventDefault();\n callbacks.onNavigate(href);\n };\n }\n createPopStateHandler(callbacks) {\n return (_event) => {\n this.isPopStateNav = true;\n const { pathname, search, hash } = globalThis.location;\n callbacks.onNavigate(`${pathname}${search}${hash}`);\n };\n }\n createMouseOverHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.tagName !== "A") return;\n const href = event.target.getAttribute("href");\n if (!href || href.startsWith("http") || href.startsWith("#")) return;\n if (!this.shouldPrefetchOnHover(event.target)) return;\n if (this.prefetchQueue.has(href)) return;\n this.prefetchQueue.add(href);\n const timeoutId = setTimeout(() => {\n callbacks.onPrefetch(href);\n this.prefetchQueue.delete(href);\n this.pendingTimeouts.delete(href);\n }, this.prefetchDelay);\n this.pendingTimeouts.set(href, timeoutId);\n };\n }\n shouldPrefetchOnHover(target) {\n const prefetchAttribute = target.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n if (prefetchAttribute === "true") return true;\n return Boolean(this.prefetchOptions.hover);\n }\n saveScrollPosition(path) {\n try {\n if (this.scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = this.scrollPositions.keys().next().value;\n if (oldest) this.scrollPositions.delete(oldest);\n }\n const scrollY = globalThis.scrollY;\n if (typeof scrollY !== "number") {\n logger3.debug("No valid scrollY value available");\n this.scrollPositions.set(path, 0);\n return;\n }\n this.scrollPositions.set(path, scrollY);\n } catch (error) {\n logger3.warn("failed to record scroll position", error);\n }\n }\n getScrollPosition(path) {\n const position = this.scrollPositions.get(path);\n if (position === void 0) {\n logger3.debug(`No scroll position stored for ${path}`);\n return 0;\n }\n return position;\n }\n isPopState() {\n return this.isPopStateNav;\n }\n clearPopStateFlag() {\n this.isPopStateNav = false;\n }\n clear() {\n for (const timeoutId of this.pendingTimeouts.values()) clearTimeout(timeoutId);\n this.pendingTimeouts.clear();\n this.prefetchQueue.clear();\n this.scrollPositions.clear();\n this.isPopStateNav = false;\n }\n};\n\n// src/rendering/client/browser-stubs/error-registry.ts\nfunction createBrowserError(name, fallbackMessage) {\n return {\n create(options = {}) {\n const error = new Error(options.detail ?? fallbackMessage);\n error.name = name;\n Object.assign(error, {\n status: options.status,\n context: options.context\n });\n return error;\n }\n };\n}\nvar NETWORK_ERROR = createBrowserError("NetworkError", "Network request failed");\nvar SECURITY_VIOLATION = createBrowserError("SecurityViolation", "Security violation");\n\n// src/html/html-detection.ts\nfunction isFullHTMLDocument(content) {\n const trimmed = content.trim().toLowerCase();\n return trimmed.startsWith("");\n}\n\n// src/routing/client/page-loader.ts\nvar logger4 = rendererLogger.component("veryfront");\nvar MAX_CACHE_SIZE = 50;\nvar DEPENDENCY_PINNING_RESPONSE_HEADER = "x-veryfront-dependency-pins";\nfunction reloadBrowserDocument(url) {\n if (typeof globalThis.location !== "undefined") {\n globalThis.location.assign(url);\n }\n}\nfunction readDependencyPinningCacheKey(doc) {\n if (!doc) return "off";\n try {\n const hydrationDataElement = findServerHydrationDataElement(doc);\n if (!hydrationDataElement?.textContent) return "off";\n const hydrationData = JSON.parse(hydrationDataElement.textContent);\n return typeof hydrationData.dependencyPinningCacheKey === "string" && hydrationData.dependencyPinningCacheKey.startsWith("on:") ? hydrationData.dependencyPinningCacheKey : "off";\n } catch (error) {\n logger4.debug("Failed to read dependency snapshot from hydration data:", error);\n return "off";\n }\n}\nvar PageLoader = class {\n constructor(doc = typeof document === "undefined" ? void 0 : document, reloadDocument = reloadBrowserDocument) {\n __publicField(this, "cache", /* @__PURE__ */ new Map());\n __publicField(this, "spaCache", /* @__PURE__ */ new Map());\n __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());\n __publicField(this, "pendingSpaRequests", /* @__PURE__ */ new Map());\n /**\n * A loader belongs to the dependency snapshot of the document that created it.\n * Keeping this immutable also prevents cached or in-flight route data from\n * crossing snapshot boundaries if the hydration element is later replaced.\n */\n __publicField(this, "dependencyPinningCacheKey");\n __publicField(this, "reloadDocument");\n __publicField(this, "snapshotRecoveryStarted", false);\n this.dependencyPinningCacheKey = readDependencyPinningCacheKey(doc);\n this.reloadDocument = reloadDocument;\n }\n evictIfFull(map) {\n if (map.size < MAX_CACHE_SIZE) return;\n const oldest = map.keys().next().value;\n if (oldest) map.delete(oldest);\n }\n getCached(path) {\n return this.cache.get(this.snapshotScopedPath(path));\n }\n isCached(path) {\n return this.cache.has(this.snapshotScopedPath(path));\n }\n setCache(path, data) {\n this.evictIfFull(this.cache);\n this.cache.set(this.snapshotScopedPath(path), data);\n }\n clearCache() {\n this.cache.clear();\n this.spaCache.clear();\n this.pendingRequests.clear();\n this.pendingSpaRequests.clear();\n }\n getSpaCached(path) {\n return this.spaCache.get(this.snapshotScopedPath(path));\n }\n isSpaDataCached(path) {\n return this.spaCache.has(this.snapshotScopedPath(path));\n }\n setSpaCache(path, data) {\n this.evictIfFull(this.spaCache);\n this.spaCache.set(this.snapshotScopedPath(path), data);\n }\n async fetchPageData(path, reloadOnSnapshotFailure = true) {\n try {\n return await this.tryFetchJSON(path) ?? await this.fetchAndParseHTML(path);\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n async tryFetchJSON(path) {\n let response;\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const dataPath = navigationUrl.pathname === "/" ? "/index" : navigationUrl.pathname;\n const endpoint = `/_veryfront/data${dataPath}.json${navigationUrl.search}`;\n response = await fetch(endpoint, {\n headers: this.navigationHeaders("client")\n });\n } catch (error) {\n logger4.debug(`JSON fetch failed for ${path}, falling back to HTML:`, error);\n return null;\n }\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) return null;\n let data;\n try {\n data = await response.json();\n } catch (error) {\n logger4.debug(`JSON response was invalid for ${path}, falling back to HTML:`, error);\n return null;\n }\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "route data"\n );\n if (typeof data.html === "string" && isFullHTMLDocument(data.html)) {\n const parsed = parsePageDataFromHTML(data.html);\n this.assertDependencySnapshot(\n parsed.dependencyPinningCacheKey,\n path,\n "route data HTML body"\n );\n return {\n ...parsed.pageData,\n ...data,\n html: parsed.content,\n managedHead: parsed.managedHead\n };\n }\n return routeRequiresDocumentNavigation(data) ? { ...data, requiresFullDocumentNavigation: true } : data;\n }\n async fetchAndParseHTML(path) {\n const response = await fetch(path, {\n headers: this.navigationHeaders("client")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch ${path}`,\n status: response.status,\n context: { path }\n });\n }\n this.assertDependencySnapshot(\n response.headers.get(DEPENDENCY_PINNING_RESPONSE_HEADER),\n path,\n "HTML response"\n );\n const html = await response.text();\n const {\n content,\n pageData,\n managedHead,\n dependencyPinningCacheKey\n } = parsePageDataFromHTML(html);\n this.assertDependencySnapshot(\n dependencyPinningCacheKey,\n path,\n "HTML body"\n );\n return { ...pageData, html: content, managedHead };\n }\n loadPage(path) {\n return this.loadPageWithSnapshotRecovery(path, true);\n }\n loadPageWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getCached(path);\n if (cachedData) {\n logger4.debug(`Loading ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingRequests, async () => {\n const data = await this.fetchPageData(path, false);\n this.setCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetch(path) {\n if (this.isCached(path)) return;\n logger4.debug(`Prefetching ${path}`);\n try {\n await this.loadPageWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n async fetchSpaPageData(path, reloadOnSnapshotFailure = true) {\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const normalizedPath = navigationUrl.pathname === "/" ? "index" : navigationUrl.pathname.replace(/^\\//, "");\n const endpoint = `/_veryfront/page-data/${normalizedPath}.json${navigationUrl.search}`;\n logger4.debug(`Fetching SPA page data from ${endpoint}`);\n const response = await fetch(endpoint, {\n headers: this.navigationHeaders("spa")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for SPA page data ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch SPA page data for ${path}`,\n status: response.status,\n context: { path }\n });\n }\n const data = await response.json();\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "SPA page data"\n );\n return data;\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n loadSpaPageData(path) {\n return this.loadSpaPageDataWithSnapshotRecovery(path, true);\n }\n loadSpaPageDataWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getSpaCached(path);\n if (cachedData) {\n logger4.debug(`Loading SPA data for ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingSpaRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending SPA request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending SPA request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingSpaRequests, async () => {\n const data = await this.fetchSpaPageData(path, false);\n this.setSpaCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetchSpaPageData(path) {\n if (this.isSpaDataCached(path)) return;\n logger4.debug(`Prefetching SPA page data for ${path}`);\n try {\n await this.loadSpaPageDataWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch SPA data for ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n createPendingRequest(path, pendingMap, fetcher) {\n const request = (async () => {\n try {\n return await fetcher();\n } finally {\n pendingMap.delete(path);\n }\n })();\n pendingMap.set(path, request);\n return request;\n }\n snapshotScopedPath(path) {\n return this.dependencyPinningCacheKey.startsWith("on:") ? `${this.dependencyPinningCacheKey}\\0${path}` : path;\n }\n navigationHeaders(type) {\n return {\n "X-Veryfront-Navigation": type,\n ...this.dependencyPinningCacheKey.startsWith("on:") ? {\n [DEPENDENCY_PINNING_RESPONSE_HEADER]: this.dependencyPinningCacheKey\n } : {}\n };\n }\n assertDependencySnapshot(actualCacheKey, path, source) {\n const expectedCacheKey = this.dependencyPinningCacheKey.startsWith("on:") ? this.dependencyPinningCacheKey : void 0;\n const normalizedActualCacheKey = typeof actualCacheKey === "string" ? actualCacheKey : void 0;\n const matches = expectedCacheKey ? normalizedActualCacheKey === expectedCacheKey : normalizedActualCacheKey === void 0 || normalizedActualCacheKey === "off";\n if (matches) return;\n this.failDependencySnapshot(\n path,\n `Dependency snapshot mismatch in ${source} for ${path}`\n );\n }\n failDependencySnapshot(path, detail) {\n throw NETWORK_ERROR.create({\n detail,\n status: 409,\n context: { path }\n });\n }\n withSnapshotRecovery(promise, path, reloadOnSnapshotFailure) {\n return promise.catch((error) => {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n });\n }\n recoverSnapshotFailure(error, path, reloadOnSnapshotFailure) {\n if (!reloadOnSnapshotFailure || typeof error !== "object" || error === null || error.status !== 409) {\n return;\n }\n if (this.snapshotRecoveryStarted) return;\n this.snapshotRecoveryStarted = true;\n try {\n this.reloadDocument(path);\n } catch (reloadError) {\n this.snapshotRecoveryStarted = false;\n logger4.warn(\n `[Veryfront] Failed to reload after dependency snapshot conflict for ${path}`,\n reloadError instanceof Error ? reloadError : new Error(String(reloadError))\n );\n }\n }\n};\n\n// src/security/client/html-sanitizer.ts\nvar SUSPICIOUS_PATTERN_SPECS = [\n { source: String.raw`]*>[\\s\\S]*?<\\/script>`, flags: "gi", name: "inline script" },\n { source: String.raw`javascript:`, flags: "gi", name: "javascript: URL" },\n { source: String.raw`\\bon\\w+\\s*=`, flags: "gi", name: "event handler attribute" },\n { source: String.raw`data:\\s*text\\/html`, flags: "gi", name: "data: HTML URL" }\n];\nfunction createSuspiciousPatterns() {\n return SUSPICIOUS_PATTERN_SPECS.map(({ source, flags, name }) => ({\n pattern: new RegExp(source, flags),\n name\n }));\n}\nfunction isDevMode() {\n const g = globalThis;\n return g.__VERYFRONT_DEV__ === true || g.Deno?.env?.get?.("VERYFRONT_ENV") === "development";\n}\nfunction validateTrustedHtml(html, options = {}) {\n const { allowInlineScripts = false, strict = false, warn = true } = options;\n for (const { pattern, name } of createSuspiciousPatterns()) {\n if (allowInlineScripts && name === "inline script") continue;\n pattern.lastIndex = 0;\n if (!pattern.test(html)) continue;\n if (warn) console.warn(`[Security] Suspicious ${name} detected in server HTML`);\n if (strict || !isDevMode()) {\n throw SECURITY_VIOLATION.create({ detail: `Potentially unsafe HTML: ${name} detected` });\n }\n }\n return html;\n}\n\n// src/routing/client/page-transition.ts\nvar logger5 = rendererLogger.component("veryfront");\nvar PageTransition = class {\n constructor(setupViewportPrefetch) {\n __publicField(this, "setupViewportPrefetch", setupViewportPrefetch);\n __publicField(this, "pendingTransitionTimeout");\n __publicField(this, "pendingRoot");\n }\n destroy() {\n this.cancelPendingTransition();\n }\n cancelPendingTransition() {\n if (this.pendingTransitionTimeout !== void 0) {\n clearTimeout(this.pendingTransitionTimeout);\n this.pendingTransitionTimeout = void 0;\n }\n if (this.pendingRoot) {\n this.pendingRoot.style.opacity = "1";\n this.pendingRoot = void 0;\n }\n }\n updatePage(data, isPopState, scrollY) {\n this.cancelPendingTransition();\n if (routeRequiresDocumentNavigation(data)) {\n throw new TypeError("Scripted routes require a full document navigation");\n }\n const rootElement = document.getElementById("root");\n const preparedHead = prepareClientRouteHeadEntries(data.managedHead, document);\n const retainedTitle = document.title;\n if (!rootElement || data.html === void 0) {\n retireClientHeadOwnership(document);\n applyPreparedClientRouteHeadDescriptors(preparedHead, document);\n this.updateDocumentMetadata(document, data, retainedTitle);\n return;\n }\n const trustedHtml = validateTrustedHtml(String(data.html));\n this.performTransition(\n rootElement,\n data,\n trustedHtml,\n preparedHead,\n retainedTitle,\n isPopState,\n scrollY\n );\n }\n updateDocumentMetadata(targetDocument, data, retainedTitle) {\n updateRouteTitle(data.frontmatter?.title || retainedTitle, targetDocument);\n updateRouteMetaTags(data.frontmatter ?? {}, targetDocument);\n }\n performTransition(rootElement, data, trustedHtml, preparedHead, retainedTitle, isPopState, scrollY) {\n rootElement.style.opacity = "0";\n this.pendingRoot = rootElement;\n this.pendingTransitionTimeout = setTimeout(() => {\n this.pendingTransitionTimeout = void 0;\n this.pendingRoot = void 0;\n try {\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = trustedHtml;\n applyHeadDirectives(rootElement);\n applyPreparedClientRouteHeadDescriptors(preparedHead, rootElement.ownerDocument);\n this.updateDocumentMetadata(rootElement.ownerDocument, data, retainedTitle);\n this.setupViewportPrefetch(rootElement);\n manageFocus(rootElement);\n this.handleScroll(isPopState, scrollY);\n } catch (error) {\n logger5.error("Route transition commit failed; reloading the document", error);\n globalThis.location?.reload();\n } finally {\n rootElement.style.opacity = "1";\n }\n }, PAGE_TRANSITION_DELAY_MS);\n }\n handleScroll(isPopState, scrollY) {\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger5.warn("scroll handling failed", error);\n }\n }\n showError(error) {\n const rootElement = document.getElementById("root");\n if (!rootElement) return;\n const errorDiv = document.createElement("div");\n errorDiv.className = "veryfront-error-page";\n const heading = document.createElement("h1");\n heading.textContent = "Oops! Something went wrong";\n const message = document.createElement("p");\n message.textContent = error.message;\n const button = document.createElement("button");\n button.type = "button";\n button.textContent = "Reload Page";\n button.onclick = () => globalThis.location.reload();\n errorDiv.append(heading, message, button);\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = "";\n rootElement.appendChild(errorDiv);\n }\n setLoadingState(loading) {\n const indicator = document.getElementById("veryfront-loading");\n if (indicator) indicator.style.display = loading ? "block" : "none";\n document.body.classList.toggle("veryfront-loading", loading);\n }\n};\n\n// src/routing/client/viewport-prefetch.ts\nvar logger6 = rendererLogger.component("veryfront");\nvar ViewportPrefetch = class {\n constructor(prefetchCallback, prefetchOptions = {}) {\n __publicField(this, "observer", null);\n __publicField(this, "prefetchCallback");\n __publicField(this, "prefetchOptions");\n this.prefetchCallback = prefetchCallback;\n this.prefetchOptions = prefetchOptions;\n }\n setup(root) {\n try {\n if (!("IntersectionObserver" in globalThis)) return;\n this.observer?.disconnect();\n this.createObserver();\n this.observeLinks(root);\n } catch (error) {\n logger6.debug("setupViewportPrefetch failed", error);\n }\n }\n createObserver() {\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!(entry.target instanceof HTMLAnchorElement)) continue;\n const href = entry.target.getAttribute("href");\n if (href) this.prefetchCallback(href);\n this.observer?.unobserve(entry.target);\n }\n },\n { rootMargin: "200px" }\n );\n }\n observeLinks(root) {\n const anchors = root.querySelectorAll(\'a[href]:not([target="_blank"])\');\n const isViewportEnabled = Boolean(this.prefetchOptions.viewport);\n for (const anchor of anchors) {\n if (!this.shouldObserveAnchor(anchor, isViewportEnabled)) continue;\n this.observer?.observe(anchor);\n }\n }\n shouldObserveAnchor(anchor, isViewportEnabled) {\n const href = anchor.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("#")) return false;\n if (anchor.getAttribute("download")) return false;\n const prefetchAttribute = anchor.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n return prefetchAttribute === "viewport" || isViewportEnabled;\n }\n disconnect() {\n if (!this.observer) return;\n try {\n this.observer.disconnect();\n } catch (error) {\n logger6.warn("prefetchObserver.disconnect failed", error);\n } finally {\n this.observer = null;\n }\n }\n};\n\n// src/rendering/client/router.ts\nvar logger7 = rendererLogger.component("veryfront");\nfunction toHistoryMode(options) {\n if (typeof options === "boolean") return options ? "push" : "none";\n return options?.history ?? "push";\n}\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "navigationSequence", 0);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n // The browser already updated the URL for a popstate, so don\'t touch history.\n onNavigate: (url) => this.navigate(url, { history: "none" }),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n getNavigationStore().setNavigator((href, options2) => this.navigate(href, options2));\n }\n registerNavigationHandler(handler) {\n logger7.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n /**\n * Notify React (and any other) subscribers that a navigation completed —\n * after full page loads, soft same-route changes, and popstate. Delegates to\n * the shared navigation store, the single subscription surface both bundles\n * share.\n */\n notify() {\n getNavigationStore().notify();\n }\n pathnameOf(url) {\n try {\n return new URL(url, this.baseUrl).pathname;\n } catch {\n return url.split("?")[0]?.split("#")[0] || this.currentPath;\n }\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger7.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger7.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger7.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger7.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData) {\n const managedHead = snapshotClientRouteHead(document);\n this.pageLoader.setCache(this.currentPath, {\n ...pageData,\n managedHead,\n ...managedHead.some((entry) => entry.tagName === "script") || document.getElementById("root")?.querySelector("script") ? { requiresFullDocumentNavigation: true } : {}\n });\n }\n }\n /**\n * Navigate to a URL. `options` selects the history behaviour: `{ history:\n * "push" }` (default), `"replace"`, or `"none"` (the URL already reflects the\n * target, as after popstate). A boolean is accepted for backward\n * compatibility — `true` pushes, `false` maps to `"none"`.\n */\n async navigate(url, options) {\n logger7.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n const navigationId = ++this.navigationSequence;\n this.pageTransition.cancelPendingTransition();\n this.pageTransition.setLoadingState(false);\n const history = toHistoryMode(options);\n const sameRoute = this.pathnameOf(url) === this.pathnameOf(this.currentPath);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (history === "replace") globalThis.history.replaceState({}, "", url);\n else if (history === "push") globalThis.history.pushState({}, "", url);\n if (sameRoute && !this.shouldRevalidate(url, sameRoute)) {\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = url;\n this.notify();\n this.options.onComplete?.(url);\n this.options.onNavigate?.(url);\n return;\n }\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url, navigationId);\n } else {\n if (await this.loadPage(url, true, navigationId)) return;\n }\n if (!this.isCurrentNavigation(navigationId)) return;\n this.notify();\n this.options.onNavigate?.(url);\n }\n isCurrentNavigation(navigationId) {\n return navigationId === this.navigationSequence;\n }\n /**\n * Whether a navigation should refetch page data. A route change always does;\n * a same-route (query/hash-only) change consults `options.shouldRevalidate`,\n * defaulting to `true` so server data is never shown stale.\n */\n shouldRevalidate(nextUrl, sameRoute) {\n const policy = this.options.shouldRevalidate;\n if (!policy) return true;\n return policy({ currentHref: this.currentPath, nextHref: nextUrl, sameRoute });\n }\n async loadSpaPage(path, navigationId) {\n logger7.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n if (!this.isCurrentNavigation(navigationId)) return;\n await this.spaNavigationHandler?.(spaData);\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger7.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n /** Returns true when navigation was handed to the browser document loader. */\n async loadPage(path, updateUI = true, navigationId) {\n if (this.pageLoader.isCached(path)) {\n logger7.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.pageTransition.setLoadingState(false);\n this.options.onComplete?.(path);\n return false;\n }\n logger7.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.options.onComplete?.(path);\n return false;\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n return false;\n } finally {\n if (this.isCurrentNavigation(navigationId)) this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data, targetPath) {\n if (!this.root) return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(targetPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n this.navigationSequence++;\n this.pageTransition.setLoadingState(false);\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nfunction boot(options = {}) {\n if (typeof window === "undefined" || !globalThis.document) return null;\n const globalWithRouter = globalThis;\n if (globalWithRouter.veryFrontRouter) return globalWithRouter.veryFrontRouter;\n const { slug: _slug, ...routerOptions } = options;\n const router = new VeryfrontRouter(routerOptions);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalWithRouter.veryFrontRouter = router;\n return router;\n}\nif (typeof window !== "undefined" && globalThis.document) {\n boot();\n}\nexport {\n VeryfrontRouter,\n boot\n};\n'; export const CLIENT_PREFETCH_BUNDLE: string | undefined = - 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; + 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar StringReplace = String.prototype.replace;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index f7dcf0e1e0..24707730e4 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),eo=new b("PREFETCH",G),to=new b("HYDRATE",G),no=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},po={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var mo=Array.prototype,ho=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Eo=Object.hasOwn,Ro=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,_o=Number.isFinite,xo=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,So=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,Ao=_(T.prototype,"host").get,To=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,bo=_(T.prototype,"pathname").get,Co=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Oo=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var $o=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),ko=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),vo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Fo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Vo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),zo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Bo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),jo=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Go=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Wo=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Ko=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Yo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,yi=ir*2;var mi=64*1024,hi=1024*1024,Ei=1024*1024;var Ri=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; + 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=String.prototype.replace;function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),to=new b("PREFETCH",G),no=new b("HYDRATE",G),ro=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},yo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var ho=Array.prototype,Eo=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Ro=Object.hasOwn,_o=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,xo=Number.isFinite,So=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,Ao=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,To=_(T.prototype,"host").get,bo=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,Co=_(T.prototype,"pathname").get,wo=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Io=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var ko=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Fo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Vo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),zo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Bo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),jo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Go=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Wo=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Ko=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Yo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Xo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,mi=ir*2;var hi=64*1024,Ei=1024*1024,Ri=1024*1024;var _i=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var sr=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Tr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Tr as consumeNdjsonStream,ft as getContainer};\n'; + 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=String.prototype.replace;var ir=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Cr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Cr as consumeNdjsonStream,ft as getContainer};\n'; From c6c5506eb8753938b481216c3a1af7b94dabe3c0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 21:16:57 +0200 Subject: [PATCH 010/104] Keep framework build failures at error severity Separate transform-phase routing from the explicit tenant-source signal used by application-error classification. Legacy build-shaped cache and bundle faults remain error-level while compiler and content failures retain warning-level capture. Constraint: Module loading uses the build-failure marker for both tenant compilation failures and framework infrastructure failures. Rejected: Treat every legacy type=build error as tenant-owned | cache, bundle, and capacity paths emit the same legacy shape. Confidence: high Scope-risk: narrow Directive: Do not infer tenant ownership from the broad build-failure marker or legacy build type. Tested: Application error classifier, render pipeline behavior, Sentry policy, targeted format/lint/typecheck, pinned generated API reference. Not-tested: Full repository suite before commit; enforced by pre-push. --- docs/api-reference/veryfront/observability.md | 8 ++--- src/observability/application-errors.test.ts | 28 ++++++++++++++--- src/observability/application-errors.ts | 20 +++++------- .../module-loader/build-failure.ts | 31 +++++++++++++++++-- .../orchestrator/pipeline.behavior.test.ts | 29 +++++++++++++++-- src/rendering/orchestrator/pipeline.ts | 13 ++++++-- 6 files changed, 102 insertions(+), 27 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index e2a65f4f3d..8e3e169182 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L281) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L309) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L281) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L309) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index e7a2f66ec0..b581e68ab3 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -24,6 +24,7 @@ import { RENDER_ERROR, SOURCEMAP_ERROR, toError, + TYPESCRIPT_ERROR, } from "#veryfront/errors"; it("application error reporter is optional", async () => { @@ -152,12 +153,19 @@ it("application error reporter downgrades tenant build errors to tagged warnings flush: () => Promise.resolve(true), }); - const compileError = toError( - createError({ type: "build", message: "MDX compilation failed" }), + const compileError = TYPESCRIPT_ERROR.create({ + detail: "TypeScript compilation failed in /pages/index.tsx", + }); + const legacyBuildError = toError( + createError({ type: "build", message: "Module transform cache write failed" }), ); const pipelineError = RENDER_ERROR.create({ detail: "Critical page module(s) failed to load:\n/pages/index.mdx: bad syntax", - context: { buildFailure: true }, + context: { buildFailure: true, tenantBuildFailure: true }, + }); + const frameworkPipelineError = RENDER_ERROR.create({ + detail: "Critical page module(s) failed to load while persisting its cache entry", + context: { buildFailure: true, tenantBuildFailure: false }, }); const mdxRegistryError = MDX_COMPILE_ERROR.create({ detail: "MDX compilation failed in /pages/index.mdx", @@ -210,8 +218,16 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(frameworkBundleError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(frameworkPipelineError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals( + captureApplicationError(legacyBuildError, { boundary: "ssr.render" }), + "event-id", + ); - assertEquals(captures.length, 8); + assertEquals(captures.length, 10); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -231,6 +247,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[6]?.context.level, undefined); assertEquals(captures[7]?.context.errorClass, undefined); assertEquals(captures[7]?.context.level, undefined); + assertEquals(captures[8]?.context.errorClass, undefined); + assertEquals(captures[8]?.context.level, undefined); + assertEquals(captures[9]?.context.errorClass, undefined); + assertEquals(captures[9]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index f4836af7e4..d2816035d7 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -224,12 +224,13 @@ export function initializeApplicationErrorReporter(options: { const TENANT_BUILD_ERROR_CLASS = "tenant-build"; /** - * Tag applied by the module loader at the point of a compilation failure. + * Tag applied by the module loader only after an explicit tenant-source + * classification. * * The tag is read through the shared symbol registry instead of importing the * rendering layer; see src/rendering/orchestrator/module-loader/build-failure.ts. */ -const BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.build-failure"); +const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-build-failure"); const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", @@ -242,23 +243,16 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ * that does not build, MDX that does not parse) rather than a framework fault. * * Recognizes the existing discriminators at their capture seam: - * - the module loader's build-failure tag, - * - `toError(createError({ type: "build" }))` structured error data, - * - the render pipeline's `buildFailure` error context, and + * - the module loader's tenant-build-failure tag, + * - the render pipeline's `tenantBuildFailure` error context, and * - tenant-facing BUILD registry slugs that do not also describe framework * cache or bundle infrastructure failures. */ function isTenantBuildError(error: unknown): boolean { try { if (error instanceof Error) { - if ((error as { [BUILD_FAILURE_TAG]?: unknown })[BUILD_FAILURE_TAG] === true) { - return true; - } - const descriptor = Object.getOwnPropertyDescriptor(error, "context"); - const data = descriptor && "value" in descriptor ? descriptor.value : undefined; if ( - typeof data === "object" && data !== null && - (data as { type?: unknown }).type === "build" + (error as { [TENANT_BUILD_FAILURE_TAG]?: unknown })[TENANT_BUILD_FAILURE_TAG] === true ) { return true; } @@ -268,7 +262,7 @@ function isTenantBuildError(error: unknown): boolean { const errorContext = snapshot.context; if ( typeof errorContext === "object" && errorContext !== null && - (errorContext as { buildFailure?: unknown }).buildFailure === true + (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true ) { return true; } diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index ec504279ab..d64c5c7002 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -14,13 +14,35 @@ * error at the point of failure instead of leaving later layers to infer it. */ +import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; + const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); +const TENANT_BUILD_FAILURE = Symbol.for("veryfront.module-loader.tenant-build-failure"); + +type TaggedError = Error & { + [BUILD_FAILURE]?: true; + [TENANT_BUILD_FAILURE]?: true; +}; + +const TENANT_BUILD_ERROR_SLUGS = new Set([ + "typescript-error", + "mdx-compile-error", + "ssg-generation-error", + "compilation-error", +]); -type TaggedError = Error & { [BUILD_FAILURE]?: true }; +function isExplicitTenantBuildFailure(error: Error): boolean { + const snapshot = snapshotVeryfrontError(error); + return snapshot?.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); +} /** Tag `error` as a build failure and return it. */ export function markBuildFailure(error: unknown): unknown { - if (error instanceof Error) (error as TaggedError)[BUILD_FAILURE] = true; + if (error instanceof Error) { + const tagged = error as TaggedError; + tagged[BUILD_FAILURE] = true; + if (isExplicitTenantBuildFailure(error)) tagged[TENANT_BUILD_FAILURE] = true; + } return error; } @@ -28,3 +50,8 @@ export function markBuildFailure(error: unknown): unknown { export function isBuildFailure(error: unknown): boolean { return error instanceof Error && (error as TaggedError)[BUILD_FAILURE] === true; } + +/** True only for a build failure explicitly classified as tenant source. */ +export function isTenantBuildFailure(error: unknown): boolean { + return error instanceof Error && (error as TaggedError)[TENANT_BUILD_FAILURE] === true; +} diff --git a/src/rendering/orchestrator/pipeline.behavior.test.ts b/src/rendering/orchestrator/pipeline.behavior.test.ts index 3687660638..e68ebccecd 100644 --- a/src/rendering/orchestrator/pipeline.behavior.test.ts +++ b/src/rendering/orchestrator/pipeline.behavior.test.ts @@ -4,7 +4,8 @@ import { afterEach, describe, it } from "#veryfront/testing/bdd.ts"; import { FakeTime } from "#std/testing/time"; import { RenderPipeline, type RenderPipelineConfig } from "./pipeline.ts"; import type { RenderOptions } from "./types.ts"; -import { markBuildFailure } from "./module-loader/build-failure.ts"; +import { isTenantBuildFailure, markBuildFailure } from "./module-loader/build-failure.ts"; +import { COMPILATION_ERROR, createError, toError } from "#veryfront/errors"; import { cachePageCss, getPageCssCacheKey } from "./css-cache.ts"; import { cacheCSSAsync, hashCSS } from "#veryfront/html/styles-builder/index.ts"; import { RELEASE_ASSET_MANIFEST_ENV_FLAG } from "#veryfront/release-assets/constants.ts"; @@ -700,12 +701,35 @@ describe("RenderPipeline behavior", () => { return context?.buildFailure; } + function tenantBuildFailureFlag(error: unknown): unknown { + const context = (error as { context?: { tenantBuildFailure?: unknown } }).context; + return context?.tenantBuildFailure; + } + it("reports a build failure as one", async () => { const error = await rejectLoad(pipelineWithFailingPageModule(() => { - throw markBuildFailure(new Error("Cannot import the static asset")); + throw markBuildFailure(COMPILATION_ERROR.create({ + detail: "Cannot import the static asset", + })); + })); + + assertEquals(buildFailureFlag(error), true); + assertEquals(tenantBuildFailureFlag(error), true); + }); + + it("keeps framework failures inside the transform phase distinct", async () => { + const frameworkError = markBuildFailure(toError(createError({ + type: "build", + message: "cache write failed", + }))); + assertEquals(isTenantBuildFailure(frameworkError), false); + + const error = await rejectLoad(pipelineWithFailingPageModule(() => { + throw frameworkError; })); assertEquals(buildFailureFlag(error), true); + assertEquals(tenantBuildFailureFlag(error), false); }); it("does not report a module-scope runtime throw as a build failure", async () => { @@ -714,6 +738,7 @@ describe("RenderPipeline behavior", () => { })); assertEquals(buildFailureFlag(error), false); + assertEquals(tenantBuildFailureFlag(error), false); }); }); diff --git a/src/rendering/orchestrator/pipeline.ts b/src/rendering/orchestrator/pipeline.ts index aef998165e..6277a77dc5 100644 --- a/src/rendering/orchestrator/pipeline.ts +++ b/src/rendering/orchestrator/pipeline.ts @@ -69,7 +69,7 @@ import { } from "#veryfront/html/styles-builder/tailwind-compiler.ts"; import { getReadyManifestForRender } from "#veryfront/release-assets/manifest-cache.ts"; import { createEsmCache, createModuleCache, loadModule } from "./module-loader/index.ts"; -import { isBuildFailure } from "./module-loader/build-failure.ts"; +import { isBuildFailure, isTenantBuildFailure } from "./module-loader/build-failure.ts"; import type { ModuleLoaderConfig } from "./module-loader/index.ts"; import { getCSSImports, @@ -396,7 +396,12 @@ export class RenderPipeline { ); const loaded: LoadedModule[] = []; - const criticalFailures: Array<{ path: string; error: string; buildFailure: boolean }> = []; + const criticalFailures: Array<{ + path: string; + error: string; + buildFailure: boolean; + tenantBuildFailure: boolean; + }> = []; for (const result of results) { if (result.mod && !result.error) { @@ -413,6 +418,7 @@ export class RenderPipeline { path: result.path, error: errorMessage, buildFailure: isBuildFailure(result.error), + tenantBuildFailure: isTenantBuildFailure(result.error), }); renderPageLog.error("Critical page module failed to load", { path: result.path, @@ -439,6 +445,9 @@ export class RenderPipeline { // one that compiled and threw at module scope is an application // error the project's own error page should present. buildFailure: criticalFailures.some((f) => f.buildFailure), + // Only explicit compiler/source classifications may affect + // observability severity. Infrastructure can fail in the same phase. + tenantBuildFailure: criticalFailures.some((f) => f.tenantBuildFailure), loadedCount: loaded.length, totalModules: modules.length, }, From 7c73e35ca6a733d28861cddbb6f51579b460a9bf Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 21:27:17 +0200 Subject: [PATCH 011/104] Materialize cached dynamic project imports Literal dynamic imports rewritten to /_vf_modules must be fetched and cached before the parent module is persisted, otherwise file-backed execution treats them as filesystem-root paths. The nested import pipeline now scans the existing dynamic import spans, carries their syntax shape through fetch results, and replaces only the quoted specifier for import() calls. Constraint: CodeRabbit thread PRRT_kwDOQaPiP86ZY__J reported escaped alias dynamic imports in cached MDX modules. Rejected: Change alias resolution to emit relative file-cache specifiers | the cache path is only known after nested materialization. Confidence: high Scope-risk: narrow Directive: Keep dynamic nested imports on the source-span scanner; do not rewrite computed import() arguments. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Tested: deno check src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/types.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Tested: deno lint src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/types.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Tested: deno task typecheck Not-tested: End-to-end browser execution before pre-push. --- .../module-fetcher/http-fetcher.ts | 20 ++--- .../module-fetcher/nested-imports.test.ts | 36 +++++++++ .../module-fetcher/nested-imports.ts | 76 ++++++++++++++++--- src/transforms/mdx/esm-module-loader/types.ts | 1 + 4 files changed, 112 insertions(+), 21 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index 2e8788fcf5..7a2ea3b24f 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -87,7 +87,7 @@ function isNameResolutionError(error: unknown): boolean { * RFC 6761 only *recommends* that resolvers map the `.localhost` tree to * loopback. macOS, systemd-resolved and CI honour it for arbitrary subdomains, * but a plain glibc NSS setup can resolve only the bare name and fail - * `.localhost` with EAI_AGAIN/ENOTFOUND — which would make this fallback + * `.localhost` with EAI_AGAIN/ENOTFOUND, which would make this fallback * unable to reach the dev server at all. * * Pinning the connection to 127.0.0.1 while keeping subdomain routing is not an @@ -96,8 +96,8 @@ function isNameResolutionError(error: unknown): boolean { * * The retry therefore carries the project in `x-project-slug`, which the dev * server reads inbound (see server/context/request-context.ts and - * server/runtime-handler/project-resolution.ts) and which fetch — unlike `Host` - * — is allowed to set. Without it a multi-project workspace would lose tenant + * server/runtime-handler/project-resolution.ts) and which fetch, unlike `Host`, + * is allowed to set. Without it a multi-project workspace would lose tenant * identity, because resolveDefaultProjectSlug() returns undefined there. */ async function fetchModuleWithLoopbackFallback( @@ -237,18 +237,20 @@ export async function fetchModuleViaHTTP( const { vfModules, relative } = findNestedImports(moduleCode); const allImports = [ - ...vfModules.map(({ original, path, start, end }) => ({ + ...vfModules.map(({ original, path, start, end, isDynamic }) => ({ original, path, start, end, + isDynamic, key: "nestedPath" as const, })), - ...relative.map(({ original, path, start, end }) => ({ + ...relative.map(({ original, path, start, end, isDynamic }) => ({ original, path, start, end, + isDynamic, key: "relativePath" as const, })), ]; @@ -256,9 +258,9 @@ export async function fetchModuleViaHTTP( const results = await parallelMap( allImports, - async ({ original, path, start, end, key }) => { + async ({ original, path, start, end, isDynamic, key }) => { const nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); - return { original, start, end, nestedFilePath, [key]: path }; + return { original, start, end, isDynamic, nestedFilePath, [key]: path }; }, { semaphore: new Semaphore(MAX_MDX_MODULE_TRANSFORM_CONCURRENCY), @@ -266,13 +268,13 @@ export async function fetchModuleViaHTTP( ); const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, nestedFilePath } of results) { + for (const { original, start, end, isDynamic, nestedFilePath } of results) { if (nestedFilePath) { replacements.push({ start, end, expected: original, - replacement: `from "file://${nestedFilePath}"`, + replacement: isDynamic ? `"file://${nestedFilePath}"` : `from "file://${nestedFilePath}"`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 32fab2753b..f3c2f3ac1d 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -75,6 +75,13 @@ import { bar } from "./local.js"; assertEquals(result.count > 0, true); }); + it("detects unresolved dynamic _vf_modules imports", () => { + const code = `export const load = () => import("/_vf_modules/components/Lazy.js");`; + const result = hasUnresolvedImports(code); + assertEquals(result.count, 1); + assertEquals(result.paths, ["/_vf_modules/components/Lazy.js"]); + }); + it("returns empty for normal resolved file:// imports", () => { const code = `import { foo } from "file:///home/user/.cache/veryfront-mdx-esm/proj/vfmod.mjs";`; @@ -164,6 +171,35 @@ import { bar } from "./local.js"; ); }); + it("materializes dynamic _vf_modules imports before caching the module", async () => { + const calls: Array<{ path: string; parent?: string }> = []; + const result = await resolveNestedModuleImports({ + moduleCode: [ + `export const load = () => import("/_vf_modules/components/Lazy.js");`, + `export const unchanged = () => import(path);`, + ].join("\n"), + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path, parent) => { + calls.push({ path, parent }); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + }); + + assertEquals(calls, [ + { path: "_vf_modules/components/Lazy.js", parent: "_vf_modules/pages/index.js" }, + ]); + assertEquals( + result, + [ + `export const load = () => import("file:///cache/_vf_modules__components__Lazy.js.mjs");`, + `export const unchanged = () => import(path);`, + ].join("\n"), + ); + }); + it("resolves admitted fan-out with bounded concurrency", async () => { const importCount = MAX_MDX_MODULE_TRANSFORM_CONCURRENCY + 4; const moduleCode = Array.from( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 0f9419b46b..1002d0ff74 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -8,6 +8,7 @@ import { LOG_PREFIX_MDX_LOADER } from "../constants.ts"; import type { NestedImportResult } from "../types.ts"; import { createStubModule } from "../utils/stub-module.ts"; import { + findDynamicImportSpans, findStaticImportFromSpans, replaceSourceSpans, type SourceSpanReplacement, @@ -33,11 +34,19 @@ function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { export function findNestedImports( moduleCode: string, ): { - vfModules: Array<{ original: string; path: string; start: number; end: number }>; - relative: Array<{ original: string; path: string; start: number; end: number }>; + vfModules: Array< + { original: string; path: string; start: number; end: number; isDynamic?: boolean } + >; + relative: Array< + { original: string; path: string; start: number; end: number; isDynamic?: boolean } + >; } { - const vfModules: Array<{ original: string; path: string; start: number; end: number }> = []; - const relative: Array<{ original: string; path: string; start: number; end: number }> = []; + const vfModules: Array< + { original: string; path: string; start: number; end: number; isDynamic?: boolean } + > = []; + const relative: Array< + { original: string; path: string; start: number; end: number; isDynamic?: boolean } + > = []; for ( const { original, path: rawPath, start, end } of findStaticImportFromSpans( @@ -55,6 +64,23 @@ export function findNestedImports( }); } + for ( + const { original, path: rawPath, start, end } of findDynamicImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ) { + // Strip file:// prefix and leading slashes to get clean _vf_modules/... path + vfModules.push({ + original, + path: rawPath.replace(/^(?:file:\/\/)?\/+/, ""), + start, + end, + isDynamic: true, + }); + } + for ( const { original, path, start, end } of findStaticImportFromSpans( moduleCode, @@ -70,6 +96,22 @@ export function findNestedImports( }); } + for ( + const { original, path, start, end } of findDynamicImportSpans( + moduleCode, + (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1], + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ) { + relative.push({ + original, + path, + start, + end, + isDynamic: true, + }); + } + return { vfModules, relative }; } @@ -77,11 +119,18 @@ export function findNestedImports( * Check for unresolved /_vf_modules/ imports. */ export function hasUnresolvedImports(moduleCode: string): { count: number; paths: string[] } { - const matches = findStaticImportFromSpans( - moduleCode, - matchUnresolvedVfModuleSpecifier, - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ); + const matches = [ + ...findStaticImportFromSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ), + ...findDynamicImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ), + ]; return { count: matches.length, paths: matches.map((match) => match.path).slice(0, 5), @@ -101,13 +150,15 @@ export async function processNestedImports( ): Promise { const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, nestedFilePath, nestedPath, relativePath } of results) { + for ( + const { original, start, end, isDynamic, nestedFilePath, nestedPath, relativePath } of results + ) { if (nestedFilePath) { replacements.push({ start, end, expected: original, - replacement: `from "file://${nestedFilePath}"`, + replacement: isDynamic ? `"file://${nestedFilePath}"` : `from "file://${nestedFilePath}"`, }); continue; } @@ -227,10 +278,11 @@ export async function resolveNestedModuleImports( const nestedResults: NestedImportResult[] = await parallelMap( allImports, - async ({ original, path, start, end, key }) => ({ + async ({ original, path, start, end, isDynamic, key }) => ({ original, start, end, + isDynamic, nestedFilePath: await input.fetchAndCacheModule( path, input.parentBasePath ?? input.normalizedPath, diff --git a/src/transforms/mdx/esm-module-loader/types.ts b/src/transforms/mdx/esm-module-loader/types.ts index 65237b32ce..d0b19b246f 100644 --- a/src/transforms/mdx/esm-module-loader/types.ts +++ b/src/transforms/mdx/esm-module-loader/types.ts @@ -56,6 +56,7 @@ export interface NestedImportResult { original: string; start: number; end: number; + isDynamic?: boolean; nestedFilePath: string | null; nestedPath?: string; relativePath?: string; From 6a383b41755ee0837151b9e771a2cc31e3463a58 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 21:41:03 +0200 Subject: [PATCH 012/104] Classify real MDX syntax failures at their source The MDX compiler previously rethrew tenant syntax failures as an ambiguous legacy build error, so the narrowed observability classifier correctly refused to downgrade them. Emit the registered MDX error shape directly and lock the production compiler path with an invalid-MDX regression. Constraint: Legacy type=build is shared by framework infrastructure and cannot safely imply tenant ownership. Rejected: Restore broad legacy build classification | cache, bundle, and capacity failures use the same shape. Confidence: high Scope-risk: narrow Directive: Tenant build ownership must be explicit at compiler/source seams. Tested: MDX compiler regression, application-error classifier, render pipeline behavior, Sentry policy, targeted format/lint/typecheck, pinned API docs. Not-tested: Full repository suite before commit; enforced by pre-push. --- .../mdx/compiler/mdx-compiler.test.ts | 22 ++++++++++++++++++- src/transforms/mdx/compiler/mdx-compiler.ts | 15 +++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 46a23095b0..8885509d88 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -1,7 +1,8 @@ import "#veryfront/schemas/_test-setup.ts"; import "./__tests__/content-processor-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertInstanceOf, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { VeryfrontError } from "#veryfront/errors"; import { compileMDXRuntime } from "./mdx-compiler.ts"; describe("transforms/mdx/compiler/mdx-compiler", () => { @@ -73,5 +74,24 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { ); assertEquals(typeof result.compiledCode, "string"); }); + + it("classifies tenant MDX syntax failures explicitly", async () => { + const error = await assertRejects( + () => + compileMDXRuntime( + "production", + "/project", + ""}`, - }), - ); + throw MDX_COMPILE_ERROR.create({ + detail: `MDX compilation error: ${ + error instanceof Error ? error.message : String(error) + } | file: ${filePath ?? ""}`, + }); } }, { From d43dd54e5b324ec847827ee4f319232897a4853a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:08:18 +0200 Subject: [PATCH 013/104] Keep late module rewrites stable under poisoned intrinsics Non-strict missing-module recovery reused the static import replacement even when the matched span came from a dynamic import, which produced invalid import(from "...") syntax. Extension normalization also captured String.replace but still let RegExp @@replace dispatch through a mutable prototype. The fix keeps dynamic replacements as quoted specifiers and calls the captured RegExp @@replace intrinsic directly. Constraint: PR review requested fixes for unresolved threads PRRT_kwDOQaPiP86ZZdRz and PRRT_kwDOQaPiP86ZZdR2 Rejected: Rewriting the dynamic import call wholesale | source spans intentionally target only the matched specifier Confidence: high Scope-risk: narrow Tested: Pinned Deno 2.7.7 focused nested-imports and url-builder regressions; pinned Deno fmt, lint, and deno check for touched files; pinned Deno generate:manifests:check reached stale pre-existing templates.ts check; git diff --check Not-tested: Full deno task verify after final scope cleanup because generate:manifests:check fails on stale src/build/production-build/templates.ts outside the requested kept file set --- .../import-rewriter/url-builder.test.ts | 23 ++++++++++++++++++ src/transforms/import-rewriter/url-builder.ts | 12 +++++----- .../module-fetcher/nested-imports.test.ts | 24 +++++++++++++++++++ .../module-fetcher/nested-imports.ts | 2 +- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/transforms/import-rewriter/url-builder.test.ts b/src/transforms/import-rewriter/url-builder.test.ts index 018164df9d..e4a3d1a63b 100644 --- a/src/transforms/import-rewriter/url-builder.test.ts +++ b/src/transforms/import-rewriter/url-builder.test.ts @@ -440,6 +440,29 @@ describe("transforms/import-rewriter/url-builder", () => { } }); + it("should use captured replace after RegExp Symbol.replace poisoning", () => { + const originalReplace = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.replace)!; + let poisonCalls = 0; + try { + Object.defineProperty(RegExp.prototype, Symbol.replace, { + ...originalReplace, + value() { + poisonCalls += 1; + throw new Error("poisoned RegExp.prototype[Symbol.replace]"); + }, + }); + + assertEquals(normalizeExtension("components/Card.tsx"), "components/Card.js"); + assertEquals( + normalizeExtension("components/Card.tsx", { removeExtension: true }), + "components/Card", + ); + assertEquals(poisonCalls, 0); + } finally { + Object.defineProperty(RegExp.prototype, Symbol.replace, originalReplace); + } + }); + it("should keep .js unchanged", () => { assertEquals(normalizeExtension("file.js"), "file.js"); }); diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index c3e59d1aff..baf96c7acd 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -34,14 +34,14 @@ type EsmShOptions = { const ObjectEntries = Object.entries; const ReflectApply = Reflect.apply; -const StringReplace = String.prototype.replace; +const RegExpSymbolReplace = RegExp.prototype[Symbol.replace]; -function stringReplace( +function regexReplace( value: string, - search: string | RegExp, + search: RegExp, replacement: string, ): string { - return ReflectApply(StringReplace, value, [search, replacement]) as string; + return ReflectApply(RegExpSymbolReplace, search, [value, replacement]) as string; } function buildEsmShParams(options?: EsmShOptions): string[] { @@ -462,8 +462,8 @@ export function buildVeryfrontModuleUrl(path: string): string { * Normalize file extension for JavaScript output. */ export function normalizeExtension(path: string, options?: { removeExtension?: boolean }): string { - if (options?.removeExtension) return stringReplace(path, /\.(tsx?|jsx|mdx)$/, ""); - return stringReplace(path, /\.(tsx?|jsx|mdx)$/, ".js"); + if (options?.removeExtension) return regexReplace(path, /\.(tsx?|jsx|mdx)$/, ""); + return regexReplace(path, /\.(tsx?|jsx|mdx)$/, ".js"); } /** diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index f3c2f3ac1d..10727dbc8d 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -1,6 +1,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; import { findNestedImports, hasUnresolvedImports, @@ -200,6 +201,29 @@ import { bar } from "./local.js"; ); }); + it("keeps dynamic import syntax when non-strict missing modules use stubs", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-stub-cache-" }); + + try { + const result = await resolveNestedModuleImports({ + moduleCode: `export const load = () => import("./Missing.js");`, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: false, + fetchAndCacheModule: () => Promise.resolve(null), + }); + + assertEquals(result.includes("import(from "), false); + assertEquals( + /^export const load = \(\) => import\("file:\/\/.*stub-[a-f0-9]+\.mjs"\);$/.test(result), + true, + ); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + it("resolves admitted fan-out with bounded concurrency", async () => { const importCount = MAX_MDX_MODULE_TRANSFORM_CONCURRENCY + 4; const moduleCode = Array.from( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 1002d0ff74..206b91063c 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -180,7 +180,7 @@ export async function processNestedImports( start, end, expected: original, - replacement: `from "file://${stubPath}"`, + replacement: isDynamic ? `"file://${stubPath}"` : `from "file://${stubPath}"`, }); } } From 2026cfd80b863ba8ea6c02941e629779dc8cf7b4 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:11:34 +0200 Subject: [PATCH 014/104] Keep bundled runtimes aligned with hardened extension normalization The previous source commit changed extension normalization to capture and apply RegExp @@replace directly. The generated production and RSC bundles also carry that runtime path, so they need to be committed separately from the hand-written fix after pinned generation confirmed exactness. Constraint: Generated artifacts must match Deno 2.7.7 output and stay limited to bundles affected by url-builder.ts Rejected: Dropping these generated diffs as incidental churn | pinned generate:manifests:check reports them as the exact current artifacts Confidence: high Scope-risk: narrow Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno task generate:manifests:check; git diff --check Not-tested: No additional behavioral tests after generated-only commit; behavior covered by previous focused tests and pre-push run --- src/build/production-build/templates.ts | 2 +- src/server/services/rsc/endpoints/rsc-bundles.generated.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/build/production-build/templates.ts b/src/build/production-build/templates.ts index fbeb8c0e2e..2738045253 100644 --- a/src/build/production-build/templates.ts +++ b/src/build/production-build/templates.ts @@ -14,4 +14,4 @@ export const CLIENT_ROUTER_BUNDLE: string | undefined = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar rendererLogger = logger;\nvar PREFETCH_MAX_SIZE_BYTES = 200 * 1024;\n\n// src/rendering/client/navigation-store.ts\nvar STORE_KEY = /* @__PURE__ */ Symbol.for("veryfront.navigation.store.v1");\nfunction getNavigationStore() {\n const holder = globalThis;\n const existing = holder[STORE_KEY];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? `${loc.pathname}${loc.search}${loc.hash}` : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n holder[STORE_KEY] = store;\n return store;\n}\n\n// src/rendering/client/router.ts\nimport ReactDOM from "react-dom/client";\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_ENTRIES = 128;\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar REACT_HEAD_ATTRIBUTE_NAMES = {\n charSet: "charset",\n className: "class",\n crossOrigin: "crossorigin",\n fetchPriority: "fetchpriority",\n htmlFor: "for",\n httpEquiv: "http-equiv",\n imageSizes: "imagesizes",\n imageSrcSet: "imagesrcset",\n noModule: "nomodule",\n referrerPolicy: "referrerpolicy"\n};\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar SUPPORTED_MANAGED_HEAD_TAGS = /* @__PURE__ */ new Set([\n "title",\n "meta",\n "link",\n "style",\n "script"\n]);\nvar HEAD_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_.:-]*$/;\nvar MAX_HEAD_PROP_ENTRIES = 128;\nvar MAX_HEAD_ATTRIBUTE_NAME_BYTES = 256;\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\nfunction normalizeManagedHeadString(value) {\n return value.replace(/\\r\\n?/g, "\\n");\n}\nfunction inspectHeadProps(value) {\n if (typeof value !== "object" || value === null || Array.isArray(value)) return null;\n let prototype;\n let keys;\n try {\n prototype = Object.getPrototypeOf(value);\n keys = Reflect.ownKeys(value);\n } catch {\n return null;\n }\n if (prototype !== Object.prototype && prototype !== null) return null;\n const inspected = /* @__PURE__ */ new Map();\n let entries = 0;\n for (const key of keys) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(value, key);\n } catch {\n return null;\n }\n if (!descriptor) return null;\n if (!descriptor.enumerable) continue;\n if (typeof key !== "string" || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return null;\n }\n entries++;\n if (entries > MAX_HEAD_PROP_ENTRIES) return null;\n inspected.set(key, descriptor.value);\n }\n return inspected;\n}\nfunction normalizeContentPrimitive(value) {\n if (value === null || value === void 0 || typeof value === "boolean") return void 0;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n return null;\n }\n const content = normalizeManagedHeadString(String(value));\n return headTextEncoder.encode(content).byteLength <= MAX_HEAD_CONTENT_BYTES ? content : null;\n}\nfunction normalizeManagedHeadAttributesFromProps(tagName, props, ambientNonce, excludedKeys = /* @__PURE__ */ new Set()) {\n const attributeMap = /* @__PURE__ */ new Map();\n for (const [key, value] of props) {\n if (key === "children" || key === "dangerouslySetInnerHTML" || excludedKeys.has(key)) {\n continue;\n }\n if (/^on/i.test(key) || typeof value === "function" || typeof value === "symbol" || typeof value === "object") {\n continue;\n }\n const name = (REACT_HEAD_ATTRIBUTE_NAMES[key] ?? key).toLowerCase();\n if (isHeadFrameworkAttribute(name) || !HEAD_ATTRIBUTE_NAME_PATTERN.test(name) || headTextEncoder.encode(name).byteLength > MAX_HEAD_ATTRIBUTE_NAME_BYTES) {\n continue;\n }\n if (BOOLEAN_HEAD_ATTRIBUTES.has(name)) {\n if (value !== false && value !== void 0) attributeMap.set(name, "");\n continue;\n }\n if (typeof value === "boolean") {\n if (name.startsWith("data-") || name.startsWith("aria-")) {\n attributeMap.set(name, String(value));\n }\n continue;\n }\n if (value === void 0) continue;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n continue;\n }\n const normalizedValue = normalizeManagedHeadString(String(value));\n if (headTextEncoder.encode(normalizedValue).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) {\n return null;\n }\n attributeMap.set(name, normalizedValue);\n }\n if (tagName === "script" || tagName === "style") {\n attributeMap.delete("nonce");\n }\n const acceptsAmbientNonce = tagName === "style" || tagName === "script" && !attributeMap.has("src");\n if (acceptsAmbientNonce && ambientNonce) {\n const nonce = normalizeManagedHeadString(ambientNonce);\n if (headTextEncoder.encode(nonce).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) return null;\n attributeMap.set("nonce", nonce);\n }\n if (tagName === "link" && attributeMap.get("rel")?.trim().toLowerCase() === "preload" && attributeMap.get("as")?.trim().toLowerCase() === "font" && !attributeMap.has("crossorigin")) {\n attributeMap.set("crossorigin", "anonymous");\n }\n if (attributeMap.size > MAX_HEAD_PROP_ENTRIES) return null;\n let totalBytes = 0;\n for (const [name, value] of attributeMap) {\n totalBytes += headTextEncoder.encode(name).byteLength + headTextEncoder.encode(value).byteLength;\n if (totalBytes > MAX_HEAD_ATTRIBUTE_BYTES) return null;\n }\n return [...attributeMap.entries()].sort(([left], [right]) => left.localeCompare(right));\n}\nfunction singletonKey(tagName, attributes) {\n if (tagName === "title") return "title";\n const record = Object.fromEntries(attributes);\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(record);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(record);\n return void 0;\n}\nfunction scriptKeys(tagName, attributes) {\n if (tagName !== "script") return [];\n const keys = [];\n const id = attributes.get("id");\n const src = attributes.get("src");\n if (id) keys.push(`script:id:${id}`);\n if (src) keys.push(`script:src:${src}`);\n return keys;\n}\nfunction declaresDocumentEncoding(attributes) {\n return attributes.has("charset") || attributes.get("http-equiv")?.trim().toLowerCase() === "content-type";\n}\nfunction createManagedHeadDescriptor(tagName, attributes, content, contentMode) {\n const attributeMap = new Map(attributes);\n return {\n tagName,\n attributes,\n ...content !== void 0 && { content },\n contentMode,\n signature: JSON.stringify([\n tagName,\n attributes,\n contentMode,\n content ?? null\n ]),\n singletonKey: singletonKey(tagName, attributeMap),\n scriptKeys: scriptKeys(tagName, attributeMap)\n };\n}\nfunction descriptorFromManagedHeadRecord(rawTagName, record, options = {}) {\n const tagName = rawTagName.toLowerCase();\n if (!SUPPORTED_MANAGED_HEAD_TAGS.has(tagName)) return null;\n const inspected = inspectHeadProps(record);\n if (!inspected) return null;\n const excludedKeys = options.contentProperty ? /* @__PURE__ */ new Set([options.contentProperty]) : /* @__PURE__ */ new Set();\n const attributes = normalizeManagedHeadAttributesFromProps(\n tagName,\n inspected,\n options.ambientNonce,\n excludedKeys\n );\n if (!attributes) return null;\n const attributeMap = new Map(attributes);\n if (tagName === "meta" && declaresDocumentEncoding(attributeMap)) return null;\n if ((tagName === "meta" || tagName === "link") && attributes.length === 0) return null;\n let content;\n if (options.contentProperty) {\n const normalized = normalizeContentPrimitive(inspected.get(options.contentProperty));\n if (normalized === null) return null;\n content = normalized;\n }\n return createManagedHeadDescriptor(tagName, attributes, content, "text");\n}\nfunction headScriptKeysIntersect(left, right) {\n if (left.length === 0 || right.length === 0) return false;\n const rightKeys = new Set(right);\n return left.some((key) => rightKeys.has(key));\n}\nfunction aggregateManagedHeadDescriptors(descriptors) {\n const aggregated = [];\n const singletonIndexes = /* @__PURE__ */ new Map();\n const scriptKeysSeen = /* @__PURE__ */ new Set();\n for (const descriptor of descriptors) {\n if (descriptor.singletonKey) {\n const index = singletonIndexes.get(descriptor.singletonKey);\n if (index !== void 0) {\n aggregated[index] = descriptor;\n continue;\n }\n singletonIndexes.set(descriptor.singletonKey, aggregated.length);\n } else if (descriptor.scriptKeys.length > 0) {\n if (descriptor.scriptKeys.some((key) => scriptKeysSeen.has(key))) continue;\n for (const key of descriptor.scriptKeys) scriptKeysSeen.add(key);\n }\n aggregated.push(descriptor);\n }\n return aggregated;\n}\nfunction managedHeadDescriptorBytes(descriptor) {\n let bytes = headTextEncoder.encode(descriptor.tagName).byteLength;\n for (const [name, value] of descriptor.attributes) {\n bytes += headTextEncoder.encode(name).byteLength;\n bytes += headTextEncoder.encode(value).byteLength;\n }\n if (descriptor.content !== void 0) {\n bytes += headTextEncoder.encode(descriptor.content).byteLength;\n }\n return bytes;\n}\nfunction assertManagedHeadDescriptorBudget(descriptors) {\n if (descriptors.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_ENTRIES}-entry request limit`\n );\n }\n let bytes = 0;\n for (const descriptor of descriptors) {\n bytes += managedHeadDescriptorBytes(descriptor);\n if (bytes > MAX_MANAGED_HEAD_BYTES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_BYTES}-byte request limit`\n );\n }\n }\n}\nfunction managedHeadDescriptorToTransportEntry(descriptor) {\n const attributes = descriptor.attributes.filter(([name]) => name !== "nonce");\n return {\n tagName: descriptor.tagName,\n attributes: attributes.map(([name, value]) => [name, value]),\n ...descriptor.content !== void 0 && { content: descriptor.content }\n };\n}\nfunction ownTransportValue(record, key) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n } catch {\n return void 0;\n }\n if (!descriptor || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return void 0;\n }\n return descriptor.value;\n}\nfunction descriptorFromManagedHeadTransportEntry(entry, ambientNonce) {\n if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n let prototype;\n try {\n prototype = Object.getPrototypeOf(entry);\n } catch {\n throw new TypeError("Managed-head transport entry cannot be inspected");\n }\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n const tagName = ownTransportValue(entry, "tagName");\n const rawAttributes = ownTransportValue(entry, "attributes");\n const content = ownTransportValue(entry, "content");\n if (typeof tagName !== "string" || tagName !== tagName.toLowerCase() || !Array.isArray(rawAttributes)) {\n throw new TypeError("Managed-head transport entry is not canonical");\n }\n if (rawAttributes.length > MAX_HEAD_PROP_ENTRIES) {\n throw new TypeError("Managed-head transport entry exceeds the attribute limit");\n }\n if (content !== void 0 && typeof content !== "string") {\n throw new TypeError("Managed-head transport content must be a string");\n }\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (!supportsText && content !== void 0) {\n throw new TypeError("Managed-head transport content is invalid for this tag");\n }\n const record = /* @__PURE__ */ Object.create(null);\n const inputAttributes = [];\n const names = /* @__PURE__ */ new Set();\n for (let index = 0; index < rawAttributes.length; index += 1) {\n const pair = ownTransportValue(rawAttributes, String(index));\n if (!Array.isArray(pair) || pair.length !== 2) {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const name = ownTransportValue(pair, "0");\n const value = ownTransportValue(pair, "1");\n if (typeof name !== "string" || typeof value !== "string") {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const normalizedName = name.toLowerCase();\n if (name !== normalizedName || normalizedName === "nonce" || names.has(normalizedName)) {\n throw new TypeError("Managed-head transport attributes are not canonical");\n }\n names.add(normalizedName);\n inputAttributes.push([normalizedName, value]);\n Object.defineProperty(record, normalizedName, {\n enumerable: true,\n value\n });\n }\n if (content !== void 0) {\n Object.defineProperty(record, "__veryfront_transport_content", {\n enumerable: true,\n value: content\n });\n }\n const descriptor = descriptorFromManagedHeadRecord(tagName, record, {\n ...supportsText && { contentProperty: "__veryfront_transport_content" },\n ...(tagName === "script" || tagName === "style") && ambientNonce ? { ambientNonce } : {}\n });\n const normalizedInput = inputAttributes.sort(([left], [right]) => left.localeCompare(right));\n const normalizedOutput = descriptor?.attributes.filter(([name]) => name !== "nonce");\n if (!descriptor || JSON.stringify(normalizedOutput) !== JSON.stringify(normalizedInput) || supportsText && (descriptor.content ?? "") !== (content ?? "")) {\n throw new TypeError("Managed-head transport entry failed validation");\n }\n return descriptor;\n}\nvar BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";\nfunction decodeBase64Url(value) {\n if (value.length % 4 === 1 || !/^[A-Za-z0-9_-]*$/.test(value)) {\n throw new TypeError("Managed-head payload is not valid base64url");\n }\n const estimatedBytes = Math.floor(value.length * 3 / 4);\n if (estimatedBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n const bytes = new Uint8Array(estimatedBytes);\n let outputIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const character of value) {\n const decoded = BASE64URL_ALPHABET.indexOf(character);\n if (decoded < 0) throw new TypeError("Managed-head payload is not valid base64url");\n buffer = buffer << 6 | decoded;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes[outputIndex++] = buffer >> bits & 255;\n buffer &= bits === 0 ? 0 : (1 << bits) - 1;\n }\n }\n if (bits > 0 && buffer !== 0) {\n throw new TypeError("Managed-head payload has non-canonical trailing bits");\n }\n return bytes.subarray(0, outputIndex);\n}\nfunction inspectManagedHeadPayload(payload, ambientNonce) {\n if (typeof payload !== "string") throw new TypeError("Managed-head payload must be a string");\n const payloadBytes = headTextEncoder.encode(payload).byteLength;\n if (payloadBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n let decoded;\n try {\n decoded = new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(payload));\n } catch (error) {\n if (error instanceof TypeError) throw error;\n throw new TypeError("Managed-head payload is not valid UTF-8", { cause: error });\n }\n let entries;\n try {\n entries = JSON.parse(decoded);\n } catch (error) {\n throw new TypeError("Managed-head payload is not valid JSON", { cause: error });\n }\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Managed-head payload exceeds the entry limit");\n }\n const rawDescriptors = entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, ambientNonce)\n );\n assertManagedHeadDescriptorBudget(rawDescriptors);\n return {\n descriptors: aggregateManagedHeadDescriptors(rawDescriptors),\n entryCount: rawDescriptors.length,\n descriptorBytes: rawDescriptors.reduce(\n (total, descriptor) => total + managedHeadDescriptorBytes(descriptor),\n 0\n ),\n payloadBytes\n };\n}\nfunction deserializeManagedHeadPayload(payload, ambientNonce) {\n return inspectManagedHeadPayload(payload, ambientNonce).descriptors;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction getManagedHeadNonce(targetDocument) {\n if (typeof targetDocument.querySelector !== "function") return void 0;\n const element = targetDocument.querySelector(\n "script[nonce], style[nonce], link[nonce]"\n );\n if (!element) return void 0;\n const nonce = element.nonce || element.getAttribute("nonce") || "";\n return nonce || void 0;\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey2 = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey2 !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey2);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nvar ROUTE_HEAD_CONTENT_PROPERTY = "__veryfront_route_head_content";\nfunction descriptorFromHeadElement(element) {\n const record = /* @__PURE__ */ Object.create(null);\n for (const { name, value } of element.attributes) {\n if (!isHeadFrameworkAttribute(name)) record[name] = value;\n }\n const tagName = element.tagName.toLowerCase();\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (supportsText) record[ROUTE_HEAD_CONTENT_PROPERTY] = element.textContent ?? "";\n return descriptorFromManagedHeadRecord(\n tagName,\n record,\n supportsText ? { contentProperty: ROUTE_HEAD_CONTENT_PROPERTY } : void 0\n );\n}\nfunction writeRouteDescriptor(element, descriptor) {\n for (const attribute of [...element.attributes]) element.removeAttribute(attribute.name);\n for (const [name, value] of descriptor.attributes) element.setAttribute(name, value);\n element.textContent = descriptor.content ?? "";\n element.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n element.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n}\nfunction prepareClientRouteHeadEntries(entries, targetDocument = document) {\n if (entries === void 0) return [];\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Route head payload exceeds the entry limit");\n }\n const descriptors = aggregateManagedHeadDescriptors(\n entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, getManagedHeadNonce(targetDocument))\n )\n );\n assertManagedHeadDescriptorBudget(descriptors);\n return descriptors;\n}\nfunction applyPreparedClientRouteHeadDescriptors(descriptors, targetDocument = document) {\n for (const descriptor of descriptors) {\n const described = [...targetDocument.head.children].flatMap((element2) => {\n const current = descriptorFromHeadElement(element2);\n return current ? [{ element: element2, descriptor: current }] : [];\n });\n if (descriptor.singletonKey) {\n const matches = described.filter(\n ({ descriptor: current }) => current.singletonKey === descriptor.singletonKey\n );\n const directive = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1"\n );\n if (directive) {\n continue;\n }\n const reusable = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element2.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true"\n );\n if (reusable) {\n writeRouteDescriptor(reusable.element, descriptor);\n continue;\n }\n }\n if (described.some(\n ({ descriptor: current }) => current.signature === descriptor.signature || headScriptKeysIntersect(current.scriptKeys, descriptor.scriptKeys)\n )) {\n continue;\n }\n const element = targetDocument.createElement(descriptor.tagName);\n writeRouteDescriptor(element, descriptor);\n targetDocument.head.appendChild(element);\n }\n}\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/routing/client/dom-utils.ts\nvar logger2 = rendererLogger.component("veryfront");\nfunction isInternalLink(target) {\n const href = target.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return false;\n const linkTarget = target.getAttribute("target");\n if (linkTarget === "_blank" || target.hasAttribute("download")) return false;\n return true;\n}\nfunction findAnchorElement(element) {\n let current = element;\n while (current && current.tagName !== "A") {\n current = current.parentElement;\n }\n return current instanceof HTMLAnchorElement ? current : null;\n}\nfunction applyHeadDirectives(container) {\n const targetDocument = container.ownerDocument ?? document;\n const nodes = [...container.querySelectorAll(\'[data-veryfront-head="1"], vf-head\')].filter(\n (node) => typeof node.getAttribute !== "function" || node.getAttribute(HEAD_REACT_OWNER_ATTRIBUTE) !== "1"\n );\n if (!nodes.length) return;\n retireClientHeadOwnership(targetDocument);\n cleanManagedHeadTags(targetDocument);\n for (const wrapper of nodes) {\n const TemplateElement = targetDocument.defaultView?.HTMLTemplateElement ?? globalThis.HTMLTemplateElement;\n const contentSource = TemplateElement && wrapper instanceof TemplateElement ? wrapper.content : wrapper;\n processHeadWrapper(contentSource, targetDocument);\n wrapper.parentElement?.removeChild(wrapper);\n }\n}\nfunction cleanManagedHeadTags(targetDocument) {\n for (const element of targetDocument.head.querySelectorAll(\n `[${HEAD_LEGACY_MANAGED_ATTRIBUTE}="1"]`\n )) {\n element.parentElement?.removeChild(element);\n }\n}\nfunction processHeadWrapper(wrapper, targetDocument) {\n const ElementConstructor = targetDocument.defaultView?.Element ?? globalThis.Element;\n const activeNonce = getManagedHeadNonce(targetDocument);\n for (const node of wrapper.childNodes) {\n if (!ElementConstructor || !(node instanceof ElementConstructor)) continue;\n const tagName = node.tagName.toLowerCase();\n if (headSingletonKey(node) === "meta:charset") continue;\n const clone = targetDocument.createElement(tagName);\n for (const { name, value } of node.attributes) {\n if (name.toLowerCase() !== "nonce") clone.setAttribute(name, value);\n }\n if (activeNonce && (tagName === "script" || tagName === "style" || tagName === "link")) {\n clone.setAttribute("nonce", activeNonce);\n }\n if (node.textContent && !clone.hasAttribute("src")) {\n clone.textContent = node.textContent;\n }\n replaceExistingHeadSingleton(targetDocument, clone);\n clone.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n clone.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(clone);\n }\n}\nfunction headSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n if (tagName !== "meta" && tagName !== "link") return void 0;\n const attributes = /* @__PURE__ */ Object.create(null);\n if (!element.attributes) return void 0;\n for (const { name, value } of element.attributes) attributes[name.toLowerCase()] = value;\n if (tagName === "meta" && attributes["http-equiv"]?.trim().toLowerCase() === "content-type") {\n return "meta:charset";\n }\n return tagName === "meta" ? headMetaSingletonKeyFromRecord(attributes) : headLinkSingletonKeyFromRecord(attributes);\n}\nfunction replaceExistingHeadSingleton(targetDocument, replacement) {\n const singletonKey2 = headSingletonKey(replacement);\n if (!singletonKey2 || singletonKey2 === "meta:charset") return;\n for (const existing of [...targetDocument.head?.children ?? []]) {\n if (headSingletonKey(existing) === singletonKey2) existing.remove();\n }\n}\nfunction manageFocus(container) {\n try {\n const focusElement = container.querySelector("[data-router-focus]") || container.querySelector("main") || container.querySelector("h1");\n focusElement?.focus?.({ preventScroll: true });\n } catch (error) {\n logger2.warn("focus management failed", error);\n }\n}\nfunction extractPageDataFromScript() {\n const pageDataScript = document.querySelector("script[data-veryfront-page]");\n if (!pageDataScript) return null;\n try {\n const content = pageDataScript.textContent;\n if (!content) {\n logger2.warn("Page data script has no content");\n return {};\n }\n return JSON.parse(content);\n } catch (error) {\n logger2.error("Failed to parse page data:", error);\n return null;\n }\n}\nfunction snapshotClientRouteHead(targetDocument = document) {\n const hydrationDataScript = findServerHydrationDataElement(targetDocument);\n if (!hydrationDataScript?.textContent) return [];\n try {\n const hydrationData = JSON.parse(hydrationDataScript.textContent);\n if (typeof hydrationData.managedHeadPayload !== "string") return [];\n const descriptors = deserializeManagedHeadPayload(\n hydrationData.managedHeadPayload\n );\n const aggregated = aggregateManagedHeadDescriptors(descriptors);\n assertManagedHeadDescriptorBudget(aggregated);\n return aggregated.map(managedHeadDescriptorToTransportEntry);\n } catch {\n return [];\n }\n}\nfunction routeRequiresDocumentNavigation(data) {\n return Boolean(\n data.requiresFullDocumentNavigation || data.managedHead?.some((entry) => entry.tagName === "script") || typeof data.html === "string" && / entry.tagName === "script") || typeof root.querySelector === "function" && root.querySelector("script")\n ) {\n pageData = { ...pageData, requiresFullDocumentNavigation: true };\n }\n return { content, pageData, managedHead, dependencyPinningCacheKey };\n}\n\n// src/rendering/client/browser-stubs/config.ts\nvar DEFAULT_PREFETCH_DELAY_MS = 100;\nvar PAGE_TRANSITION_DELAY_MS = 150;\n\n// src/routing/client/navigation-handlers.ts\nvar logger3 = rendererLogger.component("veryfront");\nvar MAX_SCROLL_POSITIONS = 100;\nvar NavigationHandlers = class {\n constructor(prefetchDelay = DEFAULT_PREFETCH_DELAY_MS, prefetchOptions = {}) {\n __publicField(this, "prefetchQueue", /* @__PURE__ */ new Set());\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "scrollPositions", /* @__PURE__ */ new Map());\n __publicField(this, "isPopStateNav", false);\n __publicField(this, "prefetchDelay");\n __publicField(this, "prefetchOptions");\n this.prefetchDelay = prefetchDelay;\n this.prefetchOptions = prefetchOptions;\n }\n createClickHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n const anchor = findAnchorElement(event.target);\n if (!anchor || !isInternalLink(anchor)) return;\n const href = anchor.getAttribute("href");\n if (!href) return;\n event.preventDefault();\n callbacks.onNavigate(href);\n };\n }\n createPopStateHandler(callbacks) {\n return (_event) => {\n this.isPopStateNav = true;\n const { pathname, search, hash } = globalThis.location;\n callbacks.onNavigate(`${pathname}${search}${hash}`);\n };\n }\n createMouseOverHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.tagName !== "A") return;\n const href = event.target.getAttribute("href");\n if (!href || href.startsWith("http") || href.startsWith("#")) return;\n if (!this.shouldPrefetchOnHover(event.target)) return;\n if (this.prefetchQueue.has(href)) return;\n this.prefetchQueue.add(href);\n const timeoutId = setTimeout(() => {\n callbacks.onPrefetch(href);\n this.prefetchQueue.delete(href);\n this.pendingTimeouts.delete(href);\n }, this.prefetchDelay);\n this.pendingTimeouts.set(href, timeoutId);\n };\n }\n shouldPrefetchOnHover(target) {\n const prefetchAttribute = target.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n if (prefetchAttribute === "true") return true;\n return Boolean(this.prefetchOptions.hover);\n }\n saveScrollPosition(path) {\n try {\n if (this.scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = this.scrollPositions.keys().next().value;\n if (oldest) this.scrollPositions.delete(oldest);\n }\n const scrollY = globalThis.scrollY;\n if (typeof scrollY !== "number") {\n logger3.debug("No valid scrollY value available");\n this.scrollPositions.set(path, 0);\n return;\n }\n this.scrollPositions.set(path, scrollY);\n } catch (error) {\n logger3.warn("failed to record scroll position", error);\n }\n }\n getScrollPosition(path) {\n const position = this.scrollPositions.get(path);\n if (position === void 0) {\n logger3.debug(`No scroll position stored for ${path}`);\n return 0;\n }\n return position;\n }\n isPopState() {\n return this.isPopStateNav;\n }\n clearPopStateFlag() {\n this.isPopStateNav = false;\n }\n clear() {\n for (const timeoutId of this.pendingTimeouts.values()) clearTimeout(timeoutId);\n this.pendingTimeouts.clear();\n this.prefetchQueue.clear();\n this.scrollPositions.clear();\n this.isPopStateNav = false;\n }\n};\n\n// src/rendering/client/browser-stubs/error-registry.ts\nfunction createBrowserError(name, fallbackMessage) {\n return {\n create(options = {}) {\n const error = new Error(options.detail ?? fallbackMessage);\n error.name = name;\n Object.assign(error, {\n status: options.status,\n context: options.context\n });\n return error;\n }\n };\n}\nvar NETWORK_ERROR = createBrowserError("NetworkError", "Network request failed");\nvar SECURITY_VIOLATION = createBrowserError("SecurityViolation", "Security violation");\n\n// src/html/html-detection.ts\nfunction isFullHTMLDocument(content) {\n const trimmed = content.trim().toLowerCase();\n return trimmed.startsWith("");\n}\n\n// src/routing/client/page-loader.ts\nvar logger4 = rendererLogger.component("veryfront");\nvar MAX_CACHE_SIZE = 50;\nvar DEPENDENCY_PINNING_RESPONSE_HEADER = "x-veryfront-dependency-pins";\nfunction reloadBrowserDocument(url) {\n if (typeof globalThis.location !== "undefined") {\n globalThis.location.assign(url);\n }\n}\nfunction readDependencyPinningCacheKey(doc) {\n if (!doc) return "off";\n try {\n const hydrationDataElement = findServerHydrationDataElement(doc);\n if (!hydrationDataElement?.textContent) return "off";\n const hydrationData = JSON.parse(hydrationDataElement.textContent);\n return typeof hydrationData.dependencyPinningCacheKey === "string" && hydrationData.dependencyPinningCacheKey.startsWith("on:") ? hydrationData.dependencyPinningCacheKey : "off";\n } catch (error) {\n logger4.debug("Failed to read dependency snapshot from hydration data:", error);\n return "off";\n }\n}\nvar PageLoader = class {\n constructor(doc = typeof document === "undefined" ? void 0 : document, reloadDocument = reloadBrowserDocument) {\n __publicField(this, "cache", /* @__PURE__ */ new Map());\n __publicField(this, "spaCache", /* @__PURE__ */ new Map());\n __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());\n __publicField(this, "pendingSpaRequests", /* @__PURE__ */ new Map());\n /**\n * A loader belongs to the dependency snapshot of the document that created it.\n * Keeping this immutable also prevents cached or in-flight route data from\n * crossing snapshot boundaries if the hydration element is later replaced.\n */\n __publicField(this, "dependencyPinningCacheKey");\n __publicField(this, "reloadDocument");\n __publicField(this, "snapshotRecoveryStarted", false);\n this.dependencyPinningCacheKey = readDependencyPinningCacheKey(doc);\n this.reloadDocument = reloadDocument;\n }\n evictIfFull(map) {\n if (map.size < MAX_CACHE_SIZE) return;\n const oldest = map.keys().next().value;\n if (oldest) map.delete(oldest);\n }\n getCached(path) {\n return this.cache.get(this.snapshotScopedPath(path));\n }\n isCached(path) {\n return this.cache.has(this.snapshotScopedPath(path));\n }\n setCache(path, data) {\n this.evictIfFull(this.cache);\n this.cache.set(this.snapshotScopedPath(path), data);\n }\n clearCache() {\n this.cache.clear();\n this.spaCache.clear();\n this.pendingRequests.clear();\n this.pendingSpaRequests.clear();\n }\n getSpaCached(path) {\n return this.spaCache.get(this.snapshotScopedPath(path));\n }\n isSpaDataCached(path) {\n return this.spaCache.has(this.snapshotScopedPath(path));\n }\n setSpaCache(path, data) {\n this.evictIfFull(this.spaCache);\n this.spaCache.set(this.snapshotScopedPath(path), data);\n }\n async fetchPageData(path, reloadOnSnapshotFailure = true) {\n try {\n return await this.tryFetchJSON(path) ?? await this.fetchAndParseHTML(path);\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n async tryFetchJSON(path) {\n let response;\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const dataPath = navigationUrl.pathname === "/" ? "/index" : navigationUrl.pathname;\n const endpoint = `/_veryfront/data${dataPath}.json${navigationUrl.search}`;\n response = await fetch(endpoint, {\n headers: this.navigationHeaders("client")\n });\n } catch (error) {\n logger4.debug(`JSON fetch failed for ${path}, falling back to HTML:`, error);\n return null;\n }\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) return null;\n let data;\n try {\n data = await response.json();\n } catch (error) {\n logger4.debug(`JSON response was invalid for ${path}, falling back to HTML:`, error);\n return null;\n }\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "route data"\n );\n if (typeof data.html === "string" && isFullHTMLDocument(data.html)) {\n const parsed = parsePageDataFromHTML(data.html);\n this.assertDependencySnapshot(\n parsed.dependencyPinningCacheKey,\n path,\n "route data HTML body"\n );\n return {\n ...parsed.pageData,\n ...data,\n html: parsed.content,\n managedHead: parsed.managedHead\n };\n }\n return routeRequiresDocumentNavigation(data) ? { ...data, requiresFullDocumentNavigation: true } : data;\n }\n async fetchAndParseHTML(path) {\n const response = await fetch(path, {\n headers: this.navigationHeaders("client")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch ${path}`,\n status: response.status,\n context: { path }\n });\n }\n this.assertDependencySnapshot(\n response.headers.get(DEPENDENCY_PINNING_RESPONSE_HEADER),\n path,\n "HTML response"\n );\n const html = await response.text();\n const {\n content,\n pageData,\n managedHead,\n dependencyPinningCacheKey\n } = parsePageDataFromHTML(html);\n this.assertDependencySnapshot(\n dependencyPinningCacheKey,\n path,\n "HTML body"\n );\n return { ...pageData, html: content, managedHead };\n }\n loadPage(path) {\n return this.loadPageWithSnapshotRecovery(path, true);\n }\n loadPageWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getCached(path);\n if (cachedData) {\n logger4.debug(`Loading ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingRequests, async () => {\n const data = await this.fetchPageData(path, false);\n this.setCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetch(path) {\n if (this.isCached(path)) return;\n logger4.debug(`Prefetching ${path}`);\n try {\n await this.loadPageWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n async fetchSpaPageData(path, reloadOnSnapshotFailure = true) {\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const normalizedPath = navigationUrl.pathname === "/" ? "index" : navigationUrl.pathname.replace(/^\\//, "");\n const endpoint = `/_veryfront/page-data/${normalizedPath}.json${navigationUrl.search}`;\n logger4.debug(`Fetching SPA page data from ${endpoint}`);\n const response = await fetch(endpoint, {\n headers: this.navigationHeaders("spa")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for SPA page data ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch SPA page data for ${path}`,\n status: response.status,\n context: { path }\n });\n }\n const data = await response.json();\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "SPA page data"\n );\n return data;\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n loadSpaPageData(path) {\n return this.loadSpaPageDataWithSnapshotRecovery(path, true);\n }\n loadSpaPageDataWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getSpaCached(path);\n if (cachedData) {\n logger4.debug(`Loading SPA data for ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingSpaRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending SPA request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending SPA request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingSpaRequests, async () => {\n const data = await this.fetchSpaPageData(path, false);\n this.setSpaCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetchSpaPageData(path) {\n if (this.isSpaDataCached(path)) return;\n logger4.debug(`Prefetching SPA page data for ${path}`);\n try {\n await this.loadSpaPageDataWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch SPA data for ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n createPendingRequest(path, pendingMap, fetcher) {\n const request = (async () => {\n try {\n return await fetcher();\n } finally {\n pendingMap.delete(path);\n }\n })();\n pendingMap.set(path, request);\n return request;\n }\n snapshotScopedPath(path) {\n return this.dependencyPinningCacheKey.startsWith("on:") ? `${this.dependencyPinningCacheKey}\\0${path}` : path;\n }\n navigationHeaders(type) {\n return {\n "X-Veryfront-Navigation": type,\n ...this.dependencyPinningCacheKey.startsWith("on:") ? {\n [DEPENDENCY_PINNING_RESPONSE_HEADER]: this.dependencyPinningCacheKey\n } : {}\n };\n }\n assertDependencySnapshot(actualCacheKey, path, source) {\n const expectedCacheKey = this.dependencyPinningCacheKey.startsWith("on:") ? this.dependencyPinningCacheKey : void 0;\n const normalizedActualCacheKey = typeof actualCacheKey === "string" ? actualCacheKey : void 0;\n const matches = expectedCacheKey ? normalizedActualCacheKey === expectedCacheKey : normalizedActualCacheKey === void 0 || normalizedActualCacheKey === "off";\n if (matches) return;\n this.failDependencySnapshot(\n path,\n `Dependency snapshot mismatch in ${source} for ${path}`\n );\n }\n failDependencySnapshot(path, detail) {\n throw NETWORK_ERROR.create({\n detail,\n status: 409,\n context: { path }\n });\n }\n withSnapshotRecovery(promise, path, reloadOnSnapshotFailure) {\n return promise.catch((error) => {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n });\n }\n recoverSnapshotFailure(error, path, reloadOnSnapshotFailure) {\n if (!reloadOnSnapshotFailure || typeof error !== "object" || error === null || error.status !== 409) {\n return;\n }\n if (this.snapshotRecoveryStarted) return;\n this.snapshotRecoveryStarted = true;\n try {\n this.reloadDocument(path);\n } catch (reloadError) {\n this.snapshotRecoveryStarted = false;\n logger4.warn(\n `[Veryfront] Failed to reload after dependency snapshot conflict for ${path}`,\n reloadError instanceof Error ? reloadError : new Error(String(reloadError))\n );\n }\n }\n};\n\n// src/security/client/html-sanitizer.ts\nvar SUSPICIOUS_PATTERN_SPECS = [\n { source: String.raw`]*>[\\s\\S]*?<\\/script>`, flags: "gi", name: "inline script" },\n { source: String.raw`javascript:`, flags: "gi", name: "javascript: URL" },\n { source: String.raw`\\bon\\w+\\s*=`, flags: "gi", name: "event handler attribute" },\n { source: String.raw`data:\\s*text\\/html`, flags: "gi", name: "data: HTML URL" }\n];\nfunction createSuspiciousPatterns() {\n return SUSPICIOUS_PATTERN_SPECS.map(({ source, flags, name }) => ({\n pattern: new RegExp(source, flags),\n name\n }));\n}\nfunction isDevMode() {\n const g = globalThis;\n return g.__VERYFRONT_DEV__ === true || g.Deno?.env?.get?.("VERYFRONT_ENV") === "development";\n}\nfunction validateTrustedHtml(html, options = {}) {\n const { allowInlineScripts = false, strict = false, warn = true } = options;\n for (const { pattern, name } of createSuspiciousPatterns()) {\n if (allowInlineScripts && name === "inline script") continue;\n pattern.lastIndex = 0;\n if (!pattern.test(html)) continue;\n if (warn) console.warn(`[Security] Suspicious ${name} detected in server HTML`);\n if (strict || !isDevMode()) {\n throw SECURITY_VIOLATION.create({ detail: `Potentially unsafe HTML: ${name} detected` });\n }\n }\n return html;\n}\n\n// src/routing/client/page-transition.ts\nvar logger5 = rendererLogger.component("veryfront");\nvar PageTransition = class {\n constructor(setupViewportPrefetch) {\n __publicField(this, "setupViewportPrefetch", setupViewportPrefetch);\n __publicField(this, "pendingTransitionTimeout");\n __publicField(this, "pendingRoot");\n }\n destroy() {\n this.cancelPendingTransition();\n }\n cancelPendingTransition() {\n if (this.pendingTransitionTimeout !== void 0) {\n clearTimeout(this.pendingTransitionTimeout);\n this.pendingTransitionTimeout = void 0;\n }\n if (this.pendingRoot) {\n this.pendingRoot.style.opacity = "1";\n this.pendingRoot = void 0;\n }\n }\n updatePage(data, isPopState, scrollY) {\n this.cancelPendingTransition();\n if (routeRequiresDocumentNavigation(data)) {\n throw new TypeError("Scripted routes require a full document navigation");\n }\n const rootElement = document.getElementById("root");\n const preparedHead = prepareClientRouteHeadEntries(data.managedHead, document);\n const retainedTitle = document.title;\n if (!rootElement || data.html === void 0) {\n retireClientHeadOwnership(document);\n applyPreparedClientRouteHeadDescriptors(preparedHead, document);\n this.updateDocumentMetadata(document, data, retainedTitle);\n return;\n }\n const trustedHtml = validateTrustedHtml(String(data.html));\n this.performTransition(\n rootElement,\n data,\n trustedHtml,\n preparedHead,\n retainedTitle,\n isPopState,\n scrollY\n );\n }\n updateDocumentMetadata(targetDocument, data, retainedTitle) {\n updateRouteTitle(data.frontmatter?.title || retainedTitle, targetDocument);\n updateRouteMetaTags(data.frontmatter ?? {}, targetDocument);\n }\n performTransition(rootElement, data, trustedHtml, preparedHead, retainedTitle, isPopState, scrollY) {\n rootElement.style.opacity = "0";\n this.pendingRoot = rootElement;\n this.pendingTransitionTimeout = setTimeout(() => {\n this.pendingTransitionTimeout = void 0;\n this.pendingRoot = void 0;\n try {\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = trustedHtml;\n applyHeadDirectives(rootElement);\n applyPreparedClientRouteHeadDescriptors(preparedHead, rootElement.ownerDocument);\n this.updateDocumentMetadata(rootElement.ownerDocument, data, retainedTitle);\n this.setupViewportPrefetch(rootElement);\n manageFocus(rootElement);\n this.handleScroll(isPopState, scrollY);\n } catch (error) {\n logger5.error("Route transition commit failed; reloading the document", error);\n globalThis.location?.reload();\n } finally {\n rootElement.style.opacity = "1";\n }\n }, PAGE_TRANSITION_DELAY_MS);\n }\n handleScroll(isPopState, scrollY) {\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger5.warn("scroll handling failed", error);\n }\n }\n showError(error) {\n const rootElement = document.getElementById("root");\n if (!rootElement) return;\n const errorDiv = document.createElement("div");\n errorDiv.className = "veryfront-error-page";\n const heading = document.createElement("h1");\n heading.textContent = "Oops! Something went wrong";\n const message = document.createElement("p");\n message.textContent = error.message;\n const button = document.createElement("button");\n button.type = "button";\n button.textContent = "Reload Page";\n button.onclick = () => globalThis.location.reload();\n errorDiv.append(heading, message, button);\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = "";\n rootElement.appendChild(errorDiv);\n }\n setLoadingState(loading) {\n const indicator = document.getElementById("veryfront-loading");\n if (indicator) indicator.style.display = loading ? "block" : "none";\n document.body.classList.toggle("veryfront-loading", loading);\n }\n};\n\n// src/routing/client/viewport-prefetch.ts\nvar logger6 = rendererLogger.component("veryfront");\nvar ViewportPrefetch = class {\n constructor(prefetchCallback, prefetchOptions = {}) {\n __publicField(this, "observer", null);\n __publicField(this, "prefetchCallback");\n __publicField(this, "prefetchOptions");\n this.prefetchCallback = prefetchCallback;\n this.prefetchOptions = prefetchOptions;\n }\n setup(root) {\n try {\n if (!("IntersectionObserver" in globalThis)) return;\n this.observer?.disconnect();\n this.createObserver();\n this.observeLinks(root);\n } catch (error) {\n logger6.debug("setupViewportPrefetch failed", error);\n }\n }\n createObserver() {\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!(entry.target instanceof HTMLAnchorElement)) continue;\n const href = entry.target.getAttribute("href");\n if (href) this.prefetchCallback(href);\n this.observer?.unobserve(entry.target);\n }\n },\n { rootMargin: "200px" }\n );\n }\n observeLinks(root) {\n const anchors = root.querySelectorAll(\'a[href]:not([target="_blank"])\');\n const isViewportEnabled = Boolean(this.prefetchOptions.viewport);\n for (const anchor of anchors) {\n if (!this.shouldObserveAnchor(anchor, isViewportEnabled)) continue;\n this.observer?.observe(anchor);\n }\n }\n shouldObserveAnchor(anchor, isViewportEnabled) {\n const href = anchor.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("#")) return false;\n if (anchor.getAttribute("download")) return false;\n const prefetchAttribute = anchor.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n return prefetchAttribute === "viewport" || isViewportEnabled;\n }\n disconnect() {\n if (!this.observer) return;\n try {\n this.observer.disconnect();\n } catch (error) {\n logger6.warn("prefetchObserver.disconnect failed", error);\n } finally {\n this.observer = null;\n }\n }\n};\n\n// src/rendering/client/router.ts\nvar logger7 = rendererLogger.component("veryfront");\nfunction toHistoryMode(options) {\n if (typeof options === "boolean") return options ? "push" : "none";\n return options?.history ?? "push";\n}\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "navigationSequence", 0);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n // The browser already updated the URL for a popstate, so don\'t touch history.\n onNavigate: (url) => this.navigate(url, { history: "none" }),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n getNavigationStore().setNavigator((href, options2) => this.navigate(href, options2));\n }\n registerNavigationHandler(handler) {\n logger7.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n /**\n * Notify React (and any other) subscribers that a navigation completed —\n * after full page loads, soft same-route changes, and popstate. Delegates to\n * the shared navigation store, the single subscription surface both bundles\n * share.\n */\n notify() {\n getNavigationStore().notify();\n }\n pathnameOf(url) {\n try {\n return new URL(url, this.baseUrl).pathname;\n } catch {\n return url.split("?")[0]?.split("#")[0] || this.currentPath;\n }\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger7.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger7.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger7.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger7.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData) {\n const managedHead = snapshotClientRouteHead(document);\n this.pageLoader.setCache(this.currentPath, {\n ...pageData,\n managedHead,\n ...managedHead.some((entry) => entry.tagName === "script") || document.getElementById("root")?.querySelector("script") ? { requiresFullDocumentNavigation: true } : {}\n });\n }\n }\n /**\n * Navigate to a URL. `options` selects the history behaviour: `{ history:\n * "push" }` (default), `"replace"`, or `"none"` (the URL already reflects the\n * target, as after popstate). A boolean is accepted for backward\n * compatibility — `true` pushes, `false` maps to `"none"`.\n */\n async navigate(url, options) {\n logger7.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n const navigationId = ++this.navigationSequence;\n this.pageTransition.cancelPendingTransition();\n this.pageTransition.setLoadingState(false);\n const history = toHistoryMode(options);\n const sameRoute = this.pathnameOf(url) === this.pathnameOf(this.currentPath);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (history === "replace") globalThis.history.replaceState({}, "", url);\n else if (history === "push") globalThis.history.pushState({}, "", url);\n if (sameRoute && !this.shouldRevalidate(url, sameRoute)) {\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = url;\n this.notify();\n this.options.onComplete?.(url);\n this.options.onNavigate?.(url);\n return;\n }\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url, navigationId);\n } else {\n if (await this.loadPage(url, true, navigationId)) return;\n }\n if (!this.isCurrentNavigation(navigationId)) return;\n this.notify();\n this.options.onNavigate?.(url);\n }\n isCurrentNavigation(navigationId) {\n return navigationId === this.navigationSequence;\n }\n /**\n * Whether a navigation should refetch page data. A route change always does;\n * a same-route (query/hash-only) change consults `options.shouldRevalidate`,\n * defaulting to `true` so server data is never shown stale.\n */\n shouldRevalidate(nextUrl, sameRoute) {\n const policy = this.options.shouldRevalidate;\n if (!policy) return true;\n return policy({ currentHref: this.currentPath, nextHref: nextUrl, sameRoute });\n }\n async loadSpaPage(path, navigationId) {\n logger7.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n if (!this.isCurrentNavigation(navigationId)) return;\n await this.spaNavigationHandler?.(spaData);\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger7.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n /** Returns true when navigation was handed to the browser document loader. */\n async loadPage(path, updateUI = true, navigationId) {\n if (this.pageLoader.isCached(path)) {\n logger7.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.pageTransition.setLoadingState(false);\n this.options.onComplete?.(path);\n return false;\n }\n logger7.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.options.onComplete?.(path);\n return false;\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n return false;\n } finally {\n if (this.isCurrentNavigation(navigationId)) this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data, targetPath) {\n if (!this.root) return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(targetPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n this.navigationSequence++;\n this.pageTransition.setLoadingState(false);\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nfunction boot(options = {}) {\n if (typeof window === "undefined" || !globalThis.document) return null;\n const globalWithRouter = globalThis;\n if (globalWithRouter.veryFrontRouter) return globalWithRouter.veryFrontRouter;\n const { slug: _slug, ...routerOptions } = options;\n const router = new VeryfrontRouter(routerOptions);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalWithRouter.veryFrontRouter = router;\n return router;\n}\nif (typeof window !== "undefined" && globalThis.document) {\n boot();\n}\nexport {\n VeryfrontRouter,\n boot\n};\n'; export const CLIENT_PREFETCH_BUNDLE: string | undefined = - 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar StringReplace = String.prototype.replace;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; + 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar RegExpSymbolReplace = RegExp.prototype[Symbol.replace];\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index 24707730e4..1ebd93f96b 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=String.prototype.replace;function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),to=new b("PREFETCH",G),no=new b("HYDRATE",G),ro=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},yo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var ho=Array.prototype,Eo=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Ro=Object.hasOwn,_o=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,xo=Number.isFinite,So=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,Ao=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,To=_(T.prototype,"host").get,bo=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,Co=_(T.prototype,"pathname").get,wo=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Io=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var ko=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Fo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Vo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),zo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Bo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),jo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Go=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Wo=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Ko=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Yo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Xo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,mi=ir*2;var hi=64*1024,Ei=1024*1024,Ri=1024*1024;var _i=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; + 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,br=Array.prototype.filter,lt=Array.prototype.join,Tr=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=RegExp.prototype[Symbol.replace];function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new T("RSC",G),to=new T("PREFETCH",G),no=new T("HYDRATE",G),ro=new T("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":Te(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},yo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var ho=Array.prototype,Eo=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Ro=Object.hasOwn,_o=Object.prototype,Bt=Set,jt=decodeURIComponent,b=URL,xo=Number.isFinite,So=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,Ao=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,bo=_(b.prototype,"host").get,To=_(b.prototype,"origin").get,en=_(b.prototype,"password").get,Co=_(b.prototype,"pathname").get,wo=_(b.prototype,"protocol").get,tn=_(b.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Io=64*1024,bn=256,Tn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,bn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${Tn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var ko=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Fo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Vo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),zo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Bo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),jo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Go=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Wo=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Ko=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Yo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Xo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,mi=ir*2;var hi=64*1024,Ei=1024*1024,Ri=1024*1024;var _i=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=String.prototype.replace;var ir=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Cr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Cr as consumeNdjsonStream,ft as getContainer};\n'; + 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=RegExp.prototype[Symbol.replace];var ir=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Cr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Cr as consumeNdjsonStream,ft as getContainer};\n'; From 0ac0f49f7c90868b9498a82b987efda0b65dc1ae Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:24:03 +0200 Subject: [PATCH 015/104] Classify tenant content parse failures without hiding framework errors MDX and Markdown compiler wrappers now require parser-origin evidence before emitting tenant build slugs. Typed framework errors and operational processor failures pass through unchanged, while malformed MDX and Markdown frontmatter receive explicit build classifications for tenant-build warning capture. Constraint: Tenant build downgrades must not catch framework or processor initialization failures Rejected: Message-only syntax regexes | can match operational failure wording Confidence: high Scope-risk: moderate Directive: Keep compiler error wrapping tied to parser-origin evidence so framework failures stay error-level Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/compiler/mdx-compiler.test.ts src/transforms/md/compiler/md-compiler.test.ts src/observability/application-errors.test.ts Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task docs:errors:check Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task docs:api-reference:check Tested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task docs:public:check Tested: git diff --check Not-tested: Full suite pending CI --- docs/api-reference/veryfront/errors.md | 9 +-- docs/api-reference/veryfront/observability.md | 8 +-- docs/guides/errors.md | 7 +++ src/errors/catalog/build-errors.test.ts | 5 +- src/errors/catalog/build-errors.ts | 15 +++++ src/errors/error-registry.test.ts | 8 +-- src/errors/error-registry/build.ts | 9 +++ src/errors/index.ts | 1 + src/observability/application-errors.test.ts | 16 ++++- src/observability/application-errors.ts | 1 + .../module-loader/build-failure.ts | 1 + src/server/handlers/dev/dashboard/api.test.ts | 4 +- .../md/compiler/md-compiler.test.ts | 62 ++++++++++++++++++- src/transforms/md/compiler/md-compiler.ts | 23 ++++--- .../mdx/compiler/mdx-compiler.test.ts | 39 ++++++++++++ src/transforms/mdx/compiler/mdx-compiler.ts | 29 +++++++-- 16 files changed, 204 insertions(+), 33 deletions(-) diff --git a/docs/api-reference/veryfront/errors.md b/docs/api-reference/veryfront/errors.md index 55da77eb72..3f9f2ead7b 100644 --- a/docs/api-reference/veryfront/errors.md +++ b/docs/api-reference/veryfront/errors.md @@ -46,7 +46,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `API_CLIENT_ERROR` | API client request/response errors (replaces VeryfrontAPIError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L93) | | `API_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/runtime.ts#L43) | | `API_ROUTE_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/route.ts#L43) | -| `ASSET_OPTIMIZATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L35) | +| `ASSET_OPTIMIZATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L43) | | `AUTHENTICATION_REQUIRED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L11) | | `BRANCH_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/deploy.ts#L100) | | `BUILD_ERROR_CATALOG` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/build-errors.ts#L4) | @@ -59,7 +59,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `CIRCULAR_DEPENDENCY` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/module.ts#L19) | | `CLIENT_BOUNDARY_VIOLATION` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/boundary.ts#L3) | | `CLIENT_ONLY_IN_SERVER` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/boundary.ts#L19) | -| `COMPILATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L59) | +| `COMPILATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L67) | | `COMPONENT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/runtime.ts#L19) | | `CONFIG_ERROR_CATALOG` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/config-errors.ts#L4) | | `CONFIG_INVALID` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/config.ts#L12) | @@ -105,6 +105,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `LAYOUT_NOT_FOUND` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/runtime.ts#L27) | | `LOCKFILE_FORMAT_MISMATCH` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/module.ts#L51) | | `LOCKFILE_READ_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/module.ts#L59) | +| `MARKDOWN_COMPILE_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L35) | | `MDX_COMPILE_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L27) | | `MIDDLEWARE_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/runtime.ts#L51) | | `MODULE_ERROR_CATALOG` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/module-errors.ts#L4) | @@ -147,8 +148,8 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); | `SERVICE_OVERLOADED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L44) | | `SOURCE_DIGEST_MISMATCH` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/deploy.ts#L84) | | `SOURCE_MAP_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/dev.ts#L35) | -| `SOURCEMAP_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L51) | -| `SSG_GENERATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L43) | +| `SOURCEMAP_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L59) | +| `SSG_GENERATION_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L51) | | `SSR_OUTPUT_LIMIT_EXCEEDED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/boundary.ts#L51) | | `TEMPLATE_NOT_FOUND` | `veryfront init --template ` (and `npm create veryfront -- --template`) was given a name that is not in the starter catalog. The detail carries the list of valid names so a wrong guess is self-correcting. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/config.ts#L104) | | `TIMEOUT_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L52) | diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 8e3e169182..9ffb5f9df7 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L276) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L304) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L276) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L304) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | diff --git a/docs/guides/errors.md b/docs/guides/errors.md index d39c544209..a79124a621 100644 --- a/docs/guides/errors.md +++ b/docs/guides/errors.md @@ -135,6 +135,13 @@ MDX compilation failed. - **HTTP status:** 500 - **What to do:** Check your MDX file syntax +### markdown-compile-error + +Markdown compilation failed. + +- **HTTP status:** 500 +- **What to do:** Check your Markdown file syntax and frontmatter + ### asset-optimization-error Asset optimization failed. diff --git a/src/errors/catalog/build-errors.test.ts b/src/errors/catalog/build-errors.test.ts index e3bdfedaf0..9e7f5e2fbb 100644 --- a/src/errors/catalog/build-errors.test.ts +++ b/src/errors/catalog/build-errors.test.ts @@ -11,6 +11,7 @@ describe("errors/catalog/build-errors", () => { "bundle-error", "typescript-error", "mdx-compile-error", + "markdown-compile-error", "asset-optimization-error", "ssg-generation-error", "sourcemap-error", @@ -37,8 +38,8 @@ describe("errors/catalog/build-errors", () => { } }); - it("should have 8 entries", () => { - assertEquals(Object.keys(BUILD_ERROR_CATALOG).length, 8); + it("should have 9 entries", () => { + assertEquals(Object.keys(BUILD_ERROR_CATALOG).length, 9); }); it("build-failed should have tips", () => { diff --git a/src/errors/catalog/build-errors.ts b/src/errors/catalog/build-errors.ts index 2e9d6a17c6..b841839063 100644 --- a/src/errors/catalog/build-errors.ts +++ b/src/errors/catalog/build-errors.ts @@ -56,6 +56,21 @@ import Button from './components/Button.jsx' `, }), + "markdown-compile-error": createErrorSolution("markdown-compile-error", { + title: "Markdown compilation failed", + message: "Failed to compile Markdown file.", + steps: [ + "Check for syntax errors in your Markdown file", + "Ensure frontmatter YAML is valid", + "Check for unclosed frontmatter blocks", + ], + example: `--- +title: My Post +--- + +# Hello World`, + }), + "asset-optimization-error": createSimpleError( "asset-optimization-error", "Asset optimization failed", diff --git a/src/errors/error-registry.test.ts b/src/errors/error-registry.test.ts index 8f2a2f63fc..d18adf8119 100644 --- a/src/errors/error-registry.test.ts +++ b/src/errors/error-registry.test.ts @@ -29,9 +29,9 @@ describe("error-registry", () => { assertEquals(slugs.length, uniqueSlugs.size, "Duplicate slugs detected"); }); - it("should have 109 registered errors", () => { + it("should have 110 registered errors", () => { const slugs = getAllSlugs(); - assertEquals(slugs.length, 109); + assertEquals(slugs.length, 110); }); }); @@ -176,7 +176,7 @@ describe("error-registry", () => { it("should return BUILD errors", () => { const errors = getErrorsByCategory("BUILD"); - assertEquals(errors.length, 8); + assertEquals(errors.length, 9); for (const error of errors) { assertEquals(error.category, "BUILD"); } @@ -318,7 +318,7 @@ describe("error-registry", () => { describe("error categories coverage", () => { const expectedCategoryCounts: Record = { CONFIG: 12, - BUILD: 8, + BUILD: 9, RUNTIME: 10, ROUTE: 6, MODULE: 8, diff --git a/src/errors/error-registry/build.ts b/src/errors/error-registry/build.ts index fef330d262..c41a68e747 100644 --- a/src/errors/error-registry/build.ts +++ b/src/errors/error-registry/build.ts @@ -32,6 +32,14 @@ export const MDX_COMPILE_ERROR = defineError({ suggestion: "Check your MDX file syntax", }); +export const MARKDOWN_COMPILE_ERROR = defineError({ + slug: "markdown-compile-error", + category: "BUILD", + status: 500, + title: "Markdown compilation failed", + suggestion: "Check your Markdown file syntax and frontmatter", +}); + export const ASSET_OPTIMIZATION_ERROR = defineError({ slug: "asset-optimization-error", category: "BUILD", @@ -70,6 +78,7 @@ export const BUILD_REGISTRY = { "bundle-error": BUNDLE_ERROR, "typescript-error": TYPESCRIPT_ERROR, "mdx-compile-error": MDX_COMPILE_ERROR, + "markdown-compile-error": MARKDOWN_COMPILE_ERROR, "asset-optimization-error": ASSET_OPTIMIZATION_ERROR, "ssg-generation-error": SSG_GENERATION_ERROR, "sourcemap-error": SOURCEMAP_ERROR, diff --git a/src/errors/index.ts b/src/errors/index.ts index 7a59c87305..3d8dcd763d 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -100,6 +100,7 @@ export { LAYOUT_NOT_FOUND, LOCKFILE_FORMAT_MISMATCH, LOCKFILE_READ_ERROR, + MARKDOWN_COMPILE_ERROR, MDX_COMPILE_ERROR, MIDDLEWARE_ERROR, // MODULE diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 09cab637a7..ba5d5496c0 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -20,6 +20,7 @@ import { CONFIG_PARSE_ERROR, createError, INITIALIZATION_ERROR, + MARKDOWN_COMPILE_ERROR, MDX_COMPILE_ERROR, RENDER_ERROR, SOURCEMAP_ERROR, @@ -170,6 +171,9 @@ it("application error reporter downgrades tenant build errors to tagged warnings const mdxRegistryError = MDX_COMPILE_ERROR.create({ detail: "MDX compilation failed in /pages/index.mdx", }); + const markdownRegistryError = MARKDOWN_COMPILE_ERROR.create({ + detail: "Markdown frontmatter failed in /pages/index.md", + }); const frameworkError = INITIALIZATION_ERROR.create({ detail: "renderer failed to initialize", }); @@ -198,6 +202,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(mdxRegistryError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(markdownRegistryError, { boundary: "ssr.render" }), + "event-id", + ); assertEquals( captureApplicationError(frameworkError, { boundary: "ssr.render" }), "event-id", @@ -227,7 +235,7 @@ it("application error reporter downgrades tenant build errors to tagged warnings "event-id", ); - assertEquals(captures.length, 10); + assertEquals(captures.length, 11); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -236,9 +244,9 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[1]?.context.level, "warning"); assertEquals(captures[2]?.context.errorClass, "tenant-build"); assertEquals(captures[2]?.context.level, "warning"); + assertEquals(captures[3]?.context.errorClass, "tenant-build"); + assertEquals(captures[3]?.context.level, "warning"); // Genuine framework failures keep their default error-level capture. - assertEquals(captures[3]?.context.errorClass, undefined); - assertEquals(captures[3]?.context.level, undefined); assertEquals(captures[4]?.context.errorClass, undefined); assertEquals(captures[4]?.context.level, undefined); assertEquals(captures[5]?.context.errorClass, undefined); @@ -251,6 +259,8 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[8]?.context.level, undefined); assertEquals(captures[9]?.context.errorClass, undefined); assertEquals(captures[9]?.context.level, undefined); + assertEquals(captures[10]?.context.errorClass, undefined); + assertEquals(captures[10]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index d2816035d7..9298b0e7d2 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -234,6 +234,7 @@ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-buil const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", + "markdown-compile-error", "ssg-generation-error", "compilation-error", ]); diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index d64c5c7002..b9d9237108 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -27,6 +27,7 @@ type TaggedError = Error & { const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", + "markdown-compile-error", "ssg-generation-error", "compilation-error", ]); diff --git a/src/server/handlers/dev/dashboard/api.test.ts b/src/server/handlers/dev/dashboard/api.test.ts index a9ea9abbc9..bc525b6e24 100644 --- a/src/server/handlers/dev/dashboard/api.test.ts +++ b/src/server/handlers/dev/dashboard/api.test.ts @@ -214,10 +214,10 @@ describe("Dashboard API - GET endpoints", () => { assertEquals("errors" in body, true); assertEquals("categories" in body, true); assertEquals("count" in body, true); - assertEquals(body.count, 65); + assertEquals(body.count, 66); assertEquals(body.categories, { config: 7, - build: 8, + build: 9, runtime: 7, route: 6, server: 8, diff --git a/src/transforms/md/compiler/md-compiler.test.ts b/src/transforms/md/compiler/md-compiler.test.ts index 526e17f8b3..fb6bd5fbbc 100644 --- a/src/transforms/md/compiler/md-compiler.test.ts +++ b/src/transforms/md/compiler/md-compiler.test.ts @@ -1,7 +1,13 @@ import "#veryfront/schemas/_test-setup.ts"; import "../../mdx/compiler/__tests__/content-processor-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertInstanceOf, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { VeryfrontError } from "#veryfront/errors"; +import { + register as registerContract, + tryResolve as tryResolveContract, +} from "#veryfront/extensions/contracts.ts"; +import type { ContentProcessor } from "#veryfront/extensions/content/index.ts"; import { compileMarkdownRuntime } from "./md-compiler.ts"; describe( @@ -31,6 +37,60 @@ describe( assertEquals(result.frontmatter.author, "Jane"); }); + it("classifies tenant Markdown frontmatter failures explicitly", async () => { + const error = await assertRejects( + () => + compileMarkdownRuntime( + "runtime", + "/tmp/project", + "---\ntitle: [unterminated\n---\n# Content", + undefined, + "broken.md", + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "markdown-compile-error"); + assertEquals(error.category, "BUILD"); + }); + + it("preserves non-source processor failures", async () => { + const previous = tryResolveContract("ContentProcessor"); + registerContract( + "ContentProcessor", + { + compileMdx() { + throw new Error("not used"); + }, + compileMarkdown() { + throw new SyntaxError("YAML backend unavailable at line 1, column 1"); + }, + } satisfies ContentProcessor, + ); + + try { + const error = await assertRejects(() => + compileMarkdownRuntime( + "runtime", + "/tmp/project", + "# Content", + undefined, + "framework-failure.md", + ) + ); + + assertInstanceOf(error, Error); + assertEquals(error instanceof VeryfrontError, false); + assertEquals( + (error as Error).message, + "YAML backend unavailable at line 1, column 1", + ); + } finally { + registerContract("ContentProcessor", previous); + } + }); + it("extracts headings", async () => { const result = await compileMarkdownRuntime( "runtime", diff --git a/src/transforms/md/compiler/md-compiler.ts b/src/transforms/md/compiler/md-compiler.ts index 0182d16e5b..18d3be55a0 100644 --- a/src/transforms/md/compiler/md-compiler.ts +++ b/src/transforms/md/compiler/md-compiler.ts @@ -6,11 +6,19 @@ import type { ContentProcessingResult, ContentProcessor, } from "#veryfront/extensions/content/index.ts"; -import { createError, toError } from "#veryfront/errors"; +import { MARKDOWN_COMPILE_ERROR, VeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; const logger = rendererLogger.component("md-compiler"); +function isMarkdownSourceCompileError(error: Error): boolean { + return error.name === "SyntaxError" && + /\bline \d+, column \d+\b/i.test(error.message) && + (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || + error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || + error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); +} + export function compileMarkdownRuntime( mode: CompilationMode, projectDir: string, @@ -45,12 +53,13 @@ export function compileMarkdownRuntime( stack: err.stack, }); - throw toError( - createError({ - type: "build", - message: `Markdown compilation error: ${err.message} | file: ${filePath ?? ""}`, - }), - ); + if (err instanceof VeryfrontError || !isMarkdownSourceCompileError(err)) { + throw err; + } + + throw MARKDOWN_COMPILE_ERROR.create({ + detail: `Markdown compilation error: ${err.message} | file: ${filePath ?? ""}`, + }); } }, { diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 8885509d88..602139e9ae 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -3,6 +3,11 @@ import "./__tests__/content-processor-setup.ts"; import { assertEquals, assertInstanceOf, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { VeryfrontError } from "#veryfront/errors"; +import { + register as registerContract, + tryResolve as tryResolveContract, +} from "#veryfront/extensions/contracts.ts"; +import type { ContentProcessor } from "#veryfront/extensions/content/index.ts"; import { compileMDXRuntime } from "./mdx-compiler.ts"; describe("transforms/mdx/compiler/mdx-compiler", () => { @@ -93,5 +98,39 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { assertEquals(error.slug, "mdx-compile-error"); assertEquals(error.category, "BUILD"); }); + + it("preserves non-source processor failures", async () => { + const previous = tryResolveContract("ContentProcessor"); + registerContract( + "ContentProcessor", + { + compileMdx() { + throw new Error("Expected ContentProcessor to initialize"); + }, + compileMarkdown() { + throw new Error("not used"); + }, + } satisfies ContentProcessor, + ); + + try { + const error = await assertRejects(() => + compileMDXRuntime( + "production", + "/project", + "# Hello", + undefined, + "framework-failure.mdx", + "server", + ) + ); + + assertInstanceOf(error, Error); + assertEquals(error instanceof VeryfrontError, false); + assertEquals((error as Error).message, "Expected ContentProcessor to initialize"); + } finally { + registerContract("ContentProcessor", previous); + } + }); }); }); diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index c0d1cc0000..301721f059 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -6,11 +6,25 @@ import type { ContentProcessingResult, ContentProcessor, } from "#veryfront/extensions/content/index.ts"; -import { MDX_COMPILE_ERROR } from "#veryfront/errors"; +import { MDX_COMPILE_ERROR, VeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; const logger = rendererLogger.component("mdx-compiler"); +function isMdxSourceCompileError(error: Error): boolean { + const candidate = error as Error & { + column?: unknown; + line?: unknown; + ruleId?: unknown; + source?: unknown; + }; + return typeof candidate.source === "string" && + /(?:^|-)mdx(?:-|$)|micromark|remark|recma|rehype/.test(candidate.source) && + typeof candidate.ruleId === "string" && + Number.isSafeInteger(candidate.line) && + Number.isSafeInteger(candidate.column); +} + export function compileMDXRuntime( mode: CompilationMode, projectDir: string, @@ -37,16 +51,19 @@ export function compileMDXRuntime( studioEmbed, }); } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); logger.error("Compilation failed:", { filePath, - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, + error: err.message, + stack: err.stack, }); + if (err instanceof VeryfrontError || !isMdxSourceCompileError(err)) { + throw err; + } + throw MDX_COMPILE_ERROR.create({ - detail: `MDX compilation error: ${ - error instanceof Error ? error.message : String(error) - } | file: ${filePath ?? ""}`, + detail: `MDX compilation error: ${err.message} | file: ${filePath ?? ""}`, }); } }, From 48f741df0751d7c9534808c25639cc9742576626 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:33:21 +0200 Subject: [PATCH 016/104] Keep reviewed alias imports visible across literal forms Dynamic import scanning skipped non-interpolated template literals, so an alias import could be rewritten into a _vf_modules template specifier and then escape unresolved-import detection inside the cached module. The fix treats backtick literals without interpolation as static specifiers while continuing to reject interpolated templates. Cross-project alias URL building now uses the captured RegExp test intrinsic for extension checks, matching the existing captured-intrinsic approach for replacement. Constraint: PR review threads PRRT_kwDOQaPiP86ZaHFq and PRRT_kwDOQaPiP86ZaHFs require preserving alias detection for non-interpolated backtick imports and avoiding mutable RegExp.prototype.test dispatch Rejected: Treating all template literals as static imports | interpolated templates are runtime expressions and must stay skipped Confidence: high Scope-risk: narrow Tested: Pinned Deno 2.7.7 red regressions for source-spans, nested-imports, and url-builder; pinned focused tests passed; pinned fmt/lint/check for touched files; pinned generate:manifests:check; git diff --check Not-tested: Full pre-push before commit; it will run on non-force push --- src/build/production-build/templates.ts | 2 +- .../rsc/endpoints/rsc-bundles.generated.ts | 4 +- .../import-rewriter/url-builder.test.ts | 26 +++++++++++ src/transforms/import-rewriter/url-builder.ts | 8 +++- .../module-fetcher/nested-imports.test.ts | 7 +++ .../utils/source-spans.test.ts | 16 ++++--- .../esm-module-loader/utils/source-spans.ts | 45 +++++++++++++++---- 7 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/build/production-build/templates.ts b/src/build/production-build/templates.ts index 2738045253..cd72768015 100644 --- a/src/build/production-build/templates.ts +++ b/src/build/production-build/templates.ts @@ -14,4 +14,4 @@ export const CLIENT_ROUTER_BUNDLE: string | undefined = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar rendererLogger = logger;\nvar PREFETCH_MAX_SIZE_BYTES = 200 * 1024;\n\n// src/rendering/client/navigation-store.ts\nvar STORE_KEY = /* @__PURE__ */ Symbol.for("veryfront.navigation.store.v1");\nfunction getNavigationStore() {\n const holder = globalThis;\n const existing = holder[STORE_KEY];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? `${loc.pathname}${loc.search}${loc.hash}` : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n holder[STORE_KEY] = store;\n return store;\n}\n\n// src/rendering/client/router.ts\nimport ReactDOM from "react-dom/client";\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_ENTRIES = 128;\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar REACT_HEAD_ATTRIBUTE_NAMES = {\n charSet: "charset",\n className: "class",\n crossOrigin: "crossorigin",\n fetchPriority: "fetchpriority",\n htmlFor: "for",\n httpEquiv: "http-equiv",\n imageSizes: "imagesizes",\n imageSrcSet: "imagesrcset",\n noModule: "nomodule",\n referrerPolicy: "referrerpolicy"\n};\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar SUPPORTED_MANAGED_HEAD_TAGS = /* @__PURE__ */ new Set([\n "title",\n "meta",\n "link",\n "style",\n "script"\n]);\nvar HEAD_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_.:-]*$/;\nvar MAX_HEAD_PROP_ENTRIES = 128;\nvar MAX_HEAD_ATTRIBUTE_NAME_BYTES = 256;\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\nfunction normalizeManagedHeadString(value) {\n return value.replace(/\\r\\n?/g, "\\n");\n}\nfunction inspectHeadProps(value) {\n if (typeof value !== "object" || value === null || Array.isArray(value)) return null;\n let prototype;\n let keys;\n try {\n prototype = Object.getPrototypeOf(value);\n keys = Reflect.ownKeys(value);\n } catch {\n return null;\n }\n if (prototype !== Object.prototype && prototype !== null) return null;\n const inspected = /* @__PURE__ */ new Map();\n let entries = 0;\n for (const key of keys) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(value, key);\n } catch {\n return null;\n }\n if (!descriptor) return null;\n if (!descriptor.enumerable) continue;\n if (typeof key !== "string" || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return null;\n }\n entries++;\n if (entries > MAX_HEAD_PROP_ENTRIES) return null;\n inspected.set(key, descriptor.value);\n }\n return inspected;\n}\nfunction normalizeContentPrimitive(value) {\n if (value === null || value === void 0 || typeof value === "boolean") return void 0;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n return null;\n }\n const content = normalizeManagedHeadString(String(value));\n return headTextEncoder.encode(content).byteLength <= MAX_HEAD_CONTENT_BYTES ? content : null;\n}\nfunction normalizeManagedHeadAttributesFromProps(tagName, props, ambientNonce, excludedKeys = /* @__PURE__ */ new Set()) {\n const attributeMap = /* @__PURE__ */ new Map();\n for (const [key, value] of props) {\n if (key === "children" || key === "dangerouslySetInnerHTML" || excludedKeys.has(key)) {\n continue;\n }\n if (/^on/i.test(key) || typeof value === "function" || typeof value === "symbol" || typeof value === "object") {\n continue;\n }\n const name = (REACT_HEAD_ATTRIBUTE_NAMES[key] ?? key).toLowerCase();\n if (isHeadFrameworkAttribute(name) || !HEAD_ATTRIBUTE_NAME_PATTERN.test(name) || headTextEncoder.encode(name).byteLength > MAX_HEAD_ATTRIBUTE_NAME_BYTES) {\n continue;\n }\n if (BOOLEAN_HEAD_ATTRIBUTES.has(name)) {\n if (value !== false && value !== void 0) attributeMap.set(name, "");\n continue;\n }\n if (typeof value === "boolean") {\n if (name.startsWith("data-") || name.startsWith("aria-")) {\n attributeMap.set(name, String(value));\n }\n continue;\n }\n if (value === void 0) continue;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n continue;\n }\n const normalizedValue = normalizeManagedHeadString(String(value));\n if (headTextEncoder.encode(normalizedValue).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) {\n return null;\n }\n attributeMap.set(name, normalizedValue);\n }\n if (tagName === "script" || tagName === "style") {\n attributeMap.delete("nonce");\n }\n const acceptsAmbientNonce = tagName === "style" || tagName === "script" && !attributeMap.has("src");\n if (acceptsAmbientNonce && ambientNonce) {\n const nonce = normalizeManagedHeadString(ambientNonce);\n if (headTextEncoder.encode(nonce).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) return null;\n attributeMap.set("nonce", nonce);\n }\n if (tagName === "link" && attributeMap.get("rel")?.trim().toLowerCase() === "preload" && attributeMap.get("as")?.trim().toLowerCase() === "font" && !attributeMap.has("crossorigin")) {\n attributeMap.set("crossorigin", "anonymous");\n }\n if (attributeMap.size > MAX_HEAD_PROP_ENTRIES) return null;\n let totalBytes = 0;\n for (const [name, value] of attributeMap) {\n totalBytes += headTextEncoder.encode(name).byteLength + headTextEncoder.encode(value).byteLength;\n if (totalBytes > MAX_HEAD_ATTRIBUTE_BYTES) return null;\n }\n return [...attributeMap.entries()].sort(([left], [right]) => left.localeCompare(right));\n}\nfunction singletonKey(tagName, attributes) {\n if (tagName === "title") return "title";\n const record = Object.fromEntries(attributes);\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(record);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(record);\n return void 0;\n}\nfunction scriptKeys(tagName, attributes) {\n if (tagName !== "script") return [];\n const keys = [];\n const id = attributes.get("id");\n const src = attributes.get("src");\n if (id) keys.push(`script:id:${id}`);\n if (src) keys.push(`script:src:${src}`);\n return keys;\n}\nfunction declaresDocumentEncoding(attributes) {\n return attributes.has("charset") || attributes.get("http-equiv")?.trim().toLowerCase() === "content-type";\n}\nfunction createManagedHeadDescriptor(tagName, attributes, content, contentMode) {\n const attributeMap = new Map(attributes);\n return {\n tagName,\n attributes,\n ...content !== void 0 && { content },\n contentMode,\n signature: JSON.stringify([\n tagName,\n attributes,\n contentMode,\n content ?? null\n ]),\n singletonKey: singletonKey(tagName, attributeMap),\n scriptKeys: scriptKeys(tagName, attributeMap)\n };\n}\nfunction descriptorFromManagedHeadRecord(rawTagName, record, options = {}) {\n const tagName = rawTagName.toLowerCase();\n if (!SUPPORTED_MANAGED_HEAD_TAGS.has(tagName)) return null;\n const inspected = inspectHeadProps(record);\n if (!inspected) return null;\n const excludedKeys = options.contentProperty ? /* @__PURE__ */ new Set([options.contentProperty]) : /* @__PURE__ */ new Set();\n const attributes = normalizeManagedHeadAttributesFromProps(\n tagName,\n inspected,\n options.ambientNonce,\n excludedKeys\n );\n if (!attributes) return null;\n const attributeMap = new Map(attributes);\n if (tagName === "meta" && declaresDocumentEncoding(attributeMap)) return null;\n if ((tagName === "meta" || tagName === "link") && attributes.length === 0) return null;\n let content;\n if (options.contentProperty) {\n const normalized = normalizeContentPrimitive(inspected.get(options.contentProperty));\n if (normalized === null) return null;\n content = normalized;\n }\n return createManagedHeadDescriptor(tagName, attributes, content, "text");\n}\nfunction headScriptKeysIntersect(left, right) {\n if (left.length === 0 || right.length === 0) return false;\n const rightKeys = new Set(right);\n return left.some((key) => rightKeys.has(key));\n}\nfunction aggregateManagedHeadDescriptors(descriptors) {\n const aggregated = [];\n const singletonIndexes = /* @__PURE__ */ new Map();\n const scriptKeysSeen = /* @__PURE__ */ new Set();\n for (const descriptor of descriptors) {\n if (descriptor.singletonKey) {\n const index = singletonIndexes.get(descriptor.singletonKey);\n if (index !== void 0) {\n aggregated[index] = descriptor;\n continue;\n }\n singletonIndexes.set(descriptor.singletonKey, aggregated.length);\n } else if (descriptor.scriptKeys.length > 0) {\n if (descriptor.scriptKeys.some((key) => scriptKeysSeen.has(key))) continue;\n for (const key of descriptor.scriptKeys) scriptKeysSeen.add(key);\n }\n aggregated.push(descriptor);\n }\n return aggregated;\n}\nfunction managedHeadDescriptorBytes(descriptor) {\n let bytes = headTextEncoder.encode(descriptor.tagName).byteLength;\n for (const [name, value] of descriptor.attributes) {\n bytes += headTextEncoder.encode(name).byteLength;\n bytes += headTextEncoder.encode(value).byteLength;\n }\n if (descriptor.content !== void 0) {\n bytes += headTextEncoder.encode(descriptor.content).byteLength;\n }\n return bytes;\n}\nfunction assertManagedHeadDescriptorBudget(descriptors) {\n if (descriptors.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_ENTRIES}-entry request limit`\n );\n }\n let bytes = 0;\n for (const descriptor of descriptors) {\n bytes += managedHeadDescriptorBytes(descriptor);\n if (bytes > MAX_MANAGED_HEAD_BYTES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_BYTES}-byte request limit`\n );\n }\n }\n}\nfunction managedHeadDescriptorToTransportEntry(descriptor) {\n const attributes = descriptor.attributes.filter(([name]) => name !== "nonce");\n return {\n tagName: descriptor.tagName,\n attributes: attributes.map(([name, value]) => [name, value]),\n ...descriptor.content !== void 0 && { content: descriptor.content }\n };\n}\nfunction ownTransportValue(record, key) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n } catch {\n return void 0;\n }\n if (!descriptor || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return void 0;\n }\n return descriptor.value;\n}\nfunction descriptorFromManagedHeadTransportEntry(entry, ambientNonce) {\n if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n let prototype;\n try {\n prototype = Object.getPrototypeOf(entry);\n } catch {\n throw new TypeError("Managed-head transport entry cannot be inspected");\n }\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n const tagName = ownTransportValue(entry, "tagName");\n const rawAttributes = ownTransportValue(entry, "attributes");\n const content = ownTransportValue(entry, "content");\n if (typeof tagName !== "string" || tagName !== tagName.toLowerCase() || !Array.isArray(rawAttributes)) {\n throw new TypeError("Managed-head transport entry is not canonical");\n }\n if (rawAttributes.length > MAX_HEAD_PROP_ENTRIES) {\n throw new TypeError("Managed-head transport entry exceeds the attribute limit");\n }\n if (content !== void 0 && typeof content !== "string") {\n throw new TypeError("Managed-head transport content must be a string");\n }\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (!supportsText && content !== void 0) {\n throw new TypeError("Managed-head transport content is invalid for this tag");\n }\n const record = /* @__PURE__ */ Object.create(null);\n const inputAttributes = [];\n const names = /* @__PURE__ */ new Set();\n for (let index = 0; index < rawAttributes.length; index += 1) {\n const pair = ownTransportValue(rawAttributes, String(index));\n if (!Array.isArray(pair) || pair.length !== 2) {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const name = ownTransportValue(pair, "0");\n const value = ownTransportValue(pair, "1");\n if (typeof name !== "string" || typeof value !== "string") {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const normalizedName = name.toLowerCase();\n if (name !== normalizedName || normalizedName === "nonce" || names.has(normalizedName)) {\n throw new TypeError("Managed-head transport attributes are not canonical");\n }\n names.add(normalizedName);\n inputAttributes.push([normalizedName, value]);\n Object.defineProperty(record, normalizedName, {\n enumerable: true,\n value\n });\n }\n if (content !== void 0) {\n Object.defineProperty(record, "__veryfront_transport_content", {\n enumerable: true,\n value: content\n });\n }\n const descriptor = descriptorFromManagedHeadRecord(tagName, record, {\n ...supportsText && { contentProperty: "__veryfront_transport_content" },\n ...(tagName === "script" || tagName === "style") && ambientNonce ? { ambientNonce } : {}\n });\n const normalizedInput = inputAttributes.sort(([left], [right]) => left.localeCompare(right));\n const normalizedOutput = descriptor?.attributes.filter(([name]) => name !== "nonce");\n if (!descriptor || JSON.stringify(normalizedOutput) !== JSON.stringify(normalizedInput) || supportsText && (descriptor.content ?? "") !== (content ?? "")) {\n throw new TypeError("Managed-head transport entry failed validation");\n }\n return descriptor;\n}\nvar BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";\nfunction decodeBase64Url(value) {\n if (value.length % 4 === 1 || !/^[A-Za-z0-9_-]*$/.test(value)) {\n throw new TypeError("Managed-head payload is not valid base64url");\n }\n const estimatedBytes = Math.floor(value.length * 3 / 4);\n if (estimatedBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n const bytes = new Uint8Array(estimatedBytes);\n let outputIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const character of value) {\n const decoded = BASE64URL_ALPHABET.indexOf(character);\n if (decoded < 0) throw new TypeError("Managed-head payload is not valid base64url");\n buffer = buffer << 6 | decoded;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes[outputIndex++] = buffer >> bits & 255;\n buffer &= bits === 0 ? 0 : (1 << bits) - 1;\n }\n }\n if (bits > 0 && buffer !== 0) {\n throw new TypeError("Managed-head payload has non-canonical trailing bits");\n }\n return bytes.subarray(0, outputIndex);\n}\nfunction inspectManagedHeadPayload(payload, ambientNonce) {\n if (typeof payload !== "string") throw new TypeError("Managed-head payload must be a string");\n const payloadBytes = headTextEncoder.encode(payload).byteLength;\n if (payloadBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n let decoded;\n try {\n decoded = new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(payload));\n } catch (error) {\n if (error instanceof TypeError) throw error;\n throw new TypeError("Managed-head payload is not valid UTF-8", { cause: error });\n }\n let entries;\n try {\n entries = JSON.parse(decoded);\n } catch (error) {\n throw new TypeError("Managed-head payload is not valid JSON", { cause: error });\n }\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Managed-head payload exceeds the entry limit");\n }\n const rawDescriptors = entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, ambientNonce)\n );\n assertManagedHeadDescriptorBudget(rawDescriptors);\n return {\n descriptors: aggregateManagedHeadDescriptors(rawDescriptors),\n entryCount: rawDescriptors.length,\n descriptorBytes: rawDescriptors.reduce(\n (total, descriptor) => total + managedHeadDescriptorBytes(descriptor),\n 0\n ),\n payloadBytes\n };\n}\nfunction deserializeManagedHeadPayload(payload, ambientNonce) {\n return inspectManagedHeadPayload(payload, ambientNonce).descriptors;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction getManagedHeadNonce(targetDocument) {\n if (typeof targetDocument.querySelector !== "function") return void 0;\n const element = targetDocument.querySelector(\n "script[nonce], style[nonce], link[nonce]"\n );\n if (!element) return void 0;\n const nonce = element.nonce || element.getAttribute("nonce") || "";\n return nonce || void 0;\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey2 = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey2 !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey2);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nvar ROUTE_HEAD_CONTENT_PROPERTY = "__veryfront_route_head_content";\nfunction descriptorFromHeadElement(element) {\n const record = /* @__PURE__ */ Object.create(null);\n for (const { name, value } of element.attributes) {\n if (!isHeadFrameworkAttribute(name)) record[name] = value;\n }\n const tagName = element.tagName.toLowerCase();\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (supportsText) record[ROUTE_HEAD_CONTENT_PROPERTY] = element.textContent ?? "";\n return descriptorFromManagedHeadRecord(\n tagName,\n record,\n supportsText ? { contentProperty: ROUTE_HEAD_CONTENT_PROPERTY } : void 0\n );\n}\nfunction writeRouteDescriptor(element, descriptor) {\n for (const attribute of [...element.attributes]) element.removeAttribute(attribute.name);\n for (const [name, value] of descriptor.attributes) element.setAttribute(name, value);\n element.textContent = descriptor.content ?? "";\n element.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n element.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n}\nfunction prepareClientRouteHeadEntries(entries, targetDocument = document) {\n if (entries === void 0) return [];\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Route head payload exceeds the entry limit");\n }\n const descriptors = aggregateManagedHeadDescriptors(\n entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, getManagedHeadNonce(targetDocument))\n )\n );\n assertManagedHeadDescriptorBudget(descriptors);\n return descriptors;\n}\nfunction applyPreparedClientRouteHeadDescriptors(descriptors, targetDocument = document) {\n for (const descriptor of descriptors) {\n const described = [...targetDocument.head.children].flatMap((element2) => {\n const current = descriptorFromHeadElement(element2);\n return current ? [{ element: element2, descriptor: current }] : [];\n });\n if (descriptor.singletonKey) {\n const matches = described.filter(\n ({ descriptor: current }) => current.singletonKey === descriptor.singletonKey\n );\n const directive = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1"\n );\n if (directive) {\n continue;\n }\n const reusable = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element2.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true"\n );\n if (reusable) {\n writeRouteDescriptor(reusable.element, descriptor);\n continue;\n }\n }\n if (described.some(\n ({ descriptor: current }) => current.signature === descriptor.signature || headScriptKeysIntersect(current.scriptKeys, descriptor.scriptKeys)\n )) {\n continue;\n }\n const element = targetDocument.createElement(descriptor.tagName);\n writeRouteDescriptor(element, descriptor);\n targetDocument.head.appendChild(element);\n }\n}\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/routing/client/dom-utils.ts\nvar logger2 = rendererLogger.component("veryfront");\nfunction isInternalLink(target) {\n const href = target.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return false;\n const linkTarget = target.getAttribute("target");\n if (linkTarget === "_blank" || target.hasAttribute("download")) return false;\n return true;\n}\nfunction findAnchorElement(element) {\n let current = element;\n while (current && current.tagName !== "A") {\n current = current.parentElement;\n }\n return current instanceof HTMLAnchorElement ? current : null;\n}\nfunction applyHeadDirectives(container) {\n const targetDocument = container.ownerDocument ?? document;\n const nodes = [...container.querySelectorAll(\'[data-veryfront-head="1"], vf-head\')].filter(\n (node) => typeof node.getAttribute !== "function" || node.getAttribute(HEAD_REACT_OWNER_ATTRIBUTE) !== "1"\n );\n if (!nodes.length) return;\n retireClientHeadOwnership(targetDocument);\n cleanManagedHeadTags(targetDocument);\n for (const wrapper of nodes) {\n const TemplateElement = targetDocument.defaultView?.HTMLTemplateElement ?? globalThis.HTMLTemplateElement;\n const contentSource = TemplateElement && wrapper instanceof TemplateElement ? wrapper.content : wrapper;\n processHeadWrapper(contentSource, targetDocument);\n wrapper.parentElement?.removeChild(wrapper);\n }\n}\nfunction cleanManagedHeadTags(targetDocument) {\n for (const element of targetDocument.head.querySelectorAll(\n `[${HEAD_LEGACY_MANAGED_ATTRIBUTE}="1"]`\n )) {\n element.parentElement?.removeChild(element);\n }\n}\nfunction processHeadWrapper(wrapper, targetDocument) {\n const ElementConstructor = targetDocument.defaultView?.Element ?? globalThis.Element;\n const activeNonce = getManagedHeadNonce(targetDocument);\n for (const node of wrapper.childNodes) {\n if (!ElementConstructor || !(node instanceof ElementConstructor)) continue;\n const tagName = node.tagName.toLowerCase();\n if (headSingletonKey(node) === "meta:charset") continue;\n const clone = targetDocument.createElement(tagName);\n for (const { name, value } of node.attributes) {\n if (name.toLowerCase() !== "nonce") clone.setAttribute(name, value);\n }\n if (activeNonce && (tagName === "script" || tagName === "style" || tagName === "link")) {\n clone.setAttribute("nonce", activeNonce);\n }\n if (node.textContent && !clone.hasAttribute("src")) {\n clone.textContent = node.textContent;\n }\n replaceExistingHeadSingleton(targetDocument, clone);\n clone.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n clone.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(clone);\n }\n}\nfunction headSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n if (tagName !== "meta" && tagName !== "link") return void 0;\n const attributes = /* @__PURE__ */ Object.create(null);\n if (!element.attributes) return void 0;\n for (const { name, value } of element.attributes) attributes[name.toLowerCase()] = value;\n if (tagName === "meta" && attributes["http-equiv"]?.trim().toLowerCase() === "content-type") {\n return "meta:charset";\n }\n return tagName === "meta" ? headMetaSingletonKeyFromRecord(attributes) : headLinkSingletonKeyFromRecord(attributes);\n}\nfunction replaceExistingHeadSingleton(targetDocument, replacement) {\n const singletonKey2 = headSingletonKey(replacement);\n if (!singletonKey2 || singletonKey2 === "meta:charset") return;\n for (const existing of [...targetDocument.head?.children ?? []]) {\n if (headSingletonKey(existing) === singletonKey2) existing.remove();\n }\n}\nfunction manageFocus(container) {\n try {\n const focusElement = container.querySelector("[data-router-focus]") || container.querySelector("main") || container.querySelector("h1");\n focusElement?.focus?.({ preventScroll: true });\n } catch (error) {\n logger2.warn("focus management failed", error);\n }\n}\nfunction extractPageDataFromScript() {\n const pageDataScript = document.querySelector("script[data-veryfront-page]");\n if (!pageDataScript) return null;\n try {\n const content = pageDataScript.textContent;\n if (!content) {\n logger2.warn("Page data script has no content");\n return {};\n }\n return JSON.parse(content);\n } catch (error) {\n logger2.error("Failed to parse page data:", error);\n return null;\n }\n}\nfunction snapshotClientRouteHead(targetDocument = document) {\n const hydrationDataScript = findServerHydrationDataElement(targetDocument);\n if (!hydrationDataScript?.textContent) return [];\n try {\n const hydrationData = JSON.parse(hydrationDataScript.textContent);\n if (typeof hydrationData.managedHeadPayload !== "string") return [];\n const descriptors = deserializeManagedHeadPayload(\n hydrationData.managedHeadPayload\n );\n const aggregated = aggregateManagedHeadDescriptors(descriptors);\n assertManagedHeadDescriptorBudget(aggregated);\n return aggregated.map(managedHeadDescriptorToTransportEntry);\n } catch {\n return [];\n }\n}\nfunction routeRequiresDocumentNavigation(data) {\n return Boolean(\n data.requiresFullDocumentNavigation || data.managedHead?.some((entry) => entry.tagName === "script") || typeof data.html === "string" && / entry.tagName === "script") || typeof root.querySelector === "function" && root.querySelector("script")\n ) {\n pageData = { ...pageData, requiresFullDocumentNavigation: true };\n }\n return { content, pageData, managedHead, dependencyPinningCacheKey };\n}\n\n// src/rendering/client/browser-stubs/config.ts\nvar DEFAULT_PREFETCH_DELAY_MS = 100;\nvar PAGE_TRANSITION_DELAY_MS = 150;\n\n// src/routing/client/navigation-handlers.ts\nvar logger3 = rendererLogger.component("veryfront");\nvar MAX_SCROLL_POSITIONS = 100;\nvar NavigationHandlers = class {\n constructor(prefetchDelay = DEFAULT_PREFETCH_DELAY_MS, prefetchOptions = {}) {\n __publicField(this, "prefetchQueue", /* @__PURE__ */ new Set());\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "scrollPositions", /* @__PURE__ */ new Map());\n __publicField(this, "isPopStateNav", false);\n __publicField(this, "prefetchDelay");\n __publicField(this, "prefetchOptions");\n this.prefetchDelay = prefetchDelay;\n this.prefetchOptions = prefetchOptions;\n }\n createClickHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n const anchor = findAnchorElement(event.target);\n if (!anchor || !isInternalLink(anchor)) return;\n const href = anchor.getAttribute("href");\n if (!href) return;\n event.preventDefault();\n callbacks.onNavigate(href);\n };\n }\n createPopStateHandler(callbacks) {\n return (_event) => {\n this.isPopStateNav = true;\n const { pathname, search, hash } = globalThis.location;\n callbacks.onNavigate(`${pathname}${search}${hash}`);\n };\n }\n createMouseOverHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.tagName !== "A") return;\n const href = event.target.getAttribute("href");\n if (!href || href.startsWith("http") || href.startsWith("#")) return;\n if (!this.shouldPrefetchOnHover(event.target)) return;\n if (this.prefetchQueue.has(href)) return;\n this.prefetchQueue.add(href);\n const timeoutId = setTimeout(() => {\n callbacks.onPrefetch(href);\n this.prefetchQueue.delete(href);\n this.pendingTimeouts.delete(href);\n }, this.prefetchDelay);\n this.pendingTimeouts.set(href, timeoutId);\n };\n }\n shouldPrefetchOnHover(target) {\n const prefetchAttribute = target.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n if (prefetchAttribute === "true") return true;\n return Boolean(this.prefetchOptions.hover);\n }\n saveScrollPosition(path) {\n try {\n if (this.scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = this.scrollPositions.keys().next().value;\n if (oldest) this.scrollPositions.delete(oldest);\n }\n const scrollY = globalThis.scrollY;\n if (typeof scrollY !== "number") {\n logger3.debug("No valid scrollY value available");\n this.scrollPositions.set(path, 0);\n return;\n }\n this.scrollPositions.set(path, scrollY);\n } catch (error) {\n logger3.warn("failed to record scroll position", error);\n }\n }\n getScrollPosition(path) {\n const position = this.scrollPositions.get(path);\n if (position === void 0) {\n logger3.debug(`No scroll position stored for ${path}`);\n return 0;\n }\n return position;\n }\n isPopState() {\n return this.isPopStateNav;\n }\n clearPopStateFlag() {\n this.isPopStateNav = false;\n }\n clear() {\n for (const timeoutId of this.pendingTimeouts.values()) clearTimeout(timeoutId);\n this.pendingTimeouts.clear();\n this.prefetchQueue.clear();\n this.scrollPositions.clear();\n this.isPopStateNav = false;\n }\n};\n\n// src/rendering/client/browser-stubs/error-registry.ts\nfunction createBrowserError(name, fallbackMessage) {\n return {\n create(options = {}) {\n const error = new Error(options.detail ?? fallbackMessage);\n error.name = name;\n Object.assign(error, {\n status: options.status,\n context: options.context\n });\n return error;\n }\n };\n}\nvar NETWORK_ERROR = createBrowserError("NetworkError", "Network request failed");\nvar SECURITY_VIOLATION = createBrowserError("SecurityViolation", "Security violation");\n\n// src/html/html-detection.ts\nfunction isFullHTMLDocument(content) {\n const trimmed = content.trim().toLowerCase();\n return trimmed.startsWith("");\n}\n\n// src/routing/client/page-loader.ts\nvar logger4 = rendererLogger.component("veryfront");\nvar MAX_CACHE_SIZE = 50;\nvar DEPENDENCY_PINNING_RESPONSE_HEADER = "x-veryfront-dependency-pins";\nfunction reloadBrowserDocument(url) {\n if (typeof globalThis.location !== "undefined") {\n globalThis.location.assign(url);\n }\n}\nfunction readDependencyPinningCacheKey(doc) {\n if (!doc) return "off";\n try {\n const hydrationDataElement = findServerHydrationDataElement(doc);\n if (!hydrationDataElement?.textContent) return "off";\n const hydrationData = JSON.parse(hydrationDataElement.textContent);\n return typeof hydrationData.dependencyPinningCacheKey === "string" && hydrationData.dependencyPinningCacheKey.startsWith("on:") ? hydrationData.dependencyPinningCacheKey : "off";\n } catch (error) {\n logger4.debug("Failed to read dependency snapshot from hydration data:", error);\n return "off";\n }\n}\nvar PageLoader = class {\n constructor(doc = typeof document === "undefined" ? void 0 : document, reloadDocument = reloadBrowserDocument) {\n __publicField(this, "cache", /* @__PURE__ */ new Map());\n __publicField(this, "spaCache", /* @__PURE__ */ new Map());\n __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());\n __publicField(this, "pendingSpaRequests", /* @__PURE__ */ new Map());\n /**\n * A loader belongs to the dependency snapshot of the document that created it.\n * Keeping this immutable also prevents cached or in-flight route data from\n * crossing snapshot boundaries if the hydration element is later replaced.\n */\n __publicField(this, "dependencyPinningCacheKey");\n __publicField(this, "reloadDocument");\n __publicField(this, "snapshotRecoveryStarted", false);\n this.dependencyPinningCacheKey = readDependencyPinningCacheKey(doc);\n this.reloadDocument = reloadDocument;\n }\n evictIfFull(map) {\n if (map.size < MAX_CACHE_SIZE) return;\n const oldest = map.keys().next().value;\n if (oldest) map.delete(oldest);\n }\n getCached(path) {\n return this.cache.get(this.snapshotScopedPath(path));\n }\n isCached(path) {\n return this.cache.has(this.snapshotScopedPath(path));\n }\n setCache(path, data) {\n this.evictIfFull(this.cache);\n this.cache.set(this.snapshotScopedPath(path), data);\n }\n clearCache() {\n this.cache.clear();\n this.spaCache.clear();\n this.pendingRequests.clear();\n this.pendingSpaRequests.clear();\n }\n getSpaCached(path) {\n return this.spaCache.get(this.snapshotScopedPath(path));\n }\n isSpaDataCached(path) {\n return this.spaCache.has(this.snapshotScopedPath(path));\n }\n setSpaCache(path, data) {\n this.evictIfFull(this.spaCache);\n this.spaCache.set(this.snapshotScopedPath(path), data);\n }\n async fetchPageData(path, reloadOnSnapshotFailure = true) {\n try {\n return await this.tryFetchJSON(path) ?? await this.fetchAndParseHTML(path);\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n async tryFetchJSON(path) {\n let response;\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const dataPath = navigationUrl.pathname === "/" ? "/index" : navigationUrl.pathname;\n const endpoint = `/_veryfront/data${dataPath}.json${navigationUrl.search}`;\n response = await fetch(endpoint, {\n headers: this.navigationHeaders("client")\n });\n } catch (error) {\n logger4.debug(`JSON fetch failed for ${path}, falling back to HTML:`, error);\n return null;\n }\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) return null;\n let data;\n try {\n data = await response.json();\n } catch (error) {\n logger4.debug(`JSON response was invalid for ${path}, falling back to HTML:`, error);\n return null;\n }\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "route data"\n );\n if (typeof data.html === "string" && isFullHTMLDocument(data.html)) {\n const parsed = parsePageDataFromHTML(data.html);\n this.assertDependencySnapshot(\n parsed.dependencyPinningCacheKey,\n path,\n "route data HTML body"\n );\n return {\n ...parsed.pageData,\n ...data,\n html: parsed.content,\n managedHead: parsed.managedHead\n };\n }\n return routeRequiresDocumentNavigation(data) ? { ...data, requiresFullDocumentNavigation: true } : data;\n }\n async fetchAndParseHTML(path) {\n const response = await fetch(path, {\n headers: this.navigationHeaders("client")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch ${path}`,\n status: response.status,\n context: { path }\n });\n }\n this.assertDependencySnapshot(\n response.headers.get(DEPENDENCY_PINNING_RESPONSE_HEADER),\n path,\n "HTML response"\n );\n const html = await response.text();\n const {\n content,\n pageData,\n managedHead,\n dependencyPinningCacheKey\n } = parsePageDataFromHTML(html);\n this.assertDependencySnapshot(\n dependencyPinningCacheKey,\n path,\n "HTML body"\n );\n return { ...pageData, html: content, managedHead };\n }\n loadPage(path) {\n return this.loadPageWithSnapshotRecovery(path, true);\n }\n loadPageWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getCached(path);\n if (cachedData) {\n logger4.debug(`Loading ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingRequests, async () => {\n const data = await this.fetchPageData(path, false);\n this.setCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetch(path) {\n if (this.isCached(path)) return;\n logger4.debug(`Prefetching ${path}`);\n try {\n await this.loadPageWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n async fetchSpaPageData(path, reloadOnSnapshotFailure = true) {\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const normalizedPath = navigationUrl.pathname === "/" ? "index" : navigationUrl.pathname.replace(/^\\//, "");\n const endpoint = `/_veryfront/page-data/${normalizedPath}.json${navigationUrl.search}`;\n logger4.debug(`Fetching SPA page data from ${endpoint}`);\n const response = await fetch(endpoint, {\n headers: this.navigationHeaders("spa")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for SPA page data ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch SPA page data for ${path}`,\n status: response.status,\n context: { path }\n });\n }\n const data = await response.json();\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "SPA page data"\n );\n return data;\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n loadSpaPageData(path) {\n return this.loadSpaPageDataWithSnapshotRecovery(path, true);\n }\n loadSpaPageDataWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getSpaCached(path);\n if (cachedData) {\n logger4.debug(`Loading SPA data for ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingSpaRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending SPA request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending SPA request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingSpaRequests, async () => {\n const data = await this.fetchSpaPageData(path, false);\n this.setSpaCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetchSpaPageData(path) {\n if (this.isSpaDataCached(path)) return;\n logger4.debug(`Prefetching SPA page data for ${path}`);\n try {\n await this.loadSpaPageDataWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch SPA data for ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n createPendingRequest(path, pendingMap, fetcher) {\n const request = (async () => {\n try {\n return await fetcher();\n } finally {\n pendingMap.delete(path);\n }\n })();\n pendingMap.set(path, request);\n return request;\n }\n snapshotScopedPath(path) {\n return this.dependencyPinningCacheKey.startsWith("on:") ? `${this.dependencyPinningCacheKey}\\0${path}` : path;\n }\n navigationHeaders(type) {\n return {\n "X-Veryfront-Navigation": type,\n ...this.dependencyPinningCacheKey.startsWith("on:") ? {\n [DEPENDENCY_PINNING_RESPONSE_HEADER]: this.dependencyPinningCacheKey\n } : {}\n };\n }\n assertDependencySnapshot(actualCacheKey, path, source) {\n const expectedCacheKey = this.dependencyPinningCacheKey.startsWith("on:") ? this.dependencyPinningCacheKey : void 0;\n const normalizedActualCacheKey = typeof actualCacheKey === "string" ? actualCacheKey : void 0;\n const matches = expectedCacheKey ? normalizedActualCacheKey === expectedCacheKey : normalizedActualCacheKey === void 0 || normalizedActualCacheKey === "off";\n if (matches) return;\n this.failDependencySnapshot(\n path,\n `Dependency snapshot mismatch in ${source} for ${path}`\n );\n }\n failDependencySnapshot(path, detail) {\n throw NETWORK_ERROR.create({\n detail,\n status: 409,\n context: { path }\n });\n }\n withSnapshotRecovery(promise, path, reloadOnSnapshotFailure) {\n return promise.catch((error) => {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n });\n }\n recoverSnapshotFailure(error, path, reloadOnSnapshotFailure) {\n if (!reloadOnSnapshotFailure || typeof error !== "object" || error === null || error.status !== 409) {\n return;\n }\n if (this.snapshotRecoveryStarted) return;\n this.snapshotRecoveryStarted = true;\n try {\n this.reloadDocument(path);\n } catch (reloadError) {\n this.snapshotRecoveryStarted = false;\n logger4.warn(\n `[Veryfront] Failed to reload after dependency snapshot conflict for ${path}`,\n reloadError instanceof Error ? reloadError : new Error(String(reloadError))\n );\n }\n }\n};\n\n// src/security/client/html-sanitizer.ts\nvar SUSPICIOUS_PATTERN_SPECS = [\n { source: String.raw`]*>[\\s\\S]*?<\\/script>`, flags: "gi", name: "inline script" },\n { source: String.raw`javascript:`, flags: "gi", name: "javascript: URL" },\n { source: String.raw`\\bon\\w+\\s*=`, flags: "gi", name: "event handler attribute" },\n { source: String.raw`data:\\s*text\\/html`, flags: "gi", name: "data: HTML URL" }\n];\nfunction createSuspiciousPatterns() {\n return SUSPICIOUS_PATTERN_SPECS.map(({ source, flags, name }) => ({\n pattern: new RegExp(source, flags),\n name\n }));\n}\nfunction isDevMode() {\n const g = globalThis;\n return g.__VERYFRONT_DEV__ === true || g.Deno?.env?.get?.("VERYFRONT_ENV") === "development";\n}\nfunction validateTrustedHtml(html, options = {}) {\n const { allowInlineScripts = false, strict = false, warn = true } = options;\n for (const { pattern, name } of createSuspiciousPatterns()) {\n if (allowInlineScripts && name === "inline script") continue;\n pattern.lastIndex = 0;\n if (!pattern.test(html)) continue;\n if (warn) console.warn(`[Security] Suspicious ${name} detected in server HTML`);\n if (strict || !isDevMode()) {\n throw SECURITY_VIOLATION.create({ detail: `Potentially unsafe HTML: ${name} detected` });\n }\n }\n return html;\n}\n\n// src/routing/client/page-transition.ts\nvar logger5 = rendererLogger.component("veryfront");\nvar PageTransition = class {\n constructor(setupViewportPrefetch) {\n __publicField(this, "setupViewportPrefetch", setupViewportPrefetch);\n __publicField(this, "pendingTransitionTimeout");\n __publicField(this, "pendingRoot");\n }\n destroy() {\n this.cancelPendingTransition();\n }\n cancelPendingTransition() {\n if (this.pendingTransitionTimeout !== void 0) {\n clearTimeout(this.pendingTransitionTimeout);\n this.pendingTransitionTimeout = void 0;\n }\n if (this.pendingRoot) {\n this.pendingRoot.style.opacity = "1";\n this.pendingRoot = void 0;\n }\n }\n updatePage(data, isPopState, scrollY) {\n this.cancelPendingTransition();\n if (routeRequiresDocumentNavigation(data)) {\n throw new TypeError("Scripted routes require a full document navigation");\n }\n const rootElement = document.getElementById("root");\n const preparedHead = prepareClientRouteHeadEntries(data.managedHead, document);\n const retainedTitle = document.title;\n if (!rootElement || data.html === void 0) {\n retireClientHeadOwnership(document);\n applyPreparedClientRouteHeadDescriptors(preparedHead, document);\n this.updateDocumentMetadata(document, data, retainedTitle);\n return;\n }\n const trustedHtml = validateTrustedHtml(String(data.html));\n this.performTransition(\n rootElement,\n data,\n trustedHtml,\n preparedHead,\n retainedTitle,\n isPopState,\n scrollY\n );\n }\n updateDocumentMetadata(targetDocument, data, retainedTitle) {\n updateRouteTitle(data.frontmatter?.title || retainedTitle, targetDocument);\n updateRouteMetaTags(data.frontmatter ?? {}, targetDocument);\n }\n performTransition(rootElement, data, trustedHtml, preparedHead, retainedTitle, isPopState, scrollY) {\n rootElement.style.opacity = "0";\n this.pendingRoot = rootElement;\n this.pendingTransitionTimeout = setTimeout(() => {\n this.pendingTransitionTimeout = void 0;\n this.pendingRoot = void 0;\n try {\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = trustedHtml;\n applyHeadDirectives(rootElement);\n applyPreparedClientRouteHeadDescriptors(preparedHead, rootElement.ownerDocument);\n this.updateDocumentMetadata(rootElement.ownerDocument, data, retainedTitle);\n this.setupViewportPrefetch(rootElement);\n manageFocus(rootElement);\n this.handleScroll(isPopState, scrollY);\n } catch (error) {\n logger5.error("Route transition commit failed; reloading the document", error);\n globalThis.location?.reload();\n } finally {\n rootElement.style.opacity = "1";\n }\n }, PAGE_TRANSITION_DELAY_MS);\n }\n handleScroll(isPopState, scrollY) {\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger5.warn("scroll handling failed", error);\n }\n }\n showError(error) {\n const rootElement = document.getElementById("root");\n if (!rootElement) return;\n const errorDiv = document.createElement("div");\n errorDiv.className = "veryfront-error-page";\n const heading = document.createElement("h1");\n heading.textContent = "Oops! Something went wrong";\n const message = document.createElement("p");\n message.textContent = error.message;\n const button = document.createElement("button");\n button.type = "button";\n button.textContent = "Reload Page";\n button.onclick = () => globalThis.location.reload();\n errorDiv.append(heading, message, button);\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = "";\n rootElement.appendChild(errorDiv);\n }\n setLoadingState(loading) {\n const indicator = document.getElementById("veryfront-loading");\n if (indicator) indicator.style.display = loading ? "block" : "none";\n document.body.classList.toggle("veryfront-loading", loading);\n }\n};\n\n// src/routing/client/viewport-prefetch.ts\nvar logger6 = rendererLogger.component("veryfront");\nvar ViewportPrefetch = class {\n constructor(prefetchCallback, prefetchOptions = {}) {\n __publicField(this, "observer", null);\n __publicField(this, "prefetchCallback");\n __publicField(this, "prefetchOptions");\n this.prefetchCallback = prefetchCallback;\n this.prefetchOptions = prefetchOptions;\n }\n setup(root) {\n try {\n if (!("IntersectionObserver" in globalThis)) return;\n this.observer?.disconnect();\n this.createObserver();\n this.observeLinks(root);\n } catch (error) {\n logger6.debug("setupViewportPrefetch failed", error);\n }\n }\n createObserver() {\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!(entry.target instanceof HTMLAnchorElement)) continue;\n const href = entry.target.getAttribute("href");\n if (href) this.prefetchCallback(href);\n this.observer?.unobserve(entry.target);\n }\n },\n { rootMargin: "200px" }\n );\n }\n observeLinks(root) {\n const anchors = root.querySelectorAll(\'a[href]:not([target="_blank"])\');\n const isViewportEnabled = Boolean(this.prefetchOptions.viewport);\n for (const anchor of anchors) {\n if (!this.shouldObserveAnchor(anchor, isViewportEnabled)) continue;\n this.observer?.observe(anchor);\n }\n }\n shouldObserveAnchor(anchor, isViewportEnabled) {\n const href = anchor.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("#")) return false;\n if (anchor.getAttribute("download")) return false;\n const prefetchAttribute = anchor.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n return prefetchAttribute === "viewport" || isViewportEnabled;\n }\n disconnect() {\n if (!this.observer) return;\n try {\n this.observer.disconnect();\n } catch (error) {\n logger6.warn("prefetchObserver.disconnect failed", error);\n } finally {\n this.observer = null;\n }\n }\n};\n\n// src/rendering/client/router.ts\nvar logger7 = rendererLogger.component("veryfront");\nfunction toHistoryMode(options) {\n if (typeof options === "boolean") return options ? "push" : "none";\n return options?.history ?? "push";\n}\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "navigationSequence", 0);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n // The browser already updated the URL for a popstate, so don\'t touch history.\n onNavigate: (url) => this.navigate(url, { history: "none" }),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n getNavigationStore().setNavigator((href, options2) => this.navigate(href, options2));\n }\n registerNavigationHandler(handler) {\n logger7.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n /**\n * Notify React (and any other) subscribers that a navigation completed —\n * after full page loads, soft same-route changes, and popstate. Delegates to\n * the shared navigation store, the single subscription surface both bundles\n * share.\n */\n notify() {\n getNavigationStore().notify();\n }\n pathnameOf(url) {\n try {\n return new URL(url, this.baseUrl).pathname;\n } catch {\n return url.split("?")[0]?.split("#")[0] || this.currentPath;\n }\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger7.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger7.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger7.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger7.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData) {\n const managedHead = snapshotClientRouteHead(document);\n this.pageLoader.setCache(this.currentPath, {\n ...pageData,\n managedHead,\n ...managedHead.some((entry) => entry.tagName === "script") || document.getElementById("root")?.querySelector("script") ? { requiresFullDocumentNavigation: true } : {}\n });\n }\n }\n /**\n * Navigate to a URL. `options` selects the history behaviour: `{ history:\n * "push" }` (default), `"replace"`, or `"none"` (the URL already reflects the\n * target, as after popstate). A boolean is accepted for backward\n * compatibility — `true` pushes, `false` maps to `"none"`.\n */\n async navigate(url, options) {\n logger7.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n const navigationId = ++this.navigationSequence;\n this.pageTransition.cancelPendingTransition();\n this.pageTransition.setLoadingState(false);\n const history = toHistoryMode(options);\n const sameRoute = this.pathnameOf(url) === this.pathnameOf(this.currentPath);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (history === "replace") globalThis.history.replaceState({}, "", url);\n else if (history === "push") globalThis.history.pushState({}, "", url);\n if (sameRoute && !this.shouldRevalidate(url, sameRoute)) {\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = url;\n this.notify();\n this.options.onComplete?.(url);\n this.options.onNavigate?.(url);\n return;\n }\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url, navigationId);\n } else {\n if (await this.loadPage(url, true, navigationId)) return;\n }\n if (!this.isCurrentNavigation(navigationId)) return;\n this.notify();\n this.options.onNavigate?.(url);\n }\n isCurrentNavigation(navigationId) {\n return navigationId === this.navigationSequence;\n }\n /**\n * Whether a navigation should refetch page data. A route change always does;\n * a same-route (query/hash-only) change consults `options.shouldRevalidate`,\n * defaulting to `true` so server data is never shown stale.\n */\n shouldRevalidate(nextUrl, sameRoute) {\n const policy = this.options.shouldRevalidate;\n if (!policy) return true;\n return policy({ currentHref: this.currentPath, nextHref: nextUrl, sameRoute });\n }\n async loadSpaPage(path, navigationId) {\n logger7.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n if (!this.isCurrentNavigation(navigationId)) return;\n await this.spaNavigationHandler?.(spaData);\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger7.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n /** Returns true when navigation was handed to the browser document loader. */\n async loadPage(path, updateUI = true, navigationId) {\n if (this.pageLoader.isCached(path)) {\n logger7.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.pageTransition.setLoadingState(false);\n this.options.onComplete?.(path);\n return false;\n }\n logger7.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.options.onComplete?.(path);\n return false;\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n return false;\n } finally {\n if (this.isCurrentNavigation(navigationId)) this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data, targetPath) {\n if (!this.root) return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(targetPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n this.navigationSequence++;\n this.pageTransition.setLoadingState(false);\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nfunction boot(options = {}) {\n if (typeof window === "undefined" || !globalThis.document) return null;\n const globalWithRouter = globalThis;\n if (globalWithRouter.veryFrontRouter) return globalWithRouter.veryFrontRouter;\n const { slug: _slug, ...routerOptions } = options;\n const router = new VeryfrontRouter(routerOptions);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalWithRouter.veryFrontRouter = router;\n return router;\n}\nif (typeof window !== "undefined" && globalThis.document) {\n boot();\n}\nexport {\n VeryfrontRouter,\n boot\n};\n'; export const CLIENT_PREFETCH_BUNDLE: string | undefined = - 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar RegExpSymbolReplace = RegExp.prototype[Symbol.replace];\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; + 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar RegExpTest = RegExp.prototype.test;\nvar RegExpSymbolReplace = RegExp.prototype[Symbol.replace];\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index 1ebd93f96b..f335ea0fbe 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,br=Array.prototype.filter,lt=Array.prototype.join,Tr=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=RegExp.prototype[Symbol.replace];function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new T("RSC",G),to=new T("PREFETCH",G),no=new T("HYDRATE",G),ro=new T("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":Te(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},yo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var ho=Array.prototype,Eo=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Ro=Object.hasOwn,_o=Object.prototype,Bt=Set,jt=decodeURIComponent,b=URL,xo=Number.isFinite,So=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,Ao=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,bo=_(b.prototype,"host").get,To=_(b.prototype,"origin").get,en=_(b.prototype,"password").get,Co=_(b.prototype,"pathname").get,wo=_(b.prototype,"protocol").get,tn=_(b.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Io=64*1024,bn=256,Tn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,bn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${Tn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var ko=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Fo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Vo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),zo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Bo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),jo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Go=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Wo=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Ko=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Yo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Xo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,mi=ir*2;var hi=64*1024,Ei=1024*1024,Ri=1024*1024;var _i=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; + 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=RegExp.prototype.test,Or=RegExp.prototype[Symbol.replace];function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),no=new b("PREFETCH",G),ro=new b("HYDRATE",G),oo=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},mo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var Eo=Array.prototype,Ro=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,_o=Object.hasOwn,xo=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,So=Number.isFinite,Ao=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,To=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,bo=_(T.prototype,"host").get,Co=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,wo=_(T.prototype,"pathname").get,Do=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Po=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var vo=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Fo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Vo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),zo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Bo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),jo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Go=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Wo=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Ko=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Yo=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Xo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Jo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,hi=ir*2;var Ei=64*1024,Ri=1024*1024,_i=1024*1024;var xi=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=RegExp.prototype[Symbol.replace];var ir=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Cr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Cr as consumeNdjsonStream,ft as getContainer};\n'; + 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=RegExp.prototype.test,Jn=RegExp.prototype[Symbol.replace];var ar=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Ir(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Ir as consumeNdjsonStream,ft as getContainer};\n'; diff --git a/src/transforms/import-rewriter/url-builder.test.ts b/src/transforms/import-rewriter/url-builder.test.ts index e4a3d1a63b..0e36c93d01 100644 --- a/src/transforms/import-rewriter/url-builder.test.ts +++ b/src/transforms/import-rewriter/url-builder.test.ts @@ -374,6 +374,32 @@ describe("transforms/import-rewriter/url-builder", () => { "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", ); }); + + it("should use captured test for escaped alias extension checks", () => { + const originalTest = Object.getOwnPropertyDescriptor(RegExp.prototype, "test")!; + let poisonCalls = 0; + try { + Object.defineProperty(RegExp.prototype, "test", { + ...originalTest, + value() { + poisonCalls += 1; + throw new Error("poisoned RegExp.prototype.test"); + }, + }); + + assertEquals( + buildCrossProjectUrl("proj", "1.0.0", "components/Button.tsx"), + "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", + ); + assertEquals( + buildCrossProjectUrl("proj", "1.0.0", "components/Button"), + "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", + ); + assertEquals(poisonCalls, 0); + } finally { + Object.defineProperty(RegExp.prototype, "test", originalTest); + } + }); }); describe("buildVeryfrontModuleUrl", () => { diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index baf96c7acd..fdb7130943 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -34,7 +34,13 @@ type EsmShOptions = { const ObjectEntries = Object.entries; const ReflectApply = Reflect.apply; +const RegExpTest = RegExp.prototype.test; const RegExpSymbolReplace = RegExp.prototype[Symbol.replace]; +const MODULE_EXTENSION_PATTERN = /\.(js|mjs|jsx|ts|tsx|mdx)$/; + +function regexTest(search: RegExp, value: string): boolean { + return ReflectApply(RegExpTest, search, [value]) as boolean; +} function regexReplace( value: string, @@ -445,7 +451,7 @@ export function buildCrossProjectUrl( version: string | null, path: string, ): string { - const modulePath = /\.(js|mjs|jsx|ts|tsx|mdx)$/.test(path) ? path : `${path}.tsx`; + const modulePath = regexTest(MODULE_EXTENSION_PATTERN, path) ? path : `${path}.tsx`; const projectRef = version && version !== "latest" ? `${projectSlug}@${version}` : projectSlug; return `/_vf_modules/_cross/${projectRef}/@/${modulePath}`; } diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 10727dbc8d..7f38db6ed1 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -83,6 +83,13 @@ import { bar } from "./local.js"; assertEquals(result.paths, ["/_vf_modules/components/Lazy.js"]); }); + it("detects unresolved dynamic _vf_modules imports in non-interpolated templates", () => { + const code = "export const load = () => import(`/_vf_modules/components/Lazy.js`);"; + const result = hasUnresolvedImports(code); + assertEquals(result.count, 1); + assertEquals(result.paths, ["/_vf_modules/components/Lazy.js"]); + }); + it("returns empty for normal resolved file:// imports", () => { const code = `import { foo } from "file:///home/user/.cache/veryfront-mdx-esm/proj/vfmod.mjs";`; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index df32d63b09..e1e52102ce 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -243,11 +243,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(specifiers(`/* import("./a.js") */ const x = 1;`), []); }); - // A template literal with no substitution is a valid specifier, but the - // scanner only treats quoted strings as literals, so it stays unresolved - // rather than being rewritten from a form it cannot verify. - it("skips a template-literal specifier", () => { - assertEquals(specifiers("import(`./a.js`);"), []); + it("finds a non-interpolated template-literal specifier", () => { + const source = "import(`./a.js`);"; + const [span] = findDynamicImportSpans(source, matchRelative, UNBOUNDED); + assertEquals(span?.original, "`./a.js`"); + assertEquals(span?.path, "./a.js"); + assertEquals( + replaceSourceSpans(source, [ + { start: span!.start, end: span!.end, replacement: `"file:///out/a.js"` }, + ]), + `import("file:///out/a.js");`, + ); }); it("spans only the quoted specifier when comments surround it", () => { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 0ad35b6c47..9444ed1468 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -167,6 +167,33 @@ function readQuotedSpecifier( return null; } +function readLiteralSpecifier( + source: string, + literalIndex: number, +): { end: number; specifier: string } | null { + const quote = source[literalIndex]; + if (quote === '"' || quote === "'") return readQuotedSpecifier(source, literalIndex); + if (quote !== "`") return null; + + let cursor = literalIndex + 1; + while (cursor < source.length) { + if (source[cursor] === "\\") { + cursor += 2; + continue; + } + if (source[cursor] === "$" && source[cursor + 1] === "{") return null; + if (source[cursor] === "`") { + return { + end: cursor + 1, + specifier: source.slice(literalIndex + 1, cursor), + }; + } + cursor++; + } + + return null; +} + function findFromSpan( source: string, statementStart: number, @@ -322,9 +349,9 @@ export function findDynamicImportSpans( continue; } - const quoteIndex = skipWhitespaceAndComments(source, parenIndex + 1); - const quoted = readQuotedSpecifier(source, quoteIndex); - if (!quoted) { + const literalIndex = skipWhitespaceAndComments(source, parenIndex + 1); + const literal = readLiteralSpecifier(source, literalIndex); + if (!literal) { cursor = parenIndex + 1; continue; } @@ -332,21 +359,21 @@ export function findDynamicImportSpans( // The literal must be the whole first argument. `)` closes the call and `,` // starts the import-attributes argument; anything else (`+`, a template // continuation, a ternary) means the runtime specifier is not this string. - const afterSpecifier = skipWhitespaceAndComments(source, quoted.end); + const afterSpecifier = skipWhitespaceAndComments(source, literal.end); const isWholeArgument = source[afterSpecifier] === ")" || source[afterSpecifier] === ","; - const matchedPath = isWholeArgument ? matcher(quoted.specifier) : null; + const matchedPath = isWholeArgument ? matcher(literal.specifier) : null; if (matchedPath) { spans.push({ - original: source.slice(quoteIndex, quoted.end), + original: source.slice(literalIndex, literal.end), path: matchedPath, - start: quoteIndex, - end: quoted.end, + start: literalIndex, + end: literal.end, }); if (spans.length >= maxMatches) return spans; } - cursor = quoted.end; + cursor = literal.end; } return spans; From 84809916cf67282601c5d2c5f13207c3e750d417 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:45:35 +0200 Subject: [PATCH 017/104] Keep MDX processor failure tests type-complete The adversarial MDX processor double intentionally throws before source parsing. The ContentProcessor contract now includes plugin accessors, so the double must implement no-op versions to keep the lint typecheck focused on the behavior under test.\n\nConstraint: PR #3723 must preserve non-source framework failures while CI typechecks tests.\nRejected: Loosening the test double with casts | would hide future ContentProcessor contract drift.\nConfidence: high\nScope-risk: narrow\nDirective: Keep these no-op accessors behaviorless; this test is about failure classification, not plugin plumbing.\nTested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/compiler/mdx-compiler.test.ts src/transforms/md/compiler/md-compiler.test.ts\nTested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno task lint:test-typecheck\nTested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno task docs:errors:check\nTested: PATH=/tmp/deno-2.7.7-aarch64-apple-darwin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/codex-path:/Users/kojiwakayama/.codex/tmp/arg0/codex-arg0xChlGW:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx09/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx13/deno277/bin:/private/tmp/claude-501/-Users-kojiwakayama-Documents-CODE-veryfront-platform-veryfront-code/9f9d23dd-dfdc-4463-9a99-bd5a663046c9/scratchpad/idx26-deno277/bin:/Users/kojiwakayama/Documents/CODE/agent-lab/.venv/bin:/Users/kojiwakayama/.veryfront/bin:/Users/kojiwakayama/.opencode/bin:/Users/kojiwakayama/.local/bin:/Users/kojiwakayama/.antigravity/antigravity/bin:/opt/homebrew/share/google-cloud-sdk/bin:/opt/homebrew/opt/openjdk/bin:/Users/kojiwakayama/.bun/bin:/Users/kojiwakayama/.krew/bin:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Users/kojiwakayama/.nvm/versions/node/v24.18.0/bin:/Users/kojiwakayama/.cargo/bin:/Applications/Docker.app/Contents/Resources/bin/:/Users/kojiwakayama/.lmstudio/bin:/Applications/Warp.app/Contents/Resources/bin deno task docs:api-reference:check\nTested: git diff --check\nNot-tested: Full pre-push hook beyond the push-time run --- src/transforms/mdx/compiler/mdx-compiler.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 602139e9ae..f29194d053 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -110,6 +110,12 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { compileMarkdown() { throw new Error("not used"); }, + getRemarkPlugins() { + return []; + }, + getRehypePlugins() { + return []; + }, } satisfies ContentProcessor, ); From a8828938edfdeef6d78d04cdf5b413c39886b64a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 22:56:13 +0200 Subject: [PATCH 018/104] Materialize side-effect module dependencies before caching Nested module discovery handled import-from and dynamic import specifiers but skipped bare side-effect imports. Alias rewrites can emit those side-effect specifiers as _vf_modules paths, so skipping them allowed a cached parent module to execute before its dependency was fetched and rewritten. The fix runs the existing side-effect scanner through the nested import path and records that replacement shape explicitly so static side-effect statements become import "file://..." rather than a malformed from-clause. Constraint: PR review thread PRRT_kwDOQaPiP86ZacEG requires bare side-effect alias imports to be fetched and materialized before the parent module is cached/executed Rejected: Treating side-effect imports as import-from spans | the replacement text is different and would produce invalid syntax Confidence: high Scope-risk: narrow Tested: Pinned Deno 2.7.7 red regressions for nested side-effect discovery/materialization and backtick side-effect scanning; focused tests passed; touched-file fmt/lint/check passed; expanded src/transforms/mdx suite passed; generate:manifests:check passed; git diff --check Not-tested: Full pre-push before commit; it will run on the guarded non-force push --- .../module-fetcher/nested-imports.test.ts | 44 +++++++++ .../module-fetcher/nested-imports.ts | 90 +++++++++++++++---- src/transforms/mdx/esm-module-loader/types.ts | 1 + .../utils/source-spans.test.ts | 10 +++ .../esm-module-loader/utils/source-spans.ts | 16 ++-- 5 files changed, 137 insertions(+), 24 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 7f38db6ed1..ad30fffb09 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -54,6 +54,18 @@ import { bar } from "./local.js"; assertEquals(result.vfModules[0]!.path.startsWith("file://"), false); } }); + + it("finds bare side-effect _vf_modules imports", () => { + const code = [ + `import "/_vf_modules/styles/theme.css";`, + `import '/_vf_modules/polyfills/runtime.js';`, + ].join("\n"); + const result = findNestedImports(code); + assertEquals(result.vfModules.map((module) => module.path), [ + "_vf_modules/styles/theme.css", + "_vf_modules/polyfills/runtime.js", + ]); + }); }); describe("hasUnresolvedImports", () => { @@ -208,6 +220,38 @@ import { bar } from "./local.js"; ); }); + it("materializes bare side-effect _vf_modules imports before caching the module", async () => { + const calls: Array<{ path: string; parent?: string }> = []; + const result = await resolveNestedModuleImports({ + moduleCode: [ + `import "/_vf_modules/styles/theme.css";`, + `import '/_vf_modules/polyfills/runtime.js';`, + `export const ready = true;`, + ].join("\n"), + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path, parent) => { + calls.push({ path, parent }); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + }); + + assertEquals(calls, [ + { path: "_vf_modules/styles/theme.css", parent: "_vf_modules/pages/index.js" }, + { path: "_vf_modules/polyfills/runtime.js", parent: "_vf_modules/pages/index.js" }, + ]); + assertEquals( + result, + [ + `import "file:///cache/_vf_modules__styles__theme.css.mjs";`, + `import "file:///cache/_vf_modules__polyfills__runtime.js.mjs";`, + `export const ready = true;`, + ].join("\n"), + ); + }); + it("keeps dynamic import syntax when non-strict missing modules use stubs", async () => { const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-stub-cache-" }); diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 206b91063c..3b813fc8e9 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -10,6 +10,7 @@ import { createStubModule } from "../utils/stub-module.ts"; import { findDynamicImportSpans, findStaticImportFromSpans, + findStaticSideEffectImportSpans, replaceSourceSpans, type SourceSpanReplacement, } from "../utils/source-spans.ts"; @@ -27,6 +28,15 @@ function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/[^?]+)(?:\?.*)?$/)?.[1] ?? null; } +type NestedImportSpan = { + original: string; + path: string; + start: number; + end: number; + isDynamic?: boolean; + isSideEffect?: boolean; +}; + /** * Find nested module imports in code. * Matches both /_vf_modules/... and file:///_vf_modules/... patterns. @@ -34,19 +44,11 @@ function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { export function findNestedImports( moduleCode: string, ): { - vfModules: Array< - { original: string; path: string; start: number; end: number; isDynamic?: boolean } - >; - relative: Array< - { original: string; path: string; start: number; end: number; isDynamic?: boolean } - >; + vfModules: NestedImportSpan[]; + relative: NestedImportSpan[]; } { - const vfModules: Array< - { original: string; path: string; start: number; end: number; isDynamic?: boolean } - > = []; - const relative: Array< - { original: string; path: string; start: number; end: number; isDynamic?: boolean } - > = []; + const vfModules: NestedImportSpan[] = []; + const relative: NestedImportSpan[] = []; for ( const { original, path: rawPath, start, end } of findStaticImportFromSpans( @@ -81,6 +83,23 @@ export function findNestedImports( }); } + for ( + const { original, path: rawPath, start, end } of findStaticSideEffectImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ) { + // Strip file:// prefix and leading slashes to get clean _vf_modules/... path + vfModules.push({ + original, + path: rawPath.replace(/^(?:file:\/\/)?\/+/, ""), + start, + end, + isSideEffect: true, + }); + } + for ( const { original, path, start, end } of findStaticImportFromSpans( moduleCode, @@ -112,6 +131,22 @@ export function findNestedImports( }); } + for ( + const { original, path, start, end } of findStaticSideEffectImportSpans( + moduleCode, + (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1], + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ) { + relative.push({ + original, + path, + start, + end, + isSideEffect: true, + }); + } + return { vfModules, relative }; } @@ -125,6 +160,11 @@ export function hasUnresolvedImports(moduleCode: string): { count: number; paths matchUnresolvedVfModuleSpecifier, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ), + ...findStaticSideEffectImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ), ...findDynamicImportSpans( moduleCode, matchUnresolvedVfModuleSpecifier, @@ -151,14 +191,27 @@ export async function processNestedImports( const replacements: SourceSpanReplacement[] = []; for ( - const { original, start, end, isDynamic, nestedFilePath, nestedPath, relativePath } of results + const { + original, + start, + end, + isDynamic, + isSideEffect, + nestedFilePath, + nestedPath, + relativePath, + } of results ) { if (nestedFilePath) { replacements.push({ start, end, expected: original, - replacement: isDynamic ? `"file://${nestedFilePath}"` : `from "file://${nestedFilePath}"`, + replacement: isDynamic + ? `"file://${nestedFilePath}"` + : isSideEffect + ? `import "file://${nestedFilePath}"` + : `from "file://${nestedFilePath}"`, }); continue; } @@ -180,7 +233,11 @@ export async function processNestedImports( start, end, expected: original, - replacement: isDynamic ? `"file://${stubPath}"` : `from "file://${stubPath}"`, + replacement: isDynamic + ? `"file://${stubPath}"` + : isSideEffect + ? `import "file://${stubPath}"` + : `from "file://${stubPath}"`, }); } } @@ -278,11 +335,12 @@ export async function resolveNestedModuleImports( const nestedResults: NestedImportResult[] = await parallelMap( allImports, - async ({ original, path, start, end, isDynamic, key }) => ({ + async ({ original, path, start, end, isDynamic, isSideEffect, key }) => ({ original, start, end, isDynamic, + isSideEffect, nestedFilePath: await input.fetchAndCacheModule( path, input.parentBasePath ?? input.normalizedPath, diff --git a/src/transforms/mdx/esm-module-loader/types.ts b/src/transforms/mdx/esm-module-loader/types.ts index d0b19b246f..6916406def 100644 --- a/src/transforms/mdx/esm-module-loader/types.ts +++ b/src/transforms/mdx/esm-module-loader/types.ts @@ -57,6 +57,7 @@ export interface NestedImportResult { start: number; end: number; isDynamic?: boolean; + isSideEffect?: boolean; nestedFilePath: string | null; nestedPath?: string; relativePath?: string; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index e1e52102ce..62fbcc8e49 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -308,5 +308,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ["./value.js"], ); }); + + it("finds a non-interpolated template-literal side-effect specifier", () => { + const [span] = findStaticSideEffectImportSpans( + "import `./value.js`;", + matchRelative, + UNBOUNDED, + ); + assertEquals(span?.original, "import `./value.js`"); + assertEquals(span?.path, "./value.js"); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 9444ed1468..3cfec343ab 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -407,25 +407,25 @@ export function findStaticSideEffectImportSpans( continue; } - const quoteIndex = skipWhitespace(source, cursor + "import".length); - const quoted = readQuotedSpecifier(source, quoteIndex); - if (!quoted) { - cursor = nextStatementCursor(source, quoteIndex); + const literalIndex = skipWhitespace(source, cursor + "import".length); + const literal = readLiteralSpecifier(source, literalIndex); + if (!literal) { + cursor = nextStatementCursor(source, literalIndex); continue; } - const matchedPath = matcher(quoted.specifier); + const matchedPath = matcher(literal.specifier); if (matchedPath) { spans.push({ - original: source.slice(cursor, quoted.end), + original: source.slice(cursor, literal.end), path: matchedPath, start: cursor, - end: quoted.end, + end: literal.end, }); if (spans.length >= maxMatches) return spans; } - cursor = quoted.end; + cursor = literal.end; } return spans; From 578b6c8324db2d9a62600bd64addbd127ea15272 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:07:21 +0200 Subject: [PATCH 019/104] Keep malformed MDX frontmatter in tenant error policy MDX frontmatter is parsed before the MDX compiler can attach its structured source metadata. Recognize only SyntaxError failures proven to originate in the YAML frontmatter stack so tenant content is downgraded without hiding processor failures. Constraint: YAML frontmatter failures do not carry MDX source or rule identifiers. Rejected: Classify every SyntaxError with line and column data | operational processors can throw the same shape. Confidence: high Scope-risk: narrow Directive: Keep operational MDX and YAML processor failures at error severity unless their source seam is explicit. Tested: MDX and Markdown compiler suites (2 tests, 34 steps); targeted fmt, lint, check, and diff checks. Not-tested: Alternate third-party YAML adapters that omit the registered stack paths. --- .../mdx/compiler/mdx-compiler.test.ts | 19 +++++++++++++++++++ src/transforms/mdx/compiler/mdx-compiler.ts | 8 +++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index f29194d053..6eec65a99c 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -99,6 +99,25 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { assertEquals(error.category, "BUILD"); }); + it("classifies tenant MDX frontmatter failures explicitly", async () => { + const error = await assertRejects( + () => + compileMDXRuntime( + "production", + "/project", + "---\ntitle: [unterminated\n---\n# Content", + undefined, + "broken-frontmatter.mdx", + "server", + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "mdx-compile-error"); + assertEquals(error.category, "BUILD"); + }); + it("preserves non-source processor failures", async () => { const previous = tryResolveContract("ContentProcessor"); registerContract( diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index 301721f059..efcd130059 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -18,11 +18,17 @@ function isMdxSourceCompileError(error: Error): boolean { ruleId?: unknown; source?: unknown; }; - return typeof candidate.source === "string" && + const isMdxParserError = typeof candidate.source === "string" && /(?:^|-)mdx(?:-|$)|micromark|remark|recma|rehype/.test(candidate.source) && typeof candidate.ruleId === "string" && Number.isSafeInteger(candidate.line) && Number.isSafeInteger(candidate.column); + const isYamlFrontmatterError = error.name === "SyntaxError" && + /\bline \d+, column \d+\b/i.test(error.message) && + (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || + error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || + error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); + return isMdxParserError || isYamlFrontmatterError; } export function compileMDXRuntime( From 7de5d71e654c309ad5d59a6df6a50d624a449275 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:26:50 +0200 Subject: [PATCH 020/104] Preserve HTTP fallback side-effect imports The HTTP fallback path already discovered side-effect imports but dropped that marker before rewriting nested module specifiers. Carrying it through keeps bare import syntax valid while preserving existing dynamic and imported binding rewrites. Constraint: Exact-head Codex review found the HTTP fallback path was separate from the nested resolver path already covered. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: deno fmt --check src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: deno lint src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: deno check src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: git diff --check --- .../module-fetcher/http-fetcher.test.ts | 34 +++++++++++++++++++ .../module-fetcher/http-fetcher.ts | 18 ++++++---- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts index e060c65831..7ea7ded749 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts @@ -113,6 +113,40 @@ describe("module-fetcher/http-fetcher", () => { } }); + it("preserves side-effect import syntax in the HTTP fallback", async () => { + const fetchedPaths: string[] = []; + const result = await fetchModuleViaHTTP( + "_vf_modules/pages/index.js", + { env: { get: () => undefined } } as unknown as RuntimeAdapter, + (path) => { + fetchedPaths.push(path); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + { debug: () => {}, warn: () => {} } as unknown as Logger, + "docs", + true, + undefined, + { + fetchFn: (() => + Promise.resolve( + new Response([ + `import "/_vf_modules/setup.js";`, + `export const ready = true;`, + ].join("\n")), + )) as typeof fetch, + }, + ); + + assertEquals(fetchedPaths, ["_vf_modules/setup.js"]); + assertEquals( + result, + [ + `import "file:///cache/_vf_modules__setup.js.mjs";`, + `export const ready = true;`, + ].join("\n"), + ); + }); + it("uses the request origin for pinned local module fetches", async () => { const logger = { debug: () => {}, warn: () => {} } as unknown as Logger; const adapter = { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index 7a2ea3b24f..b15a8e4d32 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -237,20 +237,22 @@ export async function fetchModuleViaHTTP( const { vfModules, relative } = findNestedImports(moduleCode); const allImports = [ - ...vfModules.map(({ original, path, start, end, isDynamic }) => ({ + ...vfModules.map(({ original, path, start, end, isDynamic, isSideEffect }) => ({ original, path, start, end, isDynamic, + isSideEffect, key: "nestedPath" as const, })), - ...relative.map(({ original, path, start, end, isDynamic }) => ({ + ...relative.map(({ original, path, start, end, isDynamic, isSideEffect }) => ({ original, path, start, end, isDynamic, + isSideEffect, key: "relativePath" as const, })), ]; @@ -258,9 +260,9 @@ export async function fetchModuleViaHTTP( const results = await parallelMap( allImports, - async ({ original, path, start, end, isDynamic, key }) => { + async ({ original, path, start, end, isDynamic, isSideEffect, key }) => { const nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); - return { original, start, end, isDynamic, nestedFilePath, [key]: path }; + return { original, start, end, isDynamic, isSideEffect, nestedFilePath, [key]: path }; }, { semaphore: new Semaphore(MAX_MDX_MODULE_TRANSFORM_CONCURRENCY), @@ -268,13 +270,17 @@ export async function fetchModuleViaHTTP( ); const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, isDynamic, nestedFilePath } of results) { + for (const { original, start, end, isDynamic, isSideEffect, nestedFilePath } of results) { if (nestedFilePath) { replacements.push({ start, end, expected: original, - replacement: isDynamic ? `"file://${nestedFilePath}"` : `from "file://${nestedFilePath}"`, + replacement: isDynamic + ? `"file://${nestedFilePath}"` + : isSideEffect + ? `import "file://${nestedFilePath}"` + : `from "file://${nestedFilePath}"`, }); } } From 0f8951e092ef6c5296cc99e4de3f9274454c9ea4 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:36:51 +0200 Subject: [PATCH 021/104] Keep infrastructure SSG failures at error level The SSG registry slug can wrap both tenant render failures and output filesystem failures. Removing it from slug-only tenant classification keeps ambiguous static-generation wrappers at error severity while retaining explicit compiler and parser evidence. Constraint: SSG_GENERATION_ERROR wraps rendering, mkdir, and write failures at the same boundary. Rejected: Infer tenant ownership from the SSG slug | filesystem and framework failures share that slug. Confidence: high Scope-risk: narrow Directive: Add an explicit tenant-source discriminator before downgrading any SSG wrapper. Tested: application error and render pipeline behavior suites, 16 tests and 38 steps Tested: pinned Deno 2.7.7 fmt, lint, check, and git diff check --- src/observability/application-errors.test.ts | 14 +++++++++++++- src/observability/application-errors.ts | 1 - .../orchestrator/module-loader/build-failure.ts | 1 - .../orchestrator/pipeline.behavior.test.ts | 12 +++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index ba5d5496c0..0eb7950afd 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -24,6 +24,7 @@ import { MDX_COMPILE_ERROR, RENDER_ERROR, SOURCEMAP_ERROR, + SSG_GENERATION_ERROR, toError, TYPESCRIPT_ERROR, } from "#veryfront/errors"; @@ -189,6 +190,11 @@ it("application error reporter downgrades tenant build errors to tagged warnings const frameworkBundleError = BUNDLE_ERROR.create({ detail: "Failed to regenerate framework bundle cache entry: ", }); + const ssgInfrastructureError = SSG_GENERATION_ERROR.create({ + detail: "Failed to write generated page output", + cause: Object.assign(new Error("No space left on device"), { code: "ENOSPC" }), + context: { route: "/" }, + }); assertEquals( captureApplicationError(compileError, { boundary: "ssr.render" }), @@ -234,8 +240,12 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(legacyBuildError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(ssgInfrastructureError, { boundary: "ssr.render" }), + "event-id", + ); - assertEquals(captures.length, 11); + assertEquals(captures.length, 12); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -261,6 +271,8 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[9]?.context.level, undefined); assertEquals(captures[10]?.context.errorClass, undefined); assertEquals(captures[10]?.context.level, undefined); + assertEquals(captures[11]?.context.errorClass, undefined); + assertEquals(captures[11]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 9298b0e7d2..2068df7276 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -235,7 +235,6 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", - "ssg-generation-error", "compilation-error", ]); diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index b9d9237108..b9d3d2bacb 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -28,7 +28,6 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", - "ssg-generation-error", "compilation-error", ]); diff --git a/src/rendering/orchestrator/pipeline.behavior.test.ts b/src/rendering/orchestrator/pipeline.behavior.test.ts index e68ebccecd..5bb9a430cc 100644 --- a/src/rendering/orchestrator/pipeline.behavior.test.ts +++ b/src/rendering/orchestrator/pipeline.behavior.test.ts @@ -5,7 +5,7 @@ import { FakeTime } from "#std/testing/time"; import { RenderPipeline, type RenderPipelineConfig } from "./pipeline.ts"; import type { RenderOptions } from "./types.ts"; import { isTenantBuildFailure, markBuildFailure } from "./module-loader/build-failure.ts"; -import { COMPILATION_ERROR, createError, toError } from "#veryfront/errors"; +import { COMPILATION_ERROR, createError, SSG_GENERATION_ERROR, toError } from "#veryfront/errors"; import { cachePageCss, getPageCssCacheKey } from "./css-cache.ts"; import { cacheCSSAsync, hashCSS } from "#veryfront/html/styles-builder/index.ts"; import { RELEASE_ASSET_MANIFEST_ENV_FLAG } from "#veryfront/release-assets/constants.ts"; @@ -732,6 +732,16 @@ describe("RenderPipeline behavior", () => { assertEquals(tenantBuildFailureFlag(error), false); }); + it("does not infer tenant source from an SSG wrapper", () => { + const infrastructureError = markBuildFailure(SSG_GENERATION_ERROR.create({ + detail: "Failed to write generated page output", + cause: Object.assign(new Error("No space left on device"), { code: "ENOSPC" }), + context: { route: "/" }, + })); + + assertEquals(isTenantBuildFailure(infrastructureError), false); + }); + it("does not report a module-scope runtime throw as a build failure", async () => { const error = await rejectLoad(pipelineWithFailingPageModule(() => { throw new Error("Missing API key"); From 84b95e5da961e29abaa600659fe61982f3eb16d6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:42:29 +0200 Subject: [PATCH 022/104] Resolve executable template dynamic imports Dynamic import discovery must ignore import-like template text without skipping JavaScript that runs inside template substitutions. The scanner now descends into bounded substitution ranges and preserves the existing literal-only rewrite contract. Constraint: Exact-head Codex review found unresolved _vf_modules imports inside template substitutions. Rejected: Treat all template literal contents as executable | would rewrite import-looking text that is only template output. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fallback.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Tested: deno fmt --check src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: deno lint src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: deno check src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts Tested: git diff --check --- .../module-fetcher/nested-imports.test.ts | 29 +++ .../utils/source-spans.test.ts | 20 ++ .../esm-module-loader/utils/source-spans.ts | 197 +++++++++++++++--- 3 files changed, 220 insertions(+), 26 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index ad30fffb09..6affa0fe46 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -220,6 +220,35 @@ import { bar } from "./local.js"; ); }); + it("materializes dynamic _vf_modules imports inside template substitutions", async () => { + const calls: Array<{ path: string; parent?: string }> = []; + const result = await resolveNestedModuleImports({ + moduleCode: [ + 'export const html = `

${await import("/_vf_modules/components/Lazy.js")}

`;', + 'export const text = `import("/_vf_modules/components/TextOnly.js")`;', + ].join("\n"), + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path, parent) => { + calls.push({ path, parent }); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + }); + + assertEquals(calls, [ + { path: "_vf_modules/components/Lazy.js", parent: "_vf_modules/pages/index.js" }, + ]); + assertEquals( + result, + [ + 'export const html = `

${await import("file:///cache/_vf_modules__components__Lazy.js.mjs")}

`;', + 'export const text = `import("/_vf_modules/components/TextOnly.js")`;', + ].join("\n"), + ); + }); + it("materializes bare side-effect _vf_modules imports before caching the module", async () => { const calls: Array<{ path: string; parent?: string }> = []; const result = await resolveNestedModuleImports({ diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 62fbcc8e49..7d9e041dfb 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -189,6 +189,26 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(specifiers("await import(`./${name}.js`);"), []); }); + it("finds executable imports inside template substitutions", () => { + assertEquals( + specifiers('const html = `

${await import("./inside.js")}

`;'), + ["./inside.js"], + ); + assertEquals( + specifiers('const html = `

${`${await import("./nested.js")}`}

`;'), + ["./nested.js"], + ); + }); + + it("ignores import-looking template text around substitutions", () => { + assertEquals( + specifiers( + 'const html = `import("./text.js") ${await import("./real.js")} import("./after.js")`;', + ), + ["./real.js"], + ); + }); + it("ignores a static import and a property called import", () => { assertEquals(specifiers(`import x from "./foo.js";`), []); assertEquals(specifiers(`obj.import("./foo.js");`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 3cfec343ab..e6b7b84bfa 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -194,6 +194,80 @@ function readLiteralSpecifier( return null; } +function skipFullTemplateLiteral(source: string, templateIndex: number): number { + let cursor = templateIndex + 1; + + while (cursor < source.length) { + if (source[cursor] === "\\") { + cursor += 2; + continue; + } + + if (source[cursor] === "`") return cursor + 1; + + if (source[cursor] === "$" && source[cursor + 1] === "{") { + const expressionEnd = findTemplateExpressionEnd(source, cursor + 2); + if (expressionEnd === null) return source.length; + cursor = expressionEnd + 1; + continue; + } + + cursor++; + } + + return source.length; +} + +function skipExpressionIgnored(source: string, index: number): number { + const char = source[index]; + const next = source[index + 1]; + + if (char === "/" && next === "/") { + const newline = source.indexOf("\n", index + 2); + return newline === -1 ? source.length : newline + 1; + } + + if (char === "/" && next === "*") { + const end = source.indexOf("*/", index + 2); + return end === -1 ? source.length : end + 2; + } + + if (char === '"' || char === "'") return skipIgnored(source, index); + if (char === "`") return skipFullTemplateLiteral(source, index); + + return index; +} + +function findTemplateExpressionEnd(source: string, expressionIndex: number): number | null { + let cursor = expressionIndex; + let braceDepth = 1; + + while (cursor < source.length) { + const skipped = skipExpressionIgnored(source, cursor); + if (skipped !== cursor) { + cursor = skipped; + continue; + } + + if (source[cursor] === "{") { + braceDepth++; + cursor++; + continue; + } + + if (source[cursor] === "}") { + braceDepth--; + if (braceDepth === 0) return cursor; + cursor++; + continue; + } + + cursor++; + } + + return null; +} + function findFromSpan( source: string, statementStart: number, @@ -301,33 +375,75 @@ export function findStaticImportFromSpans( return spans; } -/** - * Find `import("…")` expressions with a literal specifier. - * - * The returned span covers the quoted specifier itself (quotes included), not - * the surrounding `import(...)`, so a replacement is a bare quoted string. - * Dynamic imports whose argument is not a string literal are skipped, since - * their target is only known at runtime. That includes an argument the literal - * merely starts: rewriting the `"./foo"` in `import("./foo" + suffix)` would - * build a path out of a resolved prefix and an unresolved tail. - * - * `maxMatches` bounds the scan on the same terms as - * {@link findStaticImportFromSpans}. - */ -export function findDynamicImportSpans( +function scanTemplateExpressionDynamicImports( source: string, + templateIndex: number, + rangeEnd: number, matcher: SpecifierMatcher, maxMatches: number, -): StaticImportSpan[] { - assertMaxMatches(maxMatches); + spans: StaticImportSpan[], +): number { + let cursor = templateIndex + 1; - const spans: StaticImportSpan[] = []; - let cursor = 0; + while (cursor < rangeEnd && cursor < source.length) { + if (source[cursor] === "\\") { + cursor += 2; + continue; + } - while (cursor < source.length) { - const skipped = skipIgnored(source, cursor); - if (skipped !== cursor) { - cursor = skipped; + if (source[cursor] === "`") return cursor + 1; + + if (source[cursor] === "$" && source[cursor + 1] === "{") { + const expressionStart = cursor + 2; + const expressionEnd = findTemplateExpressionEnd(source, expressionStart); + if (expressionEnd === null) return source.length; + + scanDynamicImportRange(source, expressionStart, expressionEnd, matcher, maxMatches, spans); + if (spans.length >= maxMatches) return expressionEnd + 1; + + cursor = expressionEnd + 1; + continue; + } + + cursor++; + } + + return source.length; +} + +function scanDynamicImportRange( + source: string, + rangeStart: number, + rangeEnd: number, + matcher: SpecifierMatcher, + maxMatches: number, + spans: StaticImportSpan[], +): void { + let cursor = rangeStart; + + while (cursor < rangeEnd) { + const char = source[cursor]; + const next = source[cursor + 1]; + + if ( + (char === "/" && (next === "/" || next === "*")) || + char === '"' || + char === "'" + ) { + cursor = skipIgnored(source, cursor); + continue; + } + + if (char === "`") { + cursor = scanTemplateExpressionDynamicImports( + source, + cursor, + rangeEnd, + matcher, + maxMatches, + spans, + ); + if (spans.length >= maxMatches) return; continue; } @@ -344,14 +460,19 @@ export function findDynamicImportSpans( } const parenIndex = skipWhitespaceAndComments(source, cursor + "import".length); - if (source[parenIndex] !== "(") { + if (parenIndex >= rangeEnd || source[parenIndex] !== "(") { cursor++; continue; } const literalIndex = skipWhitespaceAndComments(source, parenIndex + 1); + if (literalIndex >= rangeEnd) { + cursor = parenIndex + 1; + continue; + } + const literal = readLiteralSpecifier(source, literalIndex); - if (!literal) { + if (!literal || literal.end > rangeEnd) { cursor = parenIndex + 1; continue; } @@ -360,7 +481,8 @@ export function findDynamicImportSpans( // starts the import-attributes argument; anything else (`+`, a template // continuation, a ternary) means the runtime specifier is not this string. const afterSpecifier = skipWhitespaceAndComments(source, literal.end); - const isWholeArgument = source[afterSpecifier] === ")" || source[afterSpecifier] === ","; + const isWholeArgument = afterSpecifier < rangeEnd && + (source[afterSpecifier] === ")" || source[afterSpecifier] === ","); const matchedPath = isWholeArgument ? matcher(literal.specifier) : null; if (matchedPath) { @@ -370,12 +492,35 @@ export function findDynamicImportSpans( start: literalIndex, end: literal.end, }); - if (spans.length >= maxMatches) return spans; + if (spans.length >= maxMatches) return; } cursor = literal.end; } +} + +/** + * Find `import("…")` expressions with a literal specifier. + * + * The returned span covers the quoted specifier itself (quotes included), not + * the surrounding `import(...)`, so a replacement is a bare quoted string. + * Dynamic imports whose argument is not a string literal are skipped, since + * their target is only known at runtime. That includes an argument the literal + * merely starts: rewriting the `"./foo"` in `import("./foo" + suffix)` would + * build a path out of a resolved prefix and an unresolved tail. + * + * `maxMatches` bounds the scan on the same terms as + * {@link findStaticImportFromSpans}. + */ +export function findDynamicImportSpans( + source: string, + matcher: SpecifierMatcher, + maxMatches: number, +): StaticImportSpan[] { + assertMaxMatches(maxMatches); + const spans: StaticImportSpan[] = []; + scanDynamicImportRange(source, 0, source.length, matcher, maxMatches, spans); return spans; } From 6c9879e9d00168eae1d4c2e595765bb316b2c71b Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:51:12 +0200 Subject: [PATCH 023/104] Keep generated observability references aligned with CI Removing the broad static-generation classifier shifts the documented source anchors for the public observability exports. Regenerating the single affected reference keeps the pinned CI documentation check deterministic. Constraint: API reference source anchors are generated under pinned Deno 2.7.7. Confidence: high Scope-risk: narrow Reversibility: clean Tested: pinned Deno 2.7.7 docs generation and docs:api-reference:check; git diff check. --- docs/api-reference/veryfront/observability.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 9ffb5f9df7..8e3e169182 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L276) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L304) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L276) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L304) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | From 16cd9552b9c4d94d0e1f7f9be7ccdb61cec36a42 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:54:20 +0200 Subject: [PATCH 024/104] Handle regex braces in template import scans Template substitution scanning must ignore regex literal bodies before matching closing braces. Otherwise a regex like /}/ ends the expression early and hides later executable dynamic imports. Constraint: The scanner remains a bounded source-span helper, not a full JavaScript parser Rejected: Parse all source with a JS AST | too broad for this hot-path span scanner and inconsistent with existing lightweight scanners Confidence: high Scope-risk: narrow Tested: Red source-spans regression for regex brace template substitution Tested: Pinned focused source-spans test, expanded MDX/module-fetcher tests, fmt-check, lint, check, git diff --check --- .../utils/source-spans.test.ts | 24 ++++++ .../esm-module-loader/utils/source-spans.ts | 84 ++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 7d9e041dfb..b09ad1f1ac 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -209,6 +209,30 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after regex braces inside template substitutions", () => { + assertEquals( + specifiers('const html = `${/}/.test(x) ? import("./after-close.js") : null}`;'), + ["./after-close.js"], + ); + assertEquals( + specifiers('const html = `${/\\}/.test(x) ? import("./after-escaped-close.js") : null}`;'), + ["./after-escaped-close.js"], + ); + assertEquals( + specifiers('const html = `${/[{}]/.test(x) ? import("./after-class.js") : null}`;'), + ["./after-class.js"], + ); + }); + + it("ignores import-looking regex text inside template substitutions", () => { + assertEquals( + specifiers( + 'const html = `${/import\\("\\.\\/not\\.js"\\)/.test(x) ? import("./real.js") : null}`;', + ), + ["./real.js"], + ); + }); + it("ignores a static import and a property called import", () => { assertEquals(specifiers(`import x from "./foo.js";`), []); assertEquals(specifiers(`obj.import("./foo.js");`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index e6b7b84bfa..5a10ff310e 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -218,7 +218,79 @@ function skipFullTemplateLiteral(source: string, templateIndex: number): number return source.length; } -function skipExpressionIgnored(source: string, index: number): number { +function previousSignificantIndex(source: string, index: number): number { + let cursor = index - 1; + while (cursor >= 0 && /\s/.test(source[cursor] ?? "")) cursor--; + return cursor; +} + +function keywordBefore(source: string, index: number): string | null { + const end = previousSignificantIndex(source, index) + 1; + let start = end; + while (start > 0 && /[A-Za-z_$]/.test(source[start - 1] ?? "")) start--; + if (start === end) return null; + return source.slice(start, end); +} + +function canStartRegexLiteral(source: string, index: number, rangeStart: number): boolean { + const previous = previousSignificantIndex(source, index); + if (previous < rangeStart) return true; + + const char = source[previous]; + if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; + + return [ + "case", + "delete", + "do", + "else", + "in", + "instanceof", + "return", + "throw", + "typeof", + "void", + "yield", + ].includes(keywordBefore(source, index) ?? ""); +} + +function skipRegexLiteral(source: string, regexIndex: number): number { + let cursor = regexIndex + 1; + let inCharacterClass = false; + + while (cursor < source.length) { + const char = source[cursor]; + + if (char === "\\") { + cursor += 2; + continue; + } + + if (char === "[" && !inCharacterClass) { + inCharacterClass = true; + cursor++; + continue; + } + + if (char === "]" && inCharacterClass) { + inCharacterClass = false; + cursor++; + continue; + } + + if (char === "/" && !inCharacterClass) { + cursor++; + while (/[A-Za-z]/.test(source[cursor] ?? "")) cursor++; + return cursor; + } + + cursor++; + } + + return source.length; +} + +function skipExpressionIgnored(source: string, index: number, rangeStart: number): number { const char = source[index]; const next = source[index + 1]; @@ -234,6 +306,9 @@ function skipExpressionIgnored(source: string, index: number): number { if (char === '"' || char === "'") return skipIgnored(source, index); if (char === "`") return skipFullTemplateLiteral(source, index); + if (char === "/" && canStartRegexLiteral(source, index, rangeStart)) { + return skipRegexLiteral(source, index); + } return index; } @@ -243,7 +318,7 @@ function findTemplateExpressionEnd(source: string, expressionIndex: number): num let braceDepth = 1; while (cursor < source.length) { - const skipped = skipExpressionIgnored(source, cursor); + const skipped = skipExpressionIgnored(source, cursor, expressionIndex); if (skipped !== cursor) { cursor = skipped; continue; @@ -434,6 +509,11 @@ function scanDynamicImportRange( continue; } + if (char === "/" && canStartRegexLiteral(source, cursor, rangeStart)) { + cursor = skipRegexLiteral(source, cursor); + continue; + } + if (char === "`") { cursor = scanTemplateExpressionDynamicImports( source, From 0874703aea7013b2893c98938de92f24a0e9239f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Fri, 14 Aug 2026 23:59:30 +0200 Subject: [PATCH 025/104] Preserve scans after postfix division Regex literal skipping must not treat division after postfix ++ or -- as a regex body. Otherwise the scanner can skip to EOF and miss later dynamic imports. Constraint: Keep the scanner heuristic bounded and avoid broad parser work Rejected: Replace the source-span scanner with full JavaScript parsing | disproportionate for this narrow PR review finding Confidence: high Scope-risk: narrow Tested: Red regression for ++/-- division before dynamic imports at top level and in template substitutions Tested: Nearby numeric division and optional-chain division regressions Tested: Pinned focused source-spans test, expanded MDX/module-fetcher tests, fmt-check, lint, check, git diff --check --- .../utils/source-spans.test.ts | 30 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 7 +++++ 2 files changed, 37 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index b09ad1f1ac..1dffe3b18d 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -233,6 +233,36 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds imports after division following postfix operators", () => { + assertEquals( + specifiers('let x = 1; x++ / 2; import("./after-plus-plus.js");'), + ["./after-plus-plus.js"], + ); + assertEquals( + specifiers('let x = 1; x-- / 2; import("./after-minus-minus.js");'), + ["./after-minus-minus.js"], + ); + assertEquals( + specifiers('const html = `${x++ / 2} ${import("./inside-plus-plus.js")}`;'), + ["./inside-plus-plus.js"], + ); + assertEquals( + specifiers('const html = `${x-- / 2} ${import("./inside-minus-minus.js")}`;'), + ["./inside-minus-minus.js"], + ); + }); + + it("finds imports after nearby division forms", () => { + assertEquals( + specifiers('const ratio = value / 2; import("./after-numeric-division.js");'), + ["./after-numeric-division.js"], + ); + assertEquals( + specifiers('const value = maybe?.count / 2; import("./after-optional-chain.js");'), + ["./after-optional-chain.js"], + ); + }); + it("ignores a static import and a property called import", () => { assertEquals(specifiers(`import x from "./foo.js";`), []); assertEquals(specifiers(`obj.import("./foo.js");`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 5a10ff310e..adbb04fcd1 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -237,6 +237,13 @@ function canStartRegexLiteral(source: string, index: number, rangeStart: number) if (previous < rangeStart) return true; const char = source[previous]; + if ( + (char === "+" || char === "-") && + previous - 1 >= rangeStart && + source[previous - 1] === char + ) { + return false; + } if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; return [ From b21526024e2840d32325e202f22211e56088afef Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:20:12 +0200 Subject: [PATCH 026/104] Classify provider-neutral frontmatter syntax failures YAML parser providers only promise to throw SyntaxError for malformed input, so the MDX compiler cannot depend on provider message text or stack paths. Mark syntax failures at the frontmatter extraction boundary and classify that marker as a tenant build compile error. Constraint: YamlParserProvider requires SyntaxError for malformed input but does not require line and column wording. Rejected: Keep matching parser-specific messages | compliant generic SyntaxError values bypass tenant-build classification. Confidence: high Scope-risk: narrow Directive: Classify frontmatter failures at the extraction boundary before adding provider-specific heuristics. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/compiler/frontmatter-extractor.test.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno fmt --check src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/frontmatter-extractor.test.ts src/transforms/mdx/compiler/mdx-compiler.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno lint src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/frontmatter-extractor.test.ts src/transforms/mdx/compiler/mdx-compiler.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno check src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/mdx-compiler.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: git diff --check --- .../mdx/compiler/frontmatter-extractor.ts | 22 +++++++++- .../mdx/compiler/mdx-compiler.test.ts | 42 +++++++++++++++++++ src/transforms/mdx/compiler/mdx-compiler.ts | 3 +- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/compiler/frontmatter-extractor.ts b/src/transforms/mdx/compiler/frontmatter-extractor.ts index 031a687689..ad7e6192e2 100644 --- a/src/transforms/mdx/compiler/frontmatter-extractor.ts +++ b/src/transforms/mdx/compiler/frontmatter-extractor.ts @@ -6,10 +6,30 @@ export interface FrontmatterExtractionResult { frontmatter: Record; } +const FRONTMATTER_SYNTAX_ERROR = Symbol.for("veryfront.transforms.mdx.frontmatter-syntax-error"); + +/** Return true when an error came from MDX or Markdown YAML frontmatter parsing. */ +export function isFrontmatterSyntaxError(error: unknown): error is SyntaxError { + return error instanceof SyntaxError && + (error as { [FRONTMATTER_SYNTAX_ERROR]?: unknown })[FRONTMATTER_SYNTAX_ERROR] === true; +} + +function createFrontmatterSyntaxError(cause: SyntaxError): SyntaxError { + const error = new SyntaxError(`Invalid YAML frontmatter: ${cause.message}`, { cause }); + Object.defineProperty(error, FRONTMATTER_SYNTAX_ERROR, { value: true }); + return error; +} + function extractYamlFrontmatter(content: string): FrontmatterExtractionResult { if (!content.trim().startsWith("---")) return { body: content, frontmatter: {} }; - const extracted = extract(content); + let extracted; + try { + extracted = extract(content); + } catch (error) { + if (error instanceof SyntaxError) throw createFrontmatterSyntaxError(error); + throw error; + } return { body: extracted.body, diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 6eec65a99c..0d74928b53 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -6,8 +6,13 @@ import { VeryfrontError } from "#veryfront/errors"; import { register as registerContract, tryResolve as tryResolveContract, + unregister as unregisterContract, } from "#veryfront/extensions/contracts.ts"; import type { ContentProcessor } from "#veryfront/extensions/content/index.ts"; +import { + type YamlParserProvider, + YamlParserProviderName, +} from "#veryfront/extensions/parser/yaml-parser.ts"; import { compileMDXRuntime } from "./mdx-compiler.ts"; describe("transforms/mdx/compiler/mdx-compiler", () => { @@ -118,6 +123,43 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { assertEquals(error.category, "BUILD"); }); + it("classifies frontmatter SyntaxErrors from compliant YAML providers", async () => { + const previous = tryResolveContract(YamlParserProviderName); + registerContract( + YamlParserProviderName, + { + parseYaml() { + throw new SyntaxError("invalid YAML"); + }, + } satisfies YamlParserProvider, + ); + + try { + const error = await assertRejects( + () => + compileMDXRuntime( + "production", + "/project", + "---\ntitle: broken\n---\n# Content", + undefined, + "broken-frontmatter.mdx", + "server", + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "mdx-compile-error"); + assertEquals(error.category, "BUILD"); + } finally { + if (previous) { + registerContract(YamlParserProviderName, previous); + } else { + unregisterContract(YamlParserProviderName); + } + } + }); + it("preserves non-source processor failures", async () => { const previous = tryResolveContract("ContentProcessor"); registerContract( diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index efcd130059..730bab4808 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -8,6 +8,7 @@ import type { } from "#veryfront/extensions/content/index.ts"; import { MDX_COMPILE_ERROR, VeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; +import { isFrontmatterSyntaxError } from "./frontmatter-extractor.ts"; const logger = rendererLogger.component("mdx-compiler"); @@ -28,7 +29,7 @@ function isMdxSourceCompileError(error: Error): boolean { (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); - return isMdxParserError || isYamlFrontmatterError; + return isMdxParserError || isYamlFrontmatterError || isFrontmatterSyntaxError(error); } export function compileMDXRuntime( From f28a8edf3de6ee3fa4cca2c5001368108105b747 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:26:20 +0200 Subject: [PATCH 027/104] Keep escaped alias and template scans deterministic Escaped @/ aliases can carry query or fragment suffixes, so the resolver now normalizes only the path portion before reattaching the suffix. The source-span scanner also fails closed on excessive template nesting and recognises regex literals after control-statement conditions so nested imports are not skipped. Constraint: Review threads identified exact production-shaped parser and resolver edge cases. Rejected: Switch to a full parser here | the existing bounded scanner already covers the narrow import-rewrite surface with smaller blast radius. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/esm/specifier-resolver.test.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/esm/specifier-resolver.test.ts src/transforms/esm/http-cache.test.ts src/transforms/import-rewriter/url-builder.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno fmt --check, deno lint, deno check on touched files --- src/transforms/esm/specifier-resolver.test.ts | 20 ++++++ src/transforms/esm/specifier-resolver.ts | 7 +- .../utils/source-spans.test.ts | 27 ++++++++ .../esm-module-loader/utils/source-spans.ts | 68 +++++++++++++++++-- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index aa65247e40..0acdf33f0e 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -225,6 +225,26 @@ describe("transforms/esm/specifier-resolver", () => { ); }); + it("preserves query and fragment suffixes in escaped @/ alias imports", async () => { + const code = + `import raw from "@/components/Card.tsx?raw"; import icon from "@/components/Icon.svg#glyph";`; + const cacheCalls: string[] = []; + const result = await buildReplacements(code, undefined, defaultOptions, async (url) => { + cacheCalls.push(url); + return "/tmp/cache/http-alias.mjs"; + }); + + assertEquals(cacheCalls, []); + assertEquals( + result.replacements.get("@/components/Card.tsx?raw"), + "/_vf_modules/components/Card.js?raw", + ); + assertEquals( + result.replacements.get("@/components/Icon.svg#glyph"), + "/_vf_modules/components/Icon.svg.js#glyph", + ); + }); + it("never resolves an @/ alias against the page origin via an import-map prefix", async () => { // A project import map commonly maps "@/" to "./". Resolving that mapped // relative path against the page origin fetches the tenant's own public diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 1abab3eae5..3dd70dad06 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -111,11 +111,14 @@ async function resolveSpecifier( // (VERYFRONT-SERVER-G). if (stringStartsWith(specifier, "@/")) { const aliasPath = stringSlice(specifier, 2); - const normalizedPath = normalizeExtension(aliasPath); + const suffixIndex = aliasPath.search(/[?#]/); + const pathOnly = suffixIndex === -1 ? aliasPath : stringSlice(aliasPath, 0, suffixIndex); + const suffix = suffixIndex === -1 ? "" : stringSlice(aliasPath, suffixIndex); + const normalizedPath = normalizeExtension(pathOnly); const jsPath = /\.(js|mjs|cjs|css)$/.test(normalizedPath) ? normalizedPath : `${normalizedPath}.js`; - return `/_vf_modules/${jsPath}`; + return `/_vf_modules/${jsPath}${suffix}`; } // Server-only packages (`redis`, `pg`, …), including their explicit `npm:` diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 1dffe3b18d..95641a7cd2 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -224,6 +224,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after regex braces following control conditions", () => { + assertEquals( + specifiers( + 'const html = `${(() => { if (ok) /}/.test(x); })() && import("./after-if-regex.js")}`;', + ), + ["./after-if-regex.js"], + ); + assertEquals( + specifiers( + 'const html = `${(() => { while (ok) /}/.test(x); })() && import("./after-while-regex.js")}`;', + ), + ["./after-while-regex.js"], + ); + }); + it("ignores import-looking regex text inside template substitutions", () => { assertEquals( specifiers( @@ -252,6 +267,18 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("bounds nested template substitution traversal", () => { + const depth = 12_000; + const source = "const html = `" + "${`".repeat(depth) + 'import("./deep.js")' + + "`}".repeat(depth) + "`;"; + + assertThrows( + () => specifiers(source), + RangeError, + "Template literal nesting exceeds scanner limit", + ); + }); + it("finds imports after nearby division forms", () => { assertEquals( specifiers('const ratio = value / 2; import("./after-numeric-division.js");'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index adbb04fcd1..c71a36fe7d 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -22,6 +22,14 @@ export interface StaticImportSpan { type SpecifierMatcher = (specifier: string) => string | null | undefined; +const MAX_TEMPLATE_LITERAL_DEPTH = 512; + +function assertTemplateLiteralDepth(depth: number): void { + if (depth > MAX_TEMPLATE_LITERAL_DEPTH) { + throw new RangeError("Template literal nesting exceeds scanner limit"); + } +} + export function replaceSourceSpans( source: string, replacements: SourceSpanReplacement[], @@ -194,7 +202,13 @@ function readLiteralSpecifier( return null; } -function skipFullTemplateLiteral(source: string, templateIndex: number): number { +function skipFullTemplateLiteral( + source: string, + templateIndex: number, + depth = 0, +): number { + assertTemplateLiteralDepth(depth); + let cursor = templateIndex + 1; while (cursor < source.length) { @@ -206,7 +220,7 @@ function skipFullTemplateLiteral(source: string, templateIndex: number): number if (source[cursor] === "`") return cursor + 1; if (source[cursor] === "$" && source[cursor + 1] === "{") { - const expressionEnd = findTemplateExpressionEnd(source, cursor + 2); + const expressionEnd = findTemplateExpressionEnd(source, cursor + 2, depth + 1); if (expressionEnd === null) return source.length; cursor = expressionEnd + 1; continue; @@ -232,11 +246,42 @@ function keywordBefore(source: string, index: number): string | null { return source.slice(start, end); } +function isControlConditionCloseParen(source: string, index: number, rangeStart: number): boolean { + let depth = 1; + let cursor = index - 1; + + while (cursor >= rangeStart) { + const char = source[cursor]; + + if (char === ")") { + depth++; + cursor--; + continue; + } + + if (char === "(") { + depth--; + if (depth === 0) { + const keyword = keywordBefore(source, cursor); + return keyword === "if" || keyword === "while" || keyword === "for" || + keyword === "with" || keyword === "switch"; + } + cursor--; + continue; + } + + cursor--; + } + + return false; +} + function canStartRegexLiteral(source: string, index: number, rangeStart: number): boolean { const previous = previousSignificantIndex(source, index); if (previous < rangeStart) return true; const char = source[previous]; + if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; if ( (char === "+" || char === "-") && previous - 1 >= rangeStart && @@ -297,7 +342,12 @@ function skipRegexLiteral(source: string, regexIndex: number): number { return source.length; } -function skipExpressionIgnored(source: string, index: number, rangeStart: number): number { +function skipExpressionIgnored( + source: string, + index: number, + rangeStart: number, + depth: number, +): number { const char = source[index]; const next = source[index + 1]; @@ -312,7 +362,7 @@ function skipExpressionIgnored(source: string, index: number, rangeStart: number } if (char === '"' || char === "'") return skipIgnored(source, index); - if (char === "`") return skipFullTemplateLiteral(source, index); + if (char === "`") return skipFullTemplateLiteral(source, index, depth + 1); if (char === "/" && canStartRegexLiteral(source, index, rangeStart)) { return skipRegexLiteral(source, index); } @@ -320,12 +370,18 @@ function skipExpressionIgnored(source: string, index: number, rangeStart: number return index; } -function findTemplateExpressionEnd(source: string, expressionIndex: number): number | null { +function findTemplateExpressionEnd( + source: string, + expressionIndex: number, + depth = 0, +): number | null { + assertTemplateLiteralDepth(depth); + let cursor = expressionIndex; let braceDepth = 1; while (cursor < source.length) { - const skipped = skipExpressionIgnored(source, cursor, expressionIndex); + const skipped = skipExpressionIgnored(source, cursor, expressionIndex, depth); if (skipped !== cursor) { cursor = skipped; continue; From 9c4db233612c9db0e5e45b6ae9c4f3b14b739f23 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:30:38 +0200 Subject: [PATCH 028/104] Classify marked Markdown frontmatter syntax failures The MDX frontmatter extractor now wraps provider-neutral YAML SyntaxErrors with an internal marker. Markdown compilation shares the same extraction boundary, so the Markdown runtime must honor that marker or CI sees raw SyntaxError values instead of tenant build VeryfrontError warnings. Constraint: CI runs the Markdown compiler tests under both Deno coverage shards and the Bun-transpiled package. Rejected: Classify every SyntaxError from the content processor | that would relabel framework/provider failures the existing test requires to pass through. Confidence: high Scope-risk: narrow Directive: Keep generic processor SyntaxErrors unclassified unless they carry frontmatter provenance or match the legacy YAML source guard. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/md/compiler/md-compiler.test.ts src/transforms/mdx/compiler/frontmatter-extractor.test.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno fmt --check src/transforms/md/compiler/md-compiler.ts src/transforms/md/compiler/md-compiler.test.ts src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/mdx-compiler.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno lint src/transforms/md/compiler/md-compiler.ts src/transforms/md/compiler/md-compiler.test.ts src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/mdx-compiler.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno check src/transforms/md/compiler/md-compiler.ts src/transforms/mdx/compiler/frontmatter-extractor.ts src/transforms/mdx/compiler/mdx-compiler.ts Tested: git diff --check Not-tested: Full CI after this follow-up commit; PR remains draft until remote checks rerun. --- src/transforms/md/compiler/md-compiler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/transforms/md/compiler/md-compiler.ts b/src/transforms/md/compiler/md-compiler.ts index 18d3be55a0..095403fc60 100644 --- a/src/transforms/md/compiler/md-compiler.ts +++ b/src/transforms/md/compiler/md-compiler.ts @@ -8,15 +8,17 @@ import type { } from "#veryfront/extensions/content/index.ts"; import { MARKDOWN_COMPILE_ERROR, VeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; +import { isFrontmatterSyntaxError } from "../../mdx/compiler/frontmatter-extractor.ts"; const logger = rendererLogger.component("md-compiler"); function isMarkdownSourceCompileError(error: Error): boolean { - return error.name === "SyntaxError" && + const isLegacyYamlError = error.name === "SyntaxError" && /\bline \d+, column \d+\b/i.test(error.message) && (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); + return isLegacyYamlError || isFrontmatterSyntaxError(error); } export function compileMarkdownRuntime( From 6e7d5be853c3780fef51f6abf66a889bc799414d Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:26:45 +0200 Subject: [PATCH 029/104] Preserve suffixes through MDX materialization The remote review-wave commit already hardens the scanner and escaped @/ resolver. This follow-up keeps the same query/hash suffix contract in the MDX alias transform and nested import materializer: resolution and fetch/cache keys use the bare path, while emitted file:// specifiers keep the suffix. Constraint: Remote branch advanced with scanner and escaped-alias fixes during verification; preserve that work and add only the missing materialization path. Rejected: Reapply a parallel source-spans implementation | remote already has the bounded scanner with equivalent review coverage Rejected: Include suffixes in fetch/cache paths | suffixes are runtime specifier metadata, not filesystem module identity Confidence: high Scope-risk: narrow Directive: Keep query/hash suffixes separate from lookup paths when rewriting MDX module specifiers. Tested: Red regressions for alias suffix preservation and nested import suffix preservation Tested: Additional control-condition regex regression against remote scanner behavior Not-tested: Post-rebase full pre-push dry-run and GitHub CI pending after amend --- .../module-fetcher/nested-imports.test.ts | 30 +++++++++ .../module-fetcher/nested-imports.ts | 61 +++++++++++++------ .../transforms/alias-imports.test.ts | 25 ++++++++ .../transforms/alias-imports.ts | 25 ++++++-- src/transforms/mdx/esm-module-loader/types.ts | 1 + .../utils/source-spans.test.ts | 15 +++++ 6 files changed, 135 insertions(+), 22 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 6affa0fe46..cedf438c50 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -220,6 +220,36 @@ import { bar } from "./local.js"; ); }); + it("preserves suffixes when materializing nested imports", async () => { + const calls: Array<{ path: string; parent?: string }> = []; + const result = await resolveNestedModuleImports({ + moduleCode: [ + `import styles from "./theme.css?inline#critical";`, + `export const load = () => import("/_vf_modules/components/Lazy.js#client");`, + ].join("\n"), + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path, parent) => { + calls.push({ path, parent }); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + }); + + assertEquals(calls, [ + { path: "_vf_modules/components/Lazy.js", parent: "_vf_modules/pages/index.js" }, + { path: "./theme.css", parent: "_vf_modules/pages/index.js" }, + ]); + assertEquals( + result, + [ + `import styles from "file:///cache/.__theme.css.mjs?inline#critical";`, + `export const load = () => import("file:///cache/_vf_modules__components__Lazy.js.mjs#client");`, + ].join("\n"), + ); + }); + it("materializes dynamic _vf_modules imports inside template substitutions", async () => { const calls: Array<{ path: string; parent?: string }> = []; const result = await resolveNestedModuleImports({ diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 3b813fc8e9..83dd4fc8fa 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -25,7 +25,19 @@ import { } from "./limits.ts"; function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { - return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/[^?]+)(?:\?.*)?$/)?.[1] ?? null; + return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/.+)$/)?.[1] ?? null; +} + +function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { + const queryStart = specifier.indexOf("?"); + const hashStart = specifier.indexOf("#"); + const suffixStart = + [queryStart, hashStart].filter((index) => index >= 0).sort((a, b) => a - b)[0]; + if (suffixStart === undefined) return { path: specifier, suffix: "" }; + return { + path: specifier.slice(0, suffixStart), + suffix: specifier.slice(suffixStart), + }; } type NestedImportSpan = { @@ -33,6 +45,7 @@ type NestedImportSpan = { path: string; start: number; end: number; + suffix?: string; isDynamic?: boolean; isSideEffect?: boolean; }; @@ -57,10 +70,12 @@ export function findNestedImports( MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path vfModules.push({ original, - path: rawPath.replace(/^(?:file:\/\/)?\/+/, ""), + path, + suffix, start, end, }); @@ -73,10 +88,12 @@ export function findNestedImports( MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path vfModules.push({ original, - path: rawPath.replace(/^(?:file:\/\/)?\/+/, ""), + path, + suffix, start, end, isDynamic: true, @@ -90,10 +107,12 @@ export function findNestedImports( MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path vfModules.push({ original, - path: rawPath.replace(/^(?:file:\/\/)?\/+/, ""), + path, + suffix, start, end, isSideEffect: true, @@ -101,30 +120,34 @@ export function findNestedImports( } for ( - const { original, path, start, end } of findStaticImportFromSpans( + const { original, path: rawPath, start, end } of findStaticImportFromSpans( moduleCode, - (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1], + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ original, path, + suffix, start, end, }); } for ( - const { original, path, start, end } of findDynamicImportSpans( + const { original, path: rawPath, start, end } of findDynamicImportSpans( moduleCode, - (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1], + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ original, path, + suffix, start, end, isDynamic: true, @@ -132,15 +155,17 @@ export function findNestedImports( } for ( - const { original, path, start, end } of findStaticSideEffectImportSpans( + const { original, path: rawPath, start, end } of findStaticSideEffectImportSpans( moduleCode, - (specifier) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1], + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) ) { + const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ original, path, + suffix, start, end, isSideEffect: true, @@ -195,6 +220,7 @@ export async function processNestedImports( original, start, end, + suffix, isDynamic, isSideEffect, nestedFilePath, @@ -208,10 +234,10 @@ export async function processNestedImports( end, expected: original, replacement: isDynamic - ? `"file://${nestedFilePath}"` + ? `"file://${nestedFilePath}${suffix ?? ""}"` : isSideEffect - ? `import "file://${nestedFilePath}"` - : `from "file://${nestedFilePath}"`, + ? `import "file://${nestedFilePath}${suffix ?? ""}"` + : `from "file://${nestedFilePath}${suffix ?? ""}"`, }); continue; } @@ -234,10 +260,10 @@ export async function processNestedImports( end, expected: original, replacement: isDynamic - ? `"file://${stubPath}"` + ? `"file://${stubPath}${suffix ?? ""}"` : isSideEffect - ? `import "file://${stubPath}"` - : `from "file://${stubPath}"`, + ? `import "file://${stubPath}${suffix ?? ""}"` + : `from "file://${stubPath}${suffix ?? ""}"`, }); } } @@ -335,10 +361,11 @@ export async function resolveNestedModuleImports( const nestedResults: NestedImportResult[] = await parallelMap( allImports, - async ({ original, path, start, end, isDynamic, isSideEffect, key }) => ({ + async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => ({ original, start, end, + suffix, isDynamic, isSideEffect, nestedFilePath: await input.fetchAndCacheModule( diff --git a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts index 6c1ddd0a11..da7cd8227f 100644 --- a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts @@ -73,4 +73,29 @@ describe("alias import transforms", () => { assertStringIncludes(result, `import Foo from "file:///cache/vfmod-`); assertEquals(fs.files.has("components/Commented.js"), false); }); + + it("preserves query and hash suffixes while resolving alias paths", async () => { + const fs = new MemoryFs({ + "components/Foo.js": `export default function Foo() { return null; }`, + "components/Bar.js": `export default function Bar() { return null; }`, + }); + + const projectAlias = await transformProjectAliasImports( + `import Foo from "@/components/Foo.js?raw#hero";`, + fs, + "/cache", + ); + const moduleAlias = await transformModuleServerImports( + `import Bar from "/_vf_modules/components/Bar.js#client";`, + fs, + "/cache", + ); + + assertStringIncludes(projectAlias, `import Foo from "file:///cache/alias-`); + assertStringIncludes(projectAlias, `?raw#hero";`); + assertStringIncludes(moduleAlias, `import Bar from "file:///cache/vfmod-`); + assertStringIncludes(moduleAlias, `#client";`); + assertEquals(fs.files.has("components/Foo.js"), true); + assertEquals(fs.files.has("components/Bar.js"), true); + }); }); diff --git a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts index e14d87680a..ce3afaa13d 100644 --- a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts +++ b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts @@ -21,9 +21,22 @@ type ImportType = "project-alias" | "vf-modules"; interface AliasImport { specifier: string; relativePath: string; + suffix: string; type: ImportType; } +function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { + const queryStart = specifier.indexOf("?"); + const hashStart = specifier.indexOf("#"); + const suffixStart = + [queryStart, hashStart].filter((index) => index >= 0).sort((a, b) => a - b)[0]; + if (suffixStart === undefined) return { path: specifier, suffix: "" }; + return { + path: specifier.slice(0, suffixStart), + suffix: specifier.slice(suffixStart), + }; +} + async function findAliasImports(code: string): Promise { const imports: AliasImport[] = []; const parsedImports = await parseImports(code); @@ -33,9 +46,11 @@ async function findAliasImports(code: string): Promise { if (!specifier) continue; if (specifier.startsWith("@/")) { + const { path, suffix } = splitSpecifierSuffix(specifier.slice(2)); imports.push({ specifier, - relativePath: specifier.slice(2), + relativePath: path, + suffix, type: "project-alias", }); continue; @@ -45,11 +60,11 @@ async function findAliasImports(code: string): Promise { if (!normalized.startsWith("_vf_modules/")) continue; const modulePath = normalized.slice("_vf_modules/".length); - const queryStart = modulePath.indexOf("?"); - const relativePath = queryStart === -1 ? modulePath : modulePath.slice(0, queryStart); + const { path, suffix } = splitSpecifierSuffix(modulePath); imports.push({ specifier, - relativePath: relativePath.replace(/\.js$/, ""), + relativePath: path.replace(/\.js$/, ""), + suffix, type: "vf-modules", }); } @@ -134,7 +149,7 @@ async function transformImport( logger.debug(`${LOG_PREFIX_MDX_LOADER} Transformed ${getPathDesc(imp)} -> ${transformedPath}`); - return { specifier: imp.specifier, replacement: `file://${transformedPath}` }; + return { specifier: imp.specifier, replacement: `file://${transformedPath}${imp.suffix}` }; } catch (error) { logger.warn(`${LOG_PREFIX_MDX_LOADER} Failed to transform ${getPathDesc(imp)}`, error); return null; diff --git a/src/transforms/mdx/esm-module-loader/types.ts b/src/transforms/mdx/esm-module-loader/types.ts index 6916406def..21c1a52fdb 100644 --- a/src/transforms/mdx/esm-module-loader/types.ts +++ b/src/transforms/mdx/esm-module-loader/types.ts @@ -58,6 +58,7 @@ export interface NestedImportResult { end: number; isDynamic?: boolean; isSideEffect?: boolean; + suffix?: string; nestedFilePath: string | null; nestedPath?: string; relativePath?: string; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 95641a7cd2..01f4f57098 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -290,6 +290,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds imports after regex literals following control statement conditions", () => { + assertEquals( + specifiers('if (ok) /"/.test(x); import("./after-if-quote.js");'), + ["./after-if-quote.js"], + ); + assertEquals( + specifiers("while (ok) /`/.test(x); import('./after-while-backtick.js');"), + ["./after-while-backtick.js"], + ); + assertEquals( + specifiers('for (; ok;) /\'/.test(x); import("./after-for-single.js");'), + ["./after-for-single.js"], + ); + }); + it("ignores a static import and a property called import", () => { assertEquals(specifiers(`import x from "./foo.js";`), []); assertEquals(specifiers(`obj.import("./foo.js");`), []); From 8cb8d1b2e7e47f2aca529af50095b8a89a6c14a5 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:54:46 +0200 Subject: [PATCH 030/104] Classify Markdown frontmatter syntax failures by parser provenance Markdown tenant-build error policy should not depend on YAML parser wording. The frontmatter extractor already marks SyntaxError instances that originate at the YAML frontmatter seam, so Markdown can share the same classifier as MDX while preserving framework failures. Constraint: YamlParserProvider only guarantees SyntaxError for malformed YAML. Rejected: Keep the stack/message legacy classifier | it misses compliant providers and duplicates MDX policy. Confidence: high Scope-risk: narrow Directive: Do not classify arbitrary SyntaxError values as tenant source failures unless they are marked by the frontmatter extractor. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/md/compiler/md-compiler.test.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno fmt --check src/transforms/md/compiler/md-compiler.ts src/transforms/md/compiler/md-compiler.test.ts Tested: deno lint src/transforms/md/compiler/md-compiler.ts src/transforms/md/compiler/md-compiler.test.ts Tested: deno check src/transforms/md/compiler/md-compiler.ts Tested: git diff --check Tested: deno task coverage:ci:shard -- --shard=2/8 --coverage-dir=coverage-shard-2-final Not-tested: Full local Deno suite remains blocked by unrelated local API cache timeouts and embedding provider 400 seen in isolated reruns. --- .../md/compiler/md-compiler.test.ts | 44 +++++++++++++++++++ src/transforms/md/compiler/md-compiler.ts | 7 +-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/transforms/md/compiler/md-compiler.test.ts b/src/transforms/md/compiler/md-compiler.test.ts index fb6bd5fbbc..854672debd 100644 --- a/src/transforms/md/compiler/md-compiler.test.ts +++ b/src/transforms/md/compiler/md-compiler.test.ts @@ -6,10 +6,34 @@ import { VeryfrontError } from "#veryfront/errors"; import { register as registerContract, tryResolve as tryResolveContract, + unregister as unregisterContract, } from "#veryfront/extensions/contracts.ts"; import type { ContentProcessor } from "#veryfront/extensions/content/index.ts"; +import { + createYamlParserProvider, + YamlParserProviderName, +} from "#veryfront/extensions/parser/yaml-parser.ts"; import { compileMarkdownRuntime } from "./md-compiler.ts"; +async function withYamlSyntaxErrorProvider(body: () => Promise): Promise { + const previous = tryResolveContract(YamlParserProviderName); + registerContract( + YamlParserProviderName, + createYamlParserProvider(() => { + throw new SyntaxError("invalid YAML"); + }), + ); + try { + await body(); + } finally { + if (previous === undefined) { + unregisterContract(YamlParserProviderName); + } else { + registerContract(YamlParserProviderName, previous); + } + } +} + describe( "transforms/md/compiler/md-compiler", { sanitizeResources: false, sanitizeOps: false }, @@ -55,6 +79,26 @@ describe( assertEquals(error.category, "BUILD"); }); + it("classifies provider-independent Markdown frontmatter SyntaxError failures", async () => { + await withYamlSyntaxErrorProvider(async () => { + const error = await assertRejects( + () => + compileMarkdownRuntime( + "runtime", + "/tmp/project", + "---\ntitle: broken\n---\n# Content", + undefined, + "provider-frontmatter.md", + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "markdown-compile-error"); + assertEquals(error.category, "BUILD"); + }); + }); + it("preserves non-source processor failures", async () => { const previous = tryResolveContract("ContentProcessor"); registerContract( diff --git a/src/transforms/md/compiler/md-compiler.ts b/src/transforms/md/compiler/md-compiler.ts index 095403fc60..be85236792 100644 --- a/src/transforms/md/compiler/md-compiler.ts +++ b/src/transforms/md/compiler/md-compiler.ts @@ -13,12 +13,7 @@ import { isFrontmatterSyntaxError } from "../../mdx/compiler/frontmatter-extract const logger = rendererLogger.component("md-compiler"); function isMarkdownSourceCompileError(error: Error): boolean { - const isLegacyYamlError = error.name === "SyntaxError" && - /\bline \d+, column \d+\b/i.test(error.message) && - (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || - error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || - error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); - return isLegacyYamlError || isFrontmatterSyntaxError(error); + return isFrontmatterSyntaxError(error); } export function compileMarkdownRuntime( From d4fad764d9c3a4ccd93363878dcaa5400e04c336 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 00:56:05 +0200 Subject: [PATCH 031/104] Keep late MDX import rewrites deterministic Codex review found three edge cases after the previous suffix and scanner work: HTTP fallback rewrites dropped query/hash suffixes, escaped project-alias splitting still depended on mutable search hooks, and template-expression scanning missed regex literals after await. The fix carries existing suffix metadata through HTTP fallback replacements, uses captured string intrinsics for alias suffix splitting, and admits await as a regex-literal prefix in the scanner. Constraint: PR #3721 review threads require red regressions before implementation and draft status must be preserved. Rejected: Rework the scanner into a parser | broader than the review case and already bounded by existing heuristics. Confidence: high Scope-risk: narrow Directive: Keep suffix-bearing specifiers split into fetch/cache path plus emitted suffix; do not feed query/hash into child module lookup. Tested: Focused Deno tests for http-fetcher, specifier-resolver, and source-spans; expanded MDX/ESM transform suite; touched-file fmt/lint/check; git diff --check. Not-tested: GitHub CI until after guarded push. --- src/transforms/esm/specifier-resolver.test.ts | 36 +++++++++++++++++ src/transforms/esm/specifier-resolver.ts | 26 ++++++++++-- .../module-fetcher/http-fetcher.test.ts | 40 +++++++++++++++++++ .../module-fetcher/http-fetcher.ts | 29 ++++++++++---- .../utils/source-spans.test.ts | 9 +++++ .../esm-module-loader/utils/source-spans.ts | 1 + 6 files changed, 129 insertions(+), 12 deletions(-) diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index 0acdf33f0e..5231f44277 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -115,6 +115,42 @@ describe("transforms/esm/specifier-resolver", () => { } }); + it("rewrites escaped project aliases without mutable search hooks", async () => { + const stringPrototypeDescriptors = Object.getOwnPropertyDescriptors(String.prototype); + const regexpSearch = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search)!; + + try { + Object.defineProperty(String.prototype, "search", { + configurable: true, + value() { + throw new Error("poisoned String.prototype.search"); + }, + writable: true, + }); + Object.defineProperty(RegExp.prototype, Symbol.search, { + configurable: true, + value() { + throw new Error("poisoned RegExp @@search"); + }, + }); + + const result = await buildReplacements( + `import Foo from "@/components/Foo.tsx?raw#hero";`, + undefined, + defaultOptions, + noopCache, + ); + + assertEquals( + result.replacements.get("@/components/Foo.tsx?raw#hero"), + "/_vf_modules/components/Foo.js?raw#hero", + ); + } finally { + Object.defineProperties(String.prototype, stringPrototypeDescriptors); + Object.defineProperty(RegExp.prototype, Symbol.search, regexpSearch); + } + }); + it("rewrites http URL when cache returns a path", async () => { const code = `import lodash from "https://esm.sh/lodash@4";`; const mockCache: CacheHttpModuleFn = async () => "/tmp/cache/http-99999.mjs"; diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 3dd70dad06..96195eca2e 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -30,9 +30,14 @@ import { } from "./http-cache-helpers.ts"; const ReflectApply = Reflect.apply; +const StringIndexOf = String.prototype.indexOf; const StringSlice = String.prototype.slice; const StringStartsWith = String.prototype.startsWith; +function stringIndexOf(value: string, search: string): number { + return ReflectApply(StringIndexOf, value, [search]) as number; +} + function stringSlice(value: string, start: number, end?: number): string { return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; } @@ -83,6 +88,22 @@ function isLocalMappedSpecifier(specifier: string): boolean { stringStartsWith(specifier, "file://"); } +function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { + const queryStart = stringIndexOf(specifier, "?"); + const hashStart = stringIndexOf(specifier, "#"); + const suffixStart = queryStart === -1 + ? hashStart + : hashStart === -1 + ? queryStart + : Math.min(queryStart, hashStart); + + if (suffixStart === -1) return { path: specifier, suffix: "" }; + return { + path: stringSlice(specifier, 0, suffixStart), + suffix: stringSlice(specifier, suffixStart), + }; +} + /** * Resolve a single import specifier to a local cached path. * @@ -110,10 +131,7 @@ async function resolveSpecifier( // against the page's public origin, which answers with HTML // (VERYFRONT-SERVER-G). if (stringStartsWith(specifier, "@/")) { - const aliasPath = stringSlice(specifier, 2); - const suffixIndex = aliasPath.search(/[?#]/); - const pathOnly = suffixIndex === -1 ? aliasPath : stringSlice(aliasPath, 0, suffixIndex); - const suffix = suffixIndex === -1 ? "" : stringSlice(aliasPath, suffixIndex); + const { path: pathOnly, suffix } = splitSpecifierSuffix(stringSlice(specifier, 2)); const normalizedPath = normalizeExtension(pathOnly); const jsPath = /\.(js|mjs|cjs|css)$/.test(normalizedPath) ? normalizedPath diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts index 7ea7ded749..1992578082 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts @@ -147,6 +147,46 @@ describe("module-fetcher/http-fetcher", () => { ); }); + it("preserves suffixes while mapping nested HTTP fallback imports", async () => { + const fetchedPaths: string[] = []; + const result = await fetchModuleViaHTTP( + "_vf_modules/pages/index.js", + { env: { get: () => undefined } } as unknown as RuntimeAdapter, + (path) => { + fetchedPaths.push(path); + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + { debug: () => {}, warn: () => {} } as unknown as Logger, + "docs", + true, + undefined, + { + fetchFn: (() => + Promise.resolve( + new Response([ + `import data from "/_vf_modules/data.json?raw#payload";`, + `import "/_vf_modules/setup.js#bootstrap";`, + `export const lazy = () => import("./Lazy.js?client");`, + ].join("\n")), + )) as typeof fetch, + }, + ); + + assertEquals(fetchedPaths, [ + "_vf_modules/data.json", + "_vf_modules/setup.js", + "./Lazy.js", + ]); + assertEquals( + result, + [ + `import data from "file:///cache/_vf_modules__data.json.mjs?raw#payload";`, + `import "file:///cache/_vf_modules__setup.js.mjs#bootstrap";`, + `export const lazy = () => import("file:///cache/.__Lazy.js.mjs?client");`, + ].join("\n"), + ); + }); + it("uses the request origin for pinned local module fetches", async () => { const logger = { debug: () => {}, warn: () => {} } as unknown as Logger; const adapter = { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index b15a8e4d32..62b300f0a6 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -237,18 +237,20 @@ export async function fetchModuleViaHTTP( const { vfModules, relative } = findNestedImports(moduleCode); const allImports = [ - ...vfModules.map(({ original, path, start, end, isDynamic, isSideEffect }) => ({ + ...vfModules.map(({ original, path, suffix, start, end, isDynamic, isSideEffect }) => ({ original, path, + suffix, start, end, isDynamic, isSideEffect, key: "nestedPath" as const, })), - ...relative.map(({ original, path, start, end, isDynamic, isSideEffect }) => ({ + ...relative.map(({ original, path, suffix, start, end, isDynamic, isSideEffect }) => ({ original, path, + suffix, start, end, isDynamic, @@ -260,9 +262,18 @@ export async function fetchModuleViaHTTP( const results = await parallelMap( allImports, - async ({ original, path, start, end, isDynamic, isSideEffect, key }) => { + async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => { const nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); - return { original, start, end, isDynamic, isSideEffect, nestedFilePath, [key]: path }; + return { + original, + start, + end, + suffix, + isDynamic, + isSideEffect, + nestedFilePath, + [key]: path, + }; }, { semaphore: new Semaphore(MAX_MDX_MODULE_TRANSFORM_CONCURRENCY), @@ -270,17 +281,19 @@ export async function fetchModuleViaHTTP( ); const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, isDynamic, isSideEffect, nestedFilePath } of results) { + for ( + const { original, start, end, suffix, isDynamic, isSideEffect, nestedFilePath } of results + ) { if (nestedFilePath) { replacements.push({ start, end, expected: original, replacement: isDynamic - ? `"file://${nestedFilePath}"` + ? `"file://${nestedFilePath}${suffix ?? ""}"` : isSideEffect - ? `import "file://${nestedFilePath}"` - : `from "file://${nestedFilePath}"`, + ? `import "file://${nestedFilePath}${suffix ?? ""}"` + : `from "file://${nestedFilePath}${suffix ?? ""}"`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 01f4f57098..8fe52d1b59 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -224,6 +224,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after await regex literals inside template substitutions", () => { + assertEquals( + specifiers( + 'const html = `${await /}/.test(x) ? import("./after-await-regex.js") : null}`;', + ), + ["./after-await-regex.js"], + ); + }); + it("finds executable imports after regex braces following control conditions", () => { assertEquals( specifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index c71a36fe7d..6c02145140 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -298,6 +298,7 @@ function canStartRegexLiteral(source: string, index: number, rangeStart: number) "else", "in", "instanceof", + "await", "return", "throw", "typeof", From 31832d42262170ff4444b00fae0e4abc65555fe4 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 01:10:51 +0200 Subject: [PATCH 032/104] Keep operational compile failures out of tenant build warnings The tenant-build classifier now requires generic compilation errors to carry an explicit source-owned marker, so infrastructure failures from the compile path continue to report at error severity. Markdown and MDX wrappers also preserve their original causes while using the repository alias for shared frontmatter helpers. Constraint: Review comments required separating esbuild service failures from tenant source compilation failures. Rejected: Treat every compilation-error slug as tenant-owned | it hides transform infrastructure failures from error-level reporting. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/pipeline.behavior.test.ts src/transforms/md/compiler/md-compiler.test.ts src/transforms/mdx/compiler/mdx-compiler.test.ts Tested: deno fmt --check, deno lint, deno check on touched files --- .../orchestrator/module-loader/build-failure.ts | 8 ++++++-- src/rendering/orchestrator/pipeline.behavior.test.ts | 12 +++++++++++- src/transforms/md/compiler/md-compiler.ts | 3 ++- src/transforms/mdx/compiler/mdx-compiler.ts | 1 + src/transforms/pipeline/stages/compile.ts | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index b9d3d2bacb..f619a454f8 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -28,12 +28,16 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", - "compilation-error", ]); function isExplicitTenantBuildFailure(error: Error): boolean { const snapshot = snapshotVeryfrontError(error); - return snapshot?.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); + if (snapshot?.category !== "BUILD") return false; + if (TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug)) return true; + if (snapshot.slug !== "compilation-error") return false; + const context = snapshot.context; + return typeof context === "object" && context !== null && + (context as { tenantSourceError?: unknown }).tenantSourceError === true; } /** Tag `error` as a build failure and return it. */ diff --git a/src/rendering/orchestrator/pipeline.behavior.test.ts b/src/rendering/orchestrator/pipeline.behavior.test.ts index 5bb9a430cc..b9f4fed035 100644 --- a/src/rendering/orchestrator/pipeline.behavior.test.ts +++ b/src/rendering/orchestrator/pipeline.behavior.test.ts @@ -706,10 +706,11 @@ describe("RenderPipeline behavior", () => { return context?.tenantBuildFailure; } - it("reports a build failure as one", async () => { + it("reports a source compilation failure as tenant-owned", async () => { const error = await rejectLoad(pipelineWithFailingPageModule(() => { throw markBuildFailure(COMPILATION_ERROR.create({ detail: "Cannot import the static asset", + context: { tenantSourceError: true }, })); })); @@ -717,6 +718,15 @@ describe("RenderPipeline behavior", () => { assertEquals(tenantBuildFailureFlag(error), true); }); + it("does not infer tenant source from operational compilation failures", () => { + const infrastructureError = markBuildFailure(COMPILATION_ERROR.create({ + detail: "ESM transform service unavailable", + cause: Object.assign(new Error("esbuild child exited"), { code: "EPIPE" }), + })); + + assertEquals(isTenantBuildFailure(infrastructureError), false); + }); + it("keeps framework failures inside the transform phase distinct", async () => { const frameworkError = markBuildFailure(toError(createError({ type: "build", diff --git a/src/transforms/md/compiler/md-compiler.ts b/src/transforms/md/compiler/md-compiler.ts index be85236792..eee48db84f 100644 --- a/src/transforms/md/compiler/md-compiler.ts +++ b/src/transforms/md/compiler/md-compiler.ts @@ -8,7 +8,7 @@ import type { } from "#veryfront/extensions/content/index.ts"; import { MARKDOWN_COMPILE_ERROR, VeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; -import { isFrontmatterSyntaxError } from "../../mdx/compiler/frontmatter-extractor.ts"; +import { isFrontmatterSyntaxError } from "#veryfront/transforms/mdx/compiler/frontmatter-extractor.ts"; const logger = rendererLogger.component("md-compiler"); @@ -56,6 +56,7 @@ export function compileMarkdownRuntime( throw MARKDOWN_COMPILE_ERROR.create({ detail: `Markdown compilation error: ${err.message} | file: ${filePath ?? ""}`, + cause: err, }); } }, diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index 730bab4808..719db596f1 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -71,6 +71,7 @@ export function compileMDXRuntime( throw MDX_COMPILE_ERROR.create({ detail: `MDX compilation error: ${err.message} | file: ${filePath ?? ""}`, + cause: err, }); } }, diff --git a/src/transforms/pipeline/stages/compile.ts b/src/transforms/pipeline/stages/compile.ts index a82dc20f66..bfceb8ab8f 100644 --- a/src/transforms/pipeline/stages/compile.ts +++ b/src/transforms/pipeline/stages/compile.ts @@ -8,6 +8,11 @@ import { type TransformContext, type TransformPlugin, TransformStage } from "../ const logger = rendererLogger.component("esm-transform"); +function isEsbuildSourceError(error: unknown): boolean { + return typeof error === "object" && error !== null && + Array.isArray((error as { errors?: unknown }).errors); +} + export const compilePlugin: TransformPlugin = { name: "esbuild-compile", stage: TransformStage.COMPILE, @@ -70,6 +75,7 @@ export const compilePlugin: TransformPlugin = { throw COMPILATION_ERROR.create({ detail: `ESM transform failed for ${ctx.filePath} (loader: ${loader}): ${errorMsg}`, cause: err, + context: { tenantSourceError: isEsbuildSourceError(err) }, }); } }, From 05c67a5c14f7ff6a3bb6db6173c67cc704b96833 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 01:17:57 +0200 Subject: [PATCH 033/104] Keep template scanning past closed control blocks Codex found that a regex literal after a closed control block inside a template substitution could be mistaken for division. The scanner would count the regex brace structurally, stop before the following dynamic import, and leave the project-runtime specifier unresolved in cached file modules. The fix recognizes a slash after a brace only when that brace closes a control-condition block, while preserving object-literal division behavior. Constraint: Address only PRRT_kwDOQaPiP86ZcD5U without a broad parser rewrite. Rejected: Treat every slash after a close brace as regex | would regress object-literal division such as {} / 2. Confidence: high Scope-risk: narrow Directive: Keep canStartRegexLiteral conservative; do not classify object literal close braces as regex starts. Tested: Focused source-spans test; expanded MDX/ESM transform suite; touched-file fmt/lint/check; git diff --check. Not-tested: GitHub CI until after guarded push. --- .../utils/source-spans.test.ts | 23 ++++++++++++ .../esm-module-loader/utils/source-spans.ts | 37 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 8fe52d1b59..3335d324f6 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -135,6 +135,14 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { return findDynamicImportSpans(source, matchRelative, UNBOUNDED).map((span) => span.path); } + function vfModuleSpecifiers(source: string): string[] { + return findDynamicImportSpans( + source, + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ).map((span) => span.path); + } + it("requires a positive safe match bound", () => { for (const maxMatches of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1]) { assertThrows( @@ -233,6 +241,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after regex literals following closed blocks", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { if (ok) {} /}/.test(x); })() && import("/_vf_modules/lazy.js")}`;', + ), + ["/_vf_modules/lazy.js"], + ); + }); + it("finds executable imports after regex braces following control conditions", () => { assertEquals( specifiers( @@ -297,6 +314,12 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { specifiers('const value = maybe?.count / 2; import("./after-optional-chain.js");'), ["./after-optional-chain.js"], ); + assertEquals( + specifiers( + 'const html = `${constValue = {} / 2} ${import("./after-object-division.js")}`;', + ), + ["./after-object-division.js"], + ); }); it("finds imports after regex literals following control statement conditions", () => { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 6c02145140..b392fbd728 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -276,12 +276,49 @@ function isControlConditionCloseParen(source: string, index: number, rangeStart: return false; } +function matchingOpenBraceIndex(source: string, index: number, rangeStart: number): number | null { + let depth = 1; + let cursor = index - 1; + + while (cursor >= rangeStart) { + const char = source[cursor]; + + if (char === "}") { + depth++; + cursor--; + continue; + } + + if (char === "{") { + depth--; + if (depth === 0) return cursor; + cursor--; + continue; + } + + cursor--; + } + + return null; +} + +function isControlBlockCloseBrace(source: string, index: number, rangeStart: number): boolean { + const openBrace = matchingOpenBraceIndex(source, index, rangeStart); + if (openBrace === null) return false; + + const beforeOpenBrace = previousSignificantIndex(source, openBrace); + return beforeOpenBrace >= rangeStart && + source[beforeOpenBrace] === ")" && + isControlConditionCloseParen(source, beforeOpenBrace, rangeStart); +} + function canStartRegexLiteral(source: string, index: number, rangeStart: number): boolean { const previous = previousSignificantIndex(source, index); if (previous < rangeStart) return true; const char = source[previous]; if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; + if (char === "}" && isControlBlockCloseBrace(source, previous, rangeStart)) return true; if ( (char === "+" || char === "-") && previous - 1 >= rangeStart && From eef4856854baa9af76d7d7d31a36d8a2268e36b9 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 01:31:44 +0200 Subject: [PATCH 034/104] Recognize for-of regex literals in template scans The MDX source span scanner now treats a slash after the for-of keyword as a regex literal start, so regex braces inside for-of template substitutions do not terminate scanning before project module imports. Constraint: Address PRRT_kwDOQaPiP86ZcIM0 on top of the current closed-block fix without broadening object-literal slash handling. Rejected: Reapply the earlier blanket close-brace expression-start patch | the current branch intentionally keeps close-brace handling limited to control blocks. Confidence: high Scope-risk: narrow Directive: Keep canStartRegexLiteral conservative around close braces; for-of support belongs in the keyword context list. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno fmt --check, deno lint, deno check, git diff --check on touched files --- .../mdx/esm-module-loader/utils/source-spans.test.ts | 9 +++++++++ .../mdx/esm-module-loader/utils/source-spans.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 3335d324f6..9e0f7ad00c 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -250,6 +250,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after regex literals following of", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { for (const x of /}/g) {} })() && import("/_vf_modules/for-of-lazy.js")}`;', + ), + ["/_vf_modules/for-of-lazy.js"], + ); + }); + it("finds executable imports after regex braces following control conditions", () => { assertEquals( specifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index b392fbd728..0d96431ab5 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -335,6 +335,7 @@ function canStartRegexLiteral(source: string, index: number, rangeStart: number) "else", "in", "instanceof", + "of", "await", "return", "throw", From f28d33f28cd76722d84bc36dbf9e1b715129f7da Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 01:34:20 +0200 Subject: [PATCH 035/104] Keep tenant compile classification explicit across capture seams Operational compile failures use the same generic registry slug as tenant source syntax failures. Use the tenantBuildFailure context flag as the only generic compilation-error downgrade path, and cover both module-loader tagging and direct observability capture. Constraint: Review comments required esbuild service failures to stay error-level. Rejected: Introduce tenantSourceError as a second context flag | it duplicates the existing render-pipeline tenantBuildFailure contract. Rejected: Treat any esbuild errors array as tenant source | operational failures can still carry structured details without a source location. Confidence: high Scope-risk: narrow Directive: Generic compilation-error must not be downgraded unless an explicit tenant source flag is set by the transform seam. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/pipeline/stages/compile.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/pipeline.behavior.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/observability/application-errors.test.ts Tested: deno task coverage:ci:shard -- --shard=2/8 --coverage-dir=coverage-shard-2-final2 Tested: deno task test:bun Tested: deno task docs:api-reference:check && deno task docs:errors:check && deno task docs:public:check Tested: deno task lint:ci --- docs/api-reference/veryfront/observability.md | 8 +++--- src/observability/application-errors.test.ts | 27 ++++++++++++++++--- src/observability/application-errors.ts | 1 - .../module-loader/build-failure.ts | 14 +++++----- .../orchestrator/pipeline.behavior.test.ts | 7 +++-- .../pipeline/stages/compile.test.ts | 25 ++++++++++++++++- src/transforms/pipeline/stages/compile.ts | 12 ++++++--- 7 files changed, 71 insertions(+), 23 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 8e3e169182..302776f458 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L274) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L302) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L275) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L303) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L274) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L302) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 0eb7950afd..9a2a920051 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -17,6 +17,7 @@ import { ASSET_OPTIMIZATION_ERROR, BUILD_FAILED, BUNDLE_ERROR, + COMPILATION_ERROR, CONFIG_PARSE_ERROR, createError, INITIALIZATION_ERROR, @@ -175,6 +176,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings const markdownRegistryError = MARKDOWN_COMPILE_ERROR.create({ detail: "Markdown frontmatter failed in /pages/index.md", }); + const sourceCompilationError = COMPILATION_ERROR.create({ + detail: "TypeScript syntax failed in /pages/index.tsx", + context: { tenantBuildFailure: true }, + }); const frameworkError = INITIALIZATION_ERROR.create({ detail: "renderer failed to initialize", }); @@ -212,6 +217,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(markdownRegistryError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(sourceCompilationError, { boundary: "ssr.render" }), + "event-id", + ); assertEquals( captureApplicationError(frameworkError, { boundary: "ssr.render" }), "event-id", @@ -245,7 +254,15 @@ it("application error reporter downgrades tenant build errors to tagged warnings "event-id", ); - assertEquals(captures.length, 12); + const genericCompilationError = COMPILATION_ERROR.create({ + detail: "esbuild service exited unexpectedly", + }); + assertEquals( + captureApplicationError(genericCompilationError, { boundary: "ssr.render" }), + "event-id", + ); + + assertEquals(captures.length, 14); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -256,9 +273,9 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[2]?.context.level, "warning"); assertEquals(captures[3]?.context.errorClass, "tenant-build"); assertEquals(captures[3]?.context.level, "warning"); + assertEquals(captures[4]?.context.errorClass, "tenant-build"); + assertEquals(captures[4]?.context.level, "warning"); // Genuine framework failures keep their default error-level capture. - assertEquals(captures[4]?.context.errorClass, undefined); - assertEquals(captures[4]?.context.level, undefined); assertEquals(captures[5]?.context.errorClass, undefined); assertEquals(captures[5]?.context.level, undefined); assertEquals(captures[6]?.context.errorClass, undefined); @@ -273,6 +290,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[10]?.context.level, undefined); assertEquals(captures[11]?.context.errorClass, undefined); assertEquals(captures[11]?.context.level, undefined); + assertEquals(captures[12]?.context.errorClass, undefined); + assertEquals(captures[12]?.context.level, undefined); + assertEquals(captures[13]?.context.errorClass, undefined); + assertEquals(captures[13]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 2068df7276..8cfb0161df 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -235,7 +235,6 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", - "compilation-error", ]); /** diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index f619a454f8..1dfd9ed88e 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -32,12 +32,14 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ function isExplicitTenantBuildFailure(error: Error): boolean { const snapshot = snapshotVeryfrontError(error); - if (snapshot?.category !== "BUILD") return false; - if (TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug)) return true; - if (snapshot.slug !== "compilation-error") return false; - const context = snapshot.context; - return typeof context === "object" && context !== null && - (context as { tenantSourceError?: unknown }).tenantSourceError === true; + const errorContext = snapshot?.context; + if ( + typeof errorContext === "object" && errorContext !== null && + (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true + ) { + return true; + } + return snapshot?.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); } /** Tag `error` as a build failure and return it. */ diff --git a/src/rendering/orchestrator/pipeline.behavior.test.ts b/src/rendering/orchestrator/pipeline.behavior.test.ts index b9f4fed035..67e6b8726f 100644 --- a/src/rendering/orchestrator/pipeline.behavior.test.ts +++ b/src/rendering/orchestrator/pipeline.behavior.test.ts @@ -710,7 +710,7 @@ describe("RenderPipeline behavior", () => { const error = await rejectLoad(pipelineWithFailingPageModule(() => { throw markBuildFailure(COMPILATION_ERROR.create({ detail: "Cannot import the static asset", - context: { tenantSourceError: true }, + context: { tenantBuildFailure: true }, })); })); @@ -718,10 +718,9 @@ describe("RenderPipeline behavior", () => { assertEquals(tenantBuildFailureFlag(error), true); }); - it("does not infer tenant source from operational compilation failures", () => { + it("keeps generic compilation failures at framework severity", () => { const infrastructureError = markBuildFailure(COMPILATION_ERROR.create({ - detail: "ESM transform service unavailable", - cause: Object.assign(new Error("esbuild child exited"), { code: "EPIPE" }), + detail: "esbuild service exited unexpectedly", })); assertEquals(isTenantBuildFailure(infrastructureError), false); diff --git a/src/transforms/pipeline/stages/compile.test.ts b/src/transforms/pipeline/stages/compile.test.ts index d83f21c669..1adb211120 100644 --- a/src/transforms/pipeline/stages/compile.test.ts +++ b/src/transforms/pipeline/stages/compile.test.ts @@ -1,7 +1,14 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals, assertExists, assertStringIncludes } from "#veryfront/testing/assert.ts"; +import { + assertEquals, + assertExists, + assertInstanceOf, + assertRejects, + assertStringIncludes, +} from "#veryfront/testing/assert.ts"; import { afterAll, describe, it } from "#veryfront/testing/bdd.ts"; import { stop as stopEsbuild } from "#veryfront/platform/compat/esbuild.ts"; +import { VeryfrontError } from "#veryfront/errors"; import { compilePlugin } from "./compile.ts"; import { TransformStage } from "../types.ts"; import type { TransformContext } from "../types.ts"; @@ -129,4 +136,20 @@ describe("transforms/pipeline/stages/compile", () => { assertStringIncludes(result, 'await Promise.resolve("production")'); }); }); + + describe("error classification", () => { + it("marks esbuild source diagnostics as tenant build failures", async () => { + const error = await assertRejects( + async () => await compilePlugin.transform(createContext("export const value = ;")), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "compilation-error"); + assertEquals( + (error.context as { tenantBuildFailure?: unknown } | undefined)?.tenantBuildFailure, + true, + ); + }); + }); }); diff --git a/src/transforms/pipeline/stages/compile.ts b/src/transforms/pipeline/stages/compile.ts index bfceb8ab8f..ba600d16ca 100644 --- a/src/transforms/pipeline/stages/compile.ts +++ b/src/transforms/pipeline/stages/compile.ts @@ -8,9 +8,13 @@ import { type TransformContext, type TransformPlugin, TransformStage } from "../ const logger = rendererLogger.component("esm-transform"); -function isEsbuildSourceError(error: unknown): boolean { - return typeof error === "object" && error !== null && - Array.isArray((error as { errors?: unknown }).errors); +function isEsbuildSourceDiagnostic(error: unknown): boolean { + const diagnostics = (error as { errors?: unknown })?.errors; + if (!Array.isArray(diagnostics)) return false; + return diagnostics.some((diagnostic) => { + const location = (diagnostic as { location?: unknown })?.location; + return typeof location === "object" && location !== null; + }); } export const compilePlugin: TransformPlugin = { @@ -75,7 +79,7 @@ export const compilePlugin: TransformPlugin = { throw COMPILATION_ERROR.create({ detail: `ESM transform failed for ${ctx.filePath} (loader: ${loader}): ${errorMsg}`, cause: err, - context: { tenantSourceError: isEsbuildSourceError(err) }, + context: { tenantBuildFailure: isEsbuildSourceDiagnostic(err) }, }); } }, From ba9e2d33eb57ae2b4c9c62a4bb104e3e80a0e521 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 02:02:25 +0200 Subject: [PATCH 036/104] Keep division scans out of literal control text The dynamic import scanner checks whether a slash can start a regex by walking backward through a candidate control-condition close paren. Literal and comment contents are not executable syntax, so the backward walk now skips over them before matching control keywords. Constraint: The scanner is intentionally lightweight and does not depend on a full JavaScript parser. Rejected: Treat every slash after a call as division | existing regex-after-control cases need to stay supported. Confidence: high Scope-risk: narrow Directive: Keep scanner regressions close to source-spans so import materialization and unresolved-import checks share coverage. Tested: deno test source-spans.test.ts; deno fmt --check source-spans files; deno lint source-spans files; deno check source-spans.test.ts Not-tested: full repository suite --- .../utils/source-spans.test.ts | 19 +++++++++++ .../esm-module-loader/utils/source-spans.ts | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 9e0f7ad00c..c29a5c6aba 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -331,6 +331,25 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds imports after division when literal contents look like control conditions", () => { + assertEquals( + specifiers('foo("if(") / 2 && import("./after-string-division.js");'), + ["./after-string-division.js"], + ); + assertEquals( + specifiers("foo('while(') / 2 && import('./after-single-string-division.js');"), + ["./after-single-string-division.js"], + ); + assertEquals( + specifiers("foo(`for(`) / 2 && import('./after-template-string-division.js');"), + ["./after-template-string-division.js"], + ); + assertEquals( + specifiers("foo(/* switch( */ value) / 2 && import('./after-comment-division.js');"), + ["./after-comment-division.js"], + ); + }); + it("finds imports after regex literals following control statement conditions", () => { assertEquals( specifiers('if (ok) /"/.test(x); import("./after-if-quote.js");'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 0d96431ab5..2403724708 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -253,6 +253,17 @@ function isControlConditionCloseParen(source: string, index: number, rangeStart: while (cursor >= rangeStart) { const char = source[cursor]; + if (char === '"' || char === "'" || char === "`") { + cursor = previousStringLiteralStart(source, cursor, rangeStart) - 1; + continue; + } + + if (char === "/" && source[cursor - 1] === "*") { + const commentStart = source.lastIndexOf("/*", cursor - 2); + cursor = commentStart >= rangeStart ? commentStart - 1 : rangeStart - 1; + continue; + } + if (char === ")") { depth++; cursor--; @@ -276,6 +287,28 @@ function isControlConditionCloseParen(source: string, index: number, rangeStart: return false; } +function previousStringLiteralStart(source: string, index: number, rangeStart: number): number { + const quote = source[index]; + let cursor = index - 1; + + while (cursor >= rangeStart) { + if (source[cursor] === quote && !isEscapedByBackslash(source, cursor)) return cursor; + cursor--; + } + + return rangeStart; +} + +function isEscapedByBackslash(source: string, index: number): boolean { + let cursor = index - 1; + let count = 0; + while (cursor >= 0 && source[cursor] === "\\") { + count++; + cursor--; + } + return count % 2 === 1; +} + function matchingOpenBraceIndex(source: string, index: number, rangeStart: number): number | null { let depth = 1; let cursor = index - 1; From cc143fb4f935b24b10a4690519bfa69b7cf5b08d Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 02:22:31 +0200 Subject: [PATCH 037/104] Keep rewritten import literals parseable Nested MDX import rewrites can preserve query and hash suffixes from source literals. Those suffixes may contain quote characters when the original import used a non-interpolated template literal, so the replacement now serializes the full URL as a JavaScript string literal instead of concatenating it inside quotes. The dynamic-import scanner also matches closed control blocks with a forward bounded pass that ignores braces inside literal text, comments, and regex literals. Constraint: Exact-head Codex review found malformed emitted imports and missed template imports after noisy control-block regex literals. Rejected: Decode and re-escape only suffix fragments | full target serialization is smaller and covers static, dynamic, and stub paths consistently. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno fmt --check, deno lint, and deno check on the touched transform files --- .../module-fetcher/nested-imports.test.ts | 19 +++++ .../module-fetcher/nested-imports.ts | 18 ++-- .../utils/source-spans.test.ts | 9 ++ .../esm-module-loader/utils/source-spans.ts | 84 ++++++++++++++++--- 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index cedf438c50..c71e101ece 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -250,6 +250,25 @@ import { bar } from "./local.js"; ); }); + it("escapes preserved suffixes when materializing dynamic import literals", async () => { + const result = await resolveNestedModuleImports({ + moduleCode: + 'export const load = () => import(`/_vf_modules/components/Lazy.js#client" + globalThis.bad + "`);', + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path) => { + return Promise.resolve(`/cache/${path.replaceAll("/", "__")}.mjs`); + }, + }); + + assertEquals( + result, + 'export const load = () => import("file:///cache/_vf_modules__components__Lazy.js.mjs#client\\" + globalThis.bad + \\"");', + ); + }); + it("materializes dynamic _vf_modules imports inside template substitutions", async () => { const calls: Array<{ path: string; parent?: string }> = []; const result = await resolveNestedModuleImports({ diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 83dd4fc8fa..7b2de9a2e0 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -50,6 +50,10 @@ type NestedImportSpan = { isSideEffect?: boolean; }; +function toImportStringLiteral(url: string): string { + return JSON.stringify(url); +} + /** * Find nested module imports in code. * Matches both /_vf_modules/... and file:///_vf_modules/... patterns. @@ -229,15 +233,16 @@ export async function processNestedImports( } of results ) { if (nestedFilePath) { + const importTarget = toImportStringLiteral(`file://${nestedFilePath}${suffix ?? ""}`); replacements.push({ start, end, expected: original, replacement: isDynamic - ? `"file://${nestedFilePath}${suffix ?? ""}"` + ? importTarget : isSideEffect - ? `import "file://${nestedFilePath}${suffix ?? ""}"` - : `from "file://${nestedFilePath}${suffix ?? ""}"`, + ? `import ${importTarget}` + : `from ${importTarget}`, }); continue; } @@ -255,15 +260,16 @@ export async function processNestedImports( const stubPath = await createStubModule(modulePath, moduleCode, original, esmCacheDir); if (stubPath) { + const importTarget = toImportStringLiteral(`file://${stubPath}${suffix ?? ""}`); replacements.push({ start, end, expected: original, replacement: isDynamic - ? `"file://${stubPath}${suffix ?? ""}"` + ? importTarget : isSideEffect - ? `import "file://${stubPath}${suffix ?? ""}"` - : `from "file://${stubPath}${suffix ?? ""}"`, + ? `import ${importTarget}` + : `from ${importTarget}`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index c29a5c6aba..2f31e4ecad 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -365,6 +365,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds imports after regex literals following noisy control blocks", () => { + assertEquals( + specifiers( + 'const html = `${(() => { if (ok) { const marker = "}"; /* { */ } /}/.test(x); return import("./after-noisy-block-regex.js"); })()}`;', + ), + ["./after-noisy-block-regex.js"], + ); + }); + it("ignores a static import and a property called import", () => { assertEquals(specifiers(`import x from "./foo.js";`), []); assertEquals(specifiers(`obj.import("./foo.js");`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 2403724708..c8afa87c17 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -309,27 +309,87 @@ function isEscapedByBackslash(source: string, index: number): boolean { return count % 2 === 1; } +function canStartRegexLiteralInBraceScan( + source: string, + index: number, + rangeStart: number, +): boolean { + const previous = previousSignificantIndex(source, index); + if (previous < rangeStart) return true; + + const char = source[previous]; + if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; + if ( + (char === "+" || char === "-") && + previous - 1 >= rangeStart && + source[previous - 1] === char + ) { + return false; + } + if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; + + return [ + "case", + "delete", + "do", + "else", + "in", + "instanceof", + "of", + "await", + "return", + "throw", + "typeof", + "void", + "yield", + ].includes(keywordBefore(source, index) ?? ""); +} + +function skipBraceScanIgnored(source: string, index: number, rangeStart: number): number { + const char = source[index]; + const next = source[index + 1]; + + if ( + (char === "/" && (next === "/" || next === "*")) || + char === '"' || + char === "'" || + char === "`" + ) { + return skipIgnored(source, index); + } + + if (char === "/" && canStartRegexLiteralInBraceScan(source, index, rangeStart)) { + return skipRegexLiteral(source, index); + } + + return index; +} + function matchingOpenBraceIndex(source: string, index: number, rangeStart: number): number | null { - let depth = 1; - let cursor = index - 1; + const openBraces: number[] = []; + let cursor = rangeStart; - while (cursor >= rangeStart) { - const char = source[cursor]; + while (cursor <= index) { + const skipped = skipBraceScanIgnored(source, cursor, rangeStart); + if (skipped !== cursor) { + cursor = skipped; + continue; + } - if (char === "}") { - depth++; - cursor--; + if (source[cursor] === "{") { + openBraces.push(cursor); + cursor++; continue; } - if (char === "{") { - depth--; - if (depth === 0) return cursor; - cursor--; + if (source[cursor] === "}") { + const openBrace = openBraces.pop(); + if (cursor === index) return openBrace ?? null; + cursor++; continue; } - cursor--; + cursor++; } return null; From 21e4e737dca0e5056d661d0c04ea4751b8b709a3 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 07:24:44 +0200 Subject: [PATCH 038/104] fix(observability): narrow tenant build classification at both open seams Two gaps let the tenant/framework split land on the wrong side. A `.mdx` or `.md` file reaches the COMPILE stage as generated JSX: PARSE has already run the MDX compiler over the tenant's source. An esbuild diagnostic there carries a valid location pointing into framework output, so treating "has a location" as "tenant source" downgraded a broken remark/rehype/recma release to a Sentry warning. Refuse to infer ownership for those two extensions; genuine source errors are already classified upstream at PARSE as mdx-compile-error / markdown-compile-error. An unresolved relative import is dropped by the dependency resolver and survives into the built module, so it only fails at import() time as ERR_MODULE_NOT_FOUND -- after the self-heal rebuild has retried it. That rejection escaped loadModule untagged, so a tenant typo in an import path was reported at error level forever. Classify it explicitly at that seam: ERR_MODULE_NOT_FOUND is not a VeryfrontError, so slug-based classification cannot see it. Also collapses the duplicated slug set and context predicate into a single owner in src/errors/tenant-classification.ts, makes SentryPolicyScope.setLevel optional so a third-party adapter keeps compiling, replaces plain tag assignment with Object.defineProperty so tagging a frozen error cannot throw from inside a catch, and deletes the unreachable YAML stack-path heuristic the frontmatter symbol tag superseded. --- docs/api-reference/veryfront/observability.md | 12 ++--- .../ext-observability-sentry/src/policy.ts | 6 ++- src/errors/tenant-classification.ts | 49 +++++++++++++++++++ src/observability/application-errors.ts | 24 ++------- .../module-loader/build-failure.ts | 48 +++++++++++------- .../orchestrator/module-loader/index.test.ts | 40 ++++++++++++++- .../orchestrator/module-loader/index.ts | 19 ++++++- src/rendering/orchestrator/pipeline.ts | 9 +++- src/transforms/mdx/compiler/mdx-compiler.ts | 13 ++--- .../pipeline/stages/compile.test.ts | 39 +++++++++++++++ src/transforms/pipeline/stages/compile.ts | 22 ++++++++- 11 files changed, 224 insertions(+), 57 deletions(-) create mode 100644 src/errors/tenant-classification.ts diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 302776f458..228c2e8b5b 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L274) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L260) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L302) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L288) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -58,7 +58,7 @@ const result = await withSpan("load-data", async () => { | `getMetricsState` | State for get metrics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L38) | | `getTraceContext` | Context for get trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L500) | | `initAutoInstrumentation` | Initialize automatic instrumentation wrappers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/orchestrator.ts#L15) | -| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L149) | +| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L150) | | `initializeOTLP` | Initialize OTLP tracing export. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L113) | | `initMetrics` | Initialize metrics collection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L20) | | `initTracing` | Initialize tracing for the current runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L18) | @@ -134,7 +134,7 @@ const result = await withSpan("load-data", async () => { | `ApplicationErrorReporter` | Provider-neutral application error capture and flush interface. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-error-contract.ts#L27) | | `ApplicationErrorReporterInitializationContext` | Runtime context passed to an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L9) | | `ApplicationErrorReporterInitializer` | Application-composition contract for an error-reporting implementation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L20) | -| `ApplicationErrorReporterLifecycle` | Active application-error reporter ownership. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L28) | +| `ApplicationErrorReporterLifecycle` | Active application-error reporter ownership. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L29) | | `ApplicationErrorReporterSession` | Reporter and cleanup ownership returned by an application-selected initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/extensions/observability/application-error-reporter.ts#L14) | | `AttributeValue` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L33) | | `AutoInstrumentConfig` | Configuration used by auto instrument. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/types.ts#L24) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L274) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L302) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L260) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L288) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | diff --git a/extensions/ext-observability-sentry/src/policy.ts b/extensions/ext-observability-sentry/src/policy.ts index d5adb0eb65..4a41a59397 100644 --- a/extensions/ext-observability-sentry/src/policy.ts +++ b/extensions/ext-observability-sentry/src/policy.ts @@ -16,7 +16,9 @@ const SENSITIVE_ATTRIBUTE_KEY_PATTERN = export type SentryPolicyScope = { setContext(name: string, context: Record): void; setFingerprint(fingerprint: string[]): void; - setLevel(level: "error" | "warning"): void; + // Optional so a third-party adapter written against the previous shape keeps + // compiling; `SentryPolicyScope` is part of this package's published surface. + setLevel?(level: "error" | "warning"): void; setTag(key: string, value: string): void; }; @@ -89,7 +91,7 @@ export function applySentryScopePolicy( if (context.processRole) scope.setTag("process_role", context.processRole); scope.setTag("veryfront.boundary", context.boundary); if (context.errorClass) scope.setTag("veryfront.error_class", context.errorClass); - if (context.level) scope.setLevel(context.level); + if (context.level) scope.setLevel?.(context.level); if (context.method) scope.setTag("http.request.method", context.method); if (context.requestId) scope.setTag("veryfront.request_id", context.requestId); if (context.traceId) { diff --git a/src/errors/tenant-classification.ts b/src/errors/tenant-classification.ts new file mode 100644 index 0000000000..5e9a86652d --- /dev/null +++ b/src/errors/tenant-classification.ts @@ -0,0 +1,49 @@ +/** + * Single owner for the "is this build failure the tenant's fault?" question. + * + * Two layers need the answer and cannot import each other: the module loader + * (which tags errors at their capture seam) and observability (which must not + * depend on the rendering layer). They exchange the verdict through a shared + * symbol, but the verdict itself is computed here so a new tenant-facing slug + * only has to be added once. Duplicating the slug set drifts silently — the + * same error would classify differently depending on which seam saw it first. + */ + +import { snapshotVeryfrontError } from "./types.ts"; + +/** + * BUILD registry slugs that describe tenant source failing to compile, as + * opposed to framework cache/bundle/asset infrastructure failing in the same + * phase. + */ +const TENANT_BUILD_ERROR_SLUGS = new Set([ + "typescript-error", + "mdx-compile-error", + "markdown-compile-error", +]); + +/** + * Whether `error` describes tenant source or content failing to build (a page + * that does not compile, MDX that does not parse) rather than a framework + * fault. + * + * Recognizes two discriminators, both written at the seam that knows: + * - an explicit `tenantBuildFailure: true` error context, set by a compiler + * stage that inspected the diagnostic, and + * - a tenant-facing BUILD registry slug. + * + * The module loader's symbol tag is deliberately *not* read here: it is set + * from this predicate, so reading it back would be circular. + */ +export function isTenantSourceBuildError(error: unknown): boolean { + const snapshot = snapshotVeryfrontError(error); + if (!snapshot) return false; + const errorContext = snapshot.context; + if ( + typeof errorContext === "object" && errorContext !== null && + (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true + ) { + return true; + } + return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); +} diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 8cfb0161df..e6edae2bd0 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -1,4 +1,5 @@ import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; +import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/timer.ts"; import { sanitizeTelemetryAttributes, sanitizeTelemetryText } from "./telemetry-error.ts"; import { MAX_APPLICATION_ERROR_CONTEXT_VALUE_LENGTH } from "./limits.ts"; @@ -231,21 +232,15 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; * rendering layer; see src/rendering/orchestrator/module-loader/build-failure.ts. */ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-build-failure"); -const TENANT_BUILD_ERROR_SLUGS = new Set([ - "typescript-error", - "mdx-compile-error", - "markdown-compile-error", -]); /** * Whether `error` describes tenant build/content failing to compile (a page * that does not build, MDX that does not parse) rather than a framework fault. * * Recognizes the existing discriminators at their capture seam: - * - the module loader's tenant-build-failure tag, - * - the render pipeline's `tenantBuildFailure` error context, and - * - tenant-facing BUILD registry slugs that do not also describe framework - * cache or bundle infrastructure failures. + * - the module loader's tenant-build-failure tag, and + * - the shared slug/context classification in `#veryfront/errors`, which is the + * single owner of the tenant-source verdict. */ function isTenantBuildError(error: unknown): boolean { try { @@ -256,16 +251,7 @@ function isTenantBuildError(error: unknown): boolean { return true; } } - const snapshot = snapshotVeryfrontError(error); - if (!snapshot) return false; - const errorContext = snapshot.context; - if ( - typeof errorContext === "object" && errorContext !== null && - (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true - ) { - return true; - } - return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); + return isTenantSourceBuildError(error); } catch { return false; } diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index 1dfd9ed88e..95b726bd43 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -14,7 +14,7 @@ * error at the point of failure instead of leaving later layers to infer it. */ -import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; +import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); const TENANT_BUILD_FAILURE = Symbol.for("veryfront.module-loader.tenant-build-failure"); @@ -24,30 +24,40 @@ type TaggedError = Error & { [TENANT_BUILD_FAILURE]?: true; }; -const TENANT_BUILD_ERROR_SLUGS = new Set([ - "typescript-error", - "mdx-compile-error", - "markdown-compile-error", -]); - -function isExplicitTenantBuildFailure(error: Error): boolean { - const snapshot = snapshotVeryfrontError(error); - const errorContext = snapshot?.context; - if ( - typeof errorContext === "object" && errorContext !== null && - (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true - ) { - return true; +/** + * Modules are strict mode, so a plain assignment onto a frozen error throws. + * These taggers run inside `catch` blocks, where a throw would replace the + * original error with a `TypeError` and lose the failure entirely. + */ +function defineTag(error: Error, tag: symbol): void { + try { + Object.defineProperty(error, tag, { value: true, configurable: true }); + } catch { + // Sealed or non-configurable: the error stays untagged, which degrades to + // the pre-classification behavior rather than destroying the error. } - return snapshot?.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); } /** Tag `error` as a build failure and return it. */ export function markBuildFailure(error: unknown): unknown { if (error instanceof Error) { - const tagged = error as TaggedError; - tagged[BUILD_FAILURE] = true; - if (isExplicitTenantBuildFailure(error)) tagged[TENANT_BUILD_FAILURE] = true; + defineTag(error, BUILD_FAILURE); + if (isTenantSourceBuildError(error)) defineTag(error, TENANT_BUILD_FAILURE); + } + return error; +} + +/** + * Tag `error` as a build failure the tenant's own source caused, and return it. + * + * For seams that know the provenance from control flow rather than from a + * registry slug — an import specifier that still does not resolve after a full + * rebuild, for instance, is a path the project authored. + */ +export function markTenantBuildFailure(error: unknown): unknown { + if (error instanceof Error) { + defineTag(error, BUILD_FAILURE); + defineTag(error, TENANT_BUILD_FAILURE); } return error; } diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 23e21bf1da..54e3b92462 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -18,7 +18,7 @@ import { transformModuleWithDeps, } from "./index.ts"; import { buildModuleTransformCacheVariant, getModuleCacheKey } from "./module-cache-lookup.ts"; -import { isBuildFailure } from "./build-failure.ts"; +import { isBuildFailure, isTenantBuildFailure } from "./build-failure.ts"; async function withModuleLoaderFixture( files: Record, @@ -335,6 +335,44 @@ describe("module-loader/loadModule build-failure tagging", () => { }, ); }); + + // A relative import that resolves to nothing is dropped by + // `resolveModuleDependencies` and survives into the built module as authored, + // so the failure only surfaces at `import()` time as ERR_MODULE_NOT_FOUND — + // after the self-heal rebuild has already retried it. That rejection used to + // leave `loadModule` untagged, so a tenant typo in an import path was + // reported at error level forever. + it("tags a missing local static import as a tenant build failure", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import "./missing";`, + `export default function Page() { return null; }`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + + // The same seam must not launder a framework fault: a module that was found + // and threw while executing is an application error, not a build failure. + it("leaves a non-resolution import failure untagged", () => { + const runtimeError = new TypeError("x is not a function"); + + assertEquals(isBuildFailure(runtimeError), false); + assertEquals(isTenantBuildFailure(runtimeError), false); + }); }); describe("module-loader/loadModule", () => { diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 39b84c13fb..8334e955f9 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -26,7 +26,7 @@ import { getModuleCacheKey, resolveCachedModulePath, } from "./module-cache-lookup.ts"; -import { markBuildFailure } from "./build-failure.ts"; +import { markBuildFailure, markTenantBuildFailure } from "./build-failure.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; import type { DependencyPinningSourceInput } from "#veryfront/transforms/esm/package-registry.ts"; @@ -403,7 +403,22 @@ export async function loadModule( throw markBuildFailure(rebuildError); } - return await import(`${toFileUrl(rebuiltPath).href}?t=${Date.now()}&rebuilt=1`); + try { + return await import(`${toFileUrl(rebuiltPath).href}?t=${Date.now()}&rebuilt=1`); + } catch (retryError) { + // A specifier that still does not resolve after a full rebuild from + // source is not an evicted cache artifact — it is a path the project + // authored that points at nothing. `resolveModuleDependencies` only + // resolves `@/` aliases and relative imports, and silently drops the + // ones it cannot find, so the unresolvable specifier survives into the + // built module and fails here. That is a tenant build failure, and it + // has to be classified explicitly: `ERR_MODULE_NOT_FOUND` is not a + // VeryfrontError, so slug-based classification cannot see it. + if (isMissingModuleError(retryError)) throw markTenantBuildFailure(retryError); + // Anything else means the module was found and ran, which is an + // ordinary application error the project's own error page presents. + throw retryError; + } } logger.error("Failed to import module:", { diff --git a/src/rendering/orchestrator/pipeline.ts b/src/rendering/orchestrator/pipeline.ts index 6277a77dc5..ab2fcf753d 100644 --- a/src/rendering/orchestrator/pipeline.ts +++ b/src/rendering/orchestrator/pipeline.ts @@ -447,7 +447,14 @@ export class RenderPipeline { buildFailure: criticalFailures.some((f) => f.buildFailure), // Only explicit compiler/source classifications may affect // observability severity. Infrastructure can fail in the same phase. - tenantBuildFailure: criticalFailures.some((f) => f.tenantBuildFailure), + // + // `every` rather than `some`: today `criticalFailures` holds at most + // one entry (collectModulesToLoad pushes exactly one `type: "page"`, + // and only pages reach here), so the two are equivalent. If that ever + // changes, one tenant mistake must not downgrade a framework fault + // that failed alongside it. The array is non-empty inside this branch, + // so `every` cannot vacuously return true. + tenantBuildFailure: criticalFailures.every((f) => f.tenantBuildFailure), loadedCount: loaded.length, totalModules: modules.length, }, diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index 719db596f1..449123c00e 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -24,12 +24,13 @@ function isMdxSourceCompileError(error: Error): boolean { typeof candidate.ruleId === "string" && Number.isSafeInteger(candidate.line) && Number.isSafeInteger(candidate.column); - const isYamlFrontmatterError = error.name === "SyntaxError" && - /\bline \d+, column \d+\b/i.test(error.message) && - (error.stack?.includes("/src/platform/compat/std/front-matter-yaml.ts") === true || - error.stack?.includes("/src/platform/compat/std/yaml.ts") === true || - error.stack?.includes("/extensions/ext-yaml/src/adapter.ts") === true); - return isMdxParserError || isYamlFrontmatterError || isFrontmatterSyntaxError(error); + // Frontmatter failures are identified by the symbol `extractFrontmatter` + // stamps at the throw site, not by matching stack-frame paths: `extract()` is + // the only frontmatter path and it tags every SyntaxError it raises. A + // stack-path heuristic would only add false positives (any SyntaxError whose + // stack happened to pass through the YAML shim) and does not survive + // `deno compile` anyway. + return isMdxParserError || isFrontmatterSyntaxError(error); } export function compileMDXRuntime( diff --git a/src/transforms/pipeline/stages/compile.test.ts b/src/transforms/pipeline/stages/compile.test.ts index 1adb211120..c426883ec7 100644 --- a/src/transforms/pipeline/stages/compile.test.ts +++ b/src/transforms/pipeline/stages/compile.test.ts @@ -151,5 +151,44 @@ describe("transforms/pipeline/stages/compile", () => { true, ); }); + + // By the time an `.mdx` file reaches COMPILE, PARSE has already turned the + // tenant's source into JSX, so `ctx.code` is the framework's MDX-compiler + // output. A remark/rehype/recma plugin emitting broken JSX still yields an + // esbuild diagnostic with a valid location — pointing into generated code. + // Claiming tenant ownership there would downgrade a broken content-MDX + // release to a Sentry warning and nobody would be paged. + it("does not claim tenant ownership of a diagnostic in MDX-compiler output", async () => { + const error = await assertRejects( + async () => + await compilePlugin.transform( + createContext("export const value = ;", "/project/app/post.mdx"), + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals(error.slug, "compilation-error"); + assertEquals( + (error.context as { tenantBuildFailure?: unknown } | undefined)?.tenantBuildFailure, + false, + ); + }); + + it("does not claim tenant ownership of a diagnostic in Markdown-compiler output", async () => { + const error = await assertRejects( + async () => + await compilePlugin.transform( + createContext("export const value = ;", "/project/app/post.md"), + ), + VeryfrontError, + ); + + assertInstanceOf(error, VeryfrontError); + assertEquals( + (error.context as { tenantBuildFailure?: unknown } | undefined)?.tenantBuildFailure, + false, + ); + }); }); }); diff --git a/src/transforms/pipeline/stages/compile.ts b/src/transforms/pipeline/stages/compile.ts index ba600d16ca..581c765af4 100644 --- a/src/transforms/pipeline/stages/compile.ts +++ b/src/transforms/pipeline/stages/compile.ts @@ -17,6 +17,23 @@ function isEsbuildSourceDiagnostic(error: unknown): boolean { }); } +/** + * `.mdx` and `.md` reach this stage as *generated* JSX: PARSE has already run + * the MDX compiler over the tenant's source, so `ctx.code` here is framework + * output. A diagnostic with a location points into that generated code, not + * into anything the project wrote, so it must not claim tenant ownership — a + * remark/rehype/recma plugin emitting broken JSX is a framework fault that has + * to page someone. + * + * Nothing is lost by refusing to infer ownership for these two extensions: + * genuine MDX and Markdown *source* errors are classified upstream at PARSE as + * `mdx-compile-error` / `markdown-compile-error`, both of which the shared + * tenant classifier already recognizes. + */ +function isGeneratedContentOutput(filePath: string): boolean { + return filePath.endsWith(".mdx") || filePath.endsWith(".md"); +} + export const compilePlugin: TransformPlugin = { name: "esbuild-compile", stage: TransformStage.COMPILE, @@ -79,7 +96,10 @@ export const compilePlugin: TransformPlugin = { throw COMPILATION_ERROR.create({ detail: `ESM transform failed for ${ctx.filePath} (loader: ${loader}): ${errorMsg}`, cause: err, - context: { tenantBuildFailure: isEsbuildSourceDiagnostic(err) }, + context: { + tenantBuildFailure: !isGeneratedContentOutput(ctx.filePath) && + isEsbuildSourceDiagnostic(err), + }, }); } }, From ab84bbaa6614d2a88b8ac63ef58a0936637e87fd Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 07:41:37 +0200 Subject: [PATCH 039/104] fix(transforms): escape emitted import literals and drop unreachable hardening Address review findings on the @/ alias pin. - http-fetcher: route all three emitted replacement shapes through toImportStringLiteral, now exported from nested-imports. The sibling path was fixed in cc143fb4f and this one was missed, so a specifier carrying a double quote or a cache path carrying a backslash emitted a module that failed to parse, taking every other import in the file with it. - source-spans: skip comments before a specifier in the two static scanners, as the dynamic scanner already does. A bare side-effect import carrying a bundler hint was neither materialised nor reported unresolved, so the module was cached with a live /_vf_modules/ specifier that failed at execute time. - alias-imports: stop carrying the query/hash suffix onto the emitted file:// URL. Splitting it off is what makes the path resolve; re-appending it to a materialized alias-.mjs only gave one source file two module records, and therefore two copies of its module-level state. Restores the pre-existing output shape while keeping the resolution fix. - url-builder: revert the Reflect.apply intrinsic wrappers. resolveSpecifier reaches canonicalizeHttpSpecifier first, whose raw /^https?:\/\//i.test() throws under a poisoned RegExp.prototype.test two calls before the hardened code runs, so the wrappers were unreachable. Verified by probe. This restores rsc-bundles.generated.ts and templates.ts to their pre-branch content. - http-bundler: reuse describeHtmlModuleResponse instead of blaming esm.sh for every host that answers HTML. - Extract the three copies of splitSpecifierSuffix into transforms/shared. The @/ extension logic is unchanged and now documented against its real source: AliasStrategy.rewrite, the framework's canonical @/ rewriter, which emits this same shape for both its ssr and browser targets. Verified against the live module server that /_vf_modules/.json.js and .md.js resolve, and that .svg/.css 404 with and without the appended .js. --- src/build/production-build/templates.ts | 2 +- .../rsc/endpoints/rsc-bundles.generated.ts | 4 +- src/transforms/esm/http-bundler.ts | 12 ++-- src/transforms/esm/specifier-resolver.test.ts | 66 +++++++++++++++++ src/transforms/esm/specifier-resolver.ts | 40 ++++------- .../import-rewriter/url-builder.test.ts | 72 ------------------- src/transforms/import-rewriter/url-builder.ts | 22 +----- .../module-fetcher/http-fetcher.test.ts | 35 +++++++++ .../module-fetcher/http-fetcher.ts | 12 ++-- .../module-fetcher/nested-imports.ts | 23 +++--- .../transforms/alias-imports.test.ts | 30 +++++++- .../transforms/alias-imports.ts | 22 +++--- .../utils/source-spans.test.ts | 44 ++++++++++++ .../esm-module-loader/utils/source-spans.ts | 16 +++-- .../shared/specifier-suffix.test.ts | 34 +++++++++ src/transforms/shared/specifier-suffix.ts | 52 ++++++++++++++ 16 files changed, 321 insertions(+), 165 deletions(-) create mode 100644 src/transforms/shared/specifier-suffix.test.ts create mode 100644 src/transforms/shared/specifier-suffix.ts diff --git a/src/build/production-build/templates.ts b/src/build/production-build/templates.ts index cd72768015..189e417e93 100644 --- a/src/build/production-build/templates.ts +++ b/src/build/production-build/templates.ts @@ -14,4 +14,4 @@ export const CLIENT_ROUTER_BUNDLE: string | undefined = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar rendererLogger = logger;\nvar PREFETCH_MAX_SIZE_BYTES = 200 * 1024;\n\n// src/rendering/client/navigation-store.ts\nvar STORE_KEY = /* @__PURE__ */ Symbol.for("veryfront.navigation.store.v1");\nfunction getNavigationStore() {\n const holder = globalThis;\n const existing = holder[STORE_KEY];\n if (existing) return existing;\n const listeners = /* @__PURE__ */ new Set();\n let navigator = null;\n const store = {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getHref() {\n const loc = globalThis.location;\n return loc ? `${loc.pathname}${loc.search}${loc.hash}` : "/";\n },\n notify() {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch {\n }\n }\n },\n navigate(href, options) {\n if (navigator) return navigator(href, options);\n globalThis.location?.assign(href);\n return Promise.resolve();\n },\n setNavigator(next) {\n navigator = next;\n }\n };\n holder[STORE_KEY] = store;\n return store;\n}\n\n// src/rendering/client/router.ts\nimport ReactDOM from "react-dom/client";\n\n// src/html/managed-head-protocol.ts\nvar HEAD_PROVENANCE_ATTRIBUTE = "data-vf-head";\nvar HEAD_LEGACY_MANAGED_ATTRIBUTE = "data-veryfront-managed";\nvar HEAD_CONTENT_HASH_ATTRIBUTE = "data-vf-hash";\nvar HEAD_REACT_MANAGED_ATTRIBUTE = "data-vf-react-head";\nvar HEAD_REACT_OWNER_ATTRIBUTE = "data-vf-react-head-owner";\nvar HEAD_ROUTE_MANAGED_ATTRIBUTE = "data-vf-route-head";\nvar HEAD_SERVER_COMMIT_ATTRIBUTE = "data-vf-server-head-commit";\nvar HEAD_SHELL_PROVENANCE_ATTRIBUTE = "data-vf-shell-head";\nvar HEAD_SSR_PAYLOAD_ATTRIBUTE = "data-vf-ssr-head";\nvar MAX_MANAGED_HEAD_ENTRIES = 128;\nvar MAX_MANAGED_HEAD_BYTES = 2 * 1024 * 1024;\nvar MAX_MANAGED_HEAD_PAYLOAD_BYTES = MAX_MANAGED_HEAD_BYTES * 2;\nvar REACT_HEAD_ATTRIBUTE_NAMES = {\n charSet: "charset",\n className: "class",\n crossOrigin: "crossorigin",\n fetchPriority: "fetchpriority",\n htmlFor: "for",\n httpEquiv: "http-equiv",\n imageSizes: "imagesizes",\n imageSrcSet: "imagesrcset",\n noModule: "nomodule",\n referrerPolicy: "referrerpolicy"\n};\nvar SINGLETON_META_KEYS = /* @__PURE__ */ new Set([\n "description",\n "robots",\n "viewport",\n "referrer",\n "color-scheme",\n "application-name",\n "generator",\n "og:title",\n "og:description",\n "og:url",\n "og:type",\n "og:site_name",\n "og:locale",\n "twitter:card",\n "twitter:site",\n "twitter:creator",\n "twitter:title",\n "twitter:description",\n "twitter:image",\n "twitter:image:alt"\n]);\nvar SINGLETON_LINK_RELS = /* @__PURE__ */ new Set([\n "canonical",\n "manifest",\n "amphtml"\n]);\nvar SUPPORTED_MANAGED_HEAD_TAGS = /* @__PURE__ */ new Set([\n "title",\n "meta",\n "link",\n "style",\n "script"\n]);\nvar HEAD_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_.:-]*$/;\nvar MAX_HEAD_PROP_ENTRIES = 128;\nvar MAX_HEAD_ATTRIBUTE_NAME_BYTES = 256;\nvar MAX_HEAD_ATTRIBUTE_VALUE_BYTES = 64 * 1024;\nvar MAX_HEAD_ATTRIBUTE_BYTES = 1024 * 1024;\nvar MAX_HEAD_CONTENT_BYTES = 1024 * 1024;\nvar headTextEncoder = new TextEncoder();\nvar BOOLEAN_HEAD_ATTRIBUTES = /* @__PURE__ */ new Set([\n "async",\n "defer",\n "disabled",\n "itemscope",\n "nomodule"\n]);\nfunction isHeadFrameworkAttribute(name) {\n switch (name.toLowerCase()) {\n case HEAD_PROVENANCE_ATTRIBUTE:\n case HEAD_LEGACY_MANAGED_ATTRIBUTE:\n case HEAD_CONTENT_HASH_ATTRIBUTE:\n case HEAD_REACT_MANAGED_ATTRIBUTE:\n case HEAD_REACT_OWNER_ATTRIBUTE:\n case HEAD_ROUTE_MANAGED_ATTRIBUTE:\n case HEAD_SERVER_COMMIT_ATTRIBUTE:\n case HEAD_SHELL_PROVENANCE_ATTRIBUTE:\n case HEAD_SSR_PAYLOAD_ATTRIBUTE:\n return true;\n default:\n return false;\n }\n}\nfunction normalizeHeadIdentityValue(value) {\n const normalized = value?.trim().toLowerCase();\n return normalized || void 0;\n}\nfunction readOwnString(record, key) {\n try {\n const descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n return descriptor && !descriptor.get && !descriptor.set && "value" in descriptor && typeof descriptor.value === "string" ? descriptor.value : void 0;\n } catch {\n return void 0;\n }\n}\nfunction headMetaSingletonKeyFromRecord(meta) {\n if (readOwnString(meta, "charset") !== void 0) return "meta:charset";\n const key = normalizeHeadIdentityValue(\n readOwnString(meta, "property") ?? readOwnString(meta, "name")\n );\n if (!key) return void 0;\n if (key === "theme-color") {\n return `meta:theme-color:${readOwnString(meta, "media")?.trim() ?? ""}`;\n }\n return SINGLETON_META_KEYS.has(key) ? `meta:${key}` : void 0;\n}\nfunction headLinkSingletonKeyFromRecord(link) {\n const rel = normalizeHeadIdentityValue(readOwnString(link, "rel"));\n return rel && SINGLETON_LINK_RELS.has(rel) ? `link:${rel}` : void 0;\n}\nfunction normalizeManagedHeadString(value) {\n return value.replace(/\\r\\n?/g, "\\n");\n}\nfunction inspectHeadProps(value) {\n if (typeof value !== "object" || value === null || Array.isArray(value)) return null;\n let prototype;\n let keys;\n try {\n prototype = Object.getPrototypeOf(value);\n keys = Reflect.ownKeys(value);\n } catch {\n return null;\n }\n if (prototype !== Object.prototype && prototype !== null) return null;\n const inspected = /* @__PURE__ */ new Map();\n let entries = 0;\n for (const key of keys) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(value, key);\n } catch {\n return null;\n }\n if (!descriptor) return null;\n if (!descriptor.enumerable) continue;\n if (typeof key !== "string" || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return null;\n }\n entries++;\n if (entries > MAX_HEAD_PROP_ENTRIES) return null;\n inspected.set(key, descriptor.value);\n }\n return inspected;\n}\nfunction normalizeContentPrimitive(value) {\n if (value === null || value === void 0 || typeof value === "boolean") return void 0;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n return null;\n }\n const content = normalizeManagedHeadString(String(value));\n return headTextEncoder.encode(content).byteLength <= MAX_HEAD_CONTENT_BYTES ? content : null;\n}\nfunction normalizeManagedHeadAttributesFromProps(tagName, props, ambientNonce, excludedKeys = /* @__PURE__ */ new Set()) {\n const attributeMap = /* @__PURE__ */ new Map();\n for (const [key, value] of props) {\n if (key === "children" || key === "dangerouslySetInnerHTML" || excludedKeys.has(key)) {\n continue;\n }\n if (/^on/i.test(key) || typeof value === "function" || typeof value === "symbol" || typeof value === "object") {\n continue;\n }\n const name = (REACT_HEAD_ATTRIBUTE_NAMES[key] ?? key).toLowerCase();\n if (isHeadFrameworkAttribute(name) || !HEAD_ATTRIBUTE_NAME_PATTERN.test(name) || headTextEncoder.encode(name).byteLength > MAX_HEAD_ATTRIBUTE_NAME_BYTES) {\n continue;\n }\n if (BOOLEAN_HEAD_ATTRIBUTES.has(name)) {\n if (value !== false && value !== void 0) attributeMap.set(name, "");\n continue;\n }\n if (typeof value === "boolean") {\n if (name.startsWith("data-") || name.startsWith("aria-")) {\n attributeMap.set(name, String(value));\n }\n continue;\n }\n if (value === void 0) continue;\n if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {\n continue;\n }\n const normalizedValue = normalizeManagedHeadString(String(value));\n if (headTextEncoder.encode(normalizedValue).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) {\n return null;\n }\n attributeMap.set(name, normalizedValue);\n }\n if (tagName === "script" || tagName === "style") {\n attributeMap.delete("nonce");\n }\n const acceptsAmbientNonce = tagName === "style" || tagName === "script" && !attributeMap.has("src");\n if (acceptsAmbientNonce && ambientNonce) {\n const nonce = normalizeManagedHeadString(ambientNonce);\n if (headTextEncoder.encode(nonce).byteLength > MAX_HEAD_ATTRIBUTE_VALUE_BYTES) return null;\n attributeMap.set("nonce", nonce);\n }\n if (tagName === "link" && attributeMap.get("rel")?.trim().toLowerCase() === "preload" && attributeMap.get("as")?.trim().toLowerCase() === "font" && !attributeMap.has("crossorigin")) {\n attributeMap.set("crossorigin", "anonymous");\n }\n if (attributeMap.size > MAX_HEAD_PROP_ENTRIES) return null;\n let totalBytes = 0;\n for (const [name, value] of attributeMap) {\n totalBytes += headTextEncoder.encode(name).byteLength + headTextEncoder.encode(value).byteLength;\n if (totalBytes > MAX_HEAD_ATTRIBUTE_BYTES) return null;\n }\n return [...attributeMap.entries()].sort(([left], [right]) => left.localeCompare(right));\n}\nfunction singletonKey(tagName, attributes) {\n if (tagName === "title") return "title";\n const record = Object.fromEntries(attributes);\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(record);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(record);\n return void 0;\n}\nfunction scriptKeys(tagName, attributes) {\n if (tagName !== "script") return [];\n const keys = [];\n const id = attributes.get("id");\n const src = attributes.get("src");\n if (id) keys.push(`script:id:${id}`);\n if (src) keys.push(`script:src:${src}`);\n return keys;\n}\nfunction declaresDocumentEncoding(attributes) {\n return attributes.has("charset") || attributes.get("http-equiv")?.trim().toLowerCase() === "content-type";\n}\nfunction createManagedHeadDescriptor(tagName, attributes, content, contentMode) {\n const attributeMap = new Map(attributes);\n return {\n tagName,\n attributes,\n ...content !== void 0 && { content },\n contentMode,\n signature: JSON.stringify([\n tagName,\n attributes,\n contentMode,\n content ?? null\n ]),\n singletonKey: singletonKey(tagName, attributeMap),\n scriptKeys: scriptKeys(tagName, attributeMap)\n };\n}\nfunction descriptorFromManagedHeadRecord(rawTagName, record, options = {}) {\n const tagName = rawTagName.toLowerCase();\n if (!SUPPORTED_MANAGED_HEAD_TAGS.has(tagName)) return null;\n const inspected = inspectHeadProps(record);\n if (!inspected) return null;\n const excludedKeys = options.contentProperty ? /* @__PURE__ */ new Set([options.contentProperty]) : /* @__PURE__ */ new Set();\n const attributes = normalizeManagedHeadAttributesFromProps(\n tagName,\n inspected,\n options.ambientNonce,\n excludedKeys\n );\n if (!attributes) return null;\n const attributeMap = new Map(attributes);\n if (tagName === "meta" && declaresDocumentEncoding(attributeMap)) return null;\n if ((tagName === "meta" || tagName === "link") && attributes.length === 0) return null;\n let content;\n if (options.contentProperty) {\n const normalized = normalizeContentPrimitive(inspected.get(options.contentProperty));\n if (normalized === null) return null;\n content = normalized;\n }\n return createManagedHeadDescriptor(tagName, attributes, content, "text");\n}\nfunction headScriptKeysIntersect(left, right) {\n if (left.length === 0 || right.length === 0) return false;\n const rightKeys = new Set(right);\n return left.some((key) => rightKeys.has(key));\n}\nfunction aggregateManagedHeadDescriptors(descriptors) {\n const aggregated = [];\n const singletonIndexes = /* @__PURE__ */ new Map();\n const scriptKeysSeen = /* @__PURE__ */ new Set();\n for (const descriptor of descriptors) {\n if (descriptor.singletonKey) {\n const index = singletonIndexes.get(descriptor.singletonKey);\n if (index !== void 0) {\n aggregated[index] = descriptor;\n continue;\n }\n singletonIndexes.set(descriptor.singletonKey, aggregated.length);\n } else if (descriptor.scriptKeys.length > 0) {\n if (descriptor.scriptKeys.some((key) => scriptKeysSeen.has(key))) continue;\n for (const key of descriptor.scriptKeys) scriptKeysSeen.add(key);\n }\n aggregated.push(descriptor);\n }\n return aggregated;\n}\nfunction managedHeadDescriptorBytes(descriptor) {\n let bytes = headTextEncoder.encode(descriptor.tagName).byteLength;\n for (const [name, value] of descriptor.attributes) {\n bytes += headTextEncoder.encode(name).byteLength;\n bytes += headTextEncoder.encode(value).byteLength;\n }\n if (descriptor.content !== void 0) {\n bytes += headTextEncoder.encode(descriptor.content).byteLength;\n }\n return bytes;\n}\nfunction assertManagedHeadDescriptorBudget(descriptors) {\n if (descriptors.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_ENTRIES}-entry request limit`\n );\n }\n let bytes = 0;\n for (const descriptor of descriptors) {\n bytes += managedHeadDescriptorBytes(descriptor);\n if (bytes > MAX_MANAGED_HEAD_BYTES) {\n throw new TypeError(\n `Managed head exceeds the ${MAX_MANAGED_HEAD_BYTES}-byte request limit`\n );\n }\n }\n}\nfunction managedHeadDescriptorToTransportEntry(descriptor) {\n const attributes = descriptor.attributes.filter(([name]) => name !== "nonce");\n return {\n tagName: descriptor.tagName,\n attributes: attributes.map(([name, value]) => [name, value]),\n ...descriptor.content !== void 0 && { content: descriptor.content }\n };\n}\nfunction ownTransportValue(record, key) {\n let descriptor;\n try {\n descriptor = Reflect.getOwnPropertyDescriptor(record, key);\n } catch {\n return void 0;\n }\n if (!descriptor || descriptor.get || descriptor.set || !("value" in descriptor)) {\n return void 0;\n }\n return descriptor.value;\n}\nfunction descriptorFromManagedHeadTransportEntry(entry, ambientNonce) {\n if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n let prototype;\n try {\n prototype = Object.getPrototypeOf(entry);\n } catch {\n throw new TypeError("Managed-head transport entry cannot be inspected");\n }\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError("Managed-head transport entries must be plain objects");\n }\n const tagName = ownTransportValue(entry, "tagName");\n const rawAttributes = ownTransportValue(entry, "attributes");\n const content = ownTransportValue(entry, "content");\n if (typeof tagName !== "string" || tagName !== tagName.toLowerCase() || !Array.isArray(rawAttributes)) {\n throw new TypeError("Managed-head transport entry is not canonical");\n }\n if (rawAttributes.length > MAX_HEAD_PROP_ENTRIES) {\n throw new TypeError("Managed-head transport entry exceeds the attribute limit");\n }\n if (content !== void 0 && typeof content !== "string") {\n throw new TypeError("Managed-head transport content must be a string");\n }\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (!supportsText && content !== void 0) {\n throw new TypeError("Managed-head transport content is invalid for this tag");\n }\n const record = /* @__PURE__ */ Object.create(null);\n const inputAttributes = [];\n const names = /* @__PURE__ */ new Set();\n for (let index = 0; index < rawAttributes.length; index += 1) {\n const pair = ownTransportValue(rawAttributes, String(index));\n if (!Array.isArray(pair) || pair.length !== 2) {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const name = ownTransportValue(pair, "0");\n const value = ownTransportValue(pair, "1");\n if (typeof name !== "string" || typeof value !== "string") {\n throw new TypeError("Managed-head transport attributes must be string pairs");\n }\n const normalizedName = name.toLowerCase();\n if (name !== normalizedName || normalizedName === "nonce" || names.has(normalizedName)) {\n throw new TypeError("Managed-head transport attributes are not canonical");\n }\n names.add(normalizedName);\n inputAttributes.push([normalizedName, value]);\n Object.defineProperty(record, normalizedName, {\n enumerable: true,\n value\n });\n }\n if (content !== void 0) {\n Object.defineProperty(record, "__veryfront_transport_content", {\n enumerable: true,\n value: content\n });\n }\n const descriptor = descriptorFromManagedHeadRecord(tagName, record, {\n ...supportsText && { contentProperty: "__veryfront_transport_content" },\n ...(tagName === "script" || tagName === "style") && ambientNonce ? { ambientNonce } : {}\n });\n const normalizedInput = inputAttributes.sort(([left], [right]) => left.localeCompare(right));\n const normalizedOutput = descriptor?.attributes.filter(([name]) => name !== "nonce");\n if (!descriptor || JSON.stringify(normalizedOutput) !== JSON.stringify(normalizedInput) || supportsText && (descriptor.content ?? "") !== (content ?? "")) {\n throw new TypeError("Managed-head transport entry failed validation");\n }\n return descriptor;\n}\nvar BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";\nfunction decodeBase64Url(value) {\n if (value.length % 4 === 1 || !/^[A-Za-z0-9_-]*$/.test(value)) {\n throw new TypeError("Managed-head payload is not valid base64url");\n }\n const estimatedBytes = Math.floor(value.length * 3 / 4);\n if (estimatedBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n const bytes = new Uint8Array(estimatedBytes);\n let outputIndex = 0;\n let buffer = 0;\n let bits = 0;\n for (const character of value) {\n const decoded = BASE64URL_ALPHABET.indexOf(character);\n if (decoded < 0) throw new TypeError("Managed-head payload is not valid base64url");\n buffer = buffer << 6 | decoded;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes[outputIndex++] = buffer >> bits & 255;\n buffer &= bits === 0 ? 0 : (1 << bits) - 1;\n }\n }\n if (bits > 0 && buffer !== 0) {\n throw new TypeError("Managed-head payload has non-canonical trailing bits");\n }\n return bytes.subarray(0, outputIndex);\n}\nfunction inspectManagedHeadPayload(payload, ambientNonce) {\n if (typeof payload !== "string") throw new TypeError("Managed-head payload must be a string");\n const payloadBytes = headTextEncoder.encode(payload).byteLength;\n if (payloadBytes > MAX_MANAGED_HEAD_PAYLOAD_BYTES) {\n throw new TypeError("Managed-head payload exceeds its encoded size limit");\n }\n let decoded;\n try {\n decoded = new TextDecoder("utf-8", { fatal: true }).decode(decodeBase64Url(payload));\n } catch (error) {\n if (error instanceof TypeError) throw error;\n throw new TypeError("Managed-head payload is not valid UTF-8", { cause: error });\n }\n let entries;\n try {\n entries = JSON.parse(decoded);\n } catch (error) {\n throw new TypeError("Managed-head payload is not valid JSON", { cause: error });\n }\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Managed-head payload exceeds the entry limit");\n }\n const rawDescriptors = entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, ambientNonce)\n );\n assertManagedHeadDescriptorBudget(rawDescriptors);\n return {\n descriptors: aggregateManagedHeadDescriptors(rawDescriptors),\n entryCount: rawDescriptors.length,\n descriptorBytes: rawDescriptors.reduce(\n (total, descriptor) => total + managedHeadDescriptorBytes(descriptor),\n 0\n ),\n payloadBytes\n };\n}\nfunction deserializeManagedHeadPayload(payload, ambientNonce) {\n return inspectManagedHeadPayload(payload, ambientNonce).descriptors;\n}\n\n// src/html/client-head-manager.ts\nvar HEAD_MANAGER_STATE_SYMBOL = /* @__PURE__ */ Symbol.for(\n "veryfront.client-head-manager.v2"\n);\nvar CROSS_PAGE_PRESERVED_SINGLETON_KEYS = /* @__PURE__ */ new Set([\n "meta:viewport",\n "link:manifest"\n]);\nfunction getClientHeadManagerState() {\n const globalState = globalThis;\n return globalState[HEAD_MANAGER_STATE_SYMBOL] ?? (globalState[HEAD_MANAGER_STATE_SYMBOL] = {\n documents: /* @__PURE__ */ new WeakMap()\n });\n}\nfunction getManagedHeadNonce(targetDocument) {\n if (typeof targetDocument.querySelector !== "function") return void 0;\n const element = targetDocument.querySelector(\n "script[nonce], style[nonce], link[nonce]"\n );\n if (!element) return void 0;\n const nonce = element.nonce || element.getAttribute("nonce") || "";\n return nonce || void 0;\n}\nfunction readElementAttributes(element) {\n const attributes = [];\n for (const attribute of element.attributes) {\n const name = attribute.name.toLowerCase();\n if (isHeadFrameworkAttribute(name)) continue;\n const nonce = name === "nonce" && "nonce" in element ? element.nonce : "";\n const value = BOOLEAN_HEAD_ATTRIBUTES.has(name) ? "" : nonce || attribute.value;\n attributes.push([name, value]);\n }\n return attributes.sort(([left], [right]) => left.localeCompare(right));\n}\nfunction elementSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n const attributes = Object.fromEntries(readElementAttributes(element));\n if (tagName === "meta") return headMetaSingletonKeyFromRecord(attributes);\n if (tagName === "link") return headLinkSingletonKeyFromRecord(attributes);\n return void 0;\n}\nfunction promoteToShellHeadBaseline(element) {\n for (const attribute of [...element.attributes]) {\n if (isHeadFrameworkAttribute(attribute.name)) {\n element.removeAttribute(attribute.name);\n }\n }\n element.setAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE, "true");\n}\nfunction isCrossPagePreservedSingleton(element, singletonKey2 = elementSingletonKey(element)) {\n return element.parentElement !== null && singletonKey2 !== void 0 && CROSS_PAGE_PRESERVED_SINGLETON_KEYS.has(singletonKey2);\n}\nfunction isFrameworkOwnedHeadElement(element) {\n return element.getAttribute(HEAD_PROVENANCE_ATTRIBUTE) === "true" || element.getAttribute(HEAD_REACT_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1" || element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true";\n}\nfunction retireFrameworkHeadElement(element) {\n if (isCrossPagePreservedSingleton(element)) {\n promoteToShellHeadBaseline(element);\n return;\n }\n element.remove();\n}\nfunction retireClientHeadOwnership(targetDocument) {\n const manager = getClientHeadManagerState().documents.get(targetDocument);\n if (manager) {\n manager.retire();\n return;\n }\n for (const element of [...targetDocument.head?.children ?? []]) {\n if (isFrameworkOwnedHeadElement(element)) retireFrameworkHeadElement(element);\n }\n}\n\n// src/html/client-route-head.ts\nvar ROUTE_HEAD_CONTENT_PROPERTY = "__veryfront_route_head_content";\nfunction descriptorFromHeadElement(element) {\n const record = /* @__PURE__ */ Object.create(null);\n for (const { name, value } of element.attributes) {\n if (!isHeadFrameworkAttribute(name)) record[name] = value;\n }\n const tagName = element.tagName.toLowerCase();\n const supportsText = tagName === "title" || tagName === "script" || tagName === "style";\n if (supportsText) record[ROUTE_HEAD_CONTENT_PROPERTY] = element.textContent ?? "";\n return descriptorFromManagedHeadRecord(\n tagName,\n record,\n supportsText ? { contentProperty: ROUTE_HEAD_CONTENT_PROPERTY } : void 0\n );\n}\nfunction writeRouteDescriptor(element, descriptor) {\n for (const attribute of [...element.attributes]) element.removeAttribute(attribute.name);\n for (const [name, value] of descriptor.attributes) element.setAttribute(name, value);\n element.textContent = descriptor.content ?? "";\n element.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n element.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n}\nfunction prepareClientRouteHeadEntries(entries, targetDocument = document) {\n if (entries === void 0) return [];\n if (!Array.isArray(entries) || entries.length > MAX_MANAGED_HEAD_ENTRIES) {\n throw new TypeError("Route head payload exceeds the entry limit");\n }\n const descriptors = aggregateManagedHeadDescriptors(\n entries.map(\n (entry) => descriptorFromManagedHeadTransportEntry(entry, getManagedHeadNonce(targetDocument))\n )\n );\n assertManagedHeadDescriptorBudget(descriptors);\n return descriptors;\n}\nfunction applyPreparedClientRouteHeadDescriptors(descriptors, targetDocument = document) {\n for (const descriptor of descriptors) {\n const described = [...targetDocument.head.children].flatMap((element2) => {\n const current = descriptorFromHeadElement(element2);\n return current ? [{ element: element2, descriptor: current }] : [];\n });\n if (descriptor.singletonKey) {\n const matches = described.filter(\n ({ descriptor: current }) => current.singletonKey === descriptor.singletonKey\n );\n const directive = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1"\n );\n if (directive) {\n continue;\n }\n const reusable = matches.find(\n ({ element: element2 }) => element2.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true" || element2.getAttribute(HEAD_SHELL_PROVENANCE_ATTRIBUTE) === "true"\n );\n if (reusable) {\n writeRouteDescriptor(reusable.element, descriptor);\n continue;\n }\n }\n if (described.some(\n ({ descriptor: current }) => current.signature === descriptor.signature || headScriptKeysIntersect(current.scriptKeys, descriptor.scriptKeys)\n )) {\n continue;\n }\n const element = targetDocument.createElement(descriptor.tagName);\n writeRouteDescriptor(element, descriptor);\n targetDocument.head.appendChild(element);\n }\n}\nfunction updateRouteTitle(title, targetDocument = document) {\n if (typeof title !== "string" || !title) return;\n const titles = [...targetDocument.head.querySelectorAll("title")];\n if (titles.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let titleElement = titles.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n for (const element of titles) {\n if (element !== titleElement) element.remove();\n }\n if (!titleElement) {\n titleElement = targetDocument.createElement("title");\n titleElement.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(titleElement);\n }\n titleElement.textContent = title;\n}\nfunction updateRouteMetaTag(targetDocument, selector, attributeName, attributeValue, content) {\n const matches = [...targetDocument.head.querySelectorAll(selector)];\n if (matches.some((element) => element.getAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE) === "1")) {\n return;\n }\n let metaTag = matches.find(\n (element) => element.getAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE) === "true"\n );\n if (!metaTag) {\n metaTag = targetDocument.createElement("meta");\n metaTag.setAttribute(attributeName, attributeValue);\n metaTag.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(metaTag);\n }\n metaTag.setAttribute("content", content);\n}\nfunction updateRouteMetaTags(metadata, targetDocument = document) {\n if (typeof metadata.description === "string" && metadata.description) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[name="description"]\',\n "name",\n "description",\n metadata.description\n );\n }\n if (typeof metadata.ogTitle === "string" && metadata.ogTitle) {\n updateRouteMetaTag(\n targetDocument,\n \'meta[property="og:title"]\',\n "property",\n "og:title",\n metadata.ogTitle\n );\n }\n}\n\n// src/html/hydration-data-element.ts\nvar HYDRATION_DATA_ELEMENT_ID = "veryfront-hydration-data";\nfunction findServerHydrationDataElement(document2) {\n try {\n const matches = [...document2.querySelectorAll(`[id="${HYDRATION_DATA_ELEMENT_ID}"]`)];\n if (matches.length !== 1) return null;\n const body = document2.body;\n if (!body) return null;\n const element = matches[0];\n if (body.firstElementChild !== element && element.parentElement !== body) return null;\n if (element.tagName?.toLowerCase() !== "script") return null;\n if (element.getAttribute("type")?.trim().toLowerCase() !== "application/json") return null;\n return element;\n } catch {\n return null;\n }\n}\n\n// src/routing/client/dom-utils.ts\nvar logger2 = rendererLogger.component("veryfront");\nfunction isInternalLink(target) {\n const href = target.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return false;\n const linkTarget = target.getAttribute("target");\n if (linkTarget === "_blank" || target.hasAttribute("download")) return false;\n return true;\n}\nfunction findAnchorElement(element) {\n let current = element;\n while (current && current.tagName !== "A") {\n current = current.parentElement;\n }\n return current instanceof HTMLAnchorElement ? current : null;\n}\nfunction applyHeadDirectives(container) {\n const targetDocument = container.ownerDocument ?? document;\n const nodes = [...container.querySelectorAll(\'[data-veryfront-head="1"], vf-head\')].filter(\n (node) => typeof node.getAttribute !== "function" || node.getAttribute(HEAD_REACT_OWNER_ATTRIBUTE) !== "1"\n );\n if (!nodes.length) return;\n retireClientHeadOwnership(targetDocument);\n cleanManagedHeadTags(targetDocument);\n for (const wrapper of nodes) {\n const TemplateElement = targetDocument.defaultView?.HTMLTemplateElement ?? globalThis.HTMLTemplateElement;\n const contentSource = TemplateElement && wrapper instanceof TemplateElement ? wrapper.content : wrapper;\n processHeadWrapper(contentSource, targetDocument);\n wrapper.parentElement?.removeChild(wrapper);\n }\n}\nfunction cleanManagedHeadTags(targetDocument) {\n for (const element of targetDocument.head.querySelectorAll(\n `[${HEAD_LEGACY_MANAGED_ATTRIBUTE}="1"]`\n )) {\n element.parentElement?.removeChild(element);\n }\n}\nfunction processHeadWrapper(wrapper, targetDocument) {\n const ElementConstructor = targetDocument.defaultView?.Element ?? globalThis.Element;\n const activeNonce = getManagedHeadNonce(targetDocument);\n for (const node of wrapper.childNodes) {\n if (!ElementConstructor || !(node instanceof ElementConstructor)) continue;\n const tagName = node.tagName.toLowerCase();\n if (headSingletonKey(node) === "meta:charset") continue;\n const clone = targetDocument.createElement(tagName);\n for (const { name, value } of node.attributes) {\n if (name.toLowerCase() !== "nonce") clone.setAttribute(name, value);\n }\n if (activeNonce && (tagName === "script" || tagName === "style" || tagName === "link")) {\n clone.setAttribute("nonce", activeNonce);\n }\n if (node.textContent && !clone.hasAttribute("src")) {\n clone.textContent = node.textContent;\n }\n replaceExistingHeadSingleton(targetDocument, clone);\n clone.setAttribute(HEAD_LEGACY_MANAGED_ATTRIBUTE, "1");\n clone.setAttribute(HEAD_ROUTE_MANAGED_ATTRIBUTE, "true");\n targetDocument.head.appendChild(clone);\n }\n}\nfunction headSingletonKey(element) {\n const tagName = element.tagName.toLowerCase();\n if (tagName === "title") return "title";\n if (tagName !== "meta" && tagName !== "link") return void 0;\n const attributes = /* @__PURE__ */ Object.create(null);\n if (!element.attributes) return void 0;\n for (const { name, value } of element.attributes) attributes[name.toLowerCase()] = value;\n if (tagName === "meta" && attributes["http-equiv"]?.trim().toLowerCase() === "content-type") {\n return "meta:charset";\n }\n return tagName === "meta" ? headMetaSingletonKeyFromRecord(attributes) : headLinkSingletonKeyFromRecord(attributes);\n}\nfunction replaceExistingHeadSingleton(targetDocument, replacement) {\n const singletonKey2 = headSingletonKey(replacement);\n if (!singletonKey2 || singletonKey2 === "meta:charset") return;\n for (const existing of [...targetDocument.head?.children ?? []]) {\n if (headSingletonKey(existing) === singletonKey2) existing.remove();\n }\n}\nfunction manageFocus(container) {\n try {\n const focusElement = container.querySelector("[data-router-focus]") || container.querySelector("main") || container.querySelector("h1");\n focusElement?.focus?.({ preventScroll: true });\n } catch (error) {\n logger2.warn("focus management failed", error);\n }\n}\nfunction extractPageDataFromScript() {\n const pageDataScript = document.querySelector("script[data-veryfront-page]");\n if (!pageDataScript) return null;\n try {\n const content = pageDataScript.textContent;\n if (!content) {\n logger2.warn("Page data script has no content");\n return {};\n }\n return JSON.parse(content);\n } catch (error) {\n logger2.error("Failed to parse page data:", error);\n return null;\n }\n}\nfunction snapshotClientRouteHead(targetDocument = document) {\n const hydrationDataScript = findServerHydrationDataElement(targetDocument);\n if (!hydrationDataScript?.textContent) return [];\n try {\n const hydrationData = JSON.parse(hydrationDataScript.textContent);\n if (typeof hydrationData.managedHeadPayload !== "string") return [];\n const descriptors = deserializeManagedHeadPayload(\n hydrationData.managedHeadPayload\n );\n const aggregated = aggregateManagedHeadDescriptors(descriptors);\n assertManagedHeadDescriptorBudget(aggregated);\n return aggregated.map(managedHeadDescriptorToTransportEntry);\n } catch {\n return [];\n }\n}\nfunction routeRequiresDocumentNavigation(data) {\n return Boolean(\n data.requiresFullDocumentNavigation || data.managedHead?.some((entry) => entry.tagName === "script") || typeof data.html === "string" && / entry.tagName === "script") || typeof root.querySelector === "function" && root.querySelector("script")\n ) {\n pageData = { ...pageData, requiresFullDocumentNavigation: true };\n }\n return { content, pageData, managedHead, dependencyPinningCacheKey };\n}\n\n// src/rendering/client/browser-stubs/config.ts\nvar DEFAULT_PREFETCH_DELAY_MS = 100;\nvar PAGE_TRANSITION_DELAY_MS = 150;\n\n// src/routing/client/navigation-handlers.ts\nvar logger3 = rendererLogger.component("veryfront");\nvar MAX_SCROLL_POSITIONS = 100;\nvar NavigationHandlers = class {\n constructor(prefetchDelay = DEFAULT_PREFETCH_DELAY_MS, prefetchOptions = {}) {\n __publicField(this, "prefetchQueue", /* @__PURE__ */ new Set());\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "scrollPositions", /* @__PURE__ */ new Map());\n __publicField(this, "isPopStateNav", false);\n __publicField(this, "prefetchDelay");\n __publicField(this, "prefetchOptions");\n this.prefetchDelay = prefetchDelay;\n this.prefetchOptions = prefetchOptions;\n }\n createClickHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n const anchor = findAnchorElement(event.target);\n if (!anchor || !isInternalLink(anchor)) return;\n const href = anchor.getAttribute("href");\n if (!href) return;\n event.preventDefault();\n callbacks.onNavigate(href);\n };\n }\n createPopStateHandler(callbacks) {\n return (_event) => {\n this.isPopStateNav = true;\n const { pathname, search, hash } = globalThis.location;\n callbacks.onNavigate(`${pathname}${search}${hash}`);\n };\n }\n createMouseOverHandler(callbacks) {\n return (event) => {\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.tagName !== "A") return;\n const href = event.target.getAttribute("href");\n if (!href || href.startsWith("http") || href.startsWith("#")) return;\n if (!this.shouldPrefetchOnHover(event.target)) return;\n if (this.prefetchQueue.has(href)) return;\n this.prefetchQueue.add(href);\n const timeoutId = setTimeout(() => {\n callbacks.onPrefetch(href);\n this.prefetchQueue.delete(href);\n this.pendingTimeouts.delete(href);\n }, this.prefetchDelay);\n this.pendingTimeouts.set(href, timeoutId);\n };\n }\n shouldPrefetchOnHover(target) {\n const prefetchAttribute = target.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n if (prefetchAttribute === "true") return true;\n return Boolean(this.prefetchOptions.hover);\n }\n saveScrollPosition(path) {\n try {\n if (this.scrollPositions.size >= MAX_SCROLL_POSITIONS) {\n const oldest = this.scrollPositions.keys().next().value;\n if (oldest) this.scrollPositions.delete(oldest);\n }\n const scrollY = globalThis.scrollY;\n if (typeof scrollY !== "number") {\n logger3.debug("No valid scrollY value available");\n this.scrollPositions.set(path, 0);\n return;\n }\n this.scrollPositions.set(path, scrollY);\n } catch (error) {\n logger3.warn("failed to record scroll position", error);\n }\n }\n getScrollPosition(path) {\n const position = this.scrollPositions.get(path);\n if (position === void 0) {\n logger3.debug(`No scroll position stored for ${path}`);\n return 0;\n }\n return position;\n }\n isPopState() {\n return this.isPopStateNav;\n }\n clearPopStateFlag() {\n this.isPopStateNav = false;\n }\n clear() {\n for (const timeoutId of this.pendingTimeouts.values()) clearTimeout(timeoutId);\n this.pendingTimeouts.clear();\n this.prefetchQueue.clear();\n this.scrollPositions.clear();\n this.isPopStateNav = false;\n }\n};\n\n// src/rendering/client/browser-stubs/error-registry.ts\nfunction createBrowserError(name, fallbackMessage) {\n return {\n create(options = {}) {\n const error = new Error(options.detail ?? fallbackMessage);\n error.name = name;\n Object.assign(error, {\n status: options.status,\n context: options.context\n });\n return error;\n }\n };\n}\nvar NETWORK_ERROR = createBrowserError("NetworkError", "Network request failed");\nvar SECURITY_VIOLATION = createBrowserError("SecurityViolation", "Security violation");\n\n// src/html/html-detection.ts\nfunction isFullHTMLDocument(content) {\n const trimmed = content.trim().toLowerCase();\n return trimmed.startsWith("");\n}\n\n// src/routing/client/page-loader.ts\nvar logger4 = rendererLogger.component("veryfront");\nvar MAX_CACHE_SIZE = 50;\nvar DEPENDENCY_PINNING_RESPONSE_HEADER = "x-veryfront-dependency-pins";\nfunction reloadBrowserDocument(url) {\n if (typeof globalThis.location !== "undefined") {\n globalThis.location.assign(url);\n }\n}\nfunction readDependencyPinningCacheKey(doc) {\n if (!doc) return "off";\n try {\n const hydrationDataElement = findServerHydrationDataElement(doc);\n if (!hydrationDataElement?.textContent) return "off";\n const hydrationData = JSON.parse(hydrationDataElement.textContent);\n return typeof hydrationData.dependencyPinningCacheKey === "string" && hydrationData.dependencyPinningCacheKey.startsWith("on:") ? hydrationData.dependencyPinningCacheKey : "off";\n } catch (error) {\n logger4.debug("Failed to read dependency snapshot from hydration data:", error);\n return "off";\n }\n}\nvar PageLoader = class {\n constructor(doc = typeof document === "undefined" ? void 0 : document, reloadDocument = reloadBrowserDocument) {\n __publicField(this, "cache", /* @__PURE__ */ new Map());\n __publicField(this, "spaCache", /* @__PURE__ */ new Map());\n __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());\n __publicField(this, "pendingSpaRequests", /* @__PURE__ */ new Map());\n /**\n * A loader belongs to the dependency snapshot of the document that created it.\n * Keeping this immutable also prevents cached or in-flight route data from\n * crossing snapshot boundaries if the hydration element is later replaced.\n */\n __publicField(this, "dependencyPinningCacheKey");\n __publicField(this, "reloadDocument");\n __publicField(this, "snapshotRecoveryStarted", false);\n this.dependencyPinningCacheKey = readDependencyPinningCacheKey(doc);\n this.reloadDocument = reloadDocument;\n }\n evictIfFull(map) {\n if (map.size < MAX_CACHE_SIZE) return;\n const oldest = map.keys().next().value;\n if (oldest) map.delete(oldest);\n }\n getCached(path) {\n return this.cache.get(this.snapshotScopedPath(path));\n }\n isCached(path) {\n return this.cache.has(this.snapshotScopedPath(path));\n }\n setCache(path, data) {\n this.evictIfFull(this.cache);\n this.cache.set(this.snapshotScopedPath(path), data);\n }\n clearCache() {\n this.cache.clear();\n this.spaCache.clear();\n this.pendingRequests.clear();\n this.pendingSpaRequests.clear();\n }\n getSpaCached(path) {\n return this.spaCache.get(this.snapshotScopedPath(path));\n }\n isSpaDataCached(path) {\n return this.spaCache.has(this.snapshotScopedPath(path));\n }\n setSpaCache(path, data) {\n this.evictIfFull(this.spaCache);\n this.spaCache.set(this.snapshotScopedPath(path), data);\n }\n async fetchPageData(path, reloadOnSnapshotFailure = true) {\n try {\n return await this.tryFetchJSON(path) ?? await this.fetchAndParseHTML(path);\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n async tryFetchJSON(path) {\n let response;\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const dataPath = navigationUrl.pathname === "/" ? "/index" : navigationUrl.pathname;\n const endpoint = `/_veryfront/data${dataPath}.json${navigationUrl.search}`;\n response = await fetch(endpoint, {\n headers: this.navigationHeaders("client")\n });\n } catch (error) {\n logger4.debug(`JSON fetch failed for ${path}, falling back to HTML:`, error);\n return null;\n }\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) return null;\n let data;\n try {\n data = await response.json();\n } catch (error) {\n logger4.debug(`JSON response was invalid for ${path}, falling back to HTML:`, error);\n return null;\n }\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "route data"\n );\n if (typeof data.html === "string" && isFullHTMLDocument(data.html)) {\n const parsed = parsePageDataFromHTML(data.html);\n this.assertDependencySnapshot(\n parsed.dependencyPinningCacheKey,\n path,\n "route data HTML body"\n );\n return {\n ...parsed.pageData,\n ...data,\n html: parsed.content,\n managedHead: parsed.managedHead\n };\n }\n return routeRequiresDocumentNavigation(data) ? { ...data, requiresFullDocumentNavigation: true } : data;\n }\n async fetchAndParseHTML(path) {\n const response = await fetch(path, {\n headers: this.navigationHeaders("client")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch ${path}`,\n status: response.status,\n context: { path }\n });\n }\n this.assertDependencySnapshot(\n response.headers.get(DEPENDENCY_PINNING_RESPONSE_HEADER),\n path,\n "HTML response"\n );\n const html = await response.text();\n const {\n content,\n pageData,\n managedHead,\n dependencyPinningCacheKey\n } = parsePageDataFromHTML(html);\n this.assertDependencySnapshot(\n dependencyPinningCacheKey,\n path,\n "HTML body"\n );\n return { ...pageData, html: content, managedHead };\n }\n loadPage(path) {\n return this.loadPageWithSnapshotRecovery(path, true);\n }\n loadPageWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getCached(path);\n if (cachedData) {\n logger4.debug(`Loading ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingRequests, async () => {\n const data = await this.fetchPageData(path, false);\n this.setCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetch(path) {\n if (this.isCached(path)) return;\n logger4.debug(`Prefetching ${path}`);\n try {\n await this.loadPageWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n async fetchSpaPageData(path, reloadOnSnapshotFailure = true) {\n try {\n const navigationUrl = new URL(path, "http://veryfront.local");\n const normalizedPath = navigationUrl.pathname === "/" ? "index" : navigationUrl.pathname.replace(/^\\//, "");\n const endpoint = `/_veryfront/page-data/${normalizedPath}.json${navigationUrl.search}`;\n logger4.debug(`Fetching SPA page data from ${endpoint}`);\n const response = await fetch(endpoint, {\n headers: this.navigationHeaders("spa")\n });\n if (response.status === 409) {\n this.failDependencySnapshot(\n path,\n `Dependency snapshot is unavailable for SPA page data ${path}`\n );\n }\n if (!response.ok) {\n throw NETWORK_ERROR.create({\n detail: `Failed to fetch SPA page data for ${path}`,\n status: response.status,\n context: { path }\n });\n }\n const data = await response.json();\n this.assertDependencySnapshot(\n data.dependencyPinningCacheKey,\n path,\n "SPA page data"\n );\n return data;\n } catch (error) {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n }\n }\n loadSpaPageData(path) {\n return this.loadSpaPageDataWithSnapshotRecovery(path, true);\n }\n loadSpaPageDataWithSnapshotRecovery(path, reloadOnSnapshotFailure) {\n const cachedData = this.getSpaCached(path);\n if (cachedData) {\n logger4.debug(`Loading SPA data for ${path} from cache`);\n return Promise.resolve(cachedData);\n }\n const pendingKey = this.snapshotScopedPath(path);\n const pending = this.pendingSpaRequests.get(pendingKey);\n if (pending) {\n logger4.debug(`Reusing pending SPA request for ${path}`);\n return this.withSnapshotRecovery(\n pending,\n path,\n reloadOnSnapshotFailure\n );\n }\n logger4.debug(`Creating pending SPA request for ${path}`);\n const request = this.createPendingRequest(pendingKey, this.pendingSpaRequests, async () => {\n const data = await this.fetchSpaPageData(path, false);\n this.setSpaCache(path, data);\n return data;\n });\n return this.withSnapshotRecovery(\n request,\n path,\n reloadOnSnapshotFailure\n );\n }\n async prefetchSpaPageData(path) {\n if (this.isSpaDataCached(path)) return;\n logger4.debug(`Prefetching SPA page data for ${path}`);\n try {\n await this.loadSpaPageDataWithSnapshotRecovery(path, false);\n } catch (error) {\n logger4.warn(\n `[Veryfront] Failed to prefetch SPA data for ${path}`,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n createPendingRequest(path, pendingMap, fetcher) {\n const request = (async () => {\n try {\n return await fetcher();\n } finally {\n pendingMap.delete(path);\n }\n })();\n pendingMap.set(path, request);\n return request;\n }\n snapshotScopedPath(path) {\n return this.dependencyPinningCacheKey.startsWith("on:") ? `${this.dependencyPinningCacheKey}\\0${path}` : path;\n }\n navigationHeaders(type) {\n return {\n "X-Veryfront-Navigation": type,\n ...this.dependencyPinningCacheKey.startsWith("on:") ? {\n [DEPENDENCY_PINNING_RESPONSE_HEADER]: this.dependencyPinningCacheKey\n } : {}\n };\n }\n assertDependencySnapshot(actualCacheKey, path, source) {\n const expectedCacheKey = this.dependencyPinningCacheKey.startsWith("on:") ? this.dependencyPinningCacheKey : void 0;\n const normalizedActualCacheKey = typeof actualCacheKey === "string" ? actualCacheKey : void 0;\n const matches = expectedCacheKey ? normalizedActualCacheKey === expectedCacheKey : normalizedActualCacheKey === void 0 || normalizedActualCacheKey === "off";\n if (matches) return;\n this.failDependencySnapshot(\n path,\n `Dependency snapshot mismatch in ${source} for ${path}`\n );\n }\n failDependencySnapshot(path, detail) {\n throw NETWORK_ERROR.create({\n detail,\n status: 409,\n context: { path }\n });\n }\n withSnapshotRecovery(promise, path, reloadOnSnapshotFailure) {\n return promise.catch((error) => {\n this.recoverSnapshotFailure(error, path, reloadOnSnapshotFailure);\n throw error;\n });\n }\n recoverSnapshotFailure(error, path, reloadOnSnapshotFailure) {\n if (!reloadOnSnapshotFailure || typeof error !== "object" || error === null || error.status !== 409) {\n return;\n }\n if (this.snapshotRecoveryStarted) return;\n this.snapshotRecoveryStarted = true;\n try {\n this.reloadDocument(path);\n } catch (reloadError) {\n this.snapshotRecoveryStarted = false;\n logger4.warn(\n `[Veryfront] Failed to reload after dependency snapshot conflict for ${path}`,\n reloadError instanceof Error ? reloadError : new Error(String(reloadError))\n );\n }\n }\n};\n\n// src/security/client/html-sanitizer.ts\nvar SUSPICIOUS_PATTERN_SPECS = [\n { source: String.raw`]*>[\\s\\S]*?<\\/script>`, flags: "gi", name: "inline script" },\n { source: String.raw`javascript:`, flags: "gi", name: "javascript: URL" },\n { source: String.raw`\\bon\\w+\\s*=`, flags: "gi", name: "event handler attribute" },\n { source: String.raw`data:\\s*text\\/html`, flags: "gi", name: "data: HTML URL" }\n];\nfunction createSuspiciousPatterns() {\n return SUSPICIOUS_PATTERN_SPECS.map(({ source, flags, name }) => ({\n pattern: new RegExp(source, flags),\n name\n }));\n}\nfunction isDevMode() {\n const g = globalThis;\n return g.__VERYFRONT_DEV__ === true || g.Deno?.env?.get?.("VERYFRONT_ENV") === "development";\n}\nfunction validateTrustedHtml(html, options = {}) {\n const { allowInlineScripts = false, strict = false, warn = true } = options;\n for (const { pattern, name } of createSuspiciousPatterns()) {\n if (allowInlineScripts && name === "inline script") continue;\n pattern.lastIndex = 0;\n if (!pattern.test(html)) continue;\n if (warn) console.warn(`[Security] Suspicious ${name} detected in server HTML`);\n if (strict || !isDevMode()) {\n throw SECURITY_VIOLATION.create({ detail: `Potentially unsafe HTML: ${name} detected` });\n }\n }\n return html;\n}\n\n// src/routing/client/page-transition.ts\nvar logger5 = rendererLogger.component("veryfront");\nvar PageTransition = class {\n constructor(setupViewportPrefetch) {\n __publicField(this, "setupViewportPrefetch", setupViewportPrefetch);\n __publicField(this, "pendingTransitionTimeout");\n __publicField(this, "pendingRoot");\n }\n destroy() {\n this.cancelPendingTransition();\n }\n cancelPendingTransition() {\n if (this.pendingTransitionTimeout !== void 0) {\n clearTimeout(this.pendingTransitionTimeout);\n this.pendingTransitionTimeout = void 0;\n }\n if (this.pendingRoot) {\n this.pendingRoot.style.opacity = "1";\n this.pendingRoot = void 0;\n }\n }\n updatePage(data, isPopState, scrollY) {\n this.cancelPendingTransition();\n if (routeRequiresDocumentNavigation(data)) {\n throw new TypeError("Scripted routes require a full document navigation");\n }\n const rootElement = document.getElementById("root");\n const preparedHead = prepareClientRouteHeadEntries(data.managedHead, document);\n const retainedTitle = document.title;\n if (!rootElement || data.html === void 0) {\n retireClientHeadOwnership(document);\n applyPreparedClientRouteHeadDescriptors(preparedHead, document);\n this.updateDocumentMetadata(document, data, retainedTitle);\n return;\n }\n const trustedHtml = validateTrustedHtml(String(data.html));\n this.performTransition(\n rootElement,\n data,\n trustedHtml,\n preparedHead,\n retainedTitle,\n isPopState,\n scrollY\n );\n }\n updateDocumentMetadata(targetDocument, data, retainedTitle) {\n updateRouteTitle(data.frontmatter?.title || retainedTitle, targetDocument);\n updateRouteMetaTags(data.frontmatter ?? {}, targetDocument);\n }\n performTransition(rootElement, data, trustedHtml, preparedHead, retainedTitle, isPopState, scrollY) {\n rootElement.style.opacity = "0";\n this.pendingRoot = rootElement;\n this.pendingTransitionTimeout = setTimeout(() => {\n this.pendingTransitionTimeout = void 0;\n this.pendingRoot = void 0;\n try {\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = trustedHtml;\n applyHeadDirectives(rootElement);\n applyPreparedClientRouteHeadDescriptors(preparedHead, rootElement.ownerDocument);\n this.updateDocumentMetadata(rootElement.ownerDocument, data, retainedTitle);\n this.setupViewportPrefetch(rootElement);\n manageFocus(rootElement);\n this.handleScroll(isPopState, scrollY);\n } catch (error) {\n logger5.error("Route transition commit failed; reloading the document", error);\n globalThis.location?.reload();\n } finally {\n rootElement.style.opacity = "1";\n }\n }, PAGE_TRANSITION_DELAY_MS);\n }\n handleScroll(isPopState, scrollY) {\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger5.warn("scroll handling failed", error);\n }\n }\n showError(error) {\n const rootElement = document.getElementById("root");\n if (!rootElement) return;\n const errorDiv = document.createElement("div");\n errorDiv.className = "veryfront-error-page";\n const heading = document.createElement("h1");\n heading.textContent = "Oops! Something went wrong";\n const message = document.createElement("p");\n message.textContent = error.message;\n const button = document.createElement("button");\n button.type = "button";\n button.textContent = "Reload Page";\n button.onclick = () => globalThis.location.reload();\n errorDiv.append(heading, message, button);\n retireClientHeadOwnership(rootElement.ownerDocument);\n rootElement.innerHTML = "";\n rootElement.appendChild(errorDiv);\n }\n setLoadingState(loading) {\n const indicator = document.getElementById("veryfront-loading");\n if (indicator) indicator.style.display = loading ? "block" : "none";\n document.body.classList.toggle("veryfront-loading", loading);\n }\n};\n\n// src/routing/client/viewport-prefetch.ts\nvar logger6 = rendererLogger.component("veryfront");\nvar ViewportPrefetch = class {\n constructor(prefetchCallback, prefetchOptions = {}) {\n __publicField(this, "observer", null);\n __publicField(this, "prefetchCallback");\n __publicField(this, "prefetchOptions");\n this.prefetchCallback = prefetchCallback;\n this.prefetchOptions = prefetchOptions;\n }\n setup(root) {\n try {\n if (!("IntersectionObserver" in globalThis)) return;\n this.observer?.disconnect();\n this.createObserver();\n this.observeLinks(root);\n } catch (error) {\n logger6.debug("setupViewportPrefetch failed", error);\n }\n }\n createObserver() {\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!(entry.target instanceof HTMLAnchorElement)) continue;\n const href = entry.target.getAttribute("href");\n if (href) this.prefetchCallback(href);\n this.observer?.unobserve(entry.target);\n }\n },\n { rootMargin: "200px" }\n );\n }\n observeLinks(root) {\n const anchors = root.querySelectorAll(\'a[href]:not([target="_blank"])\');\n const isViewportEnabled = Boolean(this.prefetchOptions.viewport);\n for (const anchor of anchors) {\n if (!this.shouldObserveAnchor(anchor, isViewportEnabled)) continue;\n this.observer?.observe(anchor);\n }\n }\n shouldObserveAnchor(anchor, isViewportEnabled) {\n const href = anchor.getAttribute("href");\n if (!href) return false;\n if (href.startsWith("http") || href.startsWith("#")) return false;\n if (anchor.getAttribute("download")) return false;\n const prefetchAttribute = anchor.getAttribute("data-prefetch");\n if (prefetchAttribute === "false") return false;\n return prefetchAttribute === "viewport" || isViewportEnabled;\n }\n disconnect() {\n if (!this.observer) return;\n try {\n this.observer.disconnect();\n } catch (error) {\n logger6.warn("prefetchObserver.disconnect failed", error);\n } finally {\n this.observer = null;\n }\n }\n};\n\n// src/rendering/client/router.ts\nvar logger7 = rendererLogger.component("veryfront");\nfunction toHistoryMode(options) {\n if (typeof options === "boolean") return options ? "push" : "none";\n return options?.history ?? "push";\n}\nvar VeryfrontRouter = class {\n constructor(options = {}) {\n __publicField(this, "baseUrl");\n __publicField(this, "currentPath");\n __publicField(this, "root", null);\n __publicField(this, "options");\n __publicField(this, "spaMode");\n __publicField(this, "spaNavigationHandler", null);\n __publicField(this, "navigationSequence", 0);\n __publicField(this, "pageLoader");\n __publicField(this, "navigationHandlers");\n __publicField(this, "pageTransition");\n __publicField(this, "viewportPrefetch");\n __publicField(this, "handleClick");\n __publicField(this, "handlePopState");\n __publicField(this, "handleMouseOver");\n const globalOptions = this.loadGlobalOptions();\n this.options = { ...globalOptions, ...options };\n this.baseUrl = this.options.baseUrl || globalThis.location.origin;\n this.currentPath = `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`;\n this.spaMode = this.options.spaMode ?? globalThis.__VERYFRONT_SPA_MODE__ ?? false;\n this.pageLoader = new PageLoader();\n this.navigationHandlers = new NavigationHandlers(\n this.options.prefetchDelay,\n this.options.prefetch\n );\n this.pageTransition = new PageTransition((root) => this.viewportPrefetch.setup(root));\n this.viewportPrefetch = new ViewportPrefetch(\n (path) => this.prefetch(path),\n this.options.prefetch\n );\n this.handleClick = this.navigationHandlers.createClickHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handlePopState = this.navigationHandlers.createPopStateHandler({\n // The browser already updated the URL for a popstate, so don\'t touch history.\n onNavigate: (url) => this.navigate(url, { history: "none" }),\n onPrefetch: (url) => this.prefetch(url)\n });\n this.handleMouseOver = this.navigationHandlers.createMouseOverHandler({\n onNavigate: (url) => this.navigate(url),\n onPrefetch: (url) => this.prefetch(url)\n });\n getNavigationStore().setNavigator((href, options2) => this.navigate(href, options2));\n }\n registerNavigationHandler(handler) {\n logger7.debug("Registering SPA navigation handler");\n this.spaNavigationHandler = handler;\n this.spaMode = true;\n }\n /**\n * Notify React (and any other) subscribers that a navigation completed —\n * after full page loads, soft same-route changes, and popstate. Delegates to\n * the shared navigation store, the single subscription surface both bundles\n * share.\n */\n notify() {\n getNavigationStore().notify();\n }\n pathnameOf(url) {\n try {\n return new URL(url, this.baseUrl).pathname;\n } catch {\n return url.split("?")[0]?.split("#")[0] || this.currentPath;\n }\n }\n loadGlobalOptions() {\n try {\n const options = globalThis.__VERYFRONT_ROUTER_OPTS__;\n if (!options) {\n logger7.debug("No global options configured");\n return {};\n }\n return options;\n } catch (error) {\n logger7.error("Failed to read global options:", error);\n return {};\n }\n }\n init() {\n logger7.debug("Initializing client-side router");\n const rootElement = document.getElementById("root");\n if (!rootElement) {\n logger7.error("Root element not found");\n return;\n }\n const ReactDOMToUse = globalThis.ReactDOM ?? ReactDOM;\n this.root = ReactDOMToUse.createRoot(rootElement);\n document.addEventListener("click", this.handleClick);\n globalThis.addEventListener("popstate", this.handlePopState);\n document.addEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.setup(document);\n this.cacheCurrentPage();\n }\n cacheCurrentPage() {\n const pageData = extractPageDataFromScript();\n if (pageData) {\n const managedHead = snapshotClientRouteHead(document);\n this.pageLoader.setCache(this.currentPath, {\n ...pageData,\n managedHead,\n ...managedHead.some((entry) => entry.tagName === "script") || document.getElementById("root")?.querySelector("script") ? { requiresFullDocumentNavigation: true } : {}\n });\n }\n }\n /**\n * Navigate to a URL. `options` selects the history behaviour: `{ history:\n * "push" }` (default), `"replace"`, or `"none"` (the URL already reflects the\n * target, as after popstate). A boolean is accepted for backward\n * compatibility — `true` pushes, `false` maps to `"none"`.\n */\n async navigate(url, options) {\n logger7.debug(`Navigating to ${url} (SPA mode: ${this.spaMode})`);\n const navigationId = ++this.navigationSequence;\n this.pageTransition.cancelPendingTransition();\n this.pageTransition.setLoadingState(false);\n const history = toHistoryMode(options);\n const sameRoute = this.pathnameOf(url) === this.pathnameOf(this.currentPath);\n this.navigationHandlers.saveScrollPosition(this.currentPath);\n this.options.onStart?.(url);\n if (history === "replace") globalThis.history.replaceState({}, "", url);\n else if (history === "push") globalThis.history.pushState({}, "", url);\n if (sameRoute && !this.shouldRevalidate(url, sameRoute)) {\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = url;\n this.notify();\n this.options.onComplete?.(url);\n this.options.onNavigate?.(url);\n return;\n }\n if (this.spaMode && this.spaNavigationHandler) {\n await this.loadSpaPage(url, navigationId);\n } else {\n if (await this.loadPage(url, true, navigationId)) return;\n }\n if (!this.isCurrentNavigation(navigationId)) return;\n this.notify();\n this.options.onNavigate?.(url);\n }\n isCurrentNavigation(navigationId) {\n return navigationId === this.navigationSequence;\n }\n /**\n * Whether a navigation should refetch page data. A route change always does;\n * a same-route (query/hash-only) change consults `options.shouldRevalidate`,\n * defaulting to `true` so server data is never shown stale.\n */\n shouldRevalidate(nextUrl, sameRoute) {\n const policy = this.options.shouldRevalidate;\n if (!policy) return true;\n return policy({ currentHref: this.currentPath, nextHref: nextUrl, sameRoute });\n }\n async loadSpaPage(path, navigationId) {\n logger7.debug(`Loading SPA page: ${path}`);\n try {\n const spaData = await this.pageLoader.loadSpaPageData(path);\n if (!this.isCurrentNavigation(navigationId)) return;\n await this.spaNavigationHandler?.(spaData);\n if (!this.isCurrentNavigation(navigationId)) return;\n this.currentPath = path;\n this.handleScrollAfterNavigation();\n this.options.onComplete?.(path);\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load SPA page ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n }\n }\n handleScrollAfterNavigation() {\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(this.currentPath);\n try {\n globalThis.scrollTo(0, isPopState ? scrollY : 0);\n } catch (error) {\n logger7.warn("scroll handling failed", error);\n }\n this.navigationHandlers.clearPopStateFlag();\n }\n /** Returns true when navigation was handed to the browser document loader. */\n async loadPage(path, updateUI = true, navigationId) {\n if (this.pageLoader.isCached(path)) {\n logger7.debug(`Loading ${path} from cache`);\n const data = this.pageLoader.getCached(path);\n if (data) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.pageTransition.setLoadingState(false);\n this.options.onComplete?.(path);\n return false;\n }\n logger7.warn(`Cache entry for ${path} was unexpectedly null, fetching fresh data`);\n }\n this.pageTransition.setLoadingState(true);\n try {\n const data = await this.pageLoader.loadPage(path);\n if (!this.isCurrentNavigation(navigationId)) return false;\n if (updateUI && data.requiresFullDocumentNavigation) {\n globalThis.location.assign(path);\n return true;\n }\n if (updateUI) this.updatePage(data, path);\n this.currentPath = path;\n this.options.onComplete?.(path);\n return false;\n } catch (error) {\n if (!this.isCurrentNavigation(navigationId)) return false;\n const normalizedError = error instanceof Error ? error : new Error(String(error));\n logger7.error(`Failed to load ${path}`, normalizedError);\n this.options.onError?.(normalizedError);\n this.pageTransition.showError(normalizedError);\n return false;\n } finally {\n if (this.isCurrentNavigation(navigationId)) this.pageTransition.setLoadingState(false);\n }\n }\n async prefetch(path) {\n if (this.spaMode) {\n await this.pageLoader.prefetchSpaPageData(path);\n return;\n }\n await this.pageLoader.prefetch(path);\n }\n updatePage(data, targetPath) {\n if (!this.root) return;\n const isPopState = this.navigationHandlers.isPopState();\n const scrollY = this.navigationHandlers.getScrollPosition(targetPath);\n this.pageTransition.updatePage(data, isPopState, scrollY);\n this.navigationHandlers.clearPopStateFlag();\n }\n destroy() {\n this.navigationSequence++;\n this.pageTransition.setLoadingState(false);\n document.removeEventListener("click", this.handleClick);\n globalThis.removeEventListener("popstate", this.handlePopState);\n document.removeEventListener("mouseover", this.handleMouseOver);\n this.viewportPrefetch.disconnect();\n this.pageLoader.clearCache();\n this.navigationHandlers.clear();\n this.pageTransition.destroy();\n }\n};\nfunction boot(options = {}) {\n if (typeof window === "undefined" || !globalThis.document) return null;\n const globalWithRouter = globalThis;\n if (globalWithRouter.veryFrontRouter) return globalWithRouter.veryFrontRouter;\n const { slug: _slug, ...routerOptions } = options;\n const router = new VeryfrontRouter(routerOptions);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => router.init(), { once: true });\n } else {\n router.init();\n }\n globalWithRouter.veryFrontRouter = router;\n return router;\n}\nif (typeof window !== "undefined" && globalThis.document) {\n boot();\n}\nexport {\n VeryfrontRouter,\n boot\n};\n'; export const CLIENT_PREFETCH_BUNDLE: string | undefined = - 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/transforms/import-rewriter/url-builder.ts\nvar RegExpTest = RegExp.prototype.test;\nvar RegExpSymbolReplace = RegExp.prototype[Symbol.replace];\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; + 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// src/rendering/client/browser-logger.ts\nvar ConditionalBrowserLogger = class {\n constructor(prefix, level) {\n __publicField(this, "prefix", prefix);\n __publicField(this, "level", level);\n }\n log(minLevel, fn, message, ...args) {\n if (this.level > minLevel) return;\n fn?.(message, ...args);\n }\n debug(message, ...args) {\n this.log(\n 0 /* DEBUG */,\n console.debug,\n `[${this.prefix}] DEBUG: ${message}`,\n ...args\n );\n }\n info(message, ...args) {\n this.log(1 /* INFO */, console.log, `[${this.prefix}] ${message}`, ...args);\n }\n warn(message, ...args) {\n this.log(\n 2 /* WARN */,\n console.warn,\n `[${this.prefix}] WARN: ${message}`,\n ...args\n );\n }\n error(message, ...args) {\n this.log(\n 3 /* ERROR */,\n console.error,\n `[${this.prefix}] ERROR: ${message}`,\n ...args\n );\n }\n};\nfunction getBrowserLogLevel() {\n if (typeof window === "undefined") return 2 /* WARN */;\n const g = globalThis;\n const isDevelopment = g.__VERYFRONT_DEV__ || g.__RSC_DEV__;\n if (!isDevelopment) return 2 /* WARN */;\n const isDebugEnabled2 = g.__VERYFRONT_DEBUG__ || g.__RSC_DEBUG__;\n return isDebugEnabled2 ? 0 /* DEBUG */ : 1 /* INFO */;\n}\nvar defaultLevel = getBrowserLogLevel();\nvar rscLogger = new ConditionalBrowserLogger("RSC", defaultLevel);\nvar prefetchLogger = new ConditionalBrowserLogger("PREFETCH", defaultLevel);\nvar hydrateLogger = new ConditionalBrowserLogger("HYDRATE", defaultLevel);\nvar browserLogger = new ConditionalBrowserLogger("VERYFRONT", defaultLevel);\n\n// src/rendering/client/prefetch/link-observer.ts\nfunction isAnchorElement(element) {\n return typeof HTMLAnchorElement !== "undefined" ? element instanceof HTMLAnchorElement : element.tagName === "A";\n}\nvar LinkObserver = class {\n constructor(options, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "intersectionObserver", null);\n __publicField(this, "mutationObserver", null);\n __publicField(this, "prefetchedUrls");\n __publicField(this, "pendingTimeouts", /* @__PURE__ */ new Map());\n __publicField(this, "elementTimeoutMap", /* @__PURE__ */ new WeakMap());\n __publicField(this, "timeoutCounter", 0);\n this.options = options;\n this.prefetchedUrls = prefetchedUrls;\n }\n init() {\n this.createIntersectionObserver();\n this.observeLinks();\n this.setupMutationObserver();\n }\n createIntersectionObserver() {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => this.handleIntersection(entries),\n { rootMargin: this.options.rootMargin }\n );\n }\n handleIntersection(entries) {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n if (!isAnchorElement(entry.target)) continue;\n const link = entry.target;\n if (this.timeoutCounter > 1e6) this.timeoutCounter = 0;\n const timeoutKey = this.timeoutCounter++;\n const timeoutId = setTimeout(() => {\n this.pendingTimeouts.delete(timeoutKey);\n this.elementTimeoutMap.delete(link);\n this.options.onLinkVisible(link);\n }, this.options.delay);\n this.pendingTimeouts.set(timeoutKey, timeoutId);\n this.elementTimeoutMap.set(link, timeoutKey);\n }\n }\n observeLinks() {\n this.observeAnchors(document.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== "childList") continue;\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.observeElement(node);\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n this.clearElementTimeouts(node);\n }\n }\n });\n this.mutationObserver.observe(document.body, { childList: true, subtree: true });\n }\n clearTimeoutForElement(element) {\n const timeoutKey = this.elementTimeoutMap.get(element);\n if (timeoutKey === void 0) return;\n const timeoutId = this.pendingTimeouts.get(timeoutKey);\n if (timeoutId !== void 0) {\n clearTimeout(timeoutId);\n this.pendingTimeouts.delete(timeoutKey);\n }\n this.elementTimeoutMap.delete(element);\n }\n clearElementTimeouts(element) {\n if (isAnchorElement(element)) this.clearTimeoutForElement(element);\n for (const link of element.querySelectorAll("a")) {\n this.clearTimeoutForElement(link);\n }\n }\n observeElement(element) {\n if (isAnchorElement(element) && this.isValidLink(element)) {\n this.intersectionObserver?.observe(element);\n }\n this.observeAnchors(element.querySelectorAll(\'a[href^="/"], a[href^="./"]\'));\n }\n observeAnchors(links) {\n for (const link of links) {\n if (!isAnchorElement(link)) continue;\n if (!this.isValidLink(link)) continue;\n this.intersectionObserver?.observe(link);\n }\n }\n isValidLink(link) {\n if (link.hostname !== globalThis.location.hostname) return false;\n if (link.hasAttribute("download")) return false;\n if (link.target === "_blank") return false;\n const url = link.href;\n if (this.prefetchedUrls.has(url)) return false;\n if (url === globalThis.location.href) return false;\n if (link.hash && link.pathname === globalThis.location.pathname) return false;\n if (link.dataset.noPrefetch) return false;\n return true;\n }\n destroy() {\n for (const timeoutId of this.pendingTimeouts.values()) {\n clearTimeout(timeoutId);\n }\n this.pendingTimeouts.clear();\n this.timeoutCounter = 0;\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.mutationObserver?.disconnect();\n this.mutationObserver = null;\n }\n};\n\n// src/rendering/client/prefetch/network-utils.ts\nvar NetworkUtils = class {\n constructor(allowedNetworks = ["4g", "wifi", "ethernet"]) {\n __publicField(this, "networkInfo");\n __publicField(this, "allowedNetworks");\n this.allowedNetworks = allowedNetworks;\n this.networkInfo = this.getNetworkConnection();\n }\n getNavigatorWithConnection() {\n if (typeof globalThis.navigator === "undefined") return null;\n return globalThis.navigator;\n }\n getNetworkConnection() {\n const nav = this.getNavigatorWithConnection();\n return nav?.connection ?? nav?.mozConnection ?? nav?.webkitConnection ?? null;\n }\n shouldPrefetch() {\n if (this.networkInfo?.saveData) return false;\n const effectiveType = this.networkInfo?.effectiveType;\n if (effectiveType != null && !this.allowedNetworks.includes(effectiveType)) return false;\n return true;\n }\n onNetworkChange(callback) {\n this.networkInfo?.addEventListener?.("change", callback);\n }\n getNetworkInfo() {\n return this.networkInfo;\n }\n};\n\n// src/utils/constants/css.ts\nvar MAX_CSS_FILE_BYTES = 16 * 1024 * 1024;\nvar MAX_CSS_TOTAL_BYTES = 64 * 1024 * 1024;\nvar MAX_CSS_OUTPUT_FILE_BYTES = 32 * 1024 * 1024;\n\n// src/utils/constants/buffers.ts\nvar DEFAULT_MAX_BODY_SIZE_BYTES = 1024 * 1024;\nvar DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;\nvar PREFETCH_QUEUE_MAX_SIZE_BYTES = DEFAULT_MAX_BODY_SIZE_BYTES;\nvar MAX_BUNDLE_CHUNK_SIZE_BYTES = 4096 * 1024;\n\n// src/utils/constants/limits.ts\nvar MAX_TIMER_DELAY_MS = 2147483647;\n\n// src/utils/constants/cache.ts\nvar SECONDS_PER_MINUTE = 60;\nvar MINUTES_PER_HOUR = 60;\nvar HOURS_PER_DAY = 24;\nvar MS_PER_SECOND = 1e3;\nvar MS_PER_MINUTE = SECONDS_PER_MINUTE * MS_PER_SECOND;\nvar MS_PER_HOUR = MINUTES_PER_HOUR * MS_PER_MINUTE;\nvar ONE_DAY_MS = HOURS_PER_DAY * MS_PER_HOUR;\nfunction getEnvString(key) {\n const g = globalThis;\n try {\n return g.Deno?.env?.get?.(key) ?? g.process?.env?.[key];\n } catch (_) {\n return void 0;\n }\n}\nvar MAX_CONFIGURED_CACHE_ENTRIES = 1e6;\nvar MAX_CONFIGURED_CACHE_SIZE_MB = 64 * 1024;\nvar MAX_CONFIGURED_CONCURRENCY = 1e4;\nvar MAX_CONFIGURED_TTL_SECONDS = 365 * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar BYTES_PER_MB = 1024 * 1024;\nvar MAX_CACHE_TTL_SECONDS = 2147483647;\nvar MAX_CACHE_TTL_MILLISECONDS = MAX_CACHE_TTL_SECONDS * MS_PER_SECOND;\nfunction getEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) return fallback;\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) return fallback;\n return parsed;\n}\nfunction getStrictEnvInteger(key, fallback, { min = 1, max }) {\n const value = getEnvString(key);\n if (value == null) return fallback;\n const normalized = value.trim();\n if (!/^\\d+$/.test(normalized)) {\n throw new RangeError(\n `${key} must be a base-10 integer between ${min} and ${max}`\n );\n }\n const parsed = Number(normalized);\n if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(`${key} must be between ${min} and ${max}`);\n }\n return parsed;\n}\nfunction getEnvCacheEntries(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_ENTRIES });\n}\nfunction getEnvCacheSizeMb(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_CACHE_SIZE_MB });\n}\nfunction getEnvTtlSeconds(key, fallback) {\n return getEnvInteger(key, fallback, { max: MAX_CONFIGURED_TTL_SECONDS });\n}\nvar DEFAULT_LRU_MAX_ENTRIES = getEnvCacheEntries("LRU_DEFAULT_MAX_ENTRIES", 100);\nvar COMPONENT_LOADER_MAX_ENTRIES = getEnvCacheEntries("COMPONENT_LOADER_MAX_ENTRIES", 200);\nvar COMPONENT_LOADER_TTL_MS = 10 * MS_PER_MINUTE;\nvar MDX_RENDERER_MAX_ENTRIES = getEnvCacheEntries("MDX_RENDERER_MAX_ENTRIES", 500);\nvar MDX_RENDERER_TTL_MS = 10 * MS_PER_MINUTE;\nvar RENDERER_CORE_MAX_ENTRIES = getEnvCacheEntries("RENDERER_CORE_MAX_ENTRIES", 200);\nvar RENDERER_CORE_TTL_MS = 5 * MS_PER_MINUTE;\nvar TSX_LAYOUT_MAX_ENTRIES = getEnvCacheEntries("TSX_LAYOUT_MAX_ENTRIES", 100);\nvar TSX_LAYOUT_TTL_MS = 10 * MS_PER_MINUTE;\nvar TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES = getEnvCacheEntries(\n "TSX_LAYOUT_PER_PROJECT_MAX_ENTRIES",\n Math.ceil(TSX_LAYOUT_MAX_ENTRIES / 10)\n);\nvar DATA_FETCHING_MAX_ENTRIES = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES",\n 500,\n { max: MAX_CONFIGURED_CACHE_ENTRIES }\n);\nvar DATA_FETCHING_MAX_ENTRIES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_ENTRIES_PER_PROJECT",\n Math.max(1, Math.ceil(DATA_FETCHING_MAX_ENTRIES / 5)),\n { max: DATA_FETCHING_MAX_ENTRIES }\n);\nvar dataFetchingMaxSizeMb = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB",\n 50,\n { max: MAX_CONFIGURED_CACHE_SIZE_MB }\n);\nvar DATA_FETCHING_MAX_SIZE_BYTES = dataFetchingMaxSizeMb * BYTES_PER_MB;\nvar DATA_FETCHING_MAX_SIZE_BYTES_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_SIZE_MB_PER_PROJECT",\n Math.max(1, Math.ceil(dataFetchingMaxSizeMb / 5)),\n { max: dataFetchingMaxSizeMb }\n) * BYTES_PER_MB;\nvar DATA_FETCHING_TTL_MS = 10 * MS_PER_MINUTE;\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS",\n 512,\n { max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT = getStrictEnvInteger(\n "DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS_PER_PROJECT",\n Math.min(128, DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS),\n { max: DATA_FETCHING_MAX_CONCURRENT_EXECUTIONS }\n);\nvar MDX_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_CACHE_TTL_DEVELOPMENT_MS = 5 * MS_PER_MINUTE;\nvar BUNDLE_MANIFEST_PROD_TTL_MS = 7 * ONE_DAY_MS;\nvar SERVER_ACTION_DEFAULT_TTL_SEC = MINUTES_PER_HOUR * SECONDS_PER_MINUTE;\nvar DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_TRANSFORM_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_SEC",\n MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_FILE_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_FILE_TTL_PREVIEW_SEC",\n 5 * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PRODUCTION_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n);\nvar DISTRIBUTED_CSS_TTL_PREVIEW_SEC = getEnvTtlSeconds(\n "DISTRIBUTED_CSS_TTL_PREVIEW_SEC",\n 10 * SECONDS_PER_MINUTE\n);\nvar LRU_DEFAULT_MAX_ENTRIES_V2 = getEnvCacheEntries("LRU_MAX_ENTRIES", 2e3);\nvar LRU_DEFAULT_MAX_SIZE_BYTES = getEnvCacheSizeMb("LRU_MAX_SIZE_MB", 200) * BYTES_PER_MB;\nvar MEMORY_CACHE_MAX_ENTRIES = getEnvCacheEntries("MEMORY_CACHE_MAX_ENTRIES", 2e3);\nvar MEMORY_CACHE_MAX_SIZE_BYTES = getEnvCacheSizeMb("MEMORY_CACHE_MAX_SIZE_MB", 50) * BYTES_PER_MB;\nvar FILE_CACHE_MAX_ENTRIES = getEnvCacheEntries("FILE_CACHE_MAX_ENTRIES", 1e3);\nvar FILE_CACHE_MAX_SIZE_MB = getEnvCacheSizeMb("FILE_CACHE_MAX_SIZE_MB", 100);\nvar MAX_CONCURRENT_REVALIDATIONS = getEnvInteger("MAX_CONCURRENT_REVALIDATIONS", 32, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar MAX_CONCURRENT_HTTP_FETCHES = getEnvInteger("MAX_CONCURRENT_HTTP_FETCHES", 50, {\n max: MAX_CONFIGURED_CONCURRENCY\n});\nvar REVALIDATION_TIMEOUT_MS = getEnvInteger("REVALIDATION_TIMEOUT_MS", 15e3, {\n max: MAX_TIMER_DELAY_MS\n});\nvar REVALIDATION_PER_PROJECT_LIMIT = getEnvInteger(\n "REVALIDATION_PER_PROJECT_LIMIT",\n Math.ceil(MAX_CONCURRENT_REVALIDATIONS / 3),\n { min: 0, max: MAX_CONFIGURED_CONCURRENCY }\n);\nvar BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "BUNDLE_MANIFEST_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar BUNDLE_MANIFEST_LRU_MAX_ENTRIES = getEnvCacheEntries(\n "BUNDLE_MANIFEST_LRU_MAX_ENTRIES",\n 5e3\n);\nvar BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_METADATA_SIZE_MB",\n 128\n) * BYTES_PER_MB;\nvar BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_BYTES = getEnvCacheSizeMb(\n "BUNDLE_MANIFEST_MEMORY_MAX_CODE_SIZE_MB",\n 256\n) * BYTES_PER_MB;\nvar HTTP_MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries(\n "HTTP_MODULE_CACHE_MAX_ENTRIES",\n 2e3\n);\nvar HTTP_MODULE_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "HTTP_MODULE_DISTRIBUTED_TTL_SEC",\n HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 24 hours (86400)\n);\nvar TRANSFORM_DISTRIBUTED_TTL_SEC = getEnvTtlSeconds(\n "TRANSFORM_DISTRIBUTED_TTL_SEC",\n 6 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE\n // 6 hours (21600)\n);\nvar MODULE_CACHE_MAX_ENTRIES = getEnvCacheEntries("MODULE_CACHE_MAX_ENTRIES", 1e4);\nvar MODULE_CACHE_TTL_MS = getEnvInteger(\n "MODULE_CACHE_TTL_MS",\n 5 * MS_PER_MINUTE,\n // 5 minutes - short enough to pick up changes, long enough to cache\n { max: MAX_TIMER_DELAY_MS }\n);\nvar ESM_CACHE_MAX_ENTRIES = getEnvCacheEntries("ESM_CACHE_MAX_ENTRIES", 5e3);\nvar ESM_CACHE_TTL_MS = getEnvInteger(\n "ESM_CACHE_TTL_MS",\n 10 * MS_PER_MINUTE,\n // 10 minutes - external modules change less frequently\n { max: MAX_TIMER_DELAY_MS }\n);\n\n// src/platform/compat/primordials/array.ts\nvar ArrayPrototypeAt = Array.prototype.at;\nvar ArrayPrototypeFilter = Array.prototype.filter;\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypeMap = Array.prototype.map;\nvar ArrayPrototypePop = Array.prototype.pop;\nvar ArrayPrototypePush = Array.prototype.push;\nvar ArrayPrototypeSort = Array.prototype.sort;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\nvar HTTP_MODULE_FETCH_TIMEOUT_MS = 1e4;\nvar HTTP_MODULE_FETCH_MAX_ATTEMPTS = 3;\nvar HTTP_MODULE_FETCH_RETRY_DELAY_MS = 100;\nvar HTTP_MODULE_FETCH_RETRY_BUDGET_MS = HTTP_MODULE_FETCH_TIMEOUT_MS * HTTP_MODULE_FETCH_MAX_ATTEMPTS + HTTP_MODULE_FETCH_RETRY_DELAY_MS * ((HTTP_MODULE_FETCH_MAX_ATTEMPTS - 1) * HTTP_MODULE_FETCH_MAX_ATTEMPTS / 2);\nvar PREFETCH_MAX_SIZE_BYTES = 200 * KB_IN_BYTES;\n\n// src/utils/constants/hmr.ts\nvar HMR_MAX_MESSAGE_SIZE_BYTES = 1024 * KB_IN_BYTES;\n\n// src/utils/constants/network.ts\nvar BYTES_PER_KB = 1024;\nvar BYTES_PER_MB2 = BYTES_PER_KB * BYTES_PER_KB;\n\n// src/utils/constants/security.ts\nvar MAX_CSRF_TTL_SECONDS = Number.MAX_SAFE_INTEGER;\n\n// src/platform/compat/constants.ts\nvar DEFAULT_PORT = 3e3;\nvar LOCALHOST = Object.freeze(\n {\n IPV4: "127.0.0.1",\n IPV6: "::1",\n HOSTNAME: "localhost"\n }\n);\n\n// src/config/defaults.ts\nvar DEFAULT_TIMEOUT_MS = 5e3;\nvar SSR_TIMEOUT_MS = 1e4;\nvar SSR_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;\nvar SANDBOX_TIMEOUT_MS = 5e3;\nvar DEFAULT_CACHE_MAX_SIZE = 100;\nvar DURATION_HISTOGRAM_BOUNDARIES_MS = Object.freeze(\n [\n 5,\n 10,\n 25,\n 50,\n 75,\n 100,\n 250,\n 500,\n 750,\n 1e3,\n 2500,\n 5e3,\n 7500,\n 1e4\n ]\n);\nvar SIZE_HISTOGRAM_BOUNDARIES_KB = Object.freeze(\n [\n 1,\n 5,\n 10,\n 25,\n 50,\n 100,\n 250,\n 500,\n 1e3,\n 2500,\n 5e3,\n 1e4\n ]\n);\nvar defaultConfig = Object.freeze(\n {\n server: Object.freeze({\n port: DEFAULT_PORT,\n hostname: "0.0.0.0"\n }),\n timeouts: Object.freeze({\n default: DEFAULT_TIMEOUT_MS,\n api: 3e4,\n ssr: SSR_TIMEOUT_MS,\n hmr: 3e4,\n sandbox: SANDBOX_TIMEOUT_MS\n }),\n cache: Object.freeze({\n jit: Object.freeze({\n maxSize: DEFAULT_CACHE_MAX_SIZE,\n tempDirPrefix: "vf-bundle-"\n })\n }),\n metrics: Object.freeze({\n ssrBoundaries: DURATION_HISTOGRAM_BOUNDARIES_MS\n })\n }\n);\n\n// src/utils/constants/server.ts\nvar INTERNAL_PREFIX = "/_veryfront";\nvar INTERNAL_PATH_PREFIXES = {\n /** React Server Components endpoints */\n RSC: `${INTERNAL_PREFIX}/rsc/`,\n /** File system access endpoints (base64 encoded paths) */\n FS: `${INTERNAL_PREFIX}/fs/`,\n /** Virtual module system */\n MODULES: `${INTERNAL_PREFIX}/modules/`,\n /** Generated page modules */\n PAGES: `${INTERNAL_PREFIX}/pages/`,\n /** Data JSON endpoints */\n DATA: `${INTERNAL_PREFIX}/data/`,\n /** Library modules and large vendor surfaces */\n LIB: `${INTERNAL_PREFIX}/lib/`,\n /** Chunk assets */\n CHUNKS: `${INTERNAL_PREFIX}/chunks/`,\n /** Client component modules */\n CLIENT: `${INTERNAL_PREFIX}/client/`\n};\nvar INTERNAL_ENDPOINTS = {\n // Development endpoints\n HMR_RUNTIME: `${INTERNAL_PREFIX}/hmr-runtime.js`,\n HMR: `${INTERNAL_PREFIX}/hmr.js`,\n ERROR_OVERLAY: `${INTERNAL_PREFIX}/error-overlay.js`,\n // Legacy endpoint retained for backward compatibility (no active handler).\n DEV_LOADER: `${INTERNAL_PREFIX}/dev-loader.js`,\n CLIENT_LOG: `${INTERNAL_PREFIX}/log`,\n // Production endpoints\n CLIENT_JS: `${INTERNAL_PREFIX}/client.js`,\n ROUTER_JS: `${INTERNAL_PREFIX}/router.js`,\n PREFETCH_JS: `${INTERNAL_PREFIX}/prefetch.js`,\n MANIFEST_JSON: `${INTERNAL_PREFIX}/manifest.json`,\n APP_JS: `${INTERNAL_PREFIX}/app.js`,\n // RSC endpoints\n RSC_CLIENT: `${INTERNAL_PREFIX}/rsc/client.js`,\n RSC_MANIFEST: `${INTERNAL_PREFIX}/rsc/manifest`,\n RSC_STREAM: `${INTERNAL_PREFIX}/rsc/stream`,\n RSC_PAYLOAD: `${INTERNAL_PREFIX}/rsc/payload`,\n RSC_RENDER: `${INTERNAL_PREFIX}/rsc/render`,\n RSC_PAGE: `${INTERNAL_PREFIX}/rsc/page`,\n RSC_MODULE: `${INTERNAL_PREFIX}/rsc/module`,\n RSC_DOM: `${INTERNAL_PREFIX}/rsc/dom.js`,\n // Library module endpoints\n LIB_CHAT_REACT: `${INTERNAL_PREFIX}/lib/chat/react.js`,\n LIB_CHAT_COMPONENTS: `${INTERNAL_PREFIX}/lib/chat/components.js`,\n LIB_CHAT_PRIMITIVES: `${INTERNAL_PREFIX}/lib/chat/primitives.js`\n};\nvar PROJECT_DIRS = {\n /** Base veryfront internal directory */\n ROOT: ".veryfront",\n /** Cache directory for build artifacts, transforms, etc. */\n CACHE: ".veryfront/cache",\n /** KV store directory */\n KV: ".veryfront/kv",\n /** Log files directory */\n LOGS: ".veryfront/logs",\n /** Temporary files directory */\n TMP: ".veryfront/tmp"\n};\nvar DEFAULT_CACHE_DIR = PROJECT_DIRS.CACHE;\nvar DEV_SERVER_ENDPOINTS = {\n HMR_RUNTIME: INTERNAL_ENDPOINTS.HMR_RUNTIME,\n ERROR_OVERLAY: INTERNAL_ENDPOINTS.ERROR_OVERLAY\n};\n\n// src/rendering/client/prefetch/prefetch-queue.ts\nvar DEFAULT_OPTIONS = {\n maxConcurrent: 4,\n maxSize: PREFETCH_QUEUE_MAX_SIZE_BYTES,\n timeout: 5e3\n};\nfunction isAbortError(error) {\n if (typeof error !== "object" || error === null) return false;\n if (!("name" in error)) return false;\n return error.name === "AbortError";\n}\nvar PrefetchQueue = class {\n constructor(options = {}, prefetchedUrls) {\n __publicField(this, "options");\n __publicField(this, "controllers", /* @__PURE__ */ new Map());\n __publicField(this, "prefetchedUrls");\n __publicField(this, "concurrent", 0);\n __publicField(this, "stopped", false);\n __publicField(this, "onResourcesFetched");\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.prefetchedUrls = prefetchedUrls ?? /* @__PURE__ */ new Set();\n }\n setResourceCallback(callback) {\n this.onResourcesFetched = callback;\n }\n enqueue(url) {\n void this.prefetch(url);\n }\n has(url) {\n return this.prefetchedUrls.has(url) || this.controllers.has(url);\n }\n get size() {\n return this.controllers.size;\n }\n clear() {\n this.stopAll();\n this.prefetchedUrls.clear();\n }\n start() {\n this.stopped = false;\n }\n stop() {\n this.stopped = true;\n this.stopAll();\n }\n getQueueSize() {\n return this.controllers.size;\n }\n getConcurrentCount() {\n return this.concurrent;\n }\n async prefetchLink(link) {\n if (this.stopped) return;\n const url = link.href;\n if (!url || this.controllers.has(url) || this.prefetchedUrls.has(url)) return;\n if (this.concurrent >= this.options.maxConcurrent) {\n prefetchLogger.debug?.(`Prefetch queue full, skipping ${url}`);\n return;\n }\n let parsedUrl;\n try {\n parsedUrl = new URL(url);\n } catch (_) {\n prefetchLogger.debug?.(`Invalid prefetch URL ${url}`);\n return;\n }\n const controller = new AbortController();\n this.controllers.set(url, controller);\n this.concurrent += 1;\n const timeoutId = this.options.timeout > 0 ? setTimeout(() => controller.abort(), this.options.timeout) : void 0;\n try {\n const response = await fetch(parsedUrl.toString(), {\n method: "GET",\n signal: controller.signal,\n headers: { "X-Veryfront-Prefetch": "1" }\n });\n if (!response.ok) return;\n if (this.isResponseTooLarge(response)) {\n prefetchLogger.debug?.(`Prefetch too large, skipping ${url}`);\n return;\n }\n this.prefetchedUrls.add(url);\n if (!this.onResourcesFetched) return;\n try {\n await this.onResourcesFetched(response, url);\n } catch (callbackError) {\n prefetchLogger.error?.(`Prefetch callback failed for ${url}`, callbackError);\n }\n } catch (error) {\n if (!isAbortError(error)) {\n prefetchLogger.error?.(`Failed to prefetch ${url}`, error);\n }\n } finally {\n if (timeoutId !== void 0) clearTimeout(timeoutId);\n this.controllers.delete(url);\n this.concurrent = Math.max(0, this.concurrent - 1);\n }\n }\n async prefetch(url) {\n const link = typeof document !== "undefined" ? document.createElement("a") : { href: url };\n link.href = url;\n await this.prefetchLink(link);\n }\n stopAll() {\n for (const controller of this.controllers.values()) {\n controller.abort();\n }\n this.controllers.clear();\n this.concurrent = 0;\n }\n isResponseTooLarge(response) {\n const rawLength = response.headers.get("content-length");\n if (rawLength === null) return false;\n const size = Number.parseInt(rawLength, 10);\n if (!Number.isFinite(size)) return false;\n return size > this.options.maxSize;\n }\n};\nvar prefetchQueue = new PrefetchQueue();\n\n// src/rendering/client/prefetch/resource-hints.ts\nvar ResourceHintsManager = class {\n constructor() {\n __publicField(this, "appliedHints", /* @__PURE__ */ new Set());\n }\n applyResourceHints(hints) {\n for (const hint of hints) {\n const key = `${hint.type}:${hint.href}`;\n if (this.appliedHints.has(key)) continue;\n const existing = document.querySelector(\n `link[rel="${hint.type}"][href="${hint.href}"]`\n );\n if (existing) {\n this.appliedHints.add(key);\n continue;\n }\n this.createAndAppendHint(hint);\n this.appliedHints.add(key);\n prefetchLogger.debug(`Added resource hint: ${hint.type} ${hint.href}`);\n }\n }\n createAndAppendHint(hint) {\n if (!document.head) {\n prefetchLogger.warn("document.head is not available, skipping resource hint");\n return;\n }\n const link = document.createElement("link");\n link.rel = hint.type;\n link.href = hint.href;\n if (hint.as) link.setAttribute("as", hint.as);\n if (hint.crossOrigin) link.setAttribute("crossorigin", hint.crossOrigin);\n if (hint.media) link.setAttribute("media", hint.media);\n document.head.appendChild(link);\n }\n extractResourceHints(html, prefetchedUrls) {\n try {\n const doc = new DOMParser().parseFromString(html, "text/html");\n const hints = [];\n this.extractPreloadLinks(doc, prefetchedUrls, hints);\n this.extractScripts(doc, prefetchedUrls, hints);\n this.extractStylesheets(doc, prefetchedUrls, hints);\n return hints;\n } catch (error) {\n prefetchLogger.error("Failed to parse prefetched page", error);\n return [];\n }\n }\n isValidResourceHintType(rel) {\n switch (rel) {\n case "prefetch":\n case "preload":\n case "preconnect":\n case "dns-prefetch":\n return true;\n default:\n return false;\n }\n }\n extractPreloadLinks(doc, prefetchedUrls, hints) {\n const links = doc.querySelectorAll(\n \'link[rel="preload"], link[rel="prefetch"]\'\n );\n for (const link of links) {\n const href = link.href;\n if (!href) continue;\n if (prefetchedUrls.has(href)) continue;\n if (!this.isValidResourceHintType(link.rel)) continue;\n hints.push({\n type: link.rel,\n href,\n as: link.getAttribute("as") ?? void 0\n });\n }\n }\n extractScripts(doc, prefetchedUrls, hints) {\n for (const script of doc.querySelectorAll("script[src]")) {\n const src = script.src;\n if (!src || prefetchedUrls.has(src)) continue;\n hints.push({ type: "prefetch", href: src, as: "script" });\n }\n }\n extractStylesheets(doc, prefetchedUrls, hints) {\n for (const link of doc.querySelectorAll(\'link[rel="stylesheet"]\')) {\n const href = link.href;\n if (!href || prefetchedUrls.has(href)) continue;\n hints.push({ type: "prefetch", href, as: "style" });\n }\n }\n static generateResourceHints(_route, assets) {\n const hints = [\n \'\',\n \'\',\n \'\'\n ];\n for (const asset of assets) {\n if (asset.endsWith(".js")) {\n hints.push(``);\n continue;\n }\n if (asset.endsWith(".css")) {\n hints.push(``);\n continue;\n }\n if (/\\.(woff2?|ttf|otf)$/.test(asset)) {\n hints.push(``);\n }\n }\n return hints.join("\\n");\n }\n};\n\n// src/rendering/client/browser-stubs/logger.ts\nfunction noop() {\n}\nvar logger = {\n debug: noop,\n info: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n component: () => logger\n};\nvar PREFETCH_MAX_SIZE_BYTES2 = 200 * 1024;\nvar PREFETCH_DEFAULT_TIMEOUT_MS2 = 1e4;\nvar PREFETCH_DEFAULT_DELAY_MS2 = 200;\n\n// src/rendering/client/prefetch.ts\nvar PrefetchManager = class {\n constructor(options = {}) {\n __publicField(this, "options");\n __publicField(this, "prefetchedUrls", /* @__PURE__ */ new Set());\n __publicField(this, "networkUtils");\n __publicField(this, "linkObserver", null);\n __publicField(this, "resourceHintsManager");\n __publicField(this, "prefetchQueue");\n this.options = {\n rootMargin: options.rootMargin ?? "50px",\n delay: options.delay ?? PREFETCH_DEFAULT_DELAY_MS2,\n maxConcurrent: options.maxConcurrent ?? 2,\n allowedNetworks: options.allowedNetworks ?? ["4g", "wifi", "ethernet"],\n maxSize: options.maxSize ?? PREFETCH_MAX_SIZE_BYTES2,\n timeout: options.timeout ?? PREFETCH_DEFAULT_TIMEOUT_MS2\n };\n this.networkUtils = new NetworkUtils(this.options.allowedNetworks);\n this.resourceHintsManager = new ResourceHintsManager();\n this.prefetchQueue = new PrefetchQueue(\n {\n maxConcurrent: this.options.maxConcurrent,\n maxSize: this.options.maxSize,\n timeout: this.options.timeout\n },\n this.prefetchedUrls\n );\n this.prefetchQueue.setResourceCallback(\n (response, url) => this.prefetchPageResources(response, url)\n );\n }\n init() {\n prefetchLogger.info("Initializing prefetch manager");\n if (!this.networkUtils.shouldPrefetch()) {\n prefetchLogger.info("Prefetching disabled due to network conditions");\n return;\n }\n this.linkObserver = new LinkObserver(\n {\n rootMargin: this.options.rootMargin,\n delay: this.options.delay,\n onLinkVisible: (link) => this.prefetchQueue.prefetchLink(link)\n },\n this.prefetchedUrls\n );\n this.linkObserver.init();\n this.networkUtils.onNetworkChange(() => {\n if (!this.networkUtils.shouldPrefetch()) this.prefetchQueue.stopAll();\n });\n }\n async prefetchPageResources(response, _pageUrl) {\n const html = await response.text();\n const hints = this.resourceHintsManager.extractResourceHints(html, this.prefetchedUrls);\n this.resourceHintsManager.applyResourceHints(hints);\n }\n applyResourceHints(hints) {\n this.resourceHintsManager.applyResourceHints(hints);\n }\n async prefetch(url) {\n await this.prefetchQueue.prefetch(url);\n }\n static generateResourceHints(route, assets) {\n return ResourceHintsManager.generateResourceHints(route, assets);\n }\n destroy() {\n this.linkObserver?.destroy();\n this.prefetchQueue.stopAll();\n this.prefetchedUrls.clear();\n }\n};\nfunction initPrefetch(options) {\n const prefetchManager = new PrefetchManager(options);\n if (document.readyState === "loading") {\n document.addEventListener("DOMContentLoaded", () => prefetchManager.init(), { once: true });\n } else {\n prefetchManager.init();\n }\n globalThis.veryFrontPrefetch = prefetchManager;\n return prefetchManager;\n}\nfunction resolveAutoInitOptions() {\n const setting = globalThis.__VERYFRONT_PREFETCH__;\n if (!setting) return null;\n if (setting === true) return {};\n if (typeof setting === "object") return setting;\n return null;\n}\nfunction shouldAutoInitPrefetch(options) {\n if (!options) return false;\n if (typeof window === "undefined" || typeof document === "undefined") return false;\n const win = window;\n const doc = document;\n if (win.__veryfrontSSRStub || doc.__veryfrontSSRStub) return false;\n if (typeof IntersectionObserver === "undefined") return false;\n if (typeof MutationObserver === "undefined") return false;\n return true;\n}\nvar autoInitOptions = resolveAutoInitOptions();\nif (shouldAutoInitPrefetch(autoInitOptions)) initPrefetch(autoInitOptions);\nexport {\n PrefetchManager,\n initPrefetch\n};\n'; diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index f335ea0fbe..f7dcf0e1e0 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;var Mr=RegExp.prototype.test,Or=RegExp.prototype[Symbol.replace];function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),no=new b("PREFETCH",G),ro=new b("HYDRATE",G),oo=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},mo={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var Eo=Array.prototype,Ro=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,_o=Object.hasOwn,xo=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,So=Number.isFinite,Ao=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,To=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,bo=_(T.prototype,"host").get,Co=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,wo=_(T.prototype,"pathname").get,Do=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Po=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var vo=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Fo=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Vo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),zo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Bo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),jo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Go=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Wo=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Ko=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Yo=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Xo=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Jo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,hi=ir*2;var Ei=64*1024,Ri=1024*1024,_i=1024*1024;var xi=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; + 'var at=Object.defineProperty;var ct=(e,t,n)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>ct(e,typeof t!="symbol"?t+"":t,n);var Ar=Array.prototype.at,Tr=Array.prototype.filter,lt=Array.prototype.join,br=Array.prototype.map,Cr=Array.prototype.pop,dt=Array.prototype.push,wr=Array.prototype.sort,Ae=Reflect.apply;function z(e,t){return Ae(lt,e,[t])}function O(e,t){Ae(dt,e,[t])}var ut="3.2.3",ft=Object.entries;function gt(e){let t=[];if(e?.external?.length&&O(t,`external=${z(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let n=[],r=ft(e.deps);for(let o=0;ot||n?.(r,...o)}debug(t,...n){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...n)}info(t,...n){this.log(1,console.log,`[${this.prefix}] ${t}`,...n)}warn(t,...n){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...n)}error(t,...n){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...n)}};function wt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var G=wt(),u=new b("RSC",G),eo=new b("PREFETCH",G),to=new b("HYDRATE",G),no=new b("VERYFRONT",G);var Dt="veryfront-hydration-data";function de(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let n=e.body;if(!n)return null;let r=t[0];return n.firstElementChild!==r&&r.parentElement!==n||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function C(e=document){try{let t=de(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return u.debug("hydration data parse failed",t),null}}function W(e,t){if(!t?.startsWith("on:"))return!1;try{let n=de(e);if(!n)return!1;let r=JSON.parse(n.textContent||"{}");return r.dependencyPinningCacheKey=t,n.textContent=JSON.stringify(r),!0}catch(n){return u.debug("hydration dependency snapshot seed failed",n),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Nt(e,t){if(!t)return e;let n=e.includes("?")?"&":"?";return`${e}${n}v=${encodeURIComponent(t)}`}function Y(e,t){if(!t?.startsWith("on:"))return e;let n=e.indexOf("#"),r=n===-1?"":e.slice(n),o=n===-1?e:e.slice(0,n),i=o.indexOf("?"),s=i===-1?o:o.slice(0,i),a=new URLSearchParams(i===-1?"":o.slice(i+1));a.set("pins",t);let l=a.toString();return`${s}${l?`?${l}`:""}${r}`}function Mt(e,t){return Nt(`${Ne}${ae(e)}.js`,t)}function Ot(e,t,n){let r=t?`&v=${encodeURIComponent(t)}`:"";return Y(`${P}module?rel=${encodeURIComponent(e)}${r}`,n)}function L(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Pt=/\\.(tsx|ts|jsx|mdx|js)$/;function Ht(e){let t=It(e),n=[e,t];return Pt.test(t)||n.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(n))}function Lt(e,t){if(!e)return null;for(let n of Ht(t)){let r=e[n];if(r)return r}return null}function X(e){if(e.strategy==="fs"){let n=e.absPath??e.rel;return n?Y(Mt(n,e.version),e.dependencyPinningCacheKey):null}let t=Lt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function J(e=document,t=I){let n=ce(e);return{react:B("react",n)?"react":be(t),reactDomClient:B("react-dom/client",n)?"react-dom/client":Ce(t)}}function Me(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var q={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},po={debug:q.gray,info:q.green,warn:q.yellow,error:q.red};var y="[REDACTED]",p=Reflect.apply,Ut=Array.prototype.pop,$t=Array.prototype.push;var mo=Array.prototype,ho=BigInt.prototype.toString,Le=Map,kt=Map.prototype.delete,vt=Map.prototype.get,Ft=Map.prototype.keys,Vt=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,zt=Object.getPrototypeOf,Eo=Object.hasOwn,Ro=Object.prototype,Bt=Set,jt=decodeURIComponent,T=URL,_o=Number.isFinite,xo=Number.isInteger,ue=RegExp.prototype.exec,Gt=_(RegExp.prototype,"global").get,Wt=_(RegExp.prototype,"unicode").get,Kt=String.prototype.charCodeAt,Yt=String.prototype.includes,Xt=String.prototype.indexOf,Oe=String.prototype.slice,Ue=String.prototype.startsWith,$e=String.prototype.toLowerCase,Jt=Set.prototype.add,So=Set.prototype.delete,qt=Set.prototype.has,Zt=zt(new Le().keys()).next,Qt=_(Map.prototype,"size").get,Ao=_(T.prototype,"host").get,To=_(T.prototype,"origin").get,en=_(T.prototype,"password").get,bo=_(T.prototype,"pathname").get,Co=_(T.prototype,"protocol").get,tn=_(T.prototype,"username").get,nn=/[^a-z0-9]/g,rn=/([a-z0-9])([A-Z])/g,on=/([A-Z])([A-Z][a-z])/g,sn=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function R(e,t,n){let r=p(Gt,t,[]),o=p(Wt,t,[]),i=0,s=!1,a="";t.lastIndex=0;try{for(;;){let l=p(ue,t,[e]);if(l===null)break;let c=l[0],d=l.index;if(a+=A(e,i,d),a+=typeof n=="string"?n:n(l),i=d+c.length,s=!0,!r)break;c.length===0&&(t.lastIndex=an(e,d,o))}}finally{t.lastIndex=0}return s?a+A(e,i):e}function fe(e){let t=p($e,e,[]);return R(t,nn,"")}function w(e,t){return p(Kt,e,[t])}function an(e,t,n){let r=t+1;if(!n||r>=e.length)return r;let o=w(e,t);if(o<55296||o>56319)return r;let i=w(e,r);return i>=56320&&i<=57343?t+2:r}function A(e,t,n){return n===void 0?p(Oe,e,[t]):p(Oe,e,[t,n])}function cn(e){let t=[],n=0;for(let r=0;r<=e.length;r++){let o=r===e.length?-1:w(e,r);o>=97&&o<=122||o>=48&&o<=57||(r>n&&(t[t.length]=A(e,n,r)),n=r+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ln=512,dn=128,U=new Le;var un=256;function fn(e){let t=e.length<=dn;if(t){let o=p(vt,U,[e]);if(o!==void 0)return o}let n=fe(e),r=n==="auth";for(let o=0;!r&&o=ln){let i=p(Ft,U,[]),s=p(Zt,i,[]).value;s!==void 0&&p(kt,U,[s])}p(Vt,U,[e,r])}return r}var Ie=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],ke=new Bt;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function ve(e){return hn(e)||e==="_"||e==="$"}function En(e){if(!e)return!1;let t=w(e,0);return ve(e)||t>=48&&t<=57||e==="."||e==="-"}function Fe(e,t){let n=t,r=e[n]===\'"\'||e[n]==="\'"?e[n++]:"";if(!ve(e[n]))return!1;for(n++;En(e[n]);)n++;if(r){if(e[n]!==r)return!1;n++}for(;e[n]===" "||e[n]==="\t";)n++;return e[n]===":"||e[n]==="="}function Ve(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||mn(e)}function ze(e,t){let n=t;for(;n=e.length||Fe(e,n)}function Rn(e,t){let n=t,r=!0;if(p(Ue,e,[y,t])){let d=t+y.length;if(Pe(e,d))return{end:d,replacement:y};n=d,r=!1}let o=r&&(e[n]===\'"\'||e[n]==="\'"||e[n]==="`")?e[n]:"",i=!1,s=()=>o?`${o}${y}${i?o:""}`:y,a=[],l="",c=-1;for(let d=n;d0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:e.length,replacement:s()};if(p(Ut,a,[]),d++,a.length===0&&Pe(e,d))return{end:d,replacement:s()};continue}if(a.length>0||!Ve(f)){d++;continue}let E=d;if(d=ze(e,d),d>=e.length||Fe(e,d))return{end:E,replacement:s()}}return{end:e.length,replacement:s()}}function He(e,t,n,r){let o=0,i="";for(let s=p(ue,t,[e]);s;s=p(ue,t,[e])){let a=s[n];if(!_n(a))continue;let l=t.lastIndex,c=r===void 0?void 0:s[r],d=l+y.length;if((c==="?"||c==="&"||c===";")&&p(Ue,e,[y,l])&&e[d]==="#")continue;let f=Rn(e,l);i+=A(e,o,s.index),i+=s[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+A(e,o)}function _n(e){if(e.length>un)return!0;let t=R(e,on,i=>`${i[1]} ${i[2]}`),n=R(t,rn,i=>`${i[1]} ${i[2]}`),r=p($e,n,[]),o=cn(r);for(let i=0;i{let r=n[1],o=n[2],i=p(Xt,o,[":"]);if(i===-1)return`${r}${y}@`;let s=A(o,0,i);return`${r}${s}:${y}@`});return t=R(t,pn,n=>{let r=n[1],o=n[2],i=n[3];return xn(r,o,i)?n[0]:`${r}${o}:${y}@`}),t=R(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,n=>{let r=n[1],o=n[2],i=Sn(o);return p(qt,ke,[fe(i)])||fn(i)?`${r}${o}=${y}`:n[0]}),t=R(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,n=>`${n[1]}${y}`),t=R(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,n=>`${n[1]}${n[2]}${y}`),t=R(t,sn,y),t=He(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=He(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var An=2048;var Oo=64*1024,Tn=256,bn="https://veryfront.com/docs/code/guides/errors#",Be="...[truncated]",pe="unknown-error";function je(e,t){if(e.length<=t)return e;let n=Math.max(0,t-Be.length);return`${Cn(e,n)}${Be}`}function Cn(e,t){let n=e.slice(0,t),r=n.charCodeAt(n.length-1);return r>=55296&&r<=56319&&(n=n.slice(0,-1)),n}function wn(e){let t="";for(let n=0;n=55296&&r<=56319){let o=e.charCodeAt(n+1);o>=56320&&o<=57343?(t+=e.slice(n,n+2),n++):t+="\\uFFFD";continue}t+=r>=56320&&r<=57343?"\\uFFFD":e.charAt(n)}return t}function D(e){return typeof e!="string"?y:je(ge(e),An)}function Dn(e){let t=typeof e=="string"?ge(e):pe,n=je(t||pe,Tn),r=wn(n);return r==="."||r===".."?pe:r}function Q(e){let t=encodeURIComponent(Dn(e));return`${bn}${t}`}var Ke=Reflect.apply,Nn=Object.freeze,Mn=Object.getOwnPropertyDescriptors,Ge=Number.isFinite,Ye=new WeakSet,On=WeakSet.prototype.add,In=WeakSet.prototype.has,Pn=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function h(e){let t={...e},n={...t,create(r){let o=r?.message,i=r?.detail,s=r?.cause,a=r?.instance,l=r?.context,c=r?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:s,instance:a,context:l})}};return Nn(n)}var ye=class extends Error{constructor(n,r){super(n);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Ke(On,Ye,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let n=We(this);return n?{type:Q(n.slug),title:D(n.title),status:n.status,detail:n.detail===void 0?void 0:D(n.detail),instance:n.instance===void 0?void 0:D(n.instance),category:n.category,suggestion:n.suggestion===void 0?void 0:D(n.suggestion),cause:typeof n.cause=="string"?D(n.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let n=We(this);return Q(n?.slug??"unknown-error")}};function Xe(e){return typeof e=="object"&&e!==null&&Ke(In,Ye,[e])===!0}function We(e){return Xe(e)?Hn(e):null}function Hn(e){try{if(!Xe(e))return null;let t=Mn(e),n=re=>{let M=t[re];return M&&"value"in M?M.value:void 0},r=n("slug"),o=n("category"),i=n("status"),s=n("title"),a=n("message"),l=n("suggestion"),c=n("exitCode"),d=n("detail"),f=n("cause"),E=n("instance"),v=n("context"),x=n("stack");return typeof r!="string"||!Pn.has(o)||typeof i!="number"||!Ge(i)||typeof s!="string"||typeof a!="string"||l!==void 0&&typeof l!="string"||c!==void 0&&(typeof c!="number"||!Ge(c))||d!==void 0&&typeof d!="string"||E!==void 0&&typeof E!="string"||x!==void 0&&typeof x!="string"?null:{slug:r,category:o,status:i,title:s,message:a,suggestion:l,exitCode:c,detail:d,cause:f,instance:E,context:v,stack:x}}catch{return null}}var $o=h({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),ko=h({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),vo=h({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Fo=h({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Vo=h({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),zo=h({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Bo=h({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),jo=h({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Go=h({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),Je=h({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Wo=h({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Ko=h({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Yo=h({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ln=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Un(){return Ln.map(({source:e,flags:t,name:n})=>({pattern:new RegExp(e,t),name:n}))}function $n(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function $(e,t={}){let{allowInlineScripts:n=!1,strict:r=!1,warn:o=!0}=t;for(let{pattern:i,name:s}of Un())if(!(n&&s==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${s} detected in server HTML`),r||!$n())))throw Je.create({detail:`Potentially unsafe HTML: ${s} detected`});return e}function k(e,t){let n=t==="root"?H:`rsc-slot-${t}`,r=e.getElementById(n);if(r)return r;let o=e.createElement("div");return o.id=n,e.body.appendChild(o),o}function kn(e,t){if(t.type!=="slot")return;let n=k(e,t.id);n.innerHTML=$(String(t.html??""))}function qe(e,t){let n=t.split(`\n`),r=n.pop()??"";for(let o of n){let i=o.trim();if(!i)continue;let s;try{s=JSON.parse(i)}catch(l){u.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!s||typeof s!="object")continue;let a=s;if(a.type==="slot"){kn(e,a);try{Vn(e,a.id||"root")}catch(l){u.debug("[client-dom] hydration optional failed",l)}}}return r}function vn(e){return new Promise((t,n)=>{let r=()=>n(new DOMException("aborted","AbortError"));if(e.aborted){r();return}e.addEventListener("abort",r,{once:!0})})}async function Ze(e,t=document,n){let r="body"in e?e:null,o=r?.body??e;if(!o)return;r&&W(t,r.headers.get(j));let i=o.getReader(),s=new TextDecoder,a="",l=!1;try{for(;;){if(n?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:d,value:f}=n?await Promise.race([c,vn(n)]):await c;if(d){l=!0;break}a+=s.decode(f,{stream:!0}),a=qe(t,a)}a&&qe(t,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||u.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){l||u.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){u.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){u.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){u.debug("[client-dom] response.body.cancel failed",c)}}}function Fn(e,t){let n=k(e,t),r=[],o=i=>{let s=i;s.dataset?.clientRef&&r.push(s);for(let a of i.children)o(a)};return o(n),r}function Vn(e,t){let n=Fn(e,t);for(let r of n){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",u.debug("[client-dom] marked for hydration",o))}}var zn=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return jn(t)?t.nodes:[]}catch{return[]}}async function he(e,t,n){return await Promise.all(e.map(r=>Bn(r,t,n)))}async function Bn(e,t,n){if(e.type==="html")return e.text??e.html??"";let r=await he(e.children??[],t,n);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...r);if(e.type==="server")return t.createElement(e.component,e.props??{},...r);let o=await n(e.component);return o?t.createElement(o,e.props??{},...r):null}function jn(e){return!me(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!me(e)||!zn.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!me(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(n=>et(n,t+1))}function me(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Gn(e){if(!e)return{};let t={};for(let[n,r]of Object.entries(e))t[n]=Array.isArray(r)?r.join("/"):r;return t}async function ee(e,t,n=document){try{let r=Me(n);if(!r)return e;let i=(await import(r)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Gn(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(r){return u.debug("router provider wrap failed",r),e}}var Wn="Unknown dependency snapshot",Kn="export default null; // Unknown dependency snapshot",Ee="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Yn(){return globalThis}async function Xn(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Wn||t===Kn}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await Xn(e))return!1;let n=Yn();if(n[Ee])return!0;n[Ee]=!0;try{t()}catch{return delete n[Ee],!1}return!0}async function te(e,t=globalThis.fetch,n=()=>globalThis.location.reload()){try{let r=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(r.length!==1||!r[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,n)}catch{return!1}}var Jn=100;function qn(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Jn){let n=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;n&&globalThis.__VF_CLIENT_MOD_CACHE.delete(n)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function tt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let n=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return n?{moduleUrl:n[1],exportName:n[2]||"default"}:(u.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Zn(e){let t=e.dataset?.rscProps;if(!t)return{};try{let n=JSON.parse(t);return n&&typeof n=="object"&&!Array.isArray(n)?n:{}}catch(n){return u.debug("hydrate: invalid client boundary props, using empty props",n),{}}}function Qn(e){return Qe(e.dataset?.rscChildren)}function er(e){return"/_veryfront/rsc/manifest"}function tr(e){return L(e)}async function nr(e=document){try{let t=C(e),n=await fetch(er(t),{headers:tr(t)});return n.ok?await n.json():(await N(n),null)}catch{return null}}async function nt(e,t,n,r={}){let o=rr(e,t,n,r.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let s=`${i}#${e.hash??""}`;try{let a=globalThis.__VF_CLIENT_MOD_CACHE?.get(s);if(a)return a}catch(a){u.debug("hydrate: cache get failed",a)}if(!o)return null;try{let a=await(r.importModule??(l=>import(l)))(o);try{qn(s,a)}catch(l){u.debug("hydrate: cache set failed",l)}return a}catch(a){return u.debug("hydrate: failed to import module",{moduleUrl:o,error:a}),await(r.recoverSnapshotFailure??te)(o),null}}function rr(e,t,n,r){if(t.moduleUrl)return Y(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:n,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:r})}function or(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),n=new Set(t);return t.filter(r=>{let o=r.parentElement;for(;o;){if(n.has(o))return!1;o=o.parentElement}return!0})}async function rt(e=document){let t=null;try{t=await nr(e)}catch(c){u.debug("hydrate: fetch manifest failed",c)}if(!t){u.debug("hydrate: no manifest");return}let n=or(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!n.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){u.debug("hydrate: hmr hash read failed",c)}if(n.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed",c)}return}let r=C(e),o=K(r),i=r?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){u.debug("hydrate: test mode flags failed",c)}let s=J(e,r?.reactVersion),[{default:a},{createRoot:l}]=await Promise.all([import(s.react),import(s.reactDomClient)]);for(let c of n){let d=c.dataset?.clientRef??"";if(!d||c.dataset?.hydrated==="true")continue;let f=tt(d);if(!f)continue;let E=await nt(t,f,o,{releaseAssetModules:i});if(!E)continue;let v=E[f.exportName]??E.default;if(typeof v=="function")try{let x=l(c),re=Zn(c),M=Qn(c),ot=await he(M,{Fragment:a.Fragment,createElement(F,oe,...V){return a.createElement(F,oe,...V)}},async F=>{let oe=t.modules.find(st=>st.id===F),V=t.components?.[F],xe=oe?.clientRef??(V?`${V}#default`:void 0);if(!xe)return null;let ie=tt(xe);if(!ie)return null;let se=await nt(t,ie,o,{releaseAssetModules:i});if(!se)return null;let Se=se[ie.exportName]??se.default;return typeof Se=="function"?Se:null}),it=await ee(a.createElement(v,re,...ot),r,e);x.render(it),c.dataset.hydrated="true"}catch(x){u.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){u.debug("hydrate: set hash failed (post)",c)}}var Re="data-vf-react-head-owner";var ir=2*1024*1024,yi=ir*2;var mi=64*1024,hi=1024*1024,Ei=1024*1024;var Ri=new TextEncoder;async function sr(){let e=C(document),t=J(document,e?.reactVersion),[n,r]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:n,ReactDOM:r}}var ar=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function _e(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||ar.has(e.tagName.toUpperCase())}function cr(e,t){return e.find(n=>n.tagName.toUpperCase()==="DIV"&&!!n.getAttribute("class")?.trim()&&!_e(n))??t}function lr(e,t){return e===t}function dr(e,t){let n=document.createElement("div");n.setAttribute("data-veryfront-hydration-root","page");let r=e.find(o=>!_e(o));r?.parentNode===t?t.insertBefore(n,r):t.appendChild(n);for(let o of e)!_e(o)&&o.parentNode===t&&n.appendChild(o);return n}function ur(e,t){for(let n of e){let r=[...n.hasAttribute(Re)?[n]:[],...n.querySelectorAll(`[${Re}]`)];for(let o of r)t.contains(o)||o.remove()}}function fr(e,t,n=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!n.getElementById("root")}function gr(e,t){return t?.pagePath?!1:!!e.getElementById(H)}function pr(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function yr(e){return e==="rsc-module"}function mr(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function hr(e,t,n){return X({strategy:t,rel:e,releaseAssetModules:n?.releaseAssetModules,dependencyPinningCacheKey:n?.dependencyPinningCacheKey})}async function Er(e,t){try{let n=await fetch(P+"stream"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";if(!n.body)return"failure";let r=new AbortController;return addEventListener("pagehide",()=>r.abort(),{once:!0}),await Ze(n,document,r.signal),"success"}catch(n){return u.debug("tryStream failed",n),"failure"}}async function ne(){try{await rt(document)}catch(e){u.debug("hydration failed",e)}}async function Rr(e,t,n){try{let{React:r,ReactDOM:o}=await sr(),i=hr(e,t,n);if(!i)return!1;u.debug("Loading component from:",i);let s;try{s=await import(i)}catch(E){throw await te(i),E}let a=s.default;if(typeof a!="function")return u.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),c=cr(l,document.body),d=lr(c,document.body)?dr(l,document.body):c;ur(l,d);let f=await ee(r.createElement(a,{}),n);return yr(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),u.debug("Page component hydrated successfully"),!0}catch(r){return u.error("Page hydration failed",r),!1}}async function _r(e,t){try{let n=await fetch(P+"payload"+e,{headers:L(t)});if(!n.ok)return await N(n)?"snapshot-conflict":"failure";let r=await n.json();if(W(document,r?.dependencyPinningCacheKey),r?.slots){for(let[o,i]of Object.entries(r.slots))k(document,o).innerHTML=$(String(i||""));return"success"}return k(document,H).innerHTML=$(String(r?.html||"")),"success"}catch(n){return u.debug("payload fetch failed",n),"failure"}}async function xr(){try{let e=C(document),t=mr(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(pr()){await ne();return}let n=e?.pagePath,r=K(e);if(n){if(fr(globalThis.window,e,document)){u.debug("Page renderer owns hydration");return}u.debug("Found page component in hydration data:",n),await Rr(n,r,e)&&u.debug("Client component hydrated successfully");return}if(!gr(document,e))return;let o=await Er(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await ne();return}let i=await _r(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await ne();return}await ne()}catch(e){u.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{xr()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{xr as boot,hr as buildPageHydrationModuleUrl,mr as buildRSCTransportQuery,ur as retireAbandonedHeadOwnerMarkers,cr as selectHydrationRoot,gr as shouldAttemptRSCTransport,pr as shouldHydrateOnly,yr as shouldRenderPageComponent,fr as shouldUsePageRendererHydration,lr as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var Xn=RegExp.prototype.test,Jn=RegExp.prototype[Symbol.replace];var ar=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Ir(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Ir as consumeNdjsonStream,ft as getContainer};\n'; + 'var Et=Object.defineProperty;var yt=(t,n,e)=>n in t?Et(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var m=(t,n,e)=>yt(t,typeof n!="symbol"?n+"":n,e);var I={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Ue={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",g=Reflect.apply,xt=Array.prototype.pop,Rt=Array.prototype.push;var ke=Array.prototype,He=BigInt.prototype.toString,v=Map,ht=Map.prototype.delete,_t=Map.prototype.get,St=Map.prototype.keys,At=Map.prototype.set;var R=Object.getOwnPropertyDescriptor,bt=Object.getPrototypeOf,ze=Object.hasOwn,Ve=Object.prototype,Tt=Set,Ct=decodeURIComponent,_=URL,je=Number.isFinite,Fe=Number.isInteger,L=RegExp.prototype.exec,It=R(RegExp.prototype,"global").get,Nt=R(RegExp.prototype,"unicode").get,Ot=String.prototype.charCodeAt,Dt=String.prototype.includes,$t=String.prototype.indexOf,V=String.prototype.slice,B=String.prototype.startsWith,W=String.prototype.toLowerCase,wt=Set.prototype.add,Ge=Set.prototype.delete,Lt=Set.prototype.has,Pt=bt(new v().keys()).next,Ut=R(Map.prototype,"size").get,ve=R(_.prototype,"host").get,Be=R(_.prototype,"origin").get,Mt=R(_.prototype,"password").get,We=R(_.prototype,"pathname").get,Ye=R(_.prototype,"protocol").get,kt=R(_.prototype,"username").get,Ht=/[^a-z0-9]/g,zt=/([a-z0-9])([A-Z])/g,Vt=/([A-Z])([A-Z][a-z])/g,jt=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function x(t,n,e){let r=g(It,n,[]),o=g(Nt,n,[]),s=0,i=!1,a="";n.lastIndex=0;try{for(;;){let u=g(L,n,[t]);if(u===null)break;let c=u[0],l=u.index;if(a+=h(t,s,l),a+=typeof e=="string"?e:e(u),s=l+c.length,i=!0,!r)break;c.length===0&&(n.lastIndex=Ft(t,l,o))}}finally{n.lastIndex=0}return i?a+h(t,s):t}function P(t){let n=g(W,t,[]);return x(n,Ht,"")}function S(t,n){return g(Ot,t,[n])}function Ft(t,n,e){let r=n+1;if(!e||r>=t.length)return r;let o=S(t,n);if(o<55296||o>56319)return r;let s=S(t,r);return s>=56320&&s<=57343?n+2:r}function h(t,n,e){return e===void 0?g(V,t,[n]):g(V,t,[n,e])}function Gt(t){let n=[],e=0;for(let r=0;r<=t.length;r++){let o=r===t.length?-1:S(t,r);o>=97&&o<=122||o>=48&&o<=57||(r>e&&(n[n.length]=h(t,e,r)),e=r+1)}return n}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,Bt=128,C=new v;var Wt=256;function Yt(t){let n=t.length<=Bt;if(n){let o=g(_t,C,[t]);if(o!==void 0)return o}let e=P(t),r=e==="auth";for(let o=0;!r&&o=vt){let s=g(St,C,[]),i=g(Pt,s,[]).value;i!==void 0&&g(ht,C,[i])}g(At,C,[t,r])}return r}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Y=new Tt;for(let t=0;t=65&&n<=90||n>=97&&n<=122}function K(t){return Zt(t)||t==="_"||t==="$"}function Qt(t){if(!t)return!1;let n=S(t,0);return K(t)||n>=48&&n<=57||t==="."||t==="-"}function X(t,n){let e=n,r=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!K(t[e]))return!1;for(e++;Qt(t[e]);)e++;if(r){if(t[e]!==r)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function J(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||qt(t)}function q(t,n){let e=n;for(;e=t.length||X(t,e)}function te(t,n){let e=n,r=!0;if(g(B,t,[p,n])){let l=n+p.length;if(F(t,l))return{end:l,replacement:p};e=l,r=!1}let o=r&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",s=!1,i=()=>o?`${o}${p}${s?o:""}`:p,a=[],u="",c=-1;for(let l=e;l0&&(f==="}"||f==="]")){if(a[a.length-1]!==f)return{end:t.length,replacement:i()};if(g(xt,a,[]),l++,a.length===0&&F(t,l))return{end:l,replacement:i()};continue}if(a.length>0||!J(f)){l++;continue}let T=l;if(l=q(t,l),l>=t.length||X(t,l))return{end:T,replacement:i()}}return{end:t.length,replacement:i()}}function G(t,n,e,r){let o=0,s="";for(let i=g(L,n,[t]);i;i=g(L,n,[t])){let a=i[e];if(!ee(a))continue;let u=n.lastIndex,c=r===void 0?void 0:i[r],l=u+p.length;if((c==="?"||c==="&"||c===";")&&g(B,t,[p,u])&&t[l]==="#")continue;let f=te(t,u);s+=h(t,o,i.index),s+=i[0],s+=f.replacement,o=f.end,n.lastIndex=f.end}return o===0?t:s+h(t,o)}function ee(t){if(t.length>Wt)return!0;let n=x(t,Vt,s=>`${s[1]} ${s[2]}`),e=x(n,zt,s=>`${s[1]} ${s[2]}`),r=g(W,e,[]),o=Gt(r);for(let s=0;s{let r=e[1],o=e[2],s=g($t,o,[":"]);if(s===-1)return`${r}${p}@`;let i=h(o,0,s);return`${r}${i}:${p}@`});return n=x(n,Xt,e=>{let r=e[1],o=e[2],s=e[3];return ne(r,o,s)?e[0]:`${r}${o}:${p}@`}),n=x(n,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,e=>{let r=e[1],o=e[2],s=re(o);return g(Lt,Y,[P(s)])||Yt(s)?`${r}${o}=${p}`:e[0]}),n=x(n,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,e=>`${e[1]}${p}`),n=x(n,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,e=>`${e[1]}${e[2]}${p}`),n=x(n,jt,p),n=G(n,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),n=G(n,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),n}var oe=2048;var Ze=64*1024,se=256,ie="https://veryfront.com/docs/code/guides/errors#",Z="...[truncated]",M="unknown-error";function Q(t,n){if(t.length<=n)return t;let e=Math.max(0,n-Z.length);return`${ae(t,e)}${Z}`}function ae(t,n){let e=t.slice(0,n),r=e.charCodeAt(e.length-1);return r>=55296&&r<=56319&&(e=e.slice(0,-1)),e}function ce(t){let n="";for(let e=0;e=55296&&r<=56319){let o=t.charCodeAt(e+1);o>=56320&&o<=57343?(n+=t.slice(e,e+2),e++):n+="\\uFFFD";continue}n+=r>=56320&&r<=57343?"\\uFFFD":t.charAt(e)}return n}function A(t){return typeof t!="string"?p:Q(U(t),oe)}function ue(t){let n=typeof t=="string"?U(t):M,e=Q(n||M,se),r=ce(e);return r==="."||r===".."?M:r}function O(t){let n=encodeURIComponent(ue(t));return`${ie}${n}`}var nt=Reflect.apply,le=Object.freeze,de=Object.getOwnPropertyDescriptors,tt=Number.isFinite,rt=new WeakSet,ge=WeakSet.prototype.add,fe=WeakSet.prototype.has,pe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function E(t){let n={...t},e={...n,create(r){let o=r?.message,s=r?.detail,i=r?.cause,a=r?.instance,u=r?.context,c=r?.status??n.status;return new k(o||s||n.title,{slug:n.slug,category:n.category,status:c,title:n.title,suggestion:n.suggestion,exitCode:n.exitCode,detail:s,cause:i,instance:a,context:u})}};return le(e)}var k=class extends Error{constructor(e,r){super(e);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");nt(ge,rt,[this]),this.name="VeryfrontError",this.slug=r.slug,this.category=r.category,this.status=r.status,this.title=r.title,this.suggestion=r.suggestion,this.exitCode=r.exitCode,this.detail=r.detail,this.cause=r.cause,this.instance=r.instance,this.context=r.context}toRFC9457(){let e=et(this);return e?{type:O(e.slug),title:A(e.title),status:e.status,detail:e.detail===void 0?void 0:A(e.detail),instance:e.instance===void 0?void 0:A(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:A(e.suggestion),cause:typeof e.cause=="string"?A(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=et(this);return O(e?.slug??"unknown-error")}};function ot(t){return typeof t=="object"&&t!==null&&nt(fe,rt,[t])===!0}function et(t){return ot(t)?me(t):null}function me(t){try{if(!ot(t))return null;let n=de(t),e=mt=>{let w=n[mt];return w&&"value"in w?w.value:void 0},r=e("slug"),o=e("category"),s=e("status"),i=e("title"),a=e("message"),u=e("suggestion"),c=e("exitCode"),l=e("detail"),f=e("cause"),T=e("instance"),pt=e("context"),$=e("stack");return typeof r!="string"||!pe.has(o)||typeof s!="number"||!tt(s)||typeof i!="string"||typeof a!="string"||u!==void 0&&typeof u!="string"||c!==void 0&&(typeof c!="number"||!tt(c))||l!==void 0&&typeof l!="string"||T!==void 0&&typeof T!="string"||$!==void 0&&typeof $!="string"?null:{slug:r,category:o,status:s,title:i,message:a,suggestion:u,exitCode:c,detail:l,cause:f,instance:T,context:pt,stack:$}}catch{return null}}var on=E({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),sn=E({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),an=E({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),cn=E({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),un=E({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ln=E({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),dn=E({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),gn=E({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),fn=E({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),st=E({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),pn=E({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),mn=E({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),En=E({slug:"nested-cwd-scope",category:"GENERAL",status:500,title:"Working directory scope nested inside another",suggestion:"Do the inner work directly in the outer scope\'s callback instead of opening a second one"});var Ee=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function ye(){return Ee.map(({source:t,flags:n,name:e})=>({pattern:new RegExp(t,n),name:e}))}function xe(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function it(t,n={}){let{allowInlineScripts:e=!1,strict:r=!1,warn:o=!0}=n;for(let{pattern:s,name:i}of ye())if(!(e&&i==="inline script")&&(s.lastIndex=0,!!s.test(t)&&(o&&console.warn(`[Security] Suspicious ${i} detected in server HTML`),r||!xe())))throw st.create({detail:`Potentially unsafe HTML: ${i} detected`});return t}var b=class{constructor(n,e){m(this,"prefix",n);m(this,"level",e)}log(n,e,r,...o){this.level>n||e?.(r,...o)}debug(n,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${n}`,...e)}info(n,...e){this.log(1,console.log,`[${this.prefix}] ${n}`,...e)}warn(n,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${n}`,...e)}error(n,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${n}`,...e)}};function Re(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var D=Re(),y=new b("RSC",D),hn=new b("PREFETCH",D),_n=new b("HYDRATE",D),Sn=new b("VERYFRONT",D);var Tn=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var he=5e3,_e=1e4,Nn=16*1024*1024,Se=5e3;var Ae=100;var be=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),On=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Dn=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:he,api:3e4,ssr:_e,hmr:3e4,sandbox:Se}),cache:Object.freeze({jit:Object.freeze({maxSize:Ae,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:be})});var d="/_veryfront",H={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},ct={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Te={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},wn=Te.CACHE;var Ln={HMR_RUNTIME:ct.HMR_RUNTIME,ERROR_OVERLAY:ct.ERROR_OVERLAY};var Ce=H.RSC,Ie=H.FS;var ut="rsc-root",z="x-veryfront-dependency-pins";var Hn=Array.prototype.at,zn=Array.prototype.filter,Vn=Array.prototype.join,jn=Array.prototype.map,Fn=Array.prototype.pop,Gn=Array.prototype.push,vn=Array.prototype.sort;var sr=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Oe="veryfront-hydration-data";function lt(t){try{let n=[...t.querySelectorAll(`[id="${Oe}"]`)];if(n.length!==1)return null;let e=t.body;if(!e)return null;let r=n[0];return e.firstElementChild!==r&&r.parentElement!==e||r.tagName?.toLowerCase()!=="script"||r.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:r}catch{return null}}function dt(t,n){if(!n?.startsWith("on:"))return!1;try{let e=lt(t);if(!e)return!1;let r=JSON.parse(e.textContent||"{}");return r.dependencyPinningCacheKey=n,e.textContent=JSON.stringify(r),!0}catch(e){return y.debug("hydration dependency snapshot seed failed",e),!1}}function ft(t,n){let e=n==="root"?ut:`rsc-slot-${n}`,r=t.getElementById(e);if(r)return r;let o=t.createElement("div");return o.id=e,t.body.appendChild(o),o}function De(t,n){if(n.type!=="slot")return;let e=ft(t,n.id);e.innerHTML=it(String(n.html??""))}function gt(t,n){let e=n.split(`\n`),r=e.pop()??"";for(let o of e){let s=o.trim();if(!s)continue;let i;try{i=JSON.parse(s)}catch(u){y.debug("[client-dom] malformed NDJSON line",{line:s,error:u instanceof Error?u.message:String(u)});continue}if(!i||typeof i!="object")continue;let a=i;if(a.type==="slot"){De(t,a);try{Le(t,a.id||"root")}catch(u){y.debug("[client-dom] hydration optional failed",u)}}}return r}function $e(t){return new Promise((n,e)=>{let r=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){r();return}t.addEventListener("abort",r,{once:!0})})}async function Tr(t,n=document,e){let r="body"in t?t:null,o=r?.body??t;if(!o)return;r&&dt(n,r.headers.get(z));let s=o.getReader(),i=new TextDecoder,a="",u=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=e?await Promise.race([c,$e(e)]):await c;if(l){u=!0;break}a+=i.decode(f,{stream:!0}),a=gt(n,a)}a&>(n,`${a}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||y.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){u||y.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){y.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){y.debug("[client-dom] stream.cancel failed",c)}if(typeof r?.body?.cancel=="function")try{await r.body.cancel()}catch(c){y.debug("[client-dom] response.body.cancel failed",c)}}}function we(t,n){let e=ft(t,n),r=[],o=s=>{let i=s;i.dataset?.clientRef&&r.push(i);for(let a of s.children)o(a)};return o(e),r}function Le(t,n){let e=we(t,n);for(let r of e){let o=r.dataset?.clientRef;o&&(r.dataset.hydrated="true",y.debug("[client-dom] marked for hydration",o))}}export{Tr as consumeNdjsonStream,ft as getContainer};\n'; diff --git a/src/transforms/esm/http-bundler.ts b/src/transforms/esm/http-bundler.ts index ba8a2d5c09..028258404d 100644 --- a/src/transforms/esm/http-bundler.ts +++ b/src/transforms/esm/http-bundler.ts @@ -8,6 +8,7 @@ import { rendererLogger as logger } from "#veryfront/utils"; import type { Plugin } from "veryfront/extensions/bundler"; import { replaceSpecifiers } from "./lexer.ts"; +import { describeHtmlModuleResponse } from "./http-cache-helpers.ts"; import { DEFAULT_REACT_VERSION, getReactUrls } from "./react-cdn.ts"; import { type EnvironmentConfig, @@ -174,12 +175,11 @@ export function createHTTPPlugin(options: HttpPluginOptions = {}): Plugin { if (isHtmlContent) { logger.warn(`${LOG_PREFIX} Received HTML instead of JS for ${safeUrl}`); - return { - errors: [{ - text: - `Received HTML instead of JavaScript from ${safeUrl}. Package may not exist or failed to build on esm.sh.`, - }], - }; + // Blaming esm.sh for every host that answers HTML sends the reader + // to a registry that was never involved. The shared helper reports + // the real cause, including an unresolved "@/" alias that fell + // through to the site origin (VERYFRONT-SERVER-G). + return { errors: [{ text: describeHtmlModuleResponse(safeUrl) }] }; } return { contents, loader: "js" }; diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index 5231f44277..f179fe9833 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -5,6 +5,7 @@ import type { CacheHttpModuleFn } from "./specifier-resolver.ts"; import { buildReplacements, rewriteModuleImports } from "./specifier-resolver.ts"; import type { CacheOptions } from "./http-cache-helpers.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; +import { aliasStrategy } from "#veryfront/transforms/import-rewriter/strategies/alias-strategy.ts"; describe("transforms/esm/specifier-resolver", () => { const defaultOptions: CacheOptions = { @@ -281,6 +282,71 @@ describe("transforms/esm/specifier-resolver", () => { ); }); + // The URL shape is not chosen here. `AliasStrategy` is the framework's + // canonical "@/" rewriter and emits this exact shape for both its `ssr` and + // its browser target, so this resolver — a late fallback for an alias that + // escaped every earlier rewrite — must agree with it byte for byte or one + // specifier resolves to two different module URLs. + it("matches AliasStrategy for every extension class", async () => { + const paths = [ + "components/ResponsiveImage", + "components/Card.tsx", + "components/Card.ts", + "components/Card.jsx", + "post.mdx", + "post.md", + "lib/data.json", + "components/Icon.svg", + "styles/globals.css", + "vendor/bundle.mjs", + "vendor/bundle.cjs", + "vendor/bundle.js", + ]; + + const code = paths.map((path, index) => `import m${index} from "@/${path}";`).join("\n"); + const result = await buildReplacements(code, undefined, defaultOptions, async () => { + throw new Error("an @/ alias must never be fetched"); + }); + + for (const path of paths) { + const expected = aliasStrategy.rewrite( + { specifier: `@/${path}` } as Parameters[0], + { target: "ssr" } as Parameters[1], + ).specifier; + + assertEquals(result.replacements.get(`@/${path}`), expected, `@/${path}`); + } + }); + + // `.json` and `.md` reach the module server as `..js`, which it + // strips before source lookup (`module-server.ts` `filePathWithoutExt`), so + // the doubled extension resolves to the real file. `.svg` and `.css` are not + // servable through `/_vf_modules/` with or without the `.js`, so appending + // it costs nothing. + it("appends .js to non-JS source extensions and passes JS-like ones through", async () => { + const expectations: ReadonlyArray = [ + ["@/lib/data.json", "/_vf_modules/lib/data.json.js"], + ["@/post.md", "/_vf_modules/post.md.js"], + ["@/post.mdx", "/_vf_modules/post.js"], + ["@/components/Icon.svg", "/_vf_modules/components/Icon.svg.js"], + ["@/components/Button", "/_vf_modules/components/Button.js"], + ["@/styles/globals.css", "/_vf_modules/styles/globals.css"], + ["@/vendor/bundle.mjs", "/_vf_modules/vendor/bundle.mjs"], + ["@/vendor/bundle.cjs", "/_vf_modules/vendor/bundle.cjs"], + ]; + + const code = expectations + .map(([specifier], index) => `import m${index} from "${specifier}";`) + .join("\n"); + const result = await buildReplacements(code, undefined, defaultOptions, async () => { + throw new Error("an @/ alias must never be fetched"); + }); + + for (const [specifier, expected] of expectations) { + assertEquals(result.replacements.get(specifier), expected, specifier); + } + }); + it("never resolves an @/ alias against the page origin via an import-map prefix", async () => { // A project import map commonly maps "@/" to "./". Resolving that mapped // relative path against the page origin fetches the tenant's own public diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 96195eca2e..c606c0aedf 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -15,6 +15,7 @@ import { normalizeExtension, } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { parseBarePackageSpecifier } from "../shared/package-specifier.ts"; +import { splitSpecifierSuffix } from "../shared/specifier-suffix.ts"; import { isServerOnlyPackage } from "../shared/server-only-packages.ts"; import { parseImports, replaceSpecifiers } from "./lexer.ts"; @@ -30,14 +31,9 @@ import { } from "./http-cache-helpers.ts"; const ReflectApply = Reflect.apply; -const StringIndexOf = String.prototype.indexOf; const StringSlice = String.prototype.slice; const StringStartsWith = String.prototype.startsWith; -function stringIndexOf(value: string, search: string): number { - return ReflectApply(StringIndexOf, value, [search]) as number; -} - function stringSlice(value: string, start: number, end?: number): string { return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; } @@ -88,22 +84,6 @@ function isLocalMappedSpecifier(specifier: string): boolean { stringStartsWith(specifier, "file://"); } -function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { - const queryStart = stringIndexOf(specifier, "?"); - const hashStart = stringIndexOf(specifier, "#"); - const suffixStart = queryStart === -1 - ? hashStart - : hashStart === -1 - ? queryStart - : Math.min(queryStart, hashStart); - - if (suffixStart === -1) return { path: specifier, suffix: "" }; - return { - path: stringSlice(specifier, 0, suffixStart), - suffix: stringSlice(specifier, suffixStart), - }; -} - /** * Resolve a single import specifier to a local cached path. * @@ -123,13 +103,19 @@ async function resolveSpecifier( ); if (isExternalScheme(specifier)) return null; - // The "@/" project alias always denotes the project's own module transport: - // the framework's default import map pins "@/" to "/_vf_modules/". An alias - // that escaped an upstream rewrite must land there too. Treating it as a - // bare specifier would route it to esm.sh as a bogus scoped package, and a - // project import map that maps "@/" to a relative prefix would resolve it - // against the page's public origin, which answers with HTML + // The "@/" project alias always denotes the project's own module transport. + // An alias that escaped every upstream rewrite must land there too: treating + // it as a bare specifier would route it to esm.sh as a bogus scoped package, + // and a project import map that maps "@/" to a relative prefix would resolve + // it against the page's public origin, which answers with HTML // (VERYFRONT-SERVER-G). + // + // The URL shape is not invented here. It reproduces `AliasStrategy.rewrite` + // (transforms/import-rewriter/strategies/alias-strategy.ts), the framework's + // canonical "@/" rewriter, which emits this same shape for both its `ssr` and + // its browser target: `normalizeExtension`, then append `.js` unless the + // result already ends in a JS-like or CSS extension. A different shape here + // would resolve one specifier to two different module URLs. if (stringStartsWith(specifier, "@/")) { const { path: pathOnly, suffix } = splitSpecifierSuffix(stringSlice(specifier, 2)); const normalizedPath = normalizeExtension(pathOnly); diff --git a/src/transforms/import-rewriter/url-builder.test.ts b/src/transforms/import-rewriter/url-builder.test.ts index 0e36c93d01..69061067c8 100644 --- a/src/transforms/import-rewriter/url-builder.test.ts +++ b/src/transforms/import-rewriter/url-builder.test.ts @@ -374,32 +374,6 @@ describe("transforms/import-rewriter/url-builder", () => { "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", ); }); - - it("should use captured test for escaped alias extension checks", () => { - const originalTest = Object.getOwnPropertyDescriptor(RegExp.prototype, "test")!; - let poisonCalls = 0; - try { - Object.defineProperty(RegExp.prototype, "test", { - ...originalTest, - value() { - poisonCalls += 1; - throw new Error("poisoned RegExp.prototype.test"); - }, - }); - - assertEquals( - buildCrossProjectUrl("proj", "1.0.0", "components/Button.tsx"), - "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", - ); - assertEquals( - buildCrossProjectUrl("proj", "1.0.0", "components/Button"), - "/_vf_modules/_cross/proj@1.0.0/@/components/Button.tsx", - ); - assertEquals(poisonCalls, 0); - } finally { - Object.defineProperty(RegExp.prototype, "test", originalTest); - } - }); }); describe("buildVeryfrontModuleUrl", () => { @@ -443,52 +417,6 @@ describe("transforms/import-rewriter/url-builder", () => { assertEquals(normalizeExtension("file.tsx", { removeExtension: true }), "file"); }); - it("should use captured replace after String.replace poisoning", () => { - const originalReplace = Object.getOwnPropertyDescriptor(String.prototype, "replace")!; - let poisonCalls = 0; - try { - Object.defineProperty(String.prototype, "replace", { - ...originalReplace, - value() { - poisonCalls += 1; - throw new Error("poisoned String.prototype.replace"); - }, - }); - - assertEquals(normalizeExtension("components/Card.tsx"), "components/Card.js"); - assertEquals( - normalizeExtension("components/Card.tsx", { removeExtension: true }), - "components/Card", - ); - assertEquals(poisonCalls, 0); - } finally { - Object.defineProperty(String.prototype, "replace", originalReplace); - } - }); - - it("should use captured replace after RegExp Symbol.replace poisoning", () => { - const originalReplace = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.replace)!; - let poisonCalls = 0; - try { - Object.defineProperty(RegExp.prototype, Symbol.replace, { - ...originalReplace, - value() { - poisonCalls += 1; - throw new Error("poisoned RegExp.prototype[Symbol.replace]"); - }, - }); - - assertEquals(normalizeExtension("components/Card.tsx"), "components/Card.js"); - assertEquals( - normalizeExtension("components/Card.tsx", { removeExtension: true }), - "components/Card", - ); - assertEquals(poisonCalls, 0); - } finally { - Object.defineProperty(RegExp.prototype, Symbol.replace, originalReplace); - } - }); - it("should keep .js unchanged", () => { assertEquals(normalizeExtension("file.js"), "file.js"); }); diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index fdb7130943..39ea179775 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -33,22 +33,6 @@ type EsmShOptions = { }; const ObjectEntries = Object.entries; -const ReflectApply = Reflect.apply; -const RegExpTest = RegExp.prototype.test; -const RegExpSymbolReplace = RegExp.prototype[Symbol.replace]; -const MODULE_EXTENSION_PATTERN = /\.(js|mjs|jsx|ts|tsx|mdx)$/; - -function regexTest(search: RegExp, value: string): boolean { - return ReflectApply(RegExpTest, search, [value]) as boolean; -} - -function regexReplace( - value: string, - search: RegExp, - replacement: string, -): string { - return ReflectApply(RegExpSymbolReplace, search, [value, replacement]) as string; -} function buildEsmShParams(options?: EsmShOptions): string[] { const params: string[] = []; @@ -451,7 +435,7 @@ export function buildCrossProjectUrl( version: string | null, path: string, ): string { - const modulePath = regexTest(MODULE_EXTENSION_PATTERN, path) ? path : `${path}.tsx`; + const modulePath = /\.(js|mjs|jsx|ts|tsx|mdx)$/.test(path) ? path : `${path}.tsx`; const projectRef = version && version !== "latest" ? `${projectSlug}@${version}` : projectSlug; return `/_vf_modules/_cross/${projectRef}/@/${modulePath}`; } @@ -468,8 +452,8 @@ export function buildVeryfrontModuleUrl(path: string): string { * Normalize file extension for JavaScript output. */ export function normalizeExtension(path: string, options?: { removeExtension?: boolean }): string { - if (options?.removeExtension) return regexReplace(path, /\.(tsx?|jsx|mdx)$/, ""); - return regexReplace(path, /\.(tsx?|jsx|mdx)$/, ".js"); + if (options?.removeExtension) return path.replace(/\.(tsx?|jsx|mdx)$/, ""); + return path.replace(/\.(tsx?|jsx|mdx)$/, ".js"); } /** diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts index 1992578082..ed46ef7f06 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts @@ -187,6 +187,41 @@ describe("module-fetcher/http-fetcher", () => { ); }); + // A single-quoted specifier may legally contain a double quote, and a cache + // path may contain a backslash. Interpolating either into a hand-written + // double-quoted literal emits a module that fails to parse, which takes down + // every other import in the file, not just the offending one. + it("escapes quotes and backslashes in emitted HTTP fallback import literals", async () => { + const result = await fetchModuleViaHTTP( + "_vf_modules/pages/index.js", + { env: { get: () => undefined } } as unknown as RuntimeAdapter, + () => Promise.resolve(`/cache/we"ird\\path.mjs`), + { debug: () => {}, warn: () => {} } as unknown as Logger, + "docs", + true, + undefined, + { + fetchFn: (() => + Promise.resolve( + new Response([ + `import a from '/_vf_modules/a.js?label="x"';`, + `import '/_vf_modules/b.js?label="y"';`, + `export const lazy = () => import('./c.js?label="z"');`, + ].join("\n")), + )) as typeof fetch, + }, + ); + + assertEquals( + result, + [ + `import a from "file:///cache/we\\"ird\\\\path.mjs?label=\\"x\\"";`, + `import "file:///cache/we\\"ird\\\\path.mjs?label=\\"y\\"";`, + `export const lazy = () => import("file:///cache/we\\"ird\\\\path.mjs?label=\\"z\\"");`, + ].join("\n"), + ); + }); + it("uses the request origin for pinned local module fetches", async () => { const logger = { debug: () => {}, warn: () => {} } as unknown as Logger; const adapter = { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index 62b300f0a6..d202eed8de 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -13,7 +13,7 @@ import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { LOG_PREFIX_MDX_LOADER } from "../constants.ts"; import { rewriteVeryfrontImports } from "./import-rewriter.ts"; -import { findNestedImports } from "./nested-imports.ts"; +import { findNestedImports, toImportStringLiteral } from "./nested-imports.ts"; import { replaceSourceSpans, type SourceSpanReplacement } from "../utils/source-spans.ts"; import { HTTP_FETCH_TIMEOUT_MS } from "#veryfront/utils/constants/http.ts"; import { readHttpModuleText } from "../../../shared/http-module-response.ts"; @@ -285,15 +285,19 @@ export async function fetchModuleViaHTTP( const { original, start, end, suffix, isDynamic, isSideEffect, nestedFilePath } of results ) { if (nestedFilePath) { + // The suffix and the cache path are author- and filesystem-controlled. + // Interpolating either into a hand-written double-quoted literal emits + // a module that fails to parse whenever one contains `"` or `\`. + const importTarget = toImportStringLiteral(`file://${nestedFilePath}${suffix ?? ""}`); replacements.push({ start, end, expected: original, replacement: isDynamic - ? `"file://${nestedFilePath}${suffix ?? ""}"` + ? importTarget : isSideEffect - ? `import "file://${nestedFilePath}${suffix ?? ""}"` - : `from "file://${nestedFilePath}${suffix ?? ""}"`, + ? `import ${importTarget}` + : `from ${importTarget}`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 7b2de9a2e0..7329656b70 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -15,6 +15,7 @@ import { type SourceSpanReplacement, } from "../utils/source-spans.ts"; import { buildMissingModuleError } from "../missing-module.ts"; +import { splitSpecifierSuffix } from "../../../shared/specifier-suffix.ts"; import type { Logger } from "#veryfront/utils"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { Semaphore } from "#veryfront/modules/react-loader/ssr-module-loader/concurrency/semaphore.ts"; @@ -28,18 +29,6 @@ function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/.+)$/)?.[1] ?? null; } -function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { - const queryStart = specifier.indexOf("?"); - const hashStart = specifier.indexOf("#"); - const suffixStart = - [queryStart, hashStart].filter((index) => index >= 0).sort((a, b) => a - b)[0]; - if (suffixStart === undefined) return { path: specifier, suffix: "" }; - return { - path: specifier.slice(0, suffixStart), - suffix: specifier.slice(suffixStart), - }; -} - type NestedImportSpan = { original: string; path: string; @@ -50,7 +39,15 @@ type NestedImportSpan = { isSideEffect?: boolean; }; -function toImportStringLiteral(url: string): string { +/** + * Serialize a resolved module URL as a JavaScript string literal. + * + * A preserved suffix is author-controlled text (`?label="x"`, a backslash in a + * cache path). Wrapping it in quotes by hand emits a module that fails to + * parse, taking every other import in the file down with it, so every emitted + * specifier must go through this. + */ +export function toImportStringLiteral(url: string): string { return JSON.stringify(url); } diff --git a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts index da7cd8227f..3ae93096d8 100644 --- a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts @@ -74,7 +74,7 @@ describe("alias import transforms", () => { assertEquals(fs.files.has("components/Commented.js"), false); }); - it("preserves query and hash suffixes while resolving alias paths", async () => { + it("resolves alias paths that carry query and hash suffixes", async () => { const fs = new MemoryFs({ "components/Foo.js": `export default function Foo() { return null; }`, "components/Bar.js": `export default function Bar() { return null; }`, @@ -92,10 +92,34 @@ describe("alias import transforms", () => { ); assertStringIncludes(projectAlias, `import Foo from "file:///cache/alias-`); - assertStringIncludes(projectAlias, `?raw#hero";`); assertStringIncludes(moduleAlias, `import Bar from "file:///cache/vfmod-`); - assertStringIncludes(moduleAlias, `#client";`); assertEquals(fs.files.has("components/Foo.js"), true); assertEquals(fs.files.has("components/Bar.js"), true); }); + + // The suffix is meaningless on a materialized `alias-.mjs` — `?raw` is + // not honoured and cache busting is moot once the content is inlined. Carrying + // it onto the emitted URL would give one source file two module records, and + // therefore two copies of its module-level state. + it("collapses suffixed and unsuffixed aliases of one file onto one module URL", async () => { + const fs = new MemoryFs({ + "components/Card.js": `export default function Card() { return null; }`, + }); + + const result = await transformProjectAliasImports( + `import Card from "@/components/Card.js";\n` + + `import CardRaw from "@/components/Card.js?raw";\n` + + `import CardFrag from "@/components/Card.js#hero";\n`, + fs, + "/cache", + ); + + const urls = [...result.matchAll(/"(file:\/\/\/cache\/alias-[^"]+)"/g)].map((match) => + match[1] + ); + assertEquals(urls.length, 3); + assertEquals(new Set(urls).size, 1); + assertEquals(result.includes("?raw"), false); + assertEquals(result.includes("#hero"), false); + }); }); diff --git a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts index ce3afaa13d..644fab6f35 100644 --- a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts +++ b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts @@ -15,6 +15,7 @@ import type { FSAdapter } from "../types.ts"; import { hashString } from "../utils/hash.ts"; import { resolveFileWithExtension } from "../resolution/file-finder.ts"; import { parseImports, replaceSpecifiers } from "../../../esm/lexer.ts"; +import { splitSpecifierSuffix } from "../../../shared/specifier-suffix.ts"; type ImportType = "project-alias" | "vf-modules"; @@ -25,18 +26,6 @@ interface AliasImport { type: ImportType; } -function splitSpecifierSuffix(specifier: string): { path: string; suffix: string } { - const queryStart = specifier.indexOf("?"); - const hashStart = specifier.indexOf("#"); - const suffixStart = - [queryStart, hashStart].filter((index) => index >= 0).sort((a, b) => a - b)[0]; - if (suffixStart === undefined) return { path: specifier, suffix: "" }; - return { - path: specifier.slice(0, suffixStart), - suffix: specifier.slice(suffixStart), - }; -} - async function findAliasImports(code: string): Promise { const imports: AliasImport[] = []; const parsedImports = await parseImports(code); @@ -149,7 +138,14 @@ async function transformImport( logger.debug(`${LOG_PREFIX_MDX_LOADER} Transformed ${getPathDesc(imp)} -> ${transformedPath}`); - return { specifier: imp.specifier, replacement: `file://${transformedPath}${imp.suffix}` }; + // The suffix is split off the specifier so the path resolves, but it is + // deliberately not carried onto the emitted URL. `alias-.mjs` is a + // fully materialized artifact: `?raw` is already not honoured, and a cache + // buster is meaningless once the content is inlined. Keeping the suffix + // would only make `@/Card.tsx` and `@/Card.tsx?raw` — which hash to one + // file — resolve to two module records, giving the same module two + // instances and two copies of its module-level state. + return { specifier: imp.specifier, replacement: `file://${transformedPath}` }; } catch (error) { logger.warn(`${LOG_PREFIX_MDX_LOADER} Failed to transform ${getPathDesc(imp)}`, error); return null; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 2f31e4ecad..eccb5ab28a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -122,6 +122,27 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ["./value-0.js", "./value-1.js", "./value-2.js"], ); }); + + // A comment is legal after the keyword and after `from`, on the same terms + // as whitespace. + it("finds specifiers behind comments after the keyword and after from", () => { + assertEquals( + findStaticImportFromSpans( + 'import /* a */ value from /* b */ "./value.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./value.js"], + ); + assertEquals( + findStaticImportFromSpans( + 'export // a\n{ value } from // b\n"./value.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./value.js"], + ); + }); }); describe("findDynamicImportSpans", () => { @@ -503,5 +524,28 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(span?.original, "import `./value.js`"); assertEquals(span?.path, "./value.js"); }); + + // Comments are legal wherever whitespace is, so a bundler hint can sit + // between the keyword and the specifier. Missing the span leaves the + // dependency neither materialised nor reported as unresolved: the module is + // cached with a live `/_vf_modules/…` specifier and fails at execute time. + it("finds a side-effect specifier behind a block comment", () => { + const [span] = findStaticSideEffectImportSpans( + 'import /* @vite-ignore */ "./value.js";', + matchRelative, + UNBOUNDED, + ); + assertEquals(span?.original, 'import /* @vite-ignore */ "./value.js"'); + assertEquals(span?.path, "./value.js"); + }); + + it("finds a side-effect specifier behind a line comment", () => { + const [span] = findStaticSideEffectImportSpans( + 'import // @vite-ignore\n"./value.js";', + matchRelative, + UNBOUNDED, + ); + assertEquals(span?.path, "./value.js"); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index c8afa87c17..2d732894de 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -559,7 +559,7 @@ function findFromSpan( !isIdentifierChar(source[cursor - 1]) && !isIdentifierChar(source[cursor + 4]) ) { - const quoteIndex = skipWhitespace(source, cursor + 4); + const quoteIndex = skipWhitespaceAndComments(source, cursor + 4); const quoted = readQuotedSpecifier(source, quoteIndex); if (!quoted) { cursor++; @@ -625,7 +625,7 @@ export function findStaticImportFromSpans( } const keywordLength = isImport ? "import".length : "export".length; - const afterKeyword = skipWhitespace(source, cursor + keywordLength); + const afterKeyword = skipWhitespaceAndComments(source, cursor + keywordLength); if (isImport && source[afterKeyword] === "(") { cursor = afterKeyword + 1; continue; @@ -779,11 +779,17 @@ function scanDynamicImportRange( * * The returned span covers the quoted specifier itself (quotes included), not * the surrounding `import(...)`, so a replacement is a bare quoted string. - * Dynamic imports whose argument is not a string literal are skipped, since - * their target is only known at runtime. That includes an argument the literal + * + * A literal here is a single- or double-quoted string, or a backtick template + * with no `${}` substitution — the three forms whose target is fully known at + * scan time. An interpolated template and any other expression are skipped, + * since their target is only known at runtime. So is an argument the literal * merely starts: rewriting the `"./foo"` in `import("./foo" + suffix)` would * build a path out of a resolved prefix and an unresolved tail. * + * The scan also runs inside template substitutions, so a dynamic import nested + * in a `${…}` expression is found. + * * `maxMatches` bounds the scan on the same terms as * {@link findStaticImportFromSpans}. */ @@ -827,7 +833,7 @@ export function findStaticSideEffectImportSpans( continue; } - const literalIndex = skipWhitespace(source, cursor + "import".length); + const literalIndex = skipWhitespaceAndComments(source, cursor + "import".length); const literal = readLiteralSpecifier(source, literalIndex); if (!literal) { cursor = nextStatementCursor(source, literalIndex); diff --git a/src/transforms/shared/specifier-suffix.test.ts b/src/transforms/shared/specifier-suffix.test.ts new file mode 100644 index 0000000000..b097e68873 --- /dev/null +++ b/src/transforms/shared/specifier-suffix.test.ts @@ -0,0 +1,34 @@ +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { splitSpecifierSuffix } from "./specifier-suffix.ts"; + +describe("transforms/shared/specifier-suffix", () => { + const cases: ReadonlyArray = [ + ["@/components/Card.tsx", "@/components/Card.tsx", ""], + ["@/components/Card.tsx?raw", "@/components/Card.tsx", "?raw"], + ["@/components/Card.tsx#hero", "@/components/Card.tsx", "#hero"], + ["@/components/Card.tsx?v=1#hero", "@/components/Card.tsx", "?v=1#hero"], + // Hash before query: the `?` belongs to the fragment, so the cut is at `#`. + ["@/a#b?c", "@/a", "#b?c"], + ["/_vf_modules/lib/data.json?v=2", "/_vf_modules/lib/data.json", "?v=2"], + ["?leading", "", "?leading"], + ["#leading", "", "#leading"], + ["", "", ""], + ]; + + for (const [specifier, path, suffix] of cases) { + it(`splits ${JSON.stringify(specifier)}`, () => { + assertEquals(splitSpecifierSuffix(specifier), { path, suffix }); + }); + } + + // The three former copies split on whichever delimiter came first. Anything + // that reassembles to the input is round-trip safe for every caller, which is + // what lets one definition replace all three. + it("round-trips path + suffix back to the input", () => { + for (const [specifier] of cases) { + const { path, suffix } = splitSpecifierSuffix(specifier); + assertEquals(`${path}${suffix}`, specifier); + } + }); +}); diff --git a/src/transforms/shared/specifier-suffix.ts b/src/transforms/shared/specifier-suffix.ts new file mode 100644 index 0000000000..2c49cd41c4 --- /dev/null +++ b/src/transforms/shared/specifier-suffix.ts @@ -0,0 +1,52 @@ +/** + * Import specifier query/hash suffix splitting. + * + * @module transforms/shared/specifier-suffix + */ + +const ReflectApply = Reflect.apply; +const StringIndexOf = String.prototype.indexOf; +const StringSlice = String.prototype.slice; + +function stringIndexOf(value: string, search: string): number { + return ReflectApply(StringIndexOf, value, [search]) as number; +} + +function stringSlice(value: string, start: number, end?: number): string { + return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; +} + +/** A specifier split into its path and its trailing `?query` / `#hash`. */ +export interface SplitSpecifier { + readonly path: string; + readonly suffix: string; +} + +/** + * Split an import specifier into the path and everything from the first `?` or + * `#` onward, whichever comes first. + * + * The cut is at whichever delimiter appears first, not at `?` by preference: + * `@/a#b?c` is a hash whose fragment happens to contain a `?`, so the suffix is + * `#b?c` and the path is `@/a`. Splitting on `?` there would leave `#b` stuck + * on the path and no file would resolve. + * + * This is the single definition of that rule. It previously existed as three + * separate copies across the alias, nested-import and HTTP-cache resolvers, + * which agreed only by coincidence. + */ +export function splitSpecifierSuffix(specifier: string): SplitSpecifier { + const queryStart = stringIndexOf(specifier, "?"); + const hashStart = stringIndexOf(specifier, "#"); + const suffixStart = queryStart === -1 + ? hashStart + : hashStart === -1 + ? queryStart + : Math.min(queryStart, hashStart); + + if (suffixStart === -1) return { path: specifier, suffix: "" }; + return { + path: stringSlice(specifier, 0, suffixStart), + suffix: stringSlice(specifier, suffixStart), + }; +} From b20d72002acf34ff4f13925a9b12986711ac38f6 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 07:46:47 +0200 Subject: [PATCH 040/104] fix(observability): classify the retry seam from resolver evidence, not the error code The previous round tagged every ERR_MODULE_NOT_FOUND at the self-heal retry as tenant source. That code is raised identically for four different causes, three of which are framework-owned: an HTTP-bundle miss (which the outer branch guards against precisely because it has dedicated recovery, and which the inner catch did not re-check), a cycle-break alias the transform leaves as authored and whose resolution the code itself marks not runtime-verified, and an artifact the rebuild failed to persist. All three were being downgraded to warning + tenant-build. Use the evidence the loader already has and was discarding. `resolveModuleDependencies` resolves only `@/` aliases and relative imports, and the transform records every specifier it had to leave as authored. Carry that set across the transform tree the same way `cycleTargets` is carried, and tag tenant only when the resolver actually dropped something. An empty set proves nothing tenant-authored survived unrewritten, so whatever is missing is framework-owned. Two things worth recording: A message-based "is this path ours?" test cannot work here. The runtime reports the resolved path, which for a dropped relative specifier lands inside the build's own temp directory -- so matching on the temp dir rejects exactly the case the predicate exists to identify. A test pins this. `findStaticImportFromSpans` requires a `from` clause, so a bare side-effect import is never resolved and never recorded as dropped. A missing one therefore stays at error level. That is the safe direction, and it is pinned by a test rather than left as a surprise. Also stops dropping the build-failure signal for a framework-owned resolution failure, while still leaving a module that ran and threw at module scope untagged -- that is an application error the project's own error page presents. Replaces the previous guard test, which constructed a bare Error, never entered loadModule, and would have passed with the branch deleted. --- .../orchestrator/module-loader/index.test.ts | 127 +++++++++++++++++- .../orchestrator/module-loader/index.ts | 99 ++++++++++++-- 2 files changed, 205 insertions(+), 21 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 54e3b92462..89199ad044 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -13,6 +13,7 @@ import { basename, dirname, join } from "#veryfront/compat/path/index.ts"; import { runWithCacheDir } from "#veryfront/utils/cache-dir.ts"; import { isMissingModuleError, + isUnresolvedTenantImport, loadModule, type ModuleLoaderConfig, transformModuleWithDeps, @@ -343,6 +344,35 @@ describe("module-loader/loadModule build-failure tagging", () => { // leave `loadModule` untagged, so a tenant typo in an import path was // reported at error level forever. it("tags a missing local static import as a tenant build failure", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./missing";`, + `export default function Page() { return label; }`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + + // `findStaticImportFromSpans` only matches imports with a `from` clause, so a + // bare side-effect import is never resolved and therefore never recorded as + // dropped. Such a typo consequently stays at error level rather than being + // downgraded. That is the safe direction — over-reporting severity, never + // hiding a framework fault — but it is a real gap, so it is pinned here + // rather than left to be discovered as a surprise. + it("leaves a missing bare side-effect import at framework severity", async () => { await withModuleLoaderFixture( { "app/page.tsx": [ @@ -358,20 +388,103 @@ describe("module-loader/loadModule build-failure tagging", () => { ); assertEquals(isMissingModuleError(error), true); + // Still a build failure — the signal must not be dropped ... assertEquals(isBuildFailure(error), true); - assertEquals(isTenantBuildFailure(error), true); + // ... but not attributed to the tenant without evidence. + assertEquals(isTenantBuildFailure(error), false); + }); + }, + ); + }); + + // The same seam must not launder a framework fault. A module whose imports + // all resolve, and which then throws while executing, is an application + // error: it must come back out of `loadModule` untagged on both predicates. + // Asserting this through the real fixture rather than on a hand-built error + // is the point — a constructed Error never enters `loadModule`, so it would + // pass identically if the classification branch were deleted or inverted. + it("leaves a resolvable import that throws at module scope untagged", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { boom } from "./dep";`, + `export default function Page() { return boom; }`, + ].join("\n"), + "app/dep.tsx": [ + `throw new Error("dependency exploded at module scope");`, + `export const boom = "unreachable";`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + "dependency exploded at module scope", + ); + + assertEquals(isMissingModuleError(error), false); + assertEquals(isBuildFailure(error), false); + assertEquals(isTenantBuildFailure(error), false); }); }, ); }); +}); + +// The retry seam sees `ERR_MODULE_NOT_FOUND` for four different causes and may +// only downgrade one of them. `isMissingModuleError` cannot tell them apart, so +// the discrimination is driven by the specifiers the resolver recorded dropping. +describe("module-loader/isUnresolvedTenantImport", () => { + const missing = () => + Object.assign( + new TypeError('Module not found "file:///tmp/out/veryfront-modules/proj-a/app/missing".'), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + it("classifies a dropped tenant specifier as tenant source", () => { + assertEquals(isUnresolvedTenantImport(missing(), new Set(["./missing"])), true); + }); + + // The cycle-breaking branch leaves a resolved target's specifier as authored + // and relies on an alias the code itself marks as not runtime-verified. That + // target resolved, so it is never recorded as dropped — and a framework path + // the repo openly marks unverified must not page as a tenant warning. + it("does not classify a failure when the resolver dropped nothing", () => { + assertEquals(isUnresolvedTenantImport(missing(), new Set()), false); + }); + + // Bundle misses are framework infrastructure with dedicated recovery on the + // outer branch, which the inner catch does not re-check. Exclude them even + // when the tenant separately has an unresolved import. + it("does not classify an HTTP-bundle miss even alongside a dropped specifier", () => { + const bundleError = Object.assign( + new TypeError( + 'Module not found "file:///tmp/veryfront-http-bundle/http-2b1f9c4e.mjs".', + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(bundleError, new Set(["./missing"])), false); + }); + + it("does not classify a failure that is not a resolution failure", () => { + assertEquals( + isUnresolvedTenantImport(new TypeError("x is not a function"), new Set(["./missing"])), + false, + ); + }); - // The same seam must not launder a framework fault: a module that was found - // and threw while executing is an application error, not a build failure. - it("leaves a non-resolution import failure untagged", () => { - const runtimeError = new TypeError("x is not a function"); + // The runtime reports the *resolved* path, which for a dropped relative + // specifier lands inside the build's own temp directory. A "does the message + // mention our temp dir?" heuristic would therefore reject the one case this + // predicate exists to catch. Pinned so nobody reintroduces it. + it("classifies a dropped specifier whose resolved path is inside the build temp dir", () => { + const tmpDir = "/tmp/out"; + const error = missing(); - assertEquals(isBuildFailure(runtimeError), false); - assertEquals(isTenantBuildFailure(runtimeError), false); + assertEquals(error.message.includes(tmpDir), true); + assertEquals(isUnresolvedTenantImport(error, new Set(["./missing"])), true); }); }); diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 8334e955f9..5f8694b897 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -82,6 +82,12 @@ export async function transformModuleWithDeps( // ancestor that eventually persists that target reads it to write a stable // alias the left-as-authored cycle edge can resolve to. cycleTargets: Set = new Set(), + // Also shared by reference across the whole transform tree: every specifier + // the dependency resolver could not resolve and therefore left as authored. + // Those are the only specifiers that can survive into the built module and + // fail at `import()` time, so this is the evidence that tells a tenant typo + // apart from a framework artifact going missing. See `loadModule`. + unresolvedSpecifiers: Set = new Set(), ): Promise { throwIfModuleLoadAborted(config); const { moduleCache, projectDir, projectId, contentSourceId, adapter, mode } = config; @@ -168,6 +174,7 @@ export async function transformModuleWithDeps( dep.isLocalLib, nextLineage, cycleTargets, + unresolvedSpecifiers, ); return { ...dep, depTempPath }; @@ -198,6 +205,7 @@ export async function transformModuleWithDeps( for (const dep of resolvedDeps) { if (dep.depFilePath) continue; + unresolvedSpecifiers.add(dep.path); logger.warn("Could not find dependency:", { path: dep.path, relativePath: dep.relativePath, @@ -300,6 +308,39 @@ export function isMissingModuleError(error: unknown): boolean { return /cannot find module|module not found/i.test(error.message); } +/** + * Whether a module-not-found failure is a specifier the project authored that + * points at nothing, as opposed to framework infrastructure going missing. + * + * `isMissingModuleError` alone cannot answer this: an `ERR_MODULE_NOT_FOUND` + * is raised the same way for a tenant typo, an HTTP bundle miss, a cycle-break + * alias that did not resolve, and a rebuilt artifact the runtime failed to + * persist. Only the first is the tenant's fault, and only the first may be + * downgraded to a warning in observability. + * + * The discriminator is evidence rather than a guess: `resolveModuleDependencies` + * resolves only `@/` aliases and relative imports, and `transformModuleWithDeps` + * records every specifier it had to leave as authored. If it left none anywhere + * in this module's transform tree, then nothing tenant-authored survived + * unrewritten and whatever is missing here is framework-owned. + * + * Note the runtime reports the *resolved* path, which for a dropped relative + * specifier lands inside the build's own temp directory — so a "is this path + * ours?" test on the message would reject exactly the case this identifies. + */ +export function isUnresolvedTenantImport( + error: unknown, + unresolvedSpecifiers: ReadonlySet, +): boolean { + if (!isMissingModuleError(error)) return false; + if (unresolvedSpecifiers.size === 0) return false; + // An HTTP bundle is framework infrastructure with dedicated recovery on the + // outer branch. A miss on one is not the tenant's doing even when the tenant + // separately has an unresolved import. + const message = error instanceof Error ? error.message : String(error); + return !/veryfront-http-bundle\/http-[a-f0-9]+\.mjs/.test(message); +} + /** * Load a module by path, transforming it and its dependencies. * @@ -318,9 +359,23 @@ export async function loadModule( // Everything up to here compiles and resolves source, so a failure is a build // failure. Everything after it is the module running. + // Every specifier the resolver had to leave as authored, across this module's + // whole transform tree. Read back at the retry seam below to tell a tenant + // typo apart from framework infrastructure going missing. + const unresolvedSpecifiers = new Set(); + let tempFilePath: string; try { - tempFilePath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config); + tempFilePath = await transformModuleWithDeps( + filePath, + tmpDir, + localAdapter, + config, + false, + undefined, + undefined, + unresolvedSpecifiers, + ); } catch (error) { throw markBuildFailure(error); } @@ -398,7 +453,16 @@ export async function loadModule( let rebuiltPath: string; try { - rebuiltPath = await transformModuleWithDeps(filePath, tmpDir, localAdapter, config); + rebuiltPath = await transformModuleWithDeps( + filePath, + tmpDir, + localAdapter, + config, + false, + undefined, + undefined, + unresolvedSpecifiers, + ); } catch (rebuildError) { throw markBuildFailure(rebuildError); } @@ -406,18 +470,25 @@ export async function loadModule( try { return await import(`${toFileUrl(rebuiltPath).href}?t=${Date.now()}&rebuilt=1`); } catch (retryError) { - // A specifier that still does not resolve after a full rebuild from - // source is not an evicted cache artifact — it is a path the project - // authored that points at nothing. `resolveModuleDependencies` only - // resolves `@/` aliases and relative imports, and silently drops the - // ones it cannot find, so the unresolvable specifier survives into the - // built module and fails here. That is a tenant build failure, and it - // has to be classified explicitly: `ERR_MODULE_NOT_FOUND` is not a - // VeryfrontError, so slug-based classification cannot see it. - if (isMissingModuleError(retryError)) throw markTenantBuildFailure(retryError); - // Anything else means the module was found and ran, which is an - // ordinary application error the project's own error page presents. - throw retryError; + // The module was found and ran, so it threw at module scope. That is an + // ordinary application error the project's own error page should + // present, not a build failure — leave it untagged. + if (!isMissingModuleError(retryError)) throw retryError; + + // Still unresolved after a full rebuild from source, and the resolver + // recorded leaving a specifier as authored: a path the project wrote + // that points at nothing. Classify it explicitly, because + // `ERR_MODULE_NOT_FOUND` is not a VeryfrontError and slug-based + // classification cannot see it. + if (isUnresolvedTenantImport(retryError, unresolvedSpecifiers)) { + throw markTenantBuildFailure(retryError); + } + + // A resolution failure the tenant did not cause: an HTTP bundle miss, a + // cycle-break alias that did not resolve, or an artifact the rebuild + // failed to persist. Still a build failure, but a framework-owned one, + // so it keeps error-level severity. + throw markBuildFailure(retryError); } } From 58ecf6e377aebe8542e84c20b6f8025b40226610 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 08:04:27 +0200 Subject: [PATCH 041/104] fix(observability): keep an evicted rebuilt artifact at error severity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The missing module at the retry seam can be the rebuilt artifact itself rather than one of its dependencies: a racing cache sweep or a failing cache volume can evict it between persist and import. The dropped-specifier evidence does not separate that from a tenant typo when the page happens to have both, so pass the artifact path and exclude it explicitly. Compare only the missing target, never the whole message. The runtime names what is absent in the first quoted token and then appends the importer's own location, and at this seam the importer is always the rebuilt artifact — so a whole-message `includes` matches every case and would classify a tenant typo as framework, silently undoing the fix. Found by running it: the fixture went red because the importer line, not the missing path, supplied the match. Both directions are now pinned by tests, and the unit fixtures carry the importer line so they exercise the real message shape. --- .../orchestrator/module-loader/index.test.ts | 38 ++++++++++++++++++- .../orchestrator/module-loader/index.ts | 31 +++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 89199ad044..28c9ecb894 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -436,9 +436,18 @@ describe("module-loader/loadModule build-failure tagging", () => { // only downgrade one of them. `isMissingModuleError` cannot tell them apart, so // the discrimination is driven by the specifiers the resolver recorded dropping. describe("module-loader/isUnresolvedTenantImport", () => { + const REBUILT = "/tmp/out/veryfront-modules/proj-a/app/page.7f3c1d92.js"; + + // The runtime names the missing target first and then appends the importer's + // own location. At this seam the importer is always the rebuilt artifact, so + // every real message mentions REBUILT somewhere — which is exactly why the + // predicate may only inspect the first quoted token. const missing = () => Object.assign( - new TypeError('Module not found "file:///tmp/out/veryfront-modules/proj-a/app/missing".'), + new TypeError( + 'Module not found "file:///tmp/out/veryfront-modules/proj-a/app/missing".\n' + + ` at file://${REBUILT}:1:23`, + ), { code: "ERR_MODULE_NOT_FOUND" }, ); @@ -468,6 +477,33 @@ describe("module-loader/isUnresolvedTenantImport", () => { assertEquals(isUnresolvedTenantImport(bundleError, new Set(["./missing"])), false); }); + // The missing module can be the rebuilt artifact itself rather than one of + // its dependencies: a racing cache sweep or a failing cache volume can evict + // it between persist and import. That is repeated cache eviction — framework + // infrastructure — and must stay at error severity even when the tenant + // separately has an unresolved import. + it("does not classify an evicted rebuilt artifact, even alongside a dropped specifier", () => { + const evicted = Object.assign( + new TypeError(`Module not found "file://${REBUILT}?t=1&rebuilt=1".`), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(evicted, new Set(["./missing"]), REBUILT), false); + // Without the artifact path the predicate cannot tell the two apart, which + // is why the call site passes it. + assertEquals(isUnresolvedTenantImport(evicted, new Set(["./missing"])), true); + }); + + // The regression this pins: the importer line also names the rebuilt + // artifact, so a whole-message `includes` would classify a tenant typo as + // framework and silently undo the fix. + it("still classifies a dropped specifier whose importer is the rebuilt artifact", () => { + const error = missing(); + + assertEquals(error.message.includes(REBUILT), true); + assertEquals(isUnresolvedTenantImport(error, new Set(["./missing"]), REBUILT), true); + }); + it("does not classify a failure that is not a resolution failure", () => { assertEquals( isUnresolvedTenantImport(new TypeError("x is not a function"), new Set(["./missing"])), diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 5f8694b897..9c9297074d 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -328,17 +328,42 @@ export function isMissingModuleError(error: unknown): boolean { * specifier lands inside the build's own temp directory — so a "is this path * ours?" test on the message would reject exactly the case this identifies. */ +/** + * The specifier a module-not-found error names as missing. + * + * Runtimes report it as the first quoted token (`Module not found "file://…"`) + * and then append the importer's own location, so only the first quote pair + * identifies what is actually absent. + */ +function missingModuleTarget(message: string): string { + return message.match(/"([^"]+)"/)?.[1] ?? ""; +} + export function isUnresolvedTenantImport( error: unknown, unresolvedSpecifiers: ReadonlySet, + rebuiltArtifactPath?: string, ): boolean { if (!isMissingModuleError(error)) return false; if (unresolvedSpecifiers.size === 0) return false; + const message = error instanceof Error ? error.message : String(error); // An HTTP bundle is framework infrastructure with dedicated recovery on the // outer branch. A miss on one is not the tenant's doing even when the tenant // separately has an unresolved import. - const message = error instanceof Error ? error.message : String(error); - return !/veryfront-http-bundle\/http-[a-f0-9]+\.mjs/.test(message); + if (/veryfront-http-bundle\/http-[a-f0-9]+\.mjs/.test(message)) return false; + // The missing module can be the rebuilt artifact *itself* rather than one of + // its dependencies — a racing cache sweep or a failing cache volume can evict + // it between persist and import. That is repeated cache eviction, which must + // stay at error severity however the tenant's own imports look. + // + // Only the *missing target* may be compared, never the whole message: the + // runtime appends the importer's location, and at this seam the importer is + // always the rebuilt artifact, so scanning the full message would exclude + // every case including the tenant typo this predicate exists to catch. + if (rebuiltArtifactPath && missingModuleTarget(message).includes(rebuiltArtifactPath)) { + return false; + } + return true; } /** @@ -480,7 +505,7 @@ export async function loadModule( // that points at nothing. Classify it explicitly, because // `ERR_MODULE_NOT_FOUND` is not a VeryfrontError and slug-based // classification cannot see it. - if (isUnresolvedTenantImport(retryError, unresolvedSpecifiers)) { + if (isUnresolvedTenantImport(retryError, unresolvedSpecifiers, rebuiltPath)) { throw markTenantBuildFailure(retryError); } From 06d2ed2425832a83c9b30492aab7bfb8527ea1a8 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:09:55 +0200 Subject: [PATCH 042/104] fix(transforms): keep brace matching linear --- .../utils/source-spans.test.ts | 17 ++- .../esm-module-loader/utils/source-spans.ts | 134 ++++++------------ 2 files changed, 60 insertions(+), 91 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index eccb5ab28a..3c65c4dbd2 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -1,5 +1,5 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; +import { assert, assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { findDynamicImportSpans, @@ -352,6 +352,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps brace-heavy division scans within a bounded runtime", () => { + const source = "x={a:1}/2;\n".repeat(7_200); + const startedAt = performance.now(); + + assertEquals(specifiers(source), []); + + const durationMs = performance.now() - startedAt; + assert( + durationMs < 750, + `Expected an 86 KB brace-heavy scan to finish within 750 ms, got ${ + durationMs.toFixed(1) + } ms`, + ); + }); + it("finds imports after division when literal contents look like control conditions", () => { assertEquals( specifiers('foo("if(") / 2 && import("./after-string-division.js");'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 2d732894de..e8520c29ab 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -309,95 +309,14 @@ function isEscapedByBackslash(source: string, index: number): boolean { return count % 2 === 1; } -function canStartRegexLiteralInBraceScan( +function isControlBlockCloseBrace( source: string, index: number, rangeStart: number, + matchingOpenBraces: ReadonlyMap, ): boolean { - const previous = previousSignificantIndex(source, index); - if (previous < rangeStart) return true; - - const char = source[previous]; - if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; - if ( - (char === "+" || char === "-") && - previous - 1 >= rangeStart && - source[previous - 1] === char - ) { - return false; - } - if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; - - return [ - "case", - "delete", - "do", - "else", - "in", - "instanceof", - "of", - "await", - "return", - "throw", - "typeof", - "void", - "yield", - ].includes(keywordBefore(source, index) ?? ""); -} - -function skipBraceScanIgnored(source: string, index: number, rangeStart: number): number { - const char = source[index]; - const next = source[index + 1]; - - if ( - (char === "/" && (next === "/" || next === "*")) || - char === '"' || - char === "'" || - char === "`" - ) { - return skipIgnored(source, index); - } - - if (char === "/" && canStartRegexLiteralInBraceScan(source, index, rangeStart)) { - return skipRegexLiteral(source, index); - } - - return index; -} - -function matchingOpenBraceIndex(source: string, index: number, rangeStart: number): number | null { - const openBraces: number[] = []; - let cursor = rangeStart; - - while (cursor <= index) { - const skipped = skipBraceScanIgnored(source, cursor, rangeStart); - if (skipped !== cursor) { - cursor = skipped; - continue; - } - - if (source[cursor] === "{") { - openBraces.push(cursor); - cursor++; - continue; - } - - if (source[cursor] === "}") { - const openBrace = openBraces.pop(); - if (cursor === index) return openBrace ?? null; - cursor++; - continue; - } - - cursor++; - } - - return null; -} - -function isControlBlockCloseBrace(source: string, index: number, rangeStart: number): boolean { - const openBrace = matchingOpenBraceIndex(source, index, rangeStart); - if (openBrace === null) return false; + const openBrace = matchingOpenBraces.get(index); + if (openBrace === undefined) return false; const beforeOpenBrace = previousSignificantIndex(source, openBrace); return beforeOpenBrace >= rangeStart && @@ -405,13 +324,21 @@ function isControlBlockCloseBrace(source: string, index: number, rangeStart: num isControlConditionCloseParen(source, beforeOpenBrace, rangeStart); } -function canStartRegexLiteral(source: string, index: number, rangeStart: number): boolean { +function canStartRegexLiteral( + source: string, + index: number, + rangeStart: number, + matchingOpenBraces: ReadonlyMap, +): boolean { const previous = previousSignificantIndex(source, index); if (previous < rangeStart) return true; const char = source[previous]; if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; - if (char === "}" && isControlBlockCloseBrace(source, previous, rangeStart)) return true; + if ( + char === "}" && + isControlBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces) + ) return true; if ( (char === "+" || char === "-") && previous - 1 >= rangeStart && @@ -479,6 +406,7 @@ function skipExpressionIgnored( index: number, rangeStart: number, depth: number, + matchingOpenBraces: ReadonlyMap, ): number { const char = source[index]; const next = source[index + 1]; @@ -495,7 +423,7 @@ function skipExpressionIgnored( if (char === '"' || char === "'") return skipIgnored(source, index); if (char === "`") return skipFullTemplateLiteral(source, index, depth + 1); - if (char === "/" && canStartRegexLiteral(source, index, rangeStart)) { + if (char === "/" && canStartRegexLiteral(source, index, rangeStart, matchingOpenBraces)) { return skipRegexLiteral(source, index); } @@ -511,15 +439,24 @@ function findTemplateExpressionEnd( let cursor = expressionIndex; let braceDepth = 1; + const openBraces: number[] = []; + const matchingOpenBraces = new Map(); while (cursor < source.length) { - const skipped = skipExpressionIgnored(source, cursor, expressionIndex, depth); + const skipped = skipExpressionIgnored( + source, + cursor, + expressionIndex, + depth, + matchingOpenBraces, + ); if (skipped !== cursor) { cursor = skipped; continue; } if (source[cursor] === "{") { + openBraces.push(cursor); braceDepth++; cursor++; continue; @@ -528,6 +465,8 @@ function findTemplateExpressionEnd( if (source[cursor] === "}") { braceDepth--; if (braceDepth === 0) return cursor; + const openBrace = openBraces.pop(); + if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); cursor++; continue; } @@ -690,6 +629,8 @@ function scanDynamicImportRange( spans: StaticImportSpan[], ): void { let cursor = rangeStart; + const openBraces: number[] = []; + const matchingOpenBraces = new Map(); while (cursor < rangeEnd) { const char = source[cursor]; @@ -704,7 +645,7 @@ function scanDynamicImportRange( continue; } - if (char === "/" && canStartRegexLiteral(source, cursor, rangeStart)) { + if (char === "/" && canStartRegexLiteral(source, cursor, rangeStart, matchingOpenBraces)) { cursor = skipRegexLiteral(source, cursor); continue; } @@ -722,6 +663,19 @@ function scanDynamicImportRange( continue; } + if (char === "{") { + openBraces.push(cursor); + cursor++; + continue; + } + + if (char === "}") { + const openBrace = openBraces.pop(); + if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); + cursor++; + continue; + } + // `import` used as an expression: not preceded by an identifier char or a // dot (which would make it `foo.import` or part of a longer word). if ( From da209cbe8161a1cb30746dcecfa8e44c6376f289 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:15:42 +0200 Subject: [PATCH 043/104] fix(observability): classify only the rebuilt graph --- .../orchestrator/module-loader/index.test.ts | 47 +++++++++++++++++++ .../orchestrator/module-loader/index.ts | 6 +++ 2 files changed, 53 insertions(+) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 28c9ecb894..281b585061 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -397,6 +397,53 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("classifies retry failures from only the rebuilt dependency graph", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./late";`, + `export default function Page() { return label; }`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + let createdLateDependency = false; + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => + loadModule(join(projectDir, "app/page.tsx"), { + ...config, + onProgress: ({ phase, filePath }) => { + if ( + createdLateDependency || + phase !== "module:persisted" || + filePath !== join(projectDir, "app/page.tsx") + ) return; + + createdLateDependency = true; + Deno.writeTextFileSync( + join(projectDir, "app/late.ts"), + [ + `import "./framework-missing";`, + `export const label = "late";`, + ].join("\n"), + ); + }, + }), + Error, + ); + + assertEquals(createdLateDependency, true); + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + // The first transform dropped `./late`, but the rebuild resolved it. + // Its separate bare side-effect failure is not resolver evidence and + // must not inherit the stale tenant classification from build one. + assertEquals(isTenantBuildFailure(error), false); + }); + }, + ); + }); + // The same seam must not launder a framework fault. A module whose imports // all resolve, and which then throws while executing, is an application // error: it must come back out of `loadModule` untagged on both predicates. diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 9c9297074d..4ac6c964b5 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -476,6 +476,12 @@ export async function loadModule( ), ); + // Classification at the retry seam must describe the rebuilt graph, not + // the artifact that just failed. A dependency may appear between the two + // transforms, so retaining its earlier dropped-specifier evidence can + // misattribute an unrelated retry failure to the tenant. + unresolvedSpecifiers.clear(); + let rebuiltPath: string; try { rebuiltPath = await transformModuleWithDeps( From a1eb2fceaaa7e1c647f60a38eb5e4aea5bd457e6 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 08:14:00 +0200 Subject: [PATCH 044/104] fix(observability): read single-quoted module targets and keep cached-dep evidence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `missingModuleTarget` matched double quotes only, which is Deno's phrasing. Node writes `Cannot find module '/…/missing' imported from /…/page.js` — single quotes around the target, importer unquoted — so the helper returned "" there and the evicted-artifact guard silently stopped firing on the Node runtime while every Deno test kept passing. Verified the shape by running Node rather than assuming it. Match either quote style, with a test per runtime phrasing. Closes a gap that clearing the evidence before the rebuild would otherwise have widened. A transform cache hit returns before dependency resolution runs, and the retry invalidates only the root module's cache entry, so on the rebuild the dependency holding a typo is served from cache and contributes nothing. Cleared plus unrecorded means a dependency-level typo is never attributed to the tenant, including on the very first load — only typos in the page file itself would classify. Memoize each module's unresolved specifiers by transform cache key and replay them on a cache hit, so evidence survives exactly as long as the artifact it describes. The memo does not reintroduce staleness: a module that is actually re-transformed overwrites its own entry, so a dependency that appears between two builds still drops out. --- .../orchestrator/module-loader/index.test.ts | 63 +++++++++++++++++++ .../orchestrator/module-loader/index.ts | 45 +++++++++++-- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 281b585061..0609c72afd 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -444,6 +444,44 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + // A transform cache hit skips dependency resolution, and the retry path + // invalidates only the root module's cache entry — so on the rebuild the + // dependency holding the typo is served from cache and contributes no + // evidence. Combined with clearing the set before the rebuild, that would + // leave a dependency-level typo permanently unattributed, including on the + // very first load. The cache-hit branch replays each module's recorded + // specifiers to close it. Both loads must classify identically. + it("attributes a typo in a cached dependency on every load", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./dep";`, + `export default function Page() { return label; }`, + ].join("\n"), + "app/dep.tsx": [ + `import { gone } from "./gone";`, + `export const label = gone;`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const pagePath = join(projectDir, "app/page.tsx"); + + const first = await assertRejects(() => loadModule(pagePath, config), Error); + assertEquals(isBuildFailure(first), true); + assertEquals(isTenantBuildFailure(first), true); + + // Same config, so `config.moduleCache` is warm for `app/dep.tsx`. + const second = await assertRejects(() => loadModule(pagePath, config), Error); + assertEquals(isBuildFailure(second), true); + // Identical failure must not get weaker attribution just because a + // dependency happened to be cached. + assertEquals(isTenantBuildFailure(second), true); + }); + }, + ); + }); + // The same seam must not launder a framework fault. A module whose imports // all resolve, and which then throws while executing, is an application // error: it must come back out of `loadModule` untagged on both predicates. @@ -551,6 +589,31 @@ describe("module-loader/isUnresolvedTenantImport", () => { assertEquals(isUnresolvedTenantImport(error, new Set(["./missing"]), REBUILT), true); }); + // Node quotes the missing target with single quotes and leaves the importer + // unquoted: `Cannot find module '/…/missing' imported from /…/page.js`. + // A double-quote-only match returns "" there, which silently disables the + // eviction guard on the Node runtime while every Deno test still passes. + it("reads a single-quoted Node target so the eviction guard still fires", () => { + const evictedOnNode = Object.assign( + new Error(`Cannot find module '${REBUILT}' imported from ${REBUILT}`), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(evictedOnNode, new Set(["./missing"]), REBUILT), false); + }); + + it("classifies a single-quoted Node target that is a dropped specifier", () => { + const nodeMissing = Object.assign( + new Error( + `Cannot find module '/tmp/out/veryfront-modules/proj-a/app/missing' ` + + `imported from ${REBUILT}`, + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(nodeMissing, new Set(["./missing"]), REBUILT), true); + }); + it("does not classify a failure that is not a resolution failure", () => { assertEquals( isUnresolvedTenantImport(new TypeError("x is not a function"), new Set(["./missing"])), diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 4ac6c964b5..d805f56051 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -34,6 +34,18 @@ export { isBuildFailure } from "./build-failure.ts"; const logger = rendererLogger.component("module-loader"); +/** + * Specifiers each transformed module left as authored, keyed by its transform + * cache key. + * + * The transform cache lets a module skip dependency resolution entirely, so the + * evidence has to outlive the resolution that produced it — otherwise a + * dependency's dangling tenant import is only ever visible on the very first + * transform. Bounded by the number of distinct cache keys, the same order as + * the transform cache it mirrors, and holding only specifier strings. + */ +const unresolvedSpecifiersByCacheKey = new Map(); + function throwIfModuleLoadAborted(config: ModuleLoaderConfig): void { config.signal?.throwIfAborted(); } @@ -87,6 +99,13 @@ export async function transformModuleWithDeps( // Those are the only specifiers that can survive into the built module and // fail at `import()` time, so this is the evidence that tells a tenant typo // apart from a framework artifact going missing. See `loadModule`. + // + // A module served from the cache below returns before + // `resolveModuleDependencies` runs, so it cannot re-derive its own evidence. + // That matters because the retry path invalidates only the *root* module's + // cache entry: without a memo, a typo living in a dependency would go + // unrecorded on every rebuild and never be attributed to the tenant. The + // cache-hit branch therefore replays what the first resolution found. unresolvedSpecifiers: Set = new Set(), ): Promise { throwIfModuleLoadAborted(config); @@ -114,6 +133,13 @@ export async function transformModuleWithDeps( moduleServerOrigin: config.moduleServerOrigin, }); if (cachedPath) { + // Replay the evidence this module produced when it was last resolved. A + // cache hit skips `resolveModuleDependencies`, so without this a dependency + // that was already transformed contributes nothing and its tenant-authored + // dangling import silently loses attribution. + for (const specifier of unresolvedSpecifiersByCacheKey.get(cacheKey) ?? []) { + unresolvedSpecifiers.add(specifier); + } markModuleLoadProgress(config, "module:cache-hit", filePath); return cachedPath; } @@ -203,8 +229,10 @@ export async function transformModuleWithDeps( }); } + const ownUnresolved: string[] = []; for (const dep of resolvedDeps) { if (dep.depFilePath) continue; + ownUnresolved.push(dep.path); unresolvedSpecifiers.add(dep.path); logger.warn("Could not find dependency:", { path: dep.path, @@ -212,6 +240,7 @@ export async function transformModuleWithDeps( projectDir, }); } + unresolvedSpecifiersByCacheKey.set(cacheKey, ownUnresolved); const effectiveProjectId = projectId ?? projectDir; const { code: transformedCode } = await transformModuleCodeWithCache({ @@ -331,12 +360,20 @@ export function isMissingModuleError(error: unknown): boolean { /** * The specifier a module-not-found error names as missing. * - * Runtimes report it as the first quoted token (`Module not found "file://…"`) - * and then append the importer's own location, so only the first quote pair - * identifies what is actually absent. + * Runtimes report it as the first quoted token and then name the importer, so + * only the first quote pair identifies what is actually absent. The quote style + * differs by runtime and both reach this seam, since `isMissingModuleError` + * matches Node's phrasing as well as Deno's: + * + * - Deno: `Module not found "file:///…/missing".` + * - Node: `Cannot find module '/…/missing' imported from /…/page.js` + * + * Note Node leaves the importer unquoted, so a double-quote-only match would + * return `""` there and silently disable every check built on this. */ function missingModuleTarget(message: string): string { - return message.match(/"([^"]+)"/)?.[1] ?? ""; + const match = message.match(/"([^"]*)"|'([^']*)'/); + return match?.[1] ?? match?.[2] ?? ""; } export function isUnresolvedTenantImport( From a3ae4cd0485cdc184d60b66067448c092871b3ad Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:21:17 +0200 Subject: [PATCH 045/104] fix(transforms): distinguish for-of regex prefixes --- .../transforms/alias-imports.ts | 2 +- .../utils/source-spans.test.ts | 8 +++ .../esm-module-loader/utils/source-spans.ts | 68 ++++++++++++++++++- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts index 644fab6f35..8f9bbf1bf9 100644 --- a/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts +++ b/src/transforms/mdx/esm-module-loader/transforms/alias-imports.ts @@ -15,7 +15,7 @@ import type { FSAdapter } from "../types.ts"; import { hashString } from "../utils/hash.ts"; import { resolveFileWithExtension } from "../resolution/file-finder.ts"; import { parseImports, replaceSpecifiers } from "../../../esm/lexer.ts"; -import { splitSpecifierSuffix } from "../../../shared/specifier-suffix.ts"; +import { splitSpecifierSuffix } from "#veryfront/transforms/shared/specifier-suffix.ts"; type ImportType = "project-alias" | "vf-modules"; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 3c65c4dbd2..703a455edc 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -344,6 +344,14 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { specifiers('const value = maybe?.count / 2; import("./after-optional-chain.js");'), ["./after-optional-chain.js"], ); + assertEquals( + specifiers('const ratio = metrics.of / 2; import("./after-of-property.js");'), + ["./after-of-property.js"], + ); + assertEquals( + specifiers('const of = 4; const ratio = of / 2; import("./after-of-identifier.js");'), + ["./after-of-identifier.js"], + ); assertEquals( specifiers( 'const html = `${constValue = {} / 2} ${import("./after-object-division.js")}`;', diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index e8520c29ab..5fc69a4539 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -324,6 +324,68 @@ function isControlBlockCloseBrace( isControlConditionCloseParen(source, beforeOpenBrace, rangeStart); } +function isForOfKeywordBefore(source: string, index: number, rangeStart: number): boolean { + const keywordEnd = previousSignificantIndex(source, index) + 1; + let keywordStart = keywordEnd; + while (keywordStart > rangeStart && /[A-Za-z_$]/.test(source[keywordStart - 1] ?? "")) { + keywordStart--; + } + if (source.slice(keywordStart, keywordEnd) !== "of") return false; + + const beforeKeyword = previousSignificantIndex(source, keywordStart); + if (beforeKeyword >= rangeStart && source[beforeKeyword] === ".") return false; + + let parenDepth = 0; + let braceDepth = 0; + let bracketDepth = 0; + let cursor = keywordStart - 1; + + while (cursor >= rangeStart) { + const char = source[cursor]; + + if (char === '"' || char === "'" || char === "`") { + cursor = previousStringLiteralStart(source, cursor, rangeStart) - 1; + continue; + } + + if (char === "/" && source[cursor - 1] === "*") { + const commentStart = source.lastIndexOf("/*", cursor - 2); + cursor = commentStart >= rangeStart ? commentStart - 1 : rangeStart - 1; + continue; + } + + if (char === ")") { + parenDepth++; + } else if (char === "(") { + if (parenDepth > 0) { + parenDepth--; + } else if (braceDepth === 0 && bracketDepth === 0) { + return keywordBefore(source, cursor) === "for"; + } + } else if (char === "}") { + braceDepth++; + } else if (char === "{") { + if (braceDepth > 0) braceDepth--; + else if (parenDepth === 0 && bracketDepth === 0) return false; + } else if (char === "]") { + bracketDepth++; + } else if (char === "[") { + if (bracketDepth > 0) bracketDepth--; + } else if ( + char === ";" && + parenDepth === 0 && + braceDepth === 0 && + bracketDepth === 0 + ) { + return false; + } + + cursor--; + } + + return false; +} + function canStartRegexLiteral( source: string, index: number, @@ -348,6 +410,9 @@ function canStartRegexLiteral( } if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; + const keyword = keywordBefore(source, index); + if (keyword === "of") return isForOfKeywordBefore(source, index, rangeStart); + return [ "case", "delete", @@ -355,14 +420,13 @@ function canStartRegexLiteral( "else", "in", "instanceof", - "of", "await", "return", "throw", "typeof", "void", "yield", - ].includes(keywordBefore(source, index) ?? ""); + ].includes(keyword ?? ""); } function skipRegexLiteral(source: string, regexIndex: number): number { From 3e9368375269ddb3c243a1d06ca5823c47eba1b0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:24:54 +0200 Subject: [PATCH 046/104] fix(observability): replay transitive cache evidence --- .../orchestrator/module-loader/index.test.ts | 32 +++++++++++++++ .../orchestrator/module-loader/index.ts | 41 ++++++++++++++----- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 0609c72afd..3b735de4e3 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -482,6 +482,38 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("attributes a typo below a cached dependency on every load", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./dep";`, + `export default function Page() { return label; }`, + ].join("\n"), + "app/dep.tsx": [ + `import { nested } from "./nested";`, + `export const label = nested;`, + ].join("\n"), + "app/nested.tsx": [ + `import { gone } from "./gone";`, + `export const nested = gone;`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const pagePath = join(projectDir, "app/page.tsx"); + + const first = await assertRejects(() => loadModule(pagePath, config), Error); + assertEquals(isBuildFailure(first), true); + assertEquals(isTenantBuildFailure(first), true); + + const second = await assertRejects(() => loadModule(pagePath, config), Error); + assertEquals(isBuildFailure(second), true); + assertEquals(isTenantBuildFailure(second), true); + }); + }, + ); + }); + // The same seam must not launder a framework fault. A module whose imports // all resolve, and which then throws while executing, is an application // error: it must come back out of `loadModule` untagged on both predicates. diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index d805f56051..ac955eddef 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -29,23 +29,35 @@ import { import { markBuildFailure, markTenantBuildFailure } from "./build-failure.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; import type { DependencyPinningSourceInput } from "#veryfront/transforms/esm/package-registry.ts"; +import { MODULE_CACHE_MAX_ENTRIES } from "#veryfront/utils/constants/cache.ts"; export { isBuildFailure } from "./build-failure.ts"; const logger = rendererLogger.component("module-loader"); /** - * Specifiers each transformed module left as authored, keyed by its transform - * cache key. + * Specifiers each transformed module subtree left as authored, keyed by the + * root module's transform cache key. * * The transform cache lets a module skip dependency resolution entirely, so the * evidence has to outlive the resolution that produced it — otherwise a * dependency's dangling tenant import is only ever visible on the very first - * transform. Bounded by the number of distinct cache keys, the same order as - * the transform cache it mirrors, and holding only specifier strings. + * transform. The memo uses the module cache's entry bound and refreshes access + * order on reads, while holding only specifier strings. */ const unresolvedSpecifiersByCacheKey = new Map(); +function cacheUnresolvedSpecifiers(cacheKey: string, specifiers: readonly string[]): void { + unresolvedSpecifiersByCacheKey.delete(cacheKey); + unresolvedSpecifiersByCacheKey.set(cacheKey, specifiers); + + while (unresolvedSpecifiersByCacheKey.size > MODULE_CACHE_MAX_ENTRIES) { + const oldestKey = unresolvedSpecifiersByCacheKey.keys().next().value; + if (oldestKey === undefined) break; + unresolvedSpecifiersByCacheKey.delete(oldestKey); + } +} + function throwIfModuleLoadAborted(config: ModuleLoaderConfig): void { config.signal?.throwIfAborted(); } @@ -137,13 +149,22 @@ export async function transformModuleWithDeps( // cache hit skips `resolveModuleDependencies`, so without this a dependency // that was already transformed contributes nothing and its tenant-authored // dangling import silently loses attribution. - for (const specifier of unresolvedSpecifiersByCacheKey.get(cacheKey) ?? []) { + const cachedUnresolvedSpecifiers = unresolvedSpecifiersByCacheKey.get(cacheKey) ?? []; + if (cachedUnresolvedSpecifiers.length > 0) { + cacheUnresolvedSpecifiers(cacheKey, cachedUnresolvedSpecifiers); + } + for (const specifier of cachedUnresolvedSpecifiers) { unresolvedSpecifiers.add(specifier); } markModuleLoadProgress(config, "module:cache-hit", filePath); return cachedPath; } + // Collect this module and every recursively transformed descendant into an + // isolated set. Once persistence succeeds, cache that complete subtree and + // merge it into the caller's aggregate evidence. + const moduleUnresolvedSpecifiers = new Set(); + const readAdapter = useLocalAdapter ? localAdapter : adapter; let fileContent = decodeFileContent(await readAdapter.fs.readFile(filePath)); markModuleLoadProgress(config, "module:source-read", filePath); @@ -200,7 +221,7 @@ export async function transformModuleWithDeps( dep.isLocalLib, nextLineage, cycleTargets, - unresolvedSpecifiers, + moduleUnresolvedSpecifiers, ); return { ...dep, depTempPath }; @@ -229,19 +250,15 @@ export async function transformModuleWithDeps( }); } - const ownUnresolved: string[] = []; for (const dep of resolvedDeps) { if (dep.depFilePath) continue; - ownUnresolved.push(dep.path); - unresolvedSpecifiers.add(dep.path); + moduleUnresolvedSpecifiers.add(dep.path); logger.warn("Could not find dependency:", { path: dep.path, relativePath: dep.relativePath, projectDir, }); } - unresolvedSpecifiersByCacheKey.set(cacheKey, ownUnresolved); - const effectiveProjectId = projectId ?? projectDir; const { code: transformedCode } = await transformModuleCodeWithCache({ fileContent, @@ -274,6 +291,8 @@ export async function transformModuleWithDeps( dependencyPinningCacheKey: config.dependencyPinningCacheKey, isCycleTarget: cycleTargets.has(filePath), }); + cacheUnresolvedSpecifiers(cacheKey, [...moduleUnresolvedSpecifiers]); + for (const specifier of moduleUnresolvedSpecifiers) unresolvedSpecifiers.add(specifier); markModuleLoadProgress(config, "module:persisted", filePath); return persistedPath; } From c491cf03090bee6dd201cffad806ff145aed1e08 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:35:21 +0200 Subject: [PATCH 047/104] fix(transforms): keep module scans context-aware --- .../module-fetcher/nested-imports.test.ts | 17 +++- .../utils/source-spans.test.ts | 50 ++++++++++++ .../esm-module-loader/utils/source-spans.ts | 77 +++++++++++++++++-- 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index c71e101ece..1cfa657413 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -56,10 +56,8 @@ import { bar } from "./local.js"; }); it("finds bare side-effect _vf_modules imports", () => { - const code = [ - `import "/_vf_modules/styles/theme.css";`, - `import '/_vf_modules/polyfills/runtime.js';`, - ].join("\n"); + const code = + `/* preload; */ import "/_vf_modules/styles/theme.css"; import '/_vf_modules/polyfills/runtime.js';`; const result = findNestedImports(code); assertEquals(result.vfModules.map((module) => module.path), [ "_vf_modules/styles/theme.css", @@ -102,6 +100,17 @@ import { bar } from "./local.js"; assertEquals(result.paths, ["/_vf_modules/components/Lazy.js"]); }); + it("detects every unresolved same-line side-effect import", () => { + const result = hasUnresolvedImports( + `/* preload; */ import "/_vf_modules/styles/theme.css"; import "/_vf_modules/polyfills/runtime.js";`, + ); + assertEquals(result.count, 2); + assertEquals(result.paths, [ + "/_vf_modules/styles/theme.css", + "/_vf_modules/polyfills/runtime.js", + ]); + }); + it("returns empty for normal resolved file:// imports", () => { const code = `import { foo } from "file:///home/user/.cache/veryfront-mdx-esm/proj/vfmod.mjs";`; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 703a455edc..1717e69ab4 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -280,6 +280,30 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("treats of as an identifier in a classic for-loop initializer", () => { + assertEquals( + vfModuleSpecifiers( + 'let of = 4; for (of / 2; shouldRun;) { import("/_vf_modules/classic-for-lazy.js") }', + ), + ["/_vf_modules/classic-for-lazy.js"], + ); + }); + + it("finds imports after regex literals following declaration blocks", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { function f() {} /}/.test(x); })() && import("/_vf_modules/function-lazy.js")}`;', + ), + ["/_vf_modules/function-lazy.js"], + ); + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { class C {} /}/.test(x); })() && import("/_vf_modules/class-lazy.js")}`;', + ), + ["/_vf_modules/class-lazy.js"], + ); + }); + it("finds executable imports after regex braces following control conditions", () => { assertEquals( specifiers( @@ -375,6 +399,23 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps repeated of-identifier division scans within a bounded runtime", () => { + const source = "let " + Array.from( + { length: 6_000 }, + (_, index) => `value${index} = of / 2`, + ).join(", ") + ";"; + const startedAt = performance.now(); + + assertEquals(specifiers(source), []); + + const durationMs = performance.now() - startedAt; + assert( + durationMs < 750, + `Expected a ${Math.round(source.length / 1024)} KB of-identifier scan to finish within ` + + `750 ms, got ${durationMs.toFixed(1)} ms`, + ); + }); + it("finds imports after division when literal contents look like control conditions", () => { assertEquals( specifiers('foo("if(") / 2 && import("./after-string-division.js");'), @@ -570,5 +611,14 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); assertEquals(span?.path, "./value.js"); }); + + it("finds same-line side-effect imports after separators and comments", () => { + const spans = findStaticSideEffectImportSpans( + '/* preload; */ import "./a.js"; /* next; */ import "./b.js";', + matchRelative, + UNBOUNDED, + ); + assertEquals(spans.map((span) => span.path), ["./a.js", "./b.js"]); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 5fc69a4539..336d52a906 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -77,13 +77,13 @@ function isStatementKeywordAt( source: string, index: number, keyword: "import" | "export", + atStatementStart: boolean, ): boolean { + if (!atStatementStart) return false; if (!source.startsWith(keyword, index)) return false; if (isIdentifierChar(source[index - 1]) || source[index - 1] === ".") return false; if (isIdentifierChar(source[index + keyword.length])) return false; - - const lineStart = source.lastIndexOf("\n", index - 1) + 1; - return /^[\t ]*$/.test(source.slice(lineStart, index)); + return true; } function skipIgnored(source: string, index: number): number { @@ -324,6 +324,24 @@ function isControlBlockCloseBrace( isControlConditionCloseParen(source, beforeOpenBrace, rangeStart); } +function isDeclarationBlockCloseBrace( + source: string, + index: number, + matchingOpenBraces: ReadonlyMap, +): boolean { + const openBrace = matchingOpenBraces.get(index); + if (openBrace === undefined) return false; + + const declarationStart = Math.max( + source.lastIndexOf(";", openBrace - 1), + source.lastIndexOf("{", openBrace - 1), + source.lastIndexOf("}", openBrace - 1), + ) + 1; + const prefix = source.slice(declarationStart, openBrace).trimStart(); + return /^(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/.test(prefix) || + /^class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test(prefix); +} + function isForOfKeywordBefore(source: string, index: number, rangeStart: number): boolean { const keywordEnd = previousSignificantIndex(source, index) + 1; let keywordStart = keywordEnd; @@ -334,6 +352,15 @@ function isForOfKeywordBefore(source: string, index: number, rangeStart: number) const beforeKeyword = previousSignificantIndex(source, keywordStart); if (beforeKeyword >= rangeStart && source[beforeKeyword] === ".") return false; + const beforeKeywordChar = source[beforeKeyword]; + if ( + !isIdentifierChar(beforeKeywordChar) && + beforeKeywordChar !== "]" && + beforeKeywordChar !== "}" && + beforeKeywordChar !== ")" + ) { + return false; + } let parenDepth = 0; let braceDepth = 0; @@ -399,7 +426,8 @@ function canStartRegexLiteral( if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; if ( char === "}" && - isControlBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces) + (isControlBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces) || + isDeclarationBlockCloseBrace(source, previous, matchingOpenBraces)) ) return true; if ( (char === "+" || char === "-") && @@ -612,17 +640,32 @@ export function findStaticImportFromSpans( const spans: StaticImportSpan[] = []; let cursor = 0; + let atStatementStart = true; while (cursor < source.length) { + const char = source[cursor]; const skipped = skipIgnored(source, cursor); if (skipped !== cursor) { + if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; + else if (char !== "/") atStatementStart = false; cursor = skipped; continue; } - const isImport = isStatementKeywordAt(source, cursor, "import"); - const isExport = isStatementKeywordAt(source, cursor, "export"); + if (char === ";" || char === "\n") { + atStatementStart = true; + cursor++; + continue; + } + if (/\s/.test(char ?? "")) { + cursor++; + continue; + } + + const isImport = isStatementKeywordAt(source, cursor, "import", atStatementStart); + const isExport = isStatementKeywordAt(source, cursor, "export", atStatementStart); if (!isImport && !isExport) { + atStatementStart = false; cursor++; continue; } @@ -630,6 +673,7 @@ export function findStaticImportFromSpans( const keywordLength = isImport ? "import".length : "export".length; const afterKeyword = skipWhitespaceAndComments(source, cursor + keywordLength); if (isImport && source[afterKeyword] === "(") { + atStatementStart = false; cursor = afterKeyword + 1; continue; } @@ -638,10 +682,12 @@ export function findStaticImportFromSpans( if (span) { spans.push(span); if (spans.length >= maxMatches) return spans; + atStatementStart = false; cursor = span.end; continue; } + atStatementStart = true; cursor = nextStatementCursor(source, afterKeyword); } @@ -838,15 +884,30 @@ export function findStaticSideEffectImportSpans( const spans: StaticImportSpan[] = []; let cursor = 0; + let atStatementStart = true; while (cursor < source.length) { + const char = source[cursor]; const skipped = skipIgnored(source, cursor); if (skipped !== cursor) { + if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; + else if (char !== "/") atStatementStart = false; cursor = skipped; continue; } - if (!isStatementKeywordAt(source, cursor, "import")) { + if (char === ";" || char === "\n") { + atStatementStart = true; + cursor++; + continue; + } + if (/\s/.test(char ?? "")) { + cursor++; + continue; + } + + if (!isStatementKeywordAt(source, cursor, "import", atStatementStart)) { + atStatementStart = false; cursor++; continue; } @@ -854,6 +915,7 @@ export function findStaticSideEffectImportSpans( const literalIndex = skipWhitespaceAndComments(source, cursor + "import".length); const literal = readLiteralSpecifier(source, literalIndex); if (!literal) { + atStatementStart = true; cursor = nextStatementCursor(source, literalIndex); continue; } @@ -869,6 +931,7 @@ export function findStaticSideEffectImportSpans( if (spans.length >= maxMatches) return spans; } + atStatementStart = false; cursor = literal.end; } From 5477b6b58e2b398052f69c1d4522408740d6c922 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:42:59 +0200 Subject: [PATCH 048/104] fix(observability): persist tenant import evidence --- .../orchestrator/module-loader/index.test.ts | 64 +++++++++++++++++++ .../orchestrator/module-loader/index.ts | 23 +++++-- .../module-loader/module-persistence.test.ts | 16 ++++- .../module-loader/module-persistence.ts | 49 +++++++++++++- 4 files changed, 144 insertions(+), 8 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 3b735de4e3..93d531f3e2 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -514,6 +514,70 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("attributes a typo replayed from a disk-cached dependency", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./dep";`, + `export default function Page() { return label; }`, + ].join("\n"), + "app/dep.tsx": [ + `import { gone } from "./gone";`, + `export const label = gone;`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const pagePath = join(projectDir, "app/page.tsx"); + const diskConfig = { + ...config, + projectId: "disk-cache-project", + contentSourceId: "main", + }; + + const first = await assertRejects(() => loadModule(pagePath, diskConfig), Error); + assertEquals(isTenantBuildFailure(first), true); + + // Mode is part of the process-local cache key but not the persisted + // MDX path-cache key. Switching it gives this simulated new worker an + // empty evidence memo while reusing the dependency from _index.json. + const restartedConfig = { + ...diskConfig, + mode: "production" as const, + moduleCache: new Map(), + }; + const second = await assertRejects(() => loadModule(pagePath, restartedConfig), Error); + assertEquals(isBuildFailure(second), true); + assertEquals(isTenantBuildFailure(second), true); + }); + }, + ); + }); + + it("attributes an executed dynamic dependency that failed to transform", async () => { + await withModuleLoaderFixture( + { + "app/page.ts": [ + `const dependency = await import("./broken");`, + `export const value = dependency.value;`, + ].join("\n"), + "app/broken.ts": `export const value: = "broken";`, + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.ts"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + // The same seam must not launder a framework fault. A module whose imports // all resolve, and which then throws while executing, is an application // error: it must come back out of `loadModule` untagged on both predicates. diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index ac955eddef..05d5c5c05f 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -19,7 +19,10 @@ import { rewriteResolvedDependencyImports, type TransformedModuleDependency, } from "./dependency-resolver.ts"; -import { persistTransformedModule } from "./module-persistence.ts"; +import { + persistTransformedModule, + readPersistedUnresolvedSpecifiers, +} from "./module-persistence.ts"; import { transformModuleCodeWithCache } from "./module-transform-cache.ts"; import { buildModuleTransformCacheVariant, @@ -30,6 +33,7 @@ import { markBuildFailure, markTenantBuildFailure } from "./build-failure.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; import type { DependencyPinningSourceInput } from "#veryfront/transforms/esm/package-registry.ts"; import { MODULE_CACHE_MAX_ENTRIES } from "#veryfront/utils/constants/cache.ts"; +import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; export { isBuildFailure } from "./build-failure.ts"; @@ -149,10 +153,10 @@ export async function transformModuleWithDeps( // cache hit skips `resolveModuleDependencies`, so without this a dependency // that was already transformed contributes nothing and its tenant-authored // dangling import silently loses attribution. - const cachedUnresolvedSpecifiers = unresolvedSpecifiersByCacheKey.get(cacheKey) ?? []; - if (cachedUnresolvedSpecifiers.length > 0) { - cacheUnresolvedSpecifiers(cacheKey, cachedUnresolvedSpecifiers); - } + const memoizedUnresolvedSpecifiers = unresolvedSpecifiersByCacheKey.get(cacheKey); + const cachedUnresolvedSpecifiers = memoizedUnresolvedSpecifiers ?? + await readPersistedUnresolvedSpecifiers(cachedPath, localAdapter); + cacheUnresolvedSpecifiers(cacheKey, cachedUnresolvedSpecifiers); for (const specifier of cachedUnresolvedSpecifiers) { unresolvedSpecifiers.add(specifier); } @@ -231,6 +235,14 @@ export async function transformModuleWithDeps( // branch must not fail the page that merely mentions it. if (!dep.isDynamic) throw error; + // A tenant-source compile failure is deliberately non-fatal until this + // dynamic edge executes. The importer remains authored, so retain that + // provenance for the retry classification seam. Infrastructure errors + // stay framework-owned even if the resulting edge is later missing. + if (isTenantSourceBuildError(error)) { + moduleUnresolvedSpecifiers.add(dep.path); + } + logger.warn("Leaving an unresolvable dynamic dependency as authored:", { path: dep.path, depFilePath: dep.depFilePath, @@ -290,6 +302,7 @@ export async function transformModuleWithDeps( moduleServerOrigin: config.moduleServerOrigin, dependencyPinningCacheKey: config.dependencyPinningCacheKey, isCycleTarget: cycleTargets.has(filePath), + unresolvedSpecifiers: [...moduleUnresolvedSpecifiers], }); cacheUnresolvedSpecifiers(cacheKey, [...moduleUnresolvedSpecifiers]); for (const specifier of moduleUnresolvedSpecifiers) unresolvedSpecifiers.add(specifier); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 955dbd4e3b..1d6b853c2a 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -6,7 +6,10 @@ import { getLocalAdapter } from "#veryfront/platform/adapters/registry.ts"; import { hashCodeHex } from "#veryfront/utils/hash-utils.ts"; import { getModulePathCache } from "#veryfront/transforms/mdx/esm-module-loader/cache/index.ts"; import { buildMdxEsmPathCacheKey } from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; -import { persistTransformedModule } from "./module-persistence.ts"; +import { + persistTransformedModule, + readPersistedUnresolvedSpecifiers, +} from "./module-persistence.ts"; describe("module-loader/module-persistence", () => { it("writes transformed code, registers MDX path-cache, and updates module cache", async () => { @@ -22,6 +25,8 @@ describe("module-loader/module-persistence", () => { await Deno.mkdir(dirname(filePath), { recursive: true }); await Deno.writeTextFile(filePath, "export const page = 1;"); + const unresolvedSpecifiers = ["./missing", "./nested-missing"]; + const result = await persistTransformedModule({ filePath, projectDir, @@ -32,12 +37,19 @@ describe("module-loader/module-persistence", () => { cacheKey, contentSourceId: "preview-main", reactVersion: "19.1.1", + unresolvedSpecifiers, }); - const expectedHash = hashCodeHex(transformedCode).slice(0, 8); + const expectedHash = hashCodeHex( + `${transformedCode}\0${JSON.stringify(unresolvedSpecifiers)}`, + ).slice(0, 8); assertEquals(result, join(tmpDir, `app/page.${expectedHash}.js`)); assertEquals(await Deno.readTextFile(result), transformedCode); assertEquals(moduleCache.get(cacheKey), result); + assertEquals( + await readPersistedUnresolvedSpecifiers(result, localAdapter), + unresolvedSpecifiers, + ); const pathCache = await getModulePathCache(tmpDir); const mdxCacheKey = buildMdxEsmPathCacheKey("_vf_modules/app/page.js", "19.1.1"); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 1adbb52534..516ae2817e 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -8,6 +8,7 @@ import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { join } from "#veryfront/compat/path/index.ts"; import { rendererLogger } from "#veryfront/utils"; import { isCacheWriteRaceError } from "#veryfront/utils/cache-file-ops.ts"; +import { isNotFoundError } from "#veryfront/platform/compat/fs.ts"; import { hashCodeHex } from "#veryfront/utils/hash-utils.ts"; import { getModulePathCache, @@ -17,6 +18,7 @@ import { buildMdxEsmPathCacheKey } from "#veryfront/transforms/mdx/esm-module-lo import { buildModuleTransformCacheVariant } from "./module-cache-lookup.ts"; const logger = rendererLogger.component("module-loader"); +const UNRESOLVED_IMPORTS_SIDECAR_SUFFIX = ".unresolved-imports.json"; /** Maximum number of directories to track to prevent memory leaks. */ const MAX_CREATED_DIRS = 5_000; @@ -72,6 +74,8 @@ export interface PersistTransformedModuleInput { reactVersion?: string; dependencyPinningCacheKey?: string; moduleServerOrigin?: string; + /** Tenant-authored imports left unresolved in this module subtree. */ + unresolvedSpecifiers?: readonly string[]; /** * True when a dynamic import elsewhere closes a cycle back onto this module. * Such an edge is left as authored (`import("../app/page.js")`), so it needs a @@ -80,6 +84,31 @@ export interface PersistTransformedModuleInput { isCycleTarget?: boolean; } +/** Read unresolved-import evidence stored beside a transformed artifact. */ +export async function readPersistedUnresolvedSpecifiers( + modulePath: string, + localAdapter: RuntimeAdapter, +): Promise { + try { + const content = await localAdapter.fs.readFile( + `${modulePath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`, + ); + const decoded = typeof content === "string" ? content : new TextDecoder().decode(content); + const parsed: unknown = JSON.parse(decoded); + if (!Array.isArray(parsed) || !parsed.every((value) => typeof value === "string")) { + return []; + } + return parsed; + } catch (error) { + if (isNotFoundError(error)) return []; + logger.debug("Unresolved-import cache evidence unavailable", { + modulePath: modulePath.slice(-60), + error: error instanceof Error ? error.message : String(error), + }); + return []; + } +} + /** * Whether transformed output exposes a default export, so a cycle alias knows * to re-export it. Covers esbuild's `export default …`, `… as default`, and @@ -135,7 +164,15 @@ async function writeCycleTargetAlias( export async function persistTransformedModule( input: PersistTransformedModuleInput, ): Promise { - const transformedHash = hashCodeHex(input.transformedCode).slice(0, 8); + const unresolvedSpecifiers = [...new Set(input.unresolvedSpecifiers ?? [])].sort(); + const serializedUnresolvedSpecifiers = JSON.stringify(unresolvedSpecifiers); + // Evidence changes the artifact identity only when evidence exists. This + // keeps the common no-evidence path stable and prevents concurrent writers + // with different classification data from sharing one mutable sidecar. + const transformedIdentity = unresolvedSpecifiers.length === 0 + ? input.transformedCode + : `${input.transformedCode}\0${serializedUnresolvedSpecifiers}`; + const transformedHash = hashCodeHex(transformedIdentity).slice(0, 8); const relativePath = input.filePath.startsWith(input.projectDir) ? input.filePath.slice(input.projectDir.length).replace(/^\/+/, "") @@ -185,6 +222,16 @@ export async function persistTransformedModule( } } + // Publish the path cache only after its tenant-attribution evidence is + // durable. A new worker can otherwise reuse the transformed artifact from + // _index.json without knowing which authored imports remained unresolved. + if (unresolvedSpecifiers.length > 0) { + await input.localAdapter.fs.writeFile( + `${tempFilePath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`, + serializedUnresolvedSpecifiers, + ); + } + if (input.contentSourceId) { const normalizedPath = `_vf_modules/${relativePath.replace(/\.(tsx?|jsx|mdx)$/, ".js")}`; const mdxCacheKey = buildMdxEsmPathCacheKey( From 798fc6a6348b03bc70a68f54cdf8232d7119e8dc Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:48:43 +0200 Subject: [PATCH 049/104] fix(transforms): keep delimiter matching linear --- .../utils/source-spans.test.ts | 38 +++ .../esm-module-loader/utils/source-spans.ts | 250 +++++++++--------- 2 files changed, 169 insertions(+), 119 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 1717e69ab4..fede96029e 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -304,6 +304,30 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds imports after regex literals following try statement blocks", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { try {} finally {} /}/.test(x); return true; })() && import("/_vf_modules/finally-lazy.js")}`;', + ), + ["/_vf_modules/finally-lazy.js"], + ); + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { try { throw x; } catch (error) {} /}/.test(x); return true; })() && import("/_vf_modules/catch-lazy.js")}`;', + ), + ["/_vf_modules/catch-lazy.js"], + ); + }); + + it("ignores line-comment parentheses when matching control conditions", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${(() => { if (ok // fake (\n) /}/.test(x); return true; })() && import("/_vf_modules/comment-condition-lazy.js")}`;', + ), + ["/_vf_modules/comment-condition-lazy.js"], + ); + }); + it("finds executable imports after regex braces following control conditions", () => { assertEquals( specifiers( @@ -416,6 +440,20 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps unmatched closing-delimiter division scans within a bounded runtime", () => { + const source = ") / 2;\n".repeat(12_000) + "} / 2;\n".repeat(12_000); + const startedAt = performance.now(); + + assertEquals(specifiers(source), []); + + const durationMs = performance.now() - startedAt; + assert( + durationMs < 750, + `Expected a ${Math.round(source.length / 1024)} KB closing-delimiter scan to finish ` + + `within 750 ms, got ${durationMs.toFixed(1)} ms`, + ); + }); + it("finds imports after division when literal contents look like control conditions", () => { assertEquals( specifiers('foo("if(") / 2 && import("./after-string-division.js");'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 336d52a906..58a1601e95 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -22,6 +22,12 @@ export interface StaticImportSpan { type SpecifierMatcher = (specifier: string) => string | null | undefined; +interface OpenParenContext { + index: number; + isForHeader: boolean; + hasSemicolon: boolean; +} + const MAX_TEMPLATE_LITERAL_DEPTH = 512; function assertTemplateLiteralDepth(depth: number): void { @@ -246,67 +252,17 @@ function keywordBefore(source: string, index: number): string | null { return source.slice(start, end); } -function isControlConditionCloseParen(source: string, index: number, rangeStart: number): boolean { - let depth = 1; - let cursor = index - 1; - - while (cursor >= rangeStart) { - const char = source[cursor]; - - if (char === '"' || char === "'" || char === "`") { - cursor = previousStringLiteralStart(source, cursor, rangeStart) - 1; - continue; - } - - if (char === "/" && source[cursor - 1] === "*") { - const commentStart = source.lastIndexOf("/*", cursor - 2); - cursor = commentStart >= rangeStart ? commentStart - 1 : rangeStart - 1; - continue; - } - - if (char === ")") { - depth++; - cursor--; - continue; - } - - if (char === "(") { - depth--; - if (depth === 0) { - const keyword = keywordBefore(source, cursor); - return keyword === "if" || keyword === "while" || keyword === "for" || - keyword === "with" || keyword === "switch"; - } - cursor--; - continue; - } - - cursor--; - } - - return false; -} - -function previousStringLiteralStart(source: string, index: number, rangeStart: number): number { - const quote = source[index]; - let cursor = index - 1; - - while (cursor >= rangeStart) { - if (source[cursor] === quote && !isEscapedByBackslash(source, cursor)) return cursor; - cursor--; - } - - return rangeStart; -} - -function isEscapedByBackslash(source: string, index: number): boolean { - let cursor = index - 1; - let count = 0; - while (cursor >= 0 && source[cursor] === "\\") { - count++; - cursor--; - } - return count % 2 === 1; +function isControlConditionCloseParen( + source: string, + index: number, + rangeStart: number, + matchingOpenParens: ReadonlyMap, +): boolean { + const openParen = matchingOpenParens.get(index); + if (openParen === undefined || openParen < rangeStart) return false; + const keyword = keywordBefore(source, openParen); + return keyword === "if" || keyword === "while" || keyword === "for" || + keyword === "with" || keyword === "switch" || keyword === "catch"; } function isControlBlockCloseBrace( @@ -314,6 +270,7 @@ function isControlBlockCloseBrace( index: number, rangeStart: number, matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, ): boolean { const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; @@ -321,7 +278,7 @@ function isControlBlockCloseBrace( const beforeOpenBrace = previousSignificantIndex(source, openBrace); return beforeOpenBrace >= rangeStart && source[beforeOpenBrace] === ")" && - isControlConditionCloseParen(source, beforeOpenBrace, rangeStart); + isControlConditionCloseParen(source, beforeOpenBrace, rangeStart, matchingOpenParens); } function isDeclarationBlockCloseBrace( @@ -342,7 +299,24 @@ function isDeclarationBlockCloseBrace( /^class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test(prefix); } -function isForOfKeywordBefore(source: string, index: number, rangeStart: number): boolean { +function isStatementBlockCloseBrace( + source: string, + index: number, + matchingOpenBraces: ReadonlyMap, +): boolean { + const openBrace = matchingOpenBraces.get(index); + if (openBrace === undefined) return false; + const keyword = keywordBefore(source, openBrace); + return keyword === "try" || keyword === "catch" || keyword === "finally" || + keyword === "do" || keyword === "else"; +} + +function isForOfKeywordBefore( + source: string, + index: number, + rangeStart: number, + currentParen: OpenParenContext | undefined, +): boolean { const keywordEnd = previousSignificantIndex(source, index) + 1; let keywordStart = keywordEnd; while (keywordStart > rangeStart && /[A-Za-z_$]/.test(source[keywordStart - 1] ?? "")) { @@ -361,56 +335,7 @@ function isForOfKeywordBefore(source: string, index: number, rangeStart: number) ) { return false; } - - let parenDepth = 0; - let braceDepth = 0; - let bracketDepth = 0; - let cursor = keywordStart - 1; - - while (cursor >= rangeStart) { - const char = source[cursor]; - - if (char === '"' || char === "'" || char === "`") { - cursor = previousStringLiteralStart(source, cursor, rangeStart) - 1; - continue; - } - - if (char === "/" && source[cursor - 1] === "*") { - const commentStart = source.lastIndexOf("/*", cursor - 2); - cursor = commentStart >= rangeStart ? commentStart - 1 : rangeStart - 1; - continue; - } - - if (char === ")") { - parenDepth++; - } else if (char === "(") { - if (parenDepth > 0) { - parenDepth--; - } else if (braceDepth === 0 && bracketDepth === 0) { - return keywordBefore(source, cursor) === "for"; - } - } else if (char === "}") { - braceDepth++; - } else if (char === "{") { - if (braceDepth > 0) braceDepth--; - else if (parenDepth === 0 && bracketDepth === 0) return false; - } else if (char === "]") { - bracketDepth++; - } else if (char === "[") { - if (bracketDepth > 0) bracketDepth--; - } else if ( - char === ";" && - parenDepth === 0 && - braceDepth === 0 && - bracketDepth === 0 - ) { - return false; - } - - cursor--; - } - - return false; + return currentParen?.isForHeader === true && !currentParen.hasSemicolon; } function canStartRegexLiteral( @@ -418,16 +343,28 @@ function canStartRegexLiteral( index: number, rangeStart: number, matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, + currentParen: OpenParenContext | undefined, ): boolean { const previous = previousSignificantIndex(source, index); if (previous < rangeStart) return true; const char = source[previous]; - if (char === ")" && isControlConditionCloseParen(source, previous, rangeStart)) return true; + if ( + char === ")" && + isControlConditionCloseParen(source, previous, rangeStart, matchingOpenParens) + ) return true; if ( char === "}" && - (isControlBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces) || - isDeclarationBlockCloseBrace(source, previous, matchingOpenBraces)) + (isControlBlockCloseBrace( + source, + previous, + rangeStart, + matchingOpenBraces, + matchingOpenParens, + ) || + isDeclarationBlockCloseBrace(source, previous, matchingOpenBraces) || + isStatementBlockCloseBrace(source, previous, matchingOpenBraces)) ) return true; if ( (char === "+" || char === "-") && @@ -439,7 +376,9 @@ function canStartRegexLiteral( if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; const keyword = keywordBefore(source, index); - if (keyword === "of") return isForOfKeywordBefore(source, index, rangeStart); + if (keyword === "of") { + return isForOfKeywordBefore(source, index, rangeStart, currentParen); + } return [ "case", @@ -499,6 +438,8 @@ function skipExpressionIgnored( rangeStart: number, depth: number, matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, + currentParen: OpenParenContext | undefined, ): number { const char = source[index]; const next = source[index + 1]; @@ -515,7 +456,17 @@ function skipExpressionIgnored( if (char === '"' || char === "'") return skipIgnored(source, index); if (char === "`") return skipFullTemplateLiteral(source, index, depth + 1); - if (char === "/" && canStartRegexLiteral(source, index, rangeStart, matchingOpenBraces)) { + if ( + char === "/" && + canStartRegexLiteral( + source, + index, + rangeStart, + matchingOpenBraces, + matchingOpenParens, + currentParen, + ) + ) { return skipRegexLiteral(source, index); } @@ -533,6 +484,8 @@ function findTemplateExpressionEnd( let braceDepth = 1; const openBraces: number[] = []; const matchingOpenBraces = new Map(); + const openParens: OpenParenContext[] = []; + const matchingOpenParens = new Map(); while (cursor < source.length) { const skipped = skipExpressionIgnored( @@ -541,6 +494,8 @@ function findTemplateExpressionEnd( expressionIndex, depth, matchingOpenBraces, + matchingOpenParens, + openParens.at(-1), ); if (skipped !== cursor) { cursor = skipped; @@ -563,6 +518,27 @@ function findTemplateExpressionEnd( continue; } + if (source[cursor] === "(") { + openParens.push({ + index: cursor, + isForHeader: keywordBefore(source, cursor) === "for", + hasSemicolon: false, + }); + cursor++; + continue; + } + + if (source[cursor] === ")") { + const openParen = openParens.pop(); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + cursor++; + continue; + } + + if (source[cursor] === ";" && openParens.at(-1)?.isForHeader) { + openParens.at(-1)!.hasSemicolon = true; + } + cursor++; } @@ -741,6 +717,8 @@ function scanDynamicImportRange( let cursor = rangeStart; const openBraces: number[] = []; const matchingOpenBraces = new Map(); + const openParens: OpenParenContext[] = []; + const matchingOpenParens = new Map(); while (cursor < rangeEnd) { const char = source[cursor]; @@ -755,7 +733,17 @@ function scanDynamicImportRange( continue; } - if (char === "/" && canStartRegexLiteral(source, cursor, rangeStart, matchingOpenBraces)) { + if ( + char === "/" && + canStartRegexLiteral( + source, + cursor, + rangeStart, + matchingOpenBraces, + matchingOpenParens, + openParens.at(-1), + ) + ) { cursor = skipRegexLiteral(source, cursor); continue; } @@ -786,6 +774,27 @@ function scanDynamicImportRange( continue; } + if (char === "(") { + openParens.push({ + index: cursor, + isForHeader: keywordBefore(source, cursor) === "for", + hasSemicolon: false, + }); + cursor++; + continue; + } + + if (char === ")") { + const openParen = openParens.pop(); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + cursor++; + continue; + } + + if (char === ";" && openParens.at(-1)?.isForHeader) { + openParens.at(-1)!.hasSemicolon = true; + } + // `import` used as an expression: not preceded by an identifier char or a // dot (which would make it `foo.import` or part of a longer word). if ( @@ -803,6 +812,9 @@ function scanDynamicImportRange( cursor++; continue; } + // The scanner jumps directly from `import` to its argument, so record the + // opening parenthesis that the ordinary character walk does not visit. + openParens.push({ index: parenIndex, isForHeader: false, hasSemicolon: false }); const literalIndex = skipWhitespaceAndComments(source, parenIndex + 1); if (literalIndex >= rangeEnd) { From 5ab7cd5af9af19fde42c06529de6a17812fae359 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:52:36 +0200 Subject: [PATCH 050/104] fix(observability): track side-effect import evidence --- .../module-loader/dependency-resolver.test.ts | 54 +++++++++++++++++++ .../module-loader/dependency-resolver.ts | 40 +++++++++++--- .../orchestrator/module-loader/index.test.ts | 18 ++----- .../module-loader/module-persistence.ts | 6 ++- .../mdx/esm-module-loader/cache-format.ts | 1 + .../mdx/esm-module-loader/cache/index.test.ts | 34 +++++++++++- .../mdx/esm-module-loader/cache/index.ts | 13 ++++- 7 files changed, 141 insertions(+), 25 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts index 0e4a2013bc..a33c3baf1d 100644 --- a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts +++ b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts @@ -102,6 +102,60 @@ describe("module-loader/dependency-resolver", () => { ); }); + it("fails closed when side-effect import collection exceeds its bound", async () => { + const adapter = await getLocalAdapter(); + const fileContent = Array.from( + { length: 501 }, + (_, index) => `import "./value-${index}";`, + ).join("\n"); + + await assertRejects( + () => + resolveModuleDependencies({ + adapter, + fileContent, + filePath: "/project/page.tsx", + projectDir: "/project", + }), + RangeError, + "more than 500 side-effect relative imports", + ); + }); + + it("resolves and rewrites side-effect alias and relative imports", async () => { + await withDependencyFixture( + { + "app/page.tsx": [ + `import "@/setup";`, + `import "./local-setup";`, + `export default function Page() { return null; }`, + ].join("\n"), + "components/setup.ts": `globalThis.aliasReady = true;`, + "app/local-setup.ts": `globalThis.localReady = true;`, + }, + async ({ projectDir }) => { + const adapter = await getLocalAdapter(); + const filePath = join(projectDir, "app/page.tsx"); + const fileContent = await Deno.readTextFile(filePath); + + const deps = await resolveModuleDependencies({ + adapter, + fileContent, + filePath, + projectDir, + }); + + assertEquals(deps.length, 2); + const rewritten = rewriteResolvedDependencyImports( + fileContent, + deps.map((dep, index) => ({ ...dep, depTempPath: `/tmp/setup-${index}.js` })), + ); + assertStringIncludes(rewritten, `import "file:///tmp/setup-0.js";`); + assertStringIncludes(rewritten, `import "file:///tmp/setup-1.js";`); + }, + ); + }); + it("resolves alias and relative imports while ignoring already transformed file imports", async () => { await withDependencyFixture( { diff --git a/src/rendering/orchestrator/module-loader/dependency-resolver.ts b/src/rendering/orchestrator/module-loader/dependency-resolver.ts index 4aeee4ec20..6cbc3bdf55 100644 --- a/src/rendering/orchestrator/module-loader/dependency-resolver.ts +++ b/src/rendering/orchestrator/module-loader/dependency-resolver.ts @@ -4,6 +4,7 @@ import { parallelMap, rendererLogger } from "#veryfront/utils"; import { findDynamicImportSpans, findStaticImportFromSpans, + findStaticSideEffectImportSpans, replaceSourceSpans, type SourceSpanReplacement, type StaticImportSpan, @@ -40,6 +41,7 @@ type AliasImport = { start: number; end: number; isDynamic: boolean; + isSideEffect: boolean; }; type RelativeImport = { full: string; @@ -48,6 +50,7 @@ type RelativeImport = { start: number; end: number; isDynamic: boolean; + isSideEffect: boolean; }; /** Resolved local module dependency discovered in a source module. */ @@ -61,6 +64,8 @@ export type ResolvedModuleDependency = { isLocalLib: boolean; /** True when discovered inside `import("…")` rather than a static import. */ isDynamic: boolean; + /** True when discovered in a bare `import "…"` statement. */ + isSideEffect?: boolean; }; /** Resolved dependency after its source module has been transformed to a temp file. */ @@ -80,7 +85,7 @@ const matchAlias = (specifier: string) => specifier.startsWith("@/") ? specifier const matchRelative = (specifier: string) => specifier.match(/^(\.\.?\/[^?]+)(?:\?.*)?$/)?.[1]; function collectAliasImports(fileContent: string): AliasImport[] { - const toAlias = (isDynamic: boolean) => + const toAlias = (isDynamic: boolean, isSideEffect = false) => ( { original, path, start, end }: { original: string; @@ -88,13 +93,17 @@ function collectAliasImports(fileContent: string): AliasImport[] { start: number; end: number; }, - ): AliasImport => ({ full: original, path, start, end, isDynamic }); + ): AliasImport => ({ full: original, path, start, end, isDynamic, isSideEffect }); return [ ...collectBoundedSpans( (maxMatches) => findStaticImportFromSpans(fileContent, matchAlias, maxMatches), "static alias", ).map(toAlias(false)), + ...collectBoundedSpans( + (maxMatches) => findStaticSideEffectImportSpans(fileContent, matchAlias, maxMatches), + "side-effect alias", + ).map(toAlias(false, true)), ...collectBoundedSpans( (maxMatches) => findDynamicImportSpans(fileContent, matchAlias, maxMatches), "dynamic alias", @@ -103,7 +112,7 @@ function collectAliasImports(fileContent: string): AliasImport[] { } function collectRelativeImports(fileContent: string, fileDir: string): RelativeImport[] { - const toRelative = (isDynamic: boolean) => + const toRelative = (isDynamic: boolean, isSideEffect = false) => ( { original, path, start, end }: { original: string; @@ -111,13 +120,25 @@ function collectRelativeImports(fileContent: string, fileDir: string): RelativeI start: number; end: number; }, - ): RelativeImport => ({ full: original, path, fromDir: fileDir, start, end, isDynamic }); + ): RelativeImport => ({ + full: original, + path, + fromDir: fileDir, + start, + end, + isDynamic, + isSideEffect, + }); return [ ...collectBoundedSpans( (maxMatches) => findStaticImportFromSpans(fileContent, matchRelative, maxMatches), "static relative", ).map(toRelative(false)), + ...collectBoundedSpans( + (maxMatches) => findStaticSideEffectImportSpans(fileContent, matchRelative, maxMatches), + "side-effect relative", + ).map(toRelative(false, true)), ...collectBoundedSpans( (maxMatches) => findDynamicImportSpans(fileContent, matchRelative, maxMatches), "dynamic relative", @@ -191,6 +212,7 @@ async function resolveRelativeImport( depFilePath, isLocalLib: false, isDynamic: imp.isDynamic, + isSideEffect: imp.isSideEffect, }; } @@ -233,9 +255,13 @@ export function rewriteResolvedDependencyImports( start: dep.start, end: dep.end, expected: dep.full, - // A dynamic span covers only the quoted specifier; a static one covers the - // whole `from "…"` clause. - replacement: dep.isDynamic ? `"${moduleUrl}"` : `from "${moduleUrl}"`, + // A dynamic span covers only the quoted specifier, a side-effect span + // covers the bare import, and a static binding span covers `from "…"`. + replacement: dep.isSideEffect + ? `import "${moduleUrl}"` + : dep.isDynamic + ? `"${moduleUrl}"` + : `from "${moduleUrl}"`, }; }); return replaceSourceSpans(fileContent, replacements); diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 93d531f3e2..8414bb15c8 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -366,13 +366,7 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); - // `findStaticImportFromSpans` only matches imports with a `from` clause, so a - // bare side-effect import is never resolved and therefore never recorded as - // dropped. Such a typo consequently stays at error level rather than being - // downgraded. That is the safe direction — over-reporting severity, never - // hiding a framework fault — but it is a real gap, so it is pinned here - // rather than left to be discovered as a surprise. - it("leaves a missing bare side-effect import at framework severity", async () => { + it("tags a missing bare side-effect import as a tenant build failure", async () => { await withModuleLoaderFixture( { "app/page.tsx": [ @@ -388,10 +382,8 @@ describe("module-loader/loadModule build-failure tagging", () => { ); assertEquals(isMissingModuleError(error), true); - // Still a build failure — the signal must not be dropped ... assertEquals(isBuildFailure(error), true); - // ... but not attributed to the tenant without evidence. - assertEquals(isTenantBuildFailure(error), false); + assertEquals(isTenantBuildFailure(error), true); }); }, ); @@ -436,9 +428,9 @@ describe("module-loader/loadModule build-failure tagging", () => { assertEquals(isMissingModuleError(error), true); assertEquals(isBuildFailure(error), true); // The first transform dropped `./late`, but the rebuild resolved it. - // Its separate bare side-effect failure is not resolver evidence and - // must not inherit the stale tenant classification from build one. - assertEquals(isTenantBuildFailure(error), false); + // Classification must come from the dependency's separate bare + // side-effect failure, not stale evidence from build one. + assertEquals(isTenantBuildFailure(error), true); }); }, ); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 516ae2817e..3c538bcf23 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -14,11 +14,13 @@ import { getModulePathCache, saveModulePathCache, } from "#veryfront/transforms/mdx/esm-module-loader/cache/index.ts"; -import { buildMdxEsmPathCacheKey } from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; +import { + buildMdxEsmPathCacheKey, + UNRESOLVED_IMPORTS_SIDECAR_SUFFIX, +} from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; import { buildModuleTransformCacheVariant } from "./module-cache-lookup.ts"; const logger = rendererLogger.component("module-loader"); -const UNRESOLVED_IMPORTS_SIDECAR_SUFFIX = ".unresolved-imports.json"; /** Maximum number of directories to track to prevent memory leaks. */ const MAX_CREATED_DIRS = 5_000; diff --git a/src/transforms/mdx/esm-module-loader/cache-format.ts b/src/transforms/mdx/esm-module-loader/cache-format.ts index 2902bd18db..fb88b5fad1 100644 --- a/src/transforms/mdx/esm-module-loader/cache-format.ts +++ b/src/transforms/mdx/esm-module-loader/cache-format.ts @@ -12,6 +12,7 @@ import { hashString } from "./utils/hash.ts"; const ALL_FILE_URL_PATTERN_SOURCE = /file:\/\/([^"'\s]+)/.source; const MJS_FILE_URL_PATTERN_SOURCE = /file:\/\/([^"'\s]+\.mjs)/.source; const CACHE_NAMESPACE_SENTINEL = "__vf_cache_namespace__"; +export const UNRESOLVED_IMPORTS_SIDECAR_SUFFIX = ".unresolved-imports.json"; const PUBLIC_RUNTIME_SPECIFIERS = [ "veryfront/head", "veryfront/router", diff --git a/src/transforms/mdx/esm-module-loader/cache/index.test.ts b/src/transforms/mdx/esm-module-loader/cache/index.test.ts index d4360cea6c..73c119910f 100644 --- a/src/transforms/mdx/esm-module-loader/cache/index.test.ts +++ b/src/transforms/mdx/esm-module-loader/cache/index.test.ts @@ -23,7 +23,11 @@ import { exists, readTextFile, remove, writeTextFile } from "#veryfront/compat/f import { runWithCacheDir } from "#veryfront/utils/cache-dir.ts"; import { cacheModule } from "../module-fetcher/module-cache.ts"; import { rendererLogger as log } from "#veryfront/utils"; -import { buildMdxEsmModuleFileName, buildMdxEsmPathCacheKey } from "../cache-format.ts"; +import { + buildMdxEsmModuleFileName, + buildMdxEsmPathCacheKey, + UNRESOLVED_IMPORTS_SIDECAR_SUFFIX, +} from "../cache-format.ts"; import { getCacheStats } from "#veryfront/utils/memory/index.ts"; import { formatCacheVersionSegment } from "#veryfront/utils/cache-version.ts"; import { hashCodeHex } from "#veryfront/utils/hash-utils.ts"; @@ -451,6 +455,34 @@ describe("invalidateModulePaths — disk persistence", () => { } }); + it("deletes unresolved-import evidence beside stale modules", async () => { + clearModulePathCache(); + + const cacheDir = await makeTempDir({ prefix: "vf-mdx-invalidate-evidence-" }); + const versionedKey = buildMdxEsmPathCacheKey("_vf_modules/components/EmptyState.js"); + const staleMjsPath = join(cacheDir, buildMdxEsmModuleFileName("stale-evidence")); + const evidencePath = `${staleMjsPath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`; + + try { + await writeTextFile(staleMjsPath, `export default "stale";`); + await writeTextFile(evidencePath, JSON.stringify(["./missing"])); + await writeTextFile( + join(cacheDir, "_index.json"), + JSON.stringify({ [versionedKey]: staleMjsPath }), + ); + await getModulePathCache(cacheDir); + + invalidateModulePaths(["components/EmptyState.tsx"]); + await waitForDiskCleanup(); + + assertEquals(await exists(staleMjsPath), false); + assertEquals(await exists(evidencePath), false); + } finally { + await remove(cacheDir, { recursive: true }).catch(() => {}); + clearModulePathCache(); + } + }); + it("cacheModule does not resurrect invalidated entries via disk content hash hit", async () => { clearModulePathCache(); diff --git a/src/transforms/mdx/esm-module-loader/cache/index.ts b/src/transforms/mdx/esm-module-loader/cache/index.ts index 680568e669..82412c56e7 100644 --- a/src/transforms/mdx/esm-module-loader/cache/index.ts +++ b/src/transforms/mdx/esm-module-loader/cache/index.ts @@ -20,7 +20,11 @@ import { LOG_PREFIX_MDX_LOADER } from "../constants.ts"; import { LRUCache } from "#veryfront/utils/lru-wrapper.ts"; import { registerCache } from "#veryfront/utils/memory/index.ts"; import { hashCodeHex } from "#veryfront/utils/hash-utils.ts"; -import { buildMdxEsmPathCacheKey, MDX_ESM_ALL_FILE_URL_PATTERN_SOURCE } from "../cache-format.ts"; +import { + buildMdxEsmPathCacheKey, + MDX_ESM_ALL_FILE_URL_PATTERN_SOURCE, + UNRESOLVED_IMPORTS_SIDECAR_SUFFIX, +} from "../cache-format.ts"; import { ensureMdxModuleDependencies } from "../module-fetcher/dependency-recovery.ts"; import { findStaticImportFromSpans } from "../utils/source-spans.ts"; import { @@ -352,7 +356,7 @@ export function invalidateModulePaths(changedPaths: string[]): void { } } - // Delete stale .mjs files from disk + // Delete stale modules and their tenant-attribution evidence from disk. for (const mjsPath of staleMjsFiles) { try { await localFs.remove(mjsPath); @@ -360,6 +364,11 @@ export function invalidateModulePaths(changedPaths: string[]): void { } catch (_) { /* expected: file may already be gone */ } + try { + await localFs.remove(`${mjsPath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`); + } catch (_) { + /* expected: most modules have no unresolved-import evidence */ + } } }; _pendingDiskCleanup = _pendingDiskCleanup.then(cleanup, cleanup).catch((error) => { From 6a572264c0e7a58b3145c2f11ad7efdcbc825d3f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 08:53:49 +0200 Subject: [PATCH 051/104] test(transforms): cover nested import parens --- .../mdx/esm-module-loader/utils/source-spans.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index fede96029e..7e6024248e 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -488,6 +488,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps nested import parentheses aligned with outer control conditions", () => { + assertEquals( + specifiers( + 'if (f(import("./inside.js"))) {} /}/.test(x); import("./after-block-regex.js");', + ), + ["./inside.js", "./after-block-regex.js"], + ); + }); + it("finds imports after regex literals following noisy control blocks", () => { assertEquals( specifiers( From 4cffb2fd608978f2c8bdc88394fdf2d6d1d26bf9 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:06:34 +0200 Subject: [PATCH 052/104] fix(transforms): close import scanner edge cases --- .../utils/source-spans.test.ts | 51 ++++++++ .../esm-module-loader/utils/source-spans.ts | 117 +++++++++++++++++- .../shared/specifier-suffix.test.ts | 17 +++ src/transforms/shared/specifier-suffix.ts | 3 +- 4 files changed, 183 insertions(+), 5 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 7e6024248e..c046e2296a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -143,6 +143,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ["./value.js"], ); }); + + it("finds static imports after top-level block declarations", () => { + assertEquals( + findStaticImportFromSpans( + 'function f(){}import value from "./after-function.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-function.js"], + ); + }); }); describe("findDynamicImportSpans", () => { @@ -190,6 +201,18 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(specifiers(`const m = await import("./foo.js");`), ["./foo.js"]); }); + it("matches cooked quoted and template-literal specifiers while preserving source spans", () => { + const quotedSource = 'import("./lazy\\x2ejs");'; + const templateSource = "import(`./lazy\\u002ejs`);"; + const [quoted] = findDynamicImportSpans(quotedSource, matchRelative, UNBOUNDED); + const [template] = findDynamicImportSpans(templateSource, matchRelative, UNBOUNDED); + + assertEquals(quoted?.path, "./lazy.js"); + assertEquals(quoted?.original, '"./lazy\\x2ejs"'); + assertEquals(template?.path, "./lazy.js"); + assertEquals(template?.original, "`./lazy\\u002ejs`"); + }); + it("finds a literal specifier with import attributes", () => { assertEquals( specifiers(`await import("./data.json", { with: { type: "json" } });`), @@ -262,6 +285,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds executable imports after regex literals following new", () => { + assertEquals( + vfModuleSpecifiers( + 'const html = `${new /}/.constructor() && import("/_vf_modules/lazy.js")}`;', + ), + ["/_vf_modules/lazy.js"], + ); + }); + it("finds executable imports after regex literals following closed blocks", () => { assertEquals( vfModuleSpecifiers( @@ -667,5 +699,24 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); assertEquals(spans.map((span) => span.path), ["./a.js", "./b.js"]); }); + + it("finds side-effect imports after top-level block declarations", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'function f(){}import "./after-function.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-function.js"], + ); + assertEquals( + findStaticSideEffectImportSpans( + 'class C {}import "./after-class.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-class.js"], + ); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 58a1601e95..efdade303d 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -29,6 +29,7 @@ interface OpenParenContext { } const MAX_TEMPLATE_LITERAL_DEPTH = 512; +const StringFromCodePoint = String.fromCodePoint; function assertTemplateLiteralDepth(depth: number): void { if (depth > MAX_TEMPLATE_LITERAL_DEPTH) { @@ -156,6 +157,109 @@ function nextStatementCursor(source: string, index: number): number { return Math.min(...candidates) + 1; } +function hexDigitValue(char: string | undefined): number { + if (char === undefined) return -1; + const code = char.charCodeAt(0); + if (code >= 48 && code <= 57) return code - 48; + if (code >= 65 && code <= 70) return code - 55; + if (code >= 97 && code <= 102) return code - 87; + return -1; +} + +function decodeHexEscape(source: string, start: number, length: number): number | null { + let value = 0; + for (let offset = 0; offset < length; offset++) { + const digit = hexDigitValue(source[start + offset]); + if (digit === -1) return null; + value = value * 16 + digit; + } + return value; +} + +function decodeLiteralContents(source: string, start: number, end: number): string | null { + let result = ""; + let cursor = start; + + while (cursor < end) { + const char = source[cursor]!; + if (char !== "\\") { + result += char; + cursor++; + continue; + } + + const escaped = source[cursor + 1]; + if (escaped === undefined || cursor + 1 >= end) return null; + + if (escaped === "\r" || escaped === "\n" || escaped === "\u2028" || escaped === "\u2029") { + cursor += escaped === "\r" && source[cursor + 2] === "\n" ? 3 : 2; + continue; + } + + const simpleEscape = { + b: "\b", + f: "\f", + n: "\n", + r: "\r", + t: "\t", + v: "\v", + }[escaped]; + if (simpleEscape !== undefined) { + result += simpleEscape; + cursor += 2; + continue; + } + + if (escaped === "0") { + if (/[0-9]/.test(source[cursor + 2] ?? "")) return null; + result += "\0"; + cursor += 2; + continue; + } + if (/[1-9]/.test(escaped)) return null; + + if (escaped === "x") { + const value = decodeHexEscape(source, cursor + 2, 2); + if (value === null || cursor + 4 > end) return null; + result += StringFromCodePoint(value); + cursor += 4; + continue; + } + + if (escaped === "u") { + if (source[cursor + 2] === "{") { + let escapeEnd = cursor + 3; + let value = 0; + let digitCount = 0; + while (escapeEnd < end && source[escapeEnd] !== "}") { + const digit = hexDigitValue(source[escapeEnd]); + if (digit === -1) return null; + value = value * 16 + digit; + digitCount++; + escapeEnd++; + } + if ( + digitCount === 0 || source[escapeEnd] !== "}" || value > 0x10ffff + ) return null; + result += StringFromCodePoint(value); + cursor = escapeEnd + 1; + continue; + } + + const value = decodeHexEscape(source, cursor + 2, 4); + if (value === null || cursor + 6 > end) return null; + result += StringFromCodePoint(value); + cursor += 6; + continue; + } + + result += escaped; + cursor += 2; + } + + return result; +} + function readQuotedSpecifier( source: string, quoteIndex: number, @@ -170,9 +274,11 @@ function readQuotedSpecifier( continue; } if (source[cursor] === quote) { + const specifier = decodeLiteralContents(source, quoteIndex + 1, cursor); + if (specifier === null) return null; return { end: cursor + 1, - specifier: source.slice(quoteIndex + 1, cursor), + specifier, }; } cursor++; @@ -197,9 +303,11 @@ function readLiteralSpecifier( } if (source[cursor] === "$" && source[cursor + 1] === "{") return null; if (source[cursor] === "`") { + const specifier = decodeLiteralContents(source, literalIndex + 1, cursor); + if (specifier === null) return null; return { end: cursor + 1, - specifier: source.slice(literalIndex + 1, cursor), + specifier, }; } cursor++; @@ -387,6 +495,7 @@ function canStartRegexLiteral( "else", "in", "instanceof", + "new", "await", "return", "throw", @@ -628,7 +737,7 @@ export function findStaticImportFromSpans( continue; } - if (char === ";" || char === "\n") { + if (char === ";" || char === "\n" || char === "}") { atStatementStart = true; cursor++; continue; @@ -908,7 +1017,7 @@ export function findStaticSideEffectImportSpans( continue; } - if (char === ";" || char === "\n") { + if (char === ";" || char === "\n" || char === "}") { atStatementStart = true; cursor++; continue; diff --git a/src/transforms/shared/specifier-suffix.test.ts b/src/transforms/shared/specifier-suffix.test.ts index b097e68873..fd63ee7a3f 100644 --- a/src/transforms/shared/specifier-suffix.test.ts +++ b/src/transforms/shared/specifier-suffix.test.ts @@ -31,4 +31,21 @@ describe("transforms/shared/specifier-suffix", () => { assertEquals(`${path}${suffix}`, specifier); } }); + + it("uses the captured minimum function after project code replaces Math.min", () => { + const mathMin = Math.min; + + try { + Math.min = () => { + throw new Error("poisoned Math.min"); + }; + + assertEquals(splitSpecifierSuffix("@/Card.tsx?raw#hero"), { + path: "@/Card.tsx", + suffix: "?raw#hero", + }); + } finally { + Math.min = mathMin; + } + }); }); diff --git a/src/transforms/shared/specifier-suffix.ts b/src/transforms/shared/specifier-suffix.ts index 2c49cd41c4..a4e115261f 100644 --- a/src/transforms/shared/specifier-suffix.ts +++ b/src/transforms/shared/specifier-suffix.ts @@ -5,6 +5,7 @@ */ const ReflectApply = Reflect.apply; +const MathMin = Math.min; const StringIndexOf = String.prototype.indexOf; const StringSlice = String.prototype.slice; @@ -42,7 +43,7 @@ export function splitSpecifierSuffix(specifier: string): SplitSpecifier { ? hashStart : hashStart === -1 ? queryStart - : Math.min(queryStart, hashStart); + : MathMin(queryStart, hashStart); if (suffixStart === -1) return { path: specifier, suffix: "" }; return { From c37f36b833737fa17683f09289ceacab85894e5c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:15:47 +0200 Subject: [PATCH 053/104] fix(transforms): preserve scanner syntax validation --- src/rendering/chunk-optimizer.test.ts | 3 +- .../chunk-optimizer/source-imports.ts | 127 +----------------- .../utils/source-spans.test.ts | 37 +++++ .../esm-module-loader/utils/source-spans.ts | 122 ++++++++++++++--- 4 files changed, 148 insertions(+), 141 deletions(-) diff --git a/src/rendering/chunk-optimizer.test.ts b/src/rendering/chunk-optimizer.test.ts index cab3f6d731..686409c44f 100644 --- a/src/rendering/chunk-optimizer.test.ts +++ b/src/rendering/chunk-optimizer.test.ts @@ -491,6 +491,7 @@ describe("rendering/chunk-optimizer", () => { "~~~", 'import React from "react";', 'import EscapedReact from "\\x72eact";', + 'import LiteralBackslash from "./literal\\\\x2ejs";', 'import ReactAgain from "react";', ].join("\n"); const fs = createMockFS( @@ -503,7 +504,7 @@ describe("rendering/chunk-optimizer", () => { const analysis = await analyzeProjectChunks("/project", fs); const page = analysis.pages.get("/project/pages/index.mdx"); assertExists(page); - assertEquals(page.local, ["./lazy.ts"]); + assertEquals(page.local, ["./lazy.ts", "./literal\\x2ejs"]); assertEquals(page.remote, []); assertEquals(page.shared, [ "side-effect", diff --git a/src/rendering/chunk-optimizer/source-imports.ts b/src/rendering/chunk-optimizer/source-imports.ts index c51727ee08..064fb21839 100644 --- a/src/rendering/chunk-optimizer/source-imports.ts +++ b/src/rendering/chunk-optimizer/source-imports.ts @@ -208,136 +208,17 @@ function maskMarkdownRegions(source: string): string { return maskHtmlComments(parts.join("")); } -function readHexDigit(char: string | undefined): number { - if (char === undefined) return -1; - const code = char.charCodeAt(0); - if (code >= 0x30 && code <= 0x39) return code - 0x30; - if (code >= 0x41 && code <= 0x46) return code - 0x41 + 10; - if (code >= 0x61 && code <= 0x66) return code - 0x61 + 10; - return -1; -} - -function readFixedHex( - value: string, - start: number, - length: number, -): { readonly value: number; readonly end: number } { - let parsed = 0; - for (let index = 0; index < length; index++) { - const digit = readHexDigit(value[start + index]); - if (digit === -1) { - throw new SyntaxError("Invalid escaped module specifier"); - } - parsed = parsed * 16 + digit; - } - return { value: parsed, end: start + length }; -} - -function decodeModuleSpecifier(value: string): string { - if (!value.includes("\\")) return value; - - let decoded = ""; - let cursor = 0; - while (cursor < value.length) { - const char = value[cursor++]!; - if (char !== "\\") { - if (char === "\n" || char === "\r") { - throw new SyntaxError("Module specifiers cannot contain line terminators"); - } - decoded += char; - continue; - } - if (cursor >= value.length) { - throw new SyntaxError("Invalid escaped module specifier"); - } - - const escaped = value[cursor++]!; - const simple = { - b: "\b", - f: "\f", - n: "\n", - r: "\r", - t: "\t", - v: "\v", - "\\": "\\", - "'": "'", - '"': '"', - } as const; - if (Object.hasOwn(simple, escaped)) { - decoded += simple[escaped as keyof typeof simple]; - continue; - } - if (escaped === "\n") continue; - if (escaped === "\r") { - if (value[cursor] === "\n") cursor++; - continue; - } - if (escaped === "0") { - if (/[0-9]/.test(value[cursor] ?? "")) { - throw new SyntaxError( - "Legacy octal escapes are not valid in module specifiers", - ); - } - decoded += "\0"; - continue; - } - if (escaped === "x") { - const parsed = readFixedHex(value, cursor, 2); - decoded += String.fromCharCode(parsed.value); - cursor = parsed.end; - continue; - } - if (escaped === "u") { - if (value[cursor] === "{") { - const closing = value.indexOf("}", cursor + 1); - if (closing === -1 || closing === cursor + 1) { - throw new SyntaxError("Invalid Unicode escape in module specifier"); - } - let codePoint = 0; - for (let index = cursor + 1; index < closing; index++) { - const digit = readHexDigit(value[index]); - if (digit === -1) { - throw new SyntaxError("Invalid Unicode escape in module specifier"); - } - codePoint = codePoint * 16 + digit; - if (codePoint > 0x10ffff) { - throw new SyntaxError( - "Module specifier Unicode escape is out of range", - ); - } - } - decoded += String.fromCodePoint(codePoint); - cursor = closing + 1; - continue; - } - const parsed = readFixedHex(value, cursor, 4); - decoded += String.fromCharCode(parsed.value); - cursor = parsed.end; - continue; - } - if (escaped >= "1" && escaped <= "9") { - throw new SyntaxError( - "Legacy numeric escapes are not valid in module specifiers", - ); - } - - decoded += escaped; - } - return decoded; -} - function validateImportSpecifier(value: string): string { - const decoded = decodeModuleSpecifier(value); if ( - decoded.length === 0 || - decoded.length > MAX_IMPORT_SPECIFIER_CHARS || - hasControlCharacter(decoded) + value.length === 0 || + value.length > MAX_IMPORT_SPECIFIER_CHARS || + hasControlCharacter(value) ) { throw new TypeError( `Chunk analysis import specifiers must contain between 1 and ${MAX_IMPORT_SPECIFIER_CHARS} characters without control characters`, ); } - return decoded; + return value; } /** diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index c046e2296a..a7e5bf4cda 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -204,13 +204,28 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { it("matches cooked quoted and template-literal specifiers while preserving source spans", () => { const quotedSource = 'import("./lazy\\x2ejs");'; const templateSource = "import(`./lazy\\u002ejs`);"; + const escapedBackslashSource = 'import("./lazy\\\\x2ejs");'; const [quoted] = findDynamicImportSpans(quotedSource, matchRelative, UNBOUNDED); const [template] = findDynamicImportSpans(templateSource, matchRelative, UNBOUNDED); + const [escapedBackslash] = findDynamicImportSpans( + escapedBackslashSource, + matchRelative, + UNBOUNDED, + ); assertEquals(quoted?.path, "./lazy.js"); assertEquals(quoted?.original, '"./lazy\\x2ejs"'); assertEquals(template?.path, "./lazy.js"); assertEquals(template?.original, "`./lazy\\u002ejs`"); + assertEquals(escapedBackslash?.path, "./lazy\\x2ejs"); + }); + + it("rejects malformed escaped import specifiers", () => { + assertThrows( + () => findDynamicImportSpans('import("./lazy\\xZZ");', matchRelative, UNBOUNDED), + SyntaxError, + "escaped module specifier", + ); }); it("finds a literal specifier with import attributes", () => { @@ -294,6 +309,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after plain and labeled blocks", () => { + assertEquals( + vfModuleSpecifiers('{} /import("\\/_vf_modules\\/plain.js")/.test(value);'), + [], + ); + assertEquals( + vfModuleSpecifiers('label: {} /import("\\/_vf_modules\\/labeled.js")/.test(value);'), + [], + ); + }); + it("finds executable imports after regex literals following closed blocks", () => { assertEquals( vfModuleSpecifiers( @@ -718,5 +744,16 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ["./after-class.js"], ); }); + + it("ignores side-effect import text inside regex literals", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'const r = /;import "\\/_vf_modules\\/a.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index efdade303d..de903889aa 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -176,22 +176,43 @@ function decodeHexEscape(source: string, start: number, length: number): number return value; } -function decodeLiteralContents(source: string, start: number, end: number): string | null { +function invalidEscapedSpecifier(): never { + throw new SyntaxError("Invalid escaped module specifier"); +} + +function isLineTerminator(char: string): boolean { + return char === "\r" || char === "\n" || char === "\u2028" || char === "\u2029"; +} + +function decodeLiteralContents( + source: string, + start: number, + end: number, + allowLineTerminators: boolean, +): string { let result = ""; let cursor = start; while (cursor < end) { const char = source[cursor]!; if (char !== "\\") { + if (isLineTerminator(char)) { + if (!allowLineTerminators) invalidEscapedSpecifier(); + if (char === "\r") { + result += "\n"; + cursor += source[cursor + 1] === "\n" ? 2 : 1; + continue; + } + } result += char; cursor++; continue; } const escaped = source[cursor + 1]; - if (escaped === undefined || cursor + 1 >= end) return null; + if (escaped === undefined || cursor + 1 >= end) invalidEscapedSpecifier(); - if (escaped === "\r" || escaped === "\n" || escaped === "\u2028" || escaped === "\u2029") { + if (isLineTerminator(escaped)) { cursor += escaped === "\r" && source[cursor + 2] === "\n" ? 3 : 2; continue; } @@ -211,16 +232,16 @@ function decodeLiteralContents(source: string, start: number, end: number): stri } if (escaped === "0") { - if (/[0-9]/.test(source[cursor + 2] ?? "")) return null; + if (/[0-9]/.test(source[cursor + 2] ?? "")) invalidEscapedSpecifier(); result += "\0"; cursor += 2; continue; } - if (/[1-9]/.test(escaped)) return null; + if (/[1-9]/.test(escaped)) invalidEscapedSpecifier(); if (escaped === "x") { const value = decodeHexEscape(source, cursor + 2, 2); - if (value === null || cursor + 4 > end) return null; + if (value === null || cursor + 4 > end) invalidEscapedSpecifier(); result += StringFromCodePoint(value); cursor += 4; continue; @@ -233,21 +254,21 @@ function decodeLiteralContents(source: string, start: number, end: number): stri let digitCount = 0; while (escapeEnd < end && source[escapeEnd] !== "}") { const digit = hexDigitValue(source[escapeEnd]); - if (digit === -1) return null; + if (digit === -1) invalidEscapedSpecifier(); value = value * 16 + digit; digitCount++; escapeEnd++; } if ( digitCount === 0 || source[escapeEnd] !== "}" || value > 0x10ffff - ) return null; + ) invalidEscapedSpecifier(); result += StringFromCodePoint(value); cursor = escapeEnd + 1; continue; } const value = decodeHexEscape(source, cursor + 2, 4); - if (value === null || cursor + 6 > end) return null; + if (value === null || cursor + 6 > end) invalidEscapedSpecifier(); result += StringFromCodePoint(value); cursor += 6; continue; @@ -274,8 +295,7 @@ function readQuotedSpecifier( continue; } if (source[cursor] === quote) { - const specifier = decodeLiteralContents(source, quoteIndex + 1, cursor); - if (specifier === null) return null; + const specifier = decodeLiteralContents(source, quoteIndex + 1, cursor, false); return { end: cursor + 1, specifier, @@ -303,8 +323,7 @@ function readLiteralSpecifier( } if (source[cursor] === "$" && source[cursor + 1] === "{") return null; if (source[cursor] === "`") { - const specifier = decodeLiteralContents(source, literalIndex + 1, cursor); - if (specifier === null) return null; + const specifier = decodeLiteralContents(source, literalIndex + 1, cursor, true); return { end: cursor + 1, specifier, @@ -419,6 +438,29 @@ function isStatementBlockCloseBrace( keyword === "do" || keyword === "else"; } +function isPlainStatementBlockCloseBrace( + source: string, + index: number, + rangeStart: number, + matchingOpenBraces: ReadonlyMap, +): boolean { + const openBrace = matchingOpenBraces.get(index); + if (openBrace === undefined) return false; + + const beforeOpenBrace = previousSignificantIndex(source, openBrace); + if (beforeOpenBrace < rangeStart) return true; + if (source[beforeOpenBrace] === ";" || source[beforeOpenBrace] === "}") return true; + if (source[beforeOpenBrace] !== ":") return false; + + const labelEnd = previousSignificantIndex(source, beforeOpenBrace) + 1; + let labelStart = labelEnd; + while (labelStart > rangeStart && isIdentifierChar(source[labelStart - 1])) labelStart--; + if (labelStart === labelEnd || !/[$A-Za-z_]/.test(source[labelStart] ?? "")) return false; + + const beforeLabel = previousSignificantIndex(source, labelStart); + return beforeLabel < rangeStart || source[beforeLabel] === ";" || source[beforeLabel] === "}"; +} + function isForOfKeywordBefore( source: string, index: number, @@ -472,7 +514,8 @@ function canStartRegexLiteral( matchingOpenParens, ) || isDeclarationBlockCloseBrace(source, previous, matchingOpenBraces) || - isStatementBlockCloseBrace(source, previous, matchingOpenBraces)) + isStatementBlockCloseBrace(source, previous, matchingOpenBraces) || + isPlainStatementBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces)) ) return true; if ( (char === "+" || char === "-") && @@ -1006,18 +1049,63 @@ export function findStaticSideEffectImportSpans( const spans: StaticImportSpan[] = []; let cursor = 0; let atStatementStart = true; + const openBraces: number[] = []; + const matchingOpenBraces = new Map(); + const openParens: OpenParenContext[] = []; + const matchingOpenParens = new Map(); while (cursor < source.length) { const char = source[cursor]; - const skipped = skipIgnored(source, cursor); + const skipped = skipExpressionIgnored( + source, + cursor, + 0, + 0, + matchingOpenBraces, + matchingOpenParens, + openParens.at(-1), + ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; - else if (char !== "/") atStatementStart = false; + else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; cursor = skipped; continue; } - if (char === ";" || char === "\n" || char === "}") { + if (char === "{") { + openBraces.push(cursor); + atStatementStart = false; + cursor++; + continue; + } + if (char === "}") { + const openBrace = openBraces.pop(); + if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); + atStatementStart = true; + cursor++; + continue; + } + if (char === "(") { + openParens.push({ + index: cursor, + isForHeader: keywordBefore(source, cursor) === "for", + hasSemicolon: false, + }); + atStatementStart = false; + cursor++; + continue; + } + if (char === ")") { + const openParen = openParens.pop(); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + atStatementStart = false; + cursor++; + continue; + } + if (char === ";" && openParens.at(-1)?.isForHeader) { + openParens.at(-1)!.hasSemicolon = true; + } + if (char === ";" || char === "\n") { atStatementStart = true; cursor++; continue; From 9d9c076a197e692e129783b3948102f19529de8b Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:20:42 +0200 Subject: [PATCH 054/104] fix(transforms): classify default regex literals --- .../mdx/esm-module-loader/utils/source-spans.test.ts | 7 +++++++ src/transforms/mdx/esm-module-loader/utils/source-spans.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index a7e5bf4cda..f18a5d5aaf 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -309,6 +309,13 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after export default", () => { + assertEquals( + vfModuleSpecifiers('export default /import("\\/_vf_modules\\/a.js")/;'), + [], + ); + }); + it("ignores import-looking regex text after plain and labeled blocks", () => { assertEquals( vfModuleSpecifiers('{} /import("\\/_vf_modules\\/plain.js")/.test(value);'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index de903889aa..d60fb4e560 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -533,6 +533,7 @@ function canStartRegexLiteral( return [ "case", + "default", "delete", "do", "else", From f4b73c2fa0b7fbecb3c0cdf41c140942cfdf5ed1 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:28:49 +0200 Subject: [PATCH 055/104] fix(transforms): make static imports regex aware --- .../utils/source-spans.test.ts | 49 +++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 52 +++++++++++++++++-- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index f18a5d5aaf..12329ed40f 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -154,6 +154,46 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ["./after-function.js"], ); }); + + it("finds static imports after regex literals containing string delimiters", () => { + const cases = [ + [`const single = /it's/; import single from "./after-single.js";`, "./after-single.js"], + ['const double = /"/; import double from "./after-double.js";', "./after-double.js"], + [ + 'const template = /`/; import template from "./after-template.js";', + "./after-template.js", + ], + ] as const; + + for (const [source, expected] of cases) { + assertEquals( + findStaticImportFromSpans(source, matchRelative, UNBOUNDED).map((span) => span.path), + [expected], + ); + } + }); + + it("ignores static import-from text inside regex literals", () => { + assertEquals( + findStaticImportFromSpans( + 'const r = /;import value from "\\/_vf_modules\\/fake.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + + it("keeps division distinct from regex literals", () => { + assertEquals( + findStaticImportFromSpans( + 'const ratio = total / 2; import value from "./after-division.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-division.js"], + ); + }); }); describe("findDynamicImportSpans", () => { @@ -316,6 +356,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after extends", () => { + assertEquals( + vfModuleSpecifiers( + 'class X extends /import("\\/_vf_modules\\/fake.js")/.constructor {}', + ), + [], + ); + }); + it("ignores import-looking regex text after plain and labeled blocks", () => { assertEquals( vfModuleSpecifiers('{} /import("\\/_vf_modules\\/plain.js")/.test(value);'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index d60fb4e560..61aa2ae968 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -537,6 +537,7 @@ function canStartRegexLiteral( "delete", "do", "else", + "extends", "in", "instanceof", "new", @@ -770,18 +771,63 @@ export function findStaticImportFromSpans( const spans: StaticImportSpan[] = []; let cursor = 0; let atStatementStart = true; + const openBraces: number[] = []; + const matchingOpenBraces = new Map(); + const openParens: OpenParenContext[] = []; + const matchingOpenParens = new Map(); while (cursor < source.length) { const char = source[cursor]; - const skipped = skipIgnored(source, cursor); + const skipped = skipExpressionIgnored( + source, + cursor, + 0, + 0, + matchingOpenBraces, + matchingOpenParens, + openParens.at(-1), + ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; - else if (char !== "/") atStatementStart = false; + else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; cursor = skipped; continue; } - if (char === ";" || char === "\n" || char === "}") { + if (char === "{") { + openBraces.push(cursor); + atStatementStart = false; + cursor++; + continue; + } + if (char === "}") { + const openBrace = openBraces.pop(); + if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); + atStatementStart = true; + cursor++; + continue; + } + if (char === "(") { + openParens.push({ + index: cursor, + isForHeader: keywordBefore(source, cursor) === "for", + hasSemicolon: false, + }); + atStatementStart = false; + cursor++; + continue; + } + if (char === ")") { + const openParen = openParens.pop(); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + atStatementStart = false; + cursor++; + continue; + } + if (char === ";" && openParens.at(-1)?.isForHeader) { + openParens.at(-1)!.hasSemicolon = true; + } + if (char === ";" || char === "\n") { atStatementStart = true; cursor++; continue; From 83262cae8860b6cb4a47e03c034158dcb85ffe25 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:33:31 +0200 Subject: [PATCH 056/104] fix(transforms): reject false dynamic imports --- .../utils/source-spans.test.ts | 18 ++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 12329ed40f..43b94b3281 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -365,6 +365,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after spread syntax", () => { + assertEquals( + vfModuleSpecifiers( + 'const values = [.../import("\\/_vf_modules\\/fake.js")/];', + ), + [], + ); + }); + it("ignores import-looking regex text after plain and labeled blocks", () => { assertEquals( vfModuleSpecifiers('{} /import("\\/_vf_modules\\/plain.js")/.test(value);'), @@ -625,6 +634,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(specifiers(`obj.import("./foo.js");`), []); }); + it("ignores private methods named import", () => { + assertEquals( + vfModuleSpecifiers( + 'class Loader { #import(value) { return value; } load() { return this.#import("/_vf_modules/fake.js"); } }', + ), + [], + ); + }); + it("ignores an import-looking string or comment", () => { assertEquals(specifiers(`const s = 'import("./foo.js")';`), []); assertEquals(specifiers(`// import("./foo.js")\nconst x = 1;`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 61aa2ae968..8d9c26a999 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -524,6 +524,14 @@ function canStartRegexLiteral( ) { return false; } + if ( + char === "." && + previous - 2 >= rangeStart && + source[previous - 1] === "." && + source[previous - 2] === "." + ) { + return true; + } if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; const keyword = keywordBefore(source, index); @@ -1000,6 +1008,7 @@ function scanDynamicImportRange( !source.startsWith("import", cursor) || isIdentifierChar(source[cursor - 1]) || source[cursor - 1] === "." || + source[cursor - 1] === "#" || isIdentifierChar(source[cursor + "import".length]) ) { cursor++; From 12ebec7875a15bcd0d6b29ded62d130f3e5eb997 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:43:07 +0200 Subject: [PATCH 057/104] fix(transforms): preserve lexer context across trivia --- .../utils/source-spans.test.ts | 99 +++++++ .../esm-module-loader/utils/source-spans.ts | 262 +++++++++++++----- 2 files changed, 286 insertions(+), 75 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 43b94b3281..eec392892d 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -184,6 +184,28 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores static import-from text in regex literals after comments", () => { + assertEquals( + findStaticImportFromSpans( + 'function f() { return /* note */ /;import value from "\\/_vf_modules\\/fake.js"/; }', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + + it("keeps control-condition context across comments", () => { + assertEquals( + findStaticImportFromSpans( + 'if /* note */ (ready) /;import value from "\\/_vf_modules\\/fake.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + it("keeps division distinct from regex literals", () => { assertEquals( findStaticImportFromSpans( @@ -643,6 +665,61 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores property methods named import across trivia", () => { + assertEquals( + vfModuleSpecifiers('const first = object./* note */import("/_vf_modules/fake.js");'), + [], + ); + assertEquals( + vfModuleSpecifiers('const second = object.\nimport("/_vf_modules/fake.js");'), + [], + ); + }); + + it("finds real dynamic imports after spread syntax", () => { + assertEquals( + vfModuleSpecifiers('const values = [...import("/_vf_modules/real.js")];'), + ["/_vf_modules/real.js"], + ); + }); + + it("ignores import-looking regex text after commented prefixes", () => { + assertEquals( + vfModuleSpecifiers( + 'function load() { return /* note */ /import("\\/_vf_modules\\/fake.js")/; }', + ), + [], + ); + }); + + it("keeps parenthesis context across comments", () => { + assertEquals( + vfModuleSpecifiers( + 'for /* note */ (const value of /import("\\/_vf_modules\\/fake.js")/) {}', + ), + [], + ); + assertEquals( + vfModuleSpecifiers( + 'if /* note */ (ready) /import("\\/_vf_modules\\/fake.js")/.test(value);', + ), + [], + ); + }); + + it("keeps block context across comments", () => { + for ( + const source of [ + 'if (ready) /* note */ {} /import("\\/_vf_modules\\/fake.js")/.test(value);', + 'try /* note */ {} finally /* note */ {} /import("\\/_vf_modules\\/fake.js")/.test(value);', + 'function /* note */ load() {} /import("\\/_vf_modules\\/fake.js")/.test(value);', + 'class /* note */ Loader {} /import("\\/_vf_modules\\/fake.js")/.test(value);', + ] + ) { + assertEquals(vfModuleSpecifiers(source), []); + } + }); + it("ignores an import-looking string or comment", () => { assertEquals(specifiers(`const s = 'import("./foo.js")';`), []); assertEquals(specifiers(`// import("./foo.js")\nconst x = 1;`), []); @@ -829,5 +906,27 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { [], ); }); + + it("ignores side-effect import text in regex literals after comments", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'function f() { return /* note */ /;import "\\/_vf_modules\\/a.js"/; }', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + + it("keeps control-condition context across comments", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'if /* note */ (ready) /;import "\\/_vf_modules\\/fake.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 8d9c26a999..a067c4ada8 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -24,10 +24,16 @@ type SpecifierMatcher = (specifier: string) => string | null | undefined; interface OpenParenContext { index: number; + isControlCondition: boolean; isForHeader: boolean; hasSemicolon: boolean; } +interface OpenBraceContext { + index: number; + previousTokenIndex: number; +} + const MAX_TEMPLATE_LITERAL_DEPTH = 512; const StringFromCodePoint = String.fromCodePoint; @@ -371,57 +377,76 @@ function previousSignificantIndex(source: string, index: number): number { return cursor; } -function keywordBefore(source: string, index: number): string | null { - const end = previousSignificantIndex(source, index) + 1; +function keywordBefore( + source: string, + index: number, + previousTokenIndex = previousSignificantIndex(source, index), +): string | null { + const end = previousTokenIndex + 1; let start = end; while (start > 0 && /[A-Za-z_$]/.test(source[start - 1] ?? "")) start--; if (start === end) return null; return source.slice(start, end); } -function isControlConditionCloseParen( +function openParenContext( source: string, + index: number, + previousTokenIndex: number, +): OpenParenContext { + const keyword = keywordBefore(source, index, previousTokenIndex); + return { + index, + isControlCondition: keyword === "if" || keyword === "while" || keyword === "for" || + keyword === "with" || keyword === "switch" || keyword === "catch", + isForHeader: keyword === "for", + hasSemicolon: false, + }; +} + +function isControlConditionCloseParen( index: number, rangeStart: number, - matchingOpenParens: ReadonlyMap, + matchingOpenParens: ReadonlyMap, ): boolean { const openParen = matchingOpenParens.get(index); - if (openParen === undefined || openParen < rangeStart) return false; - const keyword = keywordBefore(source, openParen); - return keyword === "if" || keyword === "while" || keyword === "for" || - keyword === "with" || keyword === "switch" || keyword === "catch"; + return openParen !== undefined && openParen.index >= rangeStart && + openParen.isControlCondition; } function isControlBlockCloseBrace( source: string, index: number, rangeStart: number, - matchingOpenBraces: ReadonlyMap, - matchingOpenParens: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, ): boolean { const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; - const beforeOpenBrace = previousSignificantIndex(source, openBrace); + const beforeOpenBrace = openBrace.previousTokenIndex; return beforeOpenBrace >= rangeStart && source[beforeOpenBrace] === ")" && - isControlConditionCloseParen(source, beforeOpenBrace, rangeStart, matchingOpenParens); + isControlConditionCloseParen(beforeOpenBrace, rangeStart, matchingOpenParens); } function isDeclarationBlockCloseBrace( source: string, index: number, - matchingOpenBraces: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, ): boolean { const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; const declarationStart = Math.max( - source.lastIndexOf(";", openBrace - 1), - source.lastIndexOf("{", openBrace - 1), - source.lastIndexOf("}", openBrace - 1), + source.lastIndexOf(";", openBrace.index - 1), + source.lastIndexOf("{", openBrace.index - 1), + source.lastIndexOf("}", openBrace.index - 1), ) + 1; - const prefix = source.slice(declarationStart, openBrace).trimStart(); + const prefix = source.slice(declarationStart, openBrace.index).trimStart().replace( + /\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, + " ", + ); return /^(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/.test(prefix) || /^class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test(prefix); } @@ -429,11 +454,11 @@ function isDeclarationBlockCloseBrace( function isStatementBlockCloseBrace( source: string, index: number, - matchingOpenBraces: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, ): boolean { const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; - const keyword = keywordBefore(source, openBrace); + const keyword = keywordBefore(source, openBrace.index, openBrace.previousTokenIndex); return keyword === "try" || keyword === "catch" || keyword === "finally" || keyword === "do" || keyword === "else"; } @@ -442,12 +467,12 @@ function isPlainStatementBlockCloseBrace( source: string, index: number, rangeStart: number, - matchingOpenBraces: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, ): boolean { const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; - const beforeOpenBrace = previousSignificantIndex(source, openBrace); + const beforeOpenBrace = openBrace.previousTokenIndex; if (beforeOpenBrace < rangeStart) return true; if (source[beforeOpenBrace] === ";" || source[beforeOpenBrace] === "}") return true; if (source[beforeOpenBrace] !== ":") return false; @@ -463,11 +488,11 @@ function isPlainStatementBlockCloseBrace( function isForOfKeywordBefore( source: string, - index: number, rangeStart: number, currentParen: OpenParenContext | undefined, + previousTokenIndex: number, ): boolean { - const keywordEnd = previousSignificantIndex(source, index) + 1; + const keywordEnd = previousTokenIndex + 1; let keywordStart = keywordEnd; while (keywordStart > rangeStart && /[A-Za-z_$]/.test(source[keywordStart - 1] ?? "")) { keywordStart--; @@ -492,17 +517,18 @@ function canStartRegexLiteral( source: string, index: number, rangeStart: number, - matchingOpenBraces: ReadonlyMap, - matchingOpenParens: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, currentParen: OpenParenContext | undefined, + previousTokenIndex: number, ): boolean { - const previous = previousSignificantIndex(source, index); + const previous = previousTokenIndex; if (previous < rangeStart) return true; const char = source[previous]; if ( char === ")" && - isControlConditionCloseParen(source, previous, rangeStart, matchingOpenParens) + isControlConditionCloseParen(previous, rangeStart, matchingOpenParens) ) return true; if ( char === "}" && @@ -534,9 +560,9 @@ function canStartRegexLiteral( } if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; - const keyword = keywordBefore(source, index); + const keyword = keywordBefore(source, index, previous); if (keyword === "of") { - return isForOfKeywordBefore(source, index, rangeStart, currentParen); + return isForOfKeywordBefore(source, rangeStart, currentParen, previous); } return [ @@ -599,9 +625,10 @@ function skipExpressionIgnored( index: number, rangeStart: number, depth: number, - matchingOpenBraces: ReadonlyMap, - matchingOpenParens: ReadonlyMap, + matchingOpenBraces: ReadonlyMap, + matchingOpenParens: ReadonlyMap, currentParen: OpenParenContext | undefined, + previousTokenIndex: number, ): number { const char = source[index]; const next = source[index + 1]; @@ -627,6 +654,7 @@ function skipExpressionIgnored( matchingOpenBraces, matchingOpenParens, currentParen, + previousTokenIndex, ) ) { return skipRegexLiteral(source, index); @@ -635,6 +663,31 @@ function skipExpressionIgnored( return index; } +function tokenIndexAfterIgnored( + source: string, + index: number, + skipped: number, + previousTokenIndex: number, +): number { + const isComment = source[index] === "/" && + (source[index + 1] === "/" || source[index + 1] === "*"); + return isComment ? previousTokenIndex : Math.max(index, skipped - 1); +} + +function isPropertyAccessBeforeImport( + source: string, + previousTokenIndex: number, + rangeStart: number, +): boolean { + const previous = source[previousTokenIndex]; + if (previous === "#") return true; + if (previous !== ".") return false; + const isSpread = previousTokenIndex - 2 >= rangeStart && + source[previousTokenIndex - 1] === "." && + source[previousTokenIndex - 2] === "."; + return !isSpread; +} + function findTemplateExpressionEnd( source: string, expressionIndex: number, @@ -644,10 +697,11 @@ function findTemplateExpressionEnd( let cursor = expressionIndex; let braceDepth = 1; - const openBraces: number[] = []; - const matchingOpenBraces = new Map(); + const openBraces: OpenBraceContext[] = []; + const matchingOpenBraces = new Map(); const openParens: OpenParenContext[] = []; - const matchingOpenParens = new Map(); + const matchingOpenParens = new Map(); + let previousTokenIndex = expressionIndex - 1; while (cursor < source.length) { const skipped = skipExpressionIgnored( @@ -658,15 +712,23 @@ function findTemplateExpressionEnd( matchingOpenBraces, matchingOpenParens, openParens.at(-1), + previousTokenIndex, ); if (skipped !== cursor) { + previousTokenIndex = tokenIndexAfterIgnored( + source, + cursor, + skipped, + previousTokenIndex, + ); cursor = skipped; continue; } if (source[cursor] === "{") { - openBraces.push(cursor); + openBraces.push({ index: cursor, previousTokenIndex }); braceDepth++; + previousTokenIndex = cursor; cursor++; continue; } @@ -676,23 +738,22 @@ function findTemplateExpressionEnd( if (braceDepth === 0) return cursor; const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); + previousTokenIndex = cursor; cursor++; continue; } if (source[cursor] === "(") { - openParens.push({ - index: cursor, - isForHeader: keywordBefore(source, cursor) === "for", - hasSemicolon: false, - }); + openParens.push(openParenContext(source, cursor, previousTokenIndex)); + previousTokenIndex = cursor; cursor++; continue; } if (source[cursor] === ")") { const openParen = openParens.pop(); - if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen); + previousTokenIndex = cursor; cursor++; continue; } @@ -701,6 +762,7 @@ function findTemplateExpressionEnd( openParens.at(-1)!.hasSemicolon = true; } + if (!/\s/.test(source[cursor] ?? "")) previousTokenIndex = cursor; cursor++; } @@ -779,10 +841,11 @@ export function findStaticImportFromSpans( const spans: StaticImportSpan[] = []; let cursor = 0; let atStatementStart = true; - const openBraces: number[] = []; - const matchingOpenBraces = new Map(); + const openBraces: OpenBraceContext[] = []; + const matchingOpenBraces = new Map(); const openParens: OpenParenContext[] = []; - const matchingOpenParens = new Map(); + const matchingOpenParens = new Map(); + let previousTokenIndex = -1; while (cursor < source.length) { const char = source[cursor]; @@ -794,17 +857,25 @@ export function findStaticImportFromSpans( matchingOpenBraces, matchingOpenParens, openParens.at(-1), + previousTokenIndex, ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; + previousTokenIndex = tokenIndexAfterIgnored( + source, + cursor, + skipped, + previousTokenIndex, + ); cursor = skipped; continue; } if (char === "{") { - openBraces.push(cursor); + openBraces.push({ index: cursor, previousTokenIndex }); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -812,23 +883,22 @@ export function findStaticImportFromSpans( const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); atStatementStart = true; + previousTokenIndex = cursor; cursor++; continue; } if (char === "(") { - openParens.push({ - index: cursor, - isForHeader: keywordBefore(source, cursor) === "for", - hasSemicolon: false, - }); + openParens.push(openParenContext(source, cursor, previousTokenIndex)); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } if (char === ")") { const openParen = openParens.pop(); - if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -837,6 +907,7 @@ export function findStaticImportFromSpans( } if (char === ";" || char === "\n") { atStatementStart = true; + previousTokenIndex = cursor; cursor++; continue; } @@ -849,6 +920,7 @@ export function findStaticImportFromSpans( const isExport = isStatementKeywordAt(source, cursor, "export", atStatementStart); if (!isImport && !isExport) { atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -857,6 +929,7 @@ export function findStaticImportFromSpans( const afterKeyword = skipWhitespaceAndComments(source, cursor + keywordLength); if (isImport && source[afterKeyword] === "(") { atStatementStart = false; + previousTokenIndex = afterKeyword; cursor = afterKeyword + 1; continue; } @@ -866,12 +939,14 @@ export function findStaticImportFromSpans( spans.push(span); if (spans.length >= maxMatches) return spans; atStatementStart = false; + previousTokenIndex = span.end - 1; cursor = span.end; continue; } atStatementStart = true; cursor = nextStatementCursor(source, afterKeyword); + previousTokenIndex = Math.max(previousTokenIndex, cursor - 1); } return spans; @@ -922,10 +997,11 @@ function scanDynamicImportRange( spans: StaticImportSpan[], ): void { let cursor = rangeStart; - const openBraces: number[] = []; - const matchingOpenBraces = new Map(); + const openBraces: OpenBraceContext[] = []; + const matchingOpenBraces = new Map(); const openParens: OpenParenContext[] = []; - const matchingOpenParens = new Map(); + const matchingOpenParens = new Map(); + let previousTokenIndex = rangeStart - 1; while (cursor < rangeEnd) { const char = source[cursor]; @@ -936,7 +1012,14 @@ function scanDynamicImportRange( char === '"' || char === "'" ) { - cursor = skipIgnored(source, cursor); + const skipped = skipIgnored(source, cursor); + previousTokenIndex = tokenIndexAfterIgnored( + source, + cursor, + skipped, + previousTokenIndex, + ); + cursor = skipped; continue; } @@ -949,9 +1032,11 @@ function scanDynamicImportRange( matchingOpenBraces, matchingOpenParens, openParens.at(-1), + previousTokenIndex, ) ) { cursor = skipRegexLiteral(source, cursor); + previousTokenIndex = cursor - 1; continue; } @@ -965,11 +1050,13 @@ function scanDynamicImportRange( spans, ); if (spans.length >= maxMatches) return; + previousTokenIndex = cursor - 1; continue; } if (char === "{") { - openBraces.push(cursor); + openBraces.push({ index: cursor, previousTokenIndex }); + previousTokenIndex = cursor; cursor++; continue; } @@ -977,23 +1064,22 @@ function scanDynamicImportRange( if (char === "}") { const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); + previousTokenIndex = cursor; cursor++; continue; } if (char === "(") { - openParens.push({ - index: cursor, - isForHeader: keywordBefore(source, cursor) === "for", - hasSemicolon: false, - }); + openParens.push(openParenContext(source, cursor, previousTokenIndex)); + previousTokenIndex = cursor; cursor++; continue; } if (char === ")") { const openParen = openParens.pop(); - if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen); + previousTokenIndex = cursor; cursor++; continue; } @@ -1002,36 +1088,49 @@ function scanDynamicImportRange( openParens.at(-1)!.hasSemicolon = true; } + if (/\s/.test(char ?? "")) { + cursor++; + continue; + } + // `import` used as an expression: not preceded by an identifier char or a // dot (which would make it `foo.import` or part of a longer word). if ( !source.startsWith("import", cursor) || isIdentifierChar(source[cursor - 1]) || - source[cursor - 1] === "." || - source[cursor - 1] === "#" || + isPropertyAccessBeforeImport(source, previousTokenIndex, rangeStart) || isIdentifierChar(source[cursor + "import".length]) ) { + previousTokenIndex = cursor; cursor++; continue; } const parenIndex = skipWhitespaceAndComments(source, cursor + "import".length); if (parenIndex >= rangeEnd || source[parenIndex] !== "(") { + previousTokenIndex = cursor; cursor++; continue; } // The scanner jumps directly from `import` to its argument, so record the // opening parenthesis that the ordinary character walk does not visit. - openParens.push({ index: parenIndex, isForHeader: false, hasSemicolon: false }); + openParens.push({ + index: parenIndex, + isControlCondition: false, + isForHeader: false, + hasSemicolon: false, + }); const literalIndex = skipWhitespaceAndComments(source, parenIndex + 1); if (literalIndex >= rangeEnd) { + previousTokenIndex = parenIndex; cursor = parenIndex + 1; continue; } const literal = readLiteralSpecifier(source, literalIndex); if (!literal || literal.end > rangeEnd) { + previousTokenIndex = parenIndex; cursor = parenIndex + 1; continue; } @@ -1054,6 +1153,7 @@ function scanDynamicImportRange( if (spans.length >= maxMatches) return; } + previousTokenIndex = literal.end - 1; cursor = literal.end; } } @@ -1105,10 +1205,11 @@ export function findStaticSideEffectImportSpans( const spans: StaticImportSpan[] = []; let cursor = 0; let atStatementStart = true; - const openBraces: number[] = []; - const matchingOpenBraces = new Map(); + const openBraces: OpenBraceContext[] = []; + const matchingOpenBraces = new Map(); const openParens: OpenParenContext[] = []; - const matchingOpenParens = new Map(); + const matchingOpenParens = new Map(); + let previousTokenIndex = -1; while (cursor < source.length) { const char = source[cursor]; @@ -1120,17 +1221,25 @@ export function findStaticSideEffectImportSpans( matchingOpenBraces, matchingOpenParens, openParens.at(-1), + previousTokenIndex, ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; + previousTokenIndex = tokenIndexAfterIgnored( + source, + cursor, + skipped, + previousTokenIndex, + ); cursor = skipped; continue; } if (char === "{") { - openBraces.push(cursor); + openBraces.push({ index: cursor, previousTokenIndex }); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -1138,23 +1247,22 @@ export function findStaticSideEffectImportSpans( const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); atStatementStart = true; + previousTokenIndex = cursor; cursor++; continue; } if (char === "(") { - openParens.push({ - index: cursor, - isForHeader: keywordBefore(source, cursor) === "for", - hasSemicolon: false, - }); + openParens.push(openParenContext(source, cursor, previousTokenIndex)); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } if (char === ")") { const openParen = openParens.pop(); - if (openParen !== undefined) matchingOpenParens.set(cursor, openParen.index); + if (openParen !== undefined) matchingOpenParens.set(cursor, openParen); atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -1163,6 +1271,7 @@ export function findStaticSideEffectImportSpans( } if (char === ";" || char === "\n") { atStatementStart = true; + previousTokenIndex = cursor; cursor++; continue; } @@ -1173,6 +1282,7 @@ export function findStaticSideEffectImportSpans( if (!isStatementKeywordAt(source, cursor, "import", atStatementStart)) { atStatementStart = false; + previousTokenIndex = cursor; cursor++; continue; } @@ -1182,6 +1292,7 @@ export function findStaticSideEffectImportSpans( if (!literal) { atStatementStart = true; cursor = nextStatementCursor(source, literalIndex); + previousTokenIndex = Math.max(previousTokenIndex, cursor - 1); continue; } @@ -1197,6 +1308,7 @@ export function findStaticSideEffectImportSpans( } atStatementStart = false; + previousTokenIndex = literal.end - 1; cursor = literal.end; } From 1d590e4437147675391690fb7ab0e2d2f4e236fc Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:45:28 +0200 Subject: [PATCH 058/104] fix(transforms): close scanner review gaps --- .../utils/source-spans.test.ts | 69 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 50 +++++++++++--- 2 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index eec392892d..ba44f41bcc 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -206,6 +206,28 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps nested dynamic-import parentheses aligned", () => { + assertEquals( + findStaticImportFromSpans( + 'if (\nimport("/_vf_modules/real.js")\n) /;import value from "\\/_vf_modules\\/fake.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + + it("recognizes regex literals after ASI-only statements", () => { + assertEquals( + findStaticImportFromSpans( + 'while (ready) { break\n/;import value from "\\/_vf_modules\\/fake.js"/; }', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + it("keeps division distinct from regex literals", () => { assertEquals( findStaticImportFromSpans( @@ -720,6 +742,31 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { } }); + it("treats Unicode identifier parts as import boundaries", () => { + for ( + const source of [ + 'function αimport(value) { return value; } αimport("/_vf_modules/fake.js");', + 'function importα(value) { return value; } importα("/_vf_modules/fake.js");', + 'function 𝒜import(value) { return value; } 𝒜import("/_vf_modules/fake.js");', + 'function import𝒜(value) { return value; } import𝒜("/_vf_modules/fake.js");', + ] + ) { + assertEquals(vfModuleSpecifiers(source), []); + } + }); + + it("recognizes regex literals after ASI-only statements", () => { + for ( + const source of [ + 'while (ready) { break\n/import("\\/_vf_modules\\/fake.js")/.test(value); }', + 'while (ready) { continue\n/import("\\/_vf_modules\\/fake.js")/.test(value); }', + 'debugger\n/import("\\/_vf_modules\\/fake.js")/.test(value);', + ] + ) { + assertEquals(vfModuleSpecifiers(source), []); + } + }); + it("ignores an import-looking string or comment", () => { assertEquals(specifiers(`const s = 'import("./foo.js")';`), []); assertEquals(specifiers(`// import("./foo.js")\nconst x = 1;`), []); @@ -928,5 +975,27 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { [], ); }); + + it("keeps nested dynamic-import parentheses aligned", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'if (\nimport("/_vf_modules/real.js")\n) /;import "\\/_vf_modules\\/fake.js"/;', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); + + it("recognizes regex literals after ASI-only statements", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'while (ready) { break\n/;import "\\/_vf_modules\\/fake.js"/; }', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ), + [], + ); + }); }); }); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index a067c4ada8..860997bcf6 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -36,6 +36,7 @@ interface OpenBraceContext { const MAX_TEMPLATE_LITERAL_DEPTH = 512; const StringFromCodePoint = String.fromCodePoint; +const IDENTIFIER_PART_PATTERN = /^[$_\p{ID_Continue}\u200C\u200D]$/u; function assertTemplateLiteralDepth(depth: number): void { if (depth > MAX_TEMPLATE_LITERAL_DEPTH) { @@ -83,7 +84,27 @@ export function replaceSourceSpans( } function isIdentifierChar(char: string | undefined): boolean { - return char !== undefined && /[A-Za-z0-9_$]/.test(char); + return char !== undefined && IDENTIFIER_PART_PATTERN.test(char); +} + +function identifierCharacterAt(source: string, index: number): string | undefined { + if (index < 0 || index >= source.length) return undefined; + + let characterIndex = index; + const codeUnit = source.charCodeAt(characterIndex); + if ( + codeUnit >= 0xdc00 && codeUnit <= 0xdfff && characterIndex > 0 + ) { + const previousCodeUnit = source.charCodeAt(characterIndex - 1); + if (previousCodeUnit >= 0xd800 && previousCodeUnit <= 0xdbff) characterIndex--; + } + + const codePoint = source.codePointAt(characterIndex); + return codePoint === undefined ? undefined : StringFromCodePoint(codePoint); +} + +function isIdentifierPartAt(source: string, index: number): boolean { + return isIdentifierChar(identifierCharacterAt(source, index)); } function isStatementKeywordAt( @@ -94,8 +115,8 @@ function isStatementKeywordAt( ): boolean { if (!atStatementStart) return false; if (!source.startsWith(keyword, index)) return false; - if (isIdentifierChar(source[index - 1]) || source[index - 1] === ".") return false; - if (isIdentifierChar(source[index + keyword.length])) return false; + if (isIdentifierPartAt(source, index - 1) || source[index - 1] === ".") return false; + if (isIdentifierPartAt(source, index + keyword.length)) return false; return true; } @@ -503,7 +524,7 @@ function isForOfKeywordBefore( if (beforeKeyword >= rangeStart && source[beforeKeyword] === ".") return false; const beforeKeywordChar = source[beforeKeyword]; if ( - !isIdentifierChar(beforeKeywordChar) && + !isIdentifierPartAt(source, beforeKeyword) && beforeKeywordChar !== "]" && beforeKeywordChar !== "}" && beforeKeywordChar !== ")" @@ -576,6 +597,9 @@ function canStartRegexLiteral( "instanceof", "new", "await", + "break", + "continue", + "debugger", "return", "throw", "typeof", @@ -787,8 +811,8 @@ function findFromSpan( if ( source.startsWith("from", cursor) && - !isIdentifierChar(source[cursor - 1]) && - !isIdentifierChar(source[cursor + 4]) + !isIdentifierPartAt(source, cursor - 1) && + !isIdentifierPartAt(source, cursor + 4) ) { const quoteIndex = skipWhitespaceAndComments(source, cursor + 4); const quoted = readQuotedSpecifier(source, quoteIndex); @@ -907,7 +931,7 @@ export function findStaticImportFromSpans( } if (char === ";" || char === "\n") { atStatementStart = true; - previousTokenIndex = cursor; + if (char === ";") previousTokenIndex = cursor; cursor++; continue; } @@ -929,6 +953,12 @@ export function findStaticImportFromSpans( const afterKeyword = skipWhitespaceAndComments(source, cursor + keywordLength); if (isImport && source[afterKeyword] === "(") { atStatementStart = false; + openParens.push({ + index: afterKeyword, + isControlCondition: false, + isForHeader: false, + hasSemicolon: false, + }); previousTokenIndex = afterKeyword; cursor = afterKeyword + 1; continue; @@ -1097,9 +1127,9 @@ function scanDynamicImportRange( // dot (which would make it `foo.import` or part of a longer word). if ( !source.startsWith("import", cursor) || - isIdentifierChar(source[cursor - 1]) || + isIdentifierPartAt(source, cursor - 1) || isPropertyAccessBeforeImport(source, previousTokenIndex, rangeStart) || - isIdentifierChar(source[cursor + "import".length]) + isIdentifierPartAt(source, cursor + "import".length) ) { previousTokenIndex = cursor; cursor++; @@ -1271,7 +1301,7 @@ export function findStaticSideEffectImportSpans( } if (char === ";" || char === "\n") { atStatementStart = true; - previousTokenIndex = cursor; + if (char === ";") previousTokenIndex = cursor; cursor++; continue; } From d6b2100ae2dfc979fb40a43ab423a062deaf2eca Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 09:48:32 +0200 Subject: [PATCH 059/104] fix(transforms): honor JavaScript line terminators --- .../utils/source-spans.test.ts | 26 ++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 31 ++++++++++++------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index ba44f41bcc..b3d643a056 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -155,6 +155,19 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("recognizes every ECMAScript line terminator", () => { + for (const lineTerminator of ["\r", "\u2028", "\u2029"]) { + assertEquals( + findStaticImportFromSpans( + `const ready = true // note${lineTerminator}import value from "/_vf_modules/real.js"`, + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ).map((span) => span.path), + ["/_vf_modules/real.js"], + ); + } + }); + it("finds static imports after regex literals containing string delimiters", () => { const cases = [ [`const single = /it's/; import single from "./after-single.js";`, "./after-single.js"], @@ -943,6 +956,19 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("recognizes every ECMAScript line terminator", () => { + for (const lineTerminator of ["\r", "\u2028", "\u2029"]) { + assertEquals( + findStaticSideEffectImportSpans( + `const ready = true // note${lineTerminator}import "/_vf_modules/real.js"`, + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ).map((span) => span.path), + ["/_vf_modules/real.js"], + ); + } + }); + it("ignores side-effect import text inside regex literals", () => { assertEquals( findStaticSideEffectImportSpans( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 860997bcf6..acc6ba846b 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -107,6 +107,13 @@ function isIdentifierPartAt(source: string, index: number): boolean { return isIdentifierChar(identifierCharacterAt(source, index)); } +function skipLineComment(source: string, index: number): number { + let cursor = index + 2; + while (cursor < source.length && !isLineTerminator(source[cursor]!)) cursor++; + if (cursor >= source.length) return source.length; + return source[cursor] === "\r" && source[cursor + 1] === "\n" ? cursor + 2 : cursor + 1; +} + function isStatementKeywordAt( source: string, index: number, @@ -125,8 +132,7 @@ function skipIgnored(source: string, index: number): number { const next = source[index + 1]; if (char === "/" && next === "/") { - const newline = source.indexOf("\n", index + 2); - return newline === -1 ? source.length : newline + 1; + return skipLineComment(source, index); } if (char === "/" && next === "*") { @@ -177,11 +183,15 @@ function skipWhitespaceAndComments(source: string, index: number): number { } function nextStatementCursor(source: string, index: number): number { - const semicolon = source.indexOf(";", index); - const newline = source.indexOf("\n", index); - const candidates = [semicolon, newline].filter((position) => position >= 0); - if (candidates.length === 0) return source.length; - return Math.min(...candidates) + 1; + let cursor = index; + while (cursor < source.length) { + if (source[cursor] === ";") return cursor + 1; + if (isLineTerminator(source[cursor]!)) { + return source[cursor] === "\r" && source[cursor + 1] === "\n" ? cursor + 2 : cursor + 1; + } + cursor++; + } + return source.length; } function hexDigitValue(char: string | undefined): number { @@ -658,8 +668,7 @@ function skipExpressionIgnored( const next = source[index + 1]; if (char === "/" && next === "/") { - const newline = source.indexOf("\n", index + 2); - return newline === -1 ? source.length : newline + 1; + return skipLineComment(source, index); } if (char === "/" && next === "*") { @@ -929,7 +938,7 @@ export function findStaticImportFromSpans( if (char === ";" && openParens.at(-1)?.isForHeader) { openParens.at(-1)!.hasSemicolon = true; } - if (char === ";" || char === "\n") { + if (char === ";" || (char !== undefined && isLineTerminator(char))) { atStatementStart = true; if (char === ";") previousTokenIndex = cursor; cursor++; @@ -1299,7 +1308,7 @@ export function findStaticSideEffectImportSpans( if (char === ";" && openParens.at(-1)?.isForHeader) { openParens.at(-1)!.hasSemicolon = true; } - if (char === ";" || char === "\n") { + if (char === ";" || (char !== undefined && isLineTerminator(char))) { atStatementStart = true; if (char === ";") previousTokenIndex = cursor; cursor++; From a64687f6f9bb61f7d20b3f09ebaf4fb0b110f9af Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 10:29:29 +0200 Subject: [PATCH 060/104] Keep declaration scans aligned with ECMAScript lines Line comments in declaration headers may end with Unicode line and paragraph separators. Treating only CR and LF as terminators hid the declaration boundary and could prevent later dynamic imports from being discovered. Constraint: ECMAScript recognizes U+2028 and U+2029 as line terminators.\nConfidence: high\nScope-risk: narrow\nDirective: Keep scanner comment termination aligned with the full ECMAScript line-terminator set.\nTested: source-spans suite, targeted format, lint, typecheck, and diff check. --- .../mdx/esm-module-loader/utils/source-spans.test.ts | 12 ++++++++++++ .../mdx/esm-module-loader/utils/source-spans.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index b3d643a056..13d520600f 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -484,6 +484,18 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("recognizes Unicode line terminators in declaration comments", () => { + for (const lineTerminator of ["\u2028", "\u2029"]) { + assertEquals( + vfModuleSpecifiers( + "const html = `${(() => { function // note" + lineTerminator + + ' f() {} /}/.test(x); })() && import("/_vf_modules/commented-function-lazy.js")}`;', + ), + ["/_vf_modules/commented-function-lazy.js"], + ); + } + }); + it("finds imports after regex literals following try statement blocks", () => { assertEquals( vfModuleSpecifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index acc6ba846b..1de89ad6b7 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -475,7 +475,7 @@ function isDeclarationBlockCloseBrace( source.lastIndexOf("}", openBrace.index - 1), ) + 1; const prefix = source.slice(declarationStart, openBrace.index).trimStart().replace( - /\/\*[\s\S]*?\*\/|\/\/[^\r\n]*/g, + /\/\*[\s\S]*?\*\/|\/\/[^\r\n\u2028\u2029]*/g, " ", ); return /^(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/.test(prefix) || From 335a1cd9878ac818b8cda6fd40b04daafb074ede Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 10:35:22 +0200 Subject: [PATCH 061/104] Keep tenant side-effect imports classifiable The context-aware scanner now recognizes side-effect imports after same-line statements and across comments after the import keyword. Lock that behavior at the dependency resolver and module-loader classification seams so missing tenant modules continue to produce warning-level build evidence. Constraint: Legal ESM import declarations are not restricted to line-leading positions.\nConfidence: high\nScope-risk: narrow\nDirective: Preserve both dependency resolution and tenant build-failure tagging for side-effect import syntax.\nTested: source-span, dependency-resolver, and module-loader suites (141 steps); targeted format, lint, typecheck, and diff check. --- .../module-loader/dependency-resolver.test.ts | 31 +++++++++++++++++++ .../orchestrator/module-loader/index.test.ts | 25 +++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts index a33c3baf1d..e3a9a3611e 100644 --- a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts +++ b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts @@ -156,6 +156,37 @@ describe("module-loader/dependency-resolver", () => { ); }); + it("resolves side-effect imports after statements and keyword comments", async () => { + await withDependencyFixture( + { + "app/page.tsx": [ + `const ready = true; import /* preload */ "@/setup";`, + `import /* preload */ "./local-setup";`, + `export default function Page() { return ready; }`, + ].join("\n"), + "components/setup.ts": `globalThis.aliasReady = true;`, + "app/local-setup.ts": `globalThis.localReady = true;`, + }, + async ({ projectDir }) => { + const adapter = await getLocalAdapter(); + const filePath = join(projectDir, "app/page.tsx"); + const fileContent = await Deno.readTextFile(filePath); + + const deps = await resolveModuleDependencies({ + adapter, + fileContent, + filePath, + projectDir, + }); + + assertEquals(deps.map((dependency) => dependency.relativePath), [ + "setup", + "./local-setup", + ]); + }, + ); + }); + it("resolves alias and relative imports while ignoring already transformed file imports", async () => { await withDependencyFixture( { diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 8414bb15c8..c21210bd48 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -389,6 +389,31 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("tags missing side-effect imports in every legal declaration position", async () => { + for ( + const source of [ + `const ready = true; import "./missing"; export default ready;`, + `import /* preload */ "./missing"; export default null;`, + ] + ) { + await withModuleLoaderFixture( + { "app/page.tsx": source }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + } + }); + it("classifies retry failures from only the rebuilt dependency graph", async () => { await withModuleLoaderFixture( { From c445b41d6d926e44abe0f46d0de293b34729a929 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 11:06:58 +0200 Subject: [PATCH 062/104] Preserve tenant attribution only when retry evidence matches The retry seam had enough evidence to know a tenant-authored import was unresolved, but it still treated any later module-not-found failure as tenant-owned. This narrows that verdict to the missing target, keeps unresolved-import sidecar evidence optional, and prevents malformed module specifiers from aborting unresolved-import checks. Constraint: CodeRabbit review identified four exact-head PR threads that all affected tenant/framework classification or metadata durability. Rejected: Classify on non-empty unresolved evidence alone | still downgrades unrelated framework resolution failures. Rejected: Let sidecar writes fail the transform | evidence only refines severity and missing evidence already degrades safely. Confidence: high Scope-risk: moderate Directive: Do not broaden tenant-build classification unless the missing runtime target is tied to tenant-authored resolver evidence. Tested: deno fmt --check on touched files; deno lint on touched files; deno check on touched files; focused module-loader and nested-import tests; full module-loader tests; module-fetcher plus source-spans tests; module/dependency/core/wildcard/barrel/docs/generated-manifest checks. Not-tested: full local suite is still running and has already exposed unrelated cache/OAuth timeout failures outside touched paths; exact-head GitHub CI until pushed. --- .../orchestrator/module-loader/index.test.ts | 16 ++- .../orchestrator/module-loader/index.ts | 31 ++++- .../module-loader/module-persistence.test.ts | 46 ++++++- .../module-loader/module-persistence.ts | 15 ++- .../module-fetcher/nested-imports.test.ts | 26 ++++ .../module-fetcher/nested-imports.ts | 126 ++++++++++++------ 6 files changed, 213 insertions(+), 47 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index c21210bd48..b0f5fc0b47 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -653,6 +653,18 @@ describe("module-loader/isUnresolvedTenantImport", () => { assertEquals(isUnresolvedTenantImport(missing(), new Set(["./missing"])), true); }); + it("does not classify an unrelated missing target alongside a dropped specifier", () => { + const unrelated = Object.assign( + new TypeError( + 'Module not found "file:///tmp/out/veryfront-modules/proj-a/app/cycle-alias".\n' + + ` at file://${REBUILT}:1:23`, + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(unrelated, new Set(["./missing"]), REBUILT), false); + }); + // The cycle-breaking branch leaves a resolved target's specifier as authored // and relies on an alias the code itself marks as not runtime-verified. That // target resolved, so it is never recorded as dropped — and a framework path @@ -687,9 +699,7 @@ describe("module-loader/isUnresolvedTenantImport", () => { ); assertEquals(isUnresolvedTenantImport(evicted, new Set(["./missing"]), REBUILT), false); - // Without the artifact path the predicate cannot tell the two apart, which - // is why the call site passes it. - assertEquals(isUnresolvedTenantImport(evicted, new Set(["./missing"])), true); + assertEquals(isUnresolvedTenantImport(evicted, new Set(["./missing"])), false); }); // The regression this pins: the importer line also names the rebuilt diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index 05d5c5c05f..c972119fba 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -408,6 +408,32 @@ function missingModuleTarget(message: string): string { return match?.[1] ?? match?.[2] ?? ""; } +function normalizeMissingModuleTarget(message: string): string { + const target = missingModuleTarget(message).replace(/[?#].*$/, ""); + if (target.startsWith("file://")) { + try { + return decodeURIComponent(new URL(target).pathname); + } catch { + return target.replace(/^file:\/+/, "/"); + } + } + return target; +} + +function normalizeUnresolvedSpecifier(specifier: string): string { + return specifier + .replace(/[?#].*$/, "") + .replace(/^@\//, "") + .replace(/^(\.\/|\.\.\/)+/, "") + .replace(/^\/+/, ""); +} + +function missingTargetMatchesSpecifier(target: string, specifier: string): boolean { + const normalizedSpecifier = normalizeUnresolvedSpecifier(specifier); + if (!normalizedSpecifier) return false; + return target === normalizedSpecifier || target.endsWith(`/${normalizedSpecifier}`); +} + export function isUnresolvedTenantImport( error: unknown, unresolvedSpecifiers: ReadonlySet, @@ -432,7 +458,10 @@ export function isUnresolvedTenantImport( if (rebuiltArtifactPath && missingModuleTarget(message).includes(rebuiltArtifactPath)) { return false; } - return true; + const missingTarget = normalizeMissingModuleTarget(message); + return [...unresolvedSpecifiers].some((specifier) => + missingTargetMatchesSpecifier(missingTarget, specifier) + ); } /** diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 1d6b853c2a..2e35eaf6bb 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -5,7 +5,10 @@ import { basename, dirname, join } from "#veryfront/compat/path/index.ts"; import { getLocalAdapter } from "#veryfront/platform/adapters/registry.ts"; import { hashCodeHex } from "#veryfront/utils/hash-utils.ts"; import { getModulePathCache } from "#veryfront/transforms/mdx/esm-module-loader/cache/index.ts"; -import { buildMdxEsmPathCacheKey } from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; +import { + buildMdxEsmPathCacheKey, + UNRESOLVED_IMPORTS_SIDECAR_SUFFIX, +} from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; import { persistTransformedModule, readPersistedUnresolvedSpecifiers, @@ -291,4 +294,45 @@ describe("module-loader/module-persistence", () => { await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); } }); + + it("does not fail persistence when unresolved-import evidence cannot be written", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); + const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); + const localAdapter = await getLocalAdapter(); + const filePath = join(projectDir, "lib/evidence.ts"); + const moduleCache = new Map(); + const transformedCode = "export const evidence = true;"; + + const stubFs = Object.create(localAdapter.fs) as typeof localAdapter.fs; + stubFs.writeFile = (path: string, content: string) => { + if (path.endsWith(UNRESOLVED_IMPORTS_SIDECAR_SUFFIX)) { + return Promise.reject(new Error("ENOSPC: no space left on device")); + } + return localAdapter.fs.writeFile(path, content); + }; + const stubAdapter = Object.create(localAdapter) as typeof localAdapter; + Object.defineProperty(stubAdapter, "fs", { value: stubFs }); + + try { + await Deno.mkdir(dirname(filePath), { recursive: true }); + + const result = await persistTransformedModule({ + filePath, + projectDir, + tmpDir, + transformedCode, + localAdapter: stubAdapter, + moduleCache, + cacheKey: "evidence", + unresolvedSpecifiers: ["./missing"], + }); + + assertEquals(await Deno.readTextFile(result), transformedCode); + assertEquals(moduleCache.get("evidence"), result); + assertEquals(await readPersistedUnresolvedSpecifiers(result, stubAdapter), []); + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); + await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); + } + }); }); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 3c538bcf23..fe74451e91 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -228,10 +228,17 @@ export async function persistTransformedModule( // durable. A new worker can otherwise reuse the transformed artifact from // _index.json without knowing which authored imports remained unresolved. if (unresolvedSpecifiers.length > 0) { - await input.localAdapter.fs.writeFile( - `${tempFilePath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`, - serializedUnresolvedSpecifiers, - ); + try { + await input.localAdapter.fs.writeFile( + `${tempFilePath}${UNRESOLVED_IMPORTS_SIDECAR_SUFFIX}`, + serializedUnresolvedSpecifiers, + ); + } catch (error) { + logger.warn("Failed to persist unresolved-import evidence", { + filePath: input.filePath.slice(-40), + error: error instanceof Error ? error.message : String(error), + }); + } } if (input.contentSourceId) { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 1cfa657413..ed57f276f7 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -64,6 +64,18 @@ import { bar } from "./local.js"; "_vf_modules/polyfills/runtime.js", ]); }); + + it("does not abort on malformed escaped import specifiers", () => { + const code = [ + `import bad from "./bad\\xZZ";`, + `import good from "/_vf_modules/components/Good.js";`, + ].join("\n"); + + const result = findNestedImports(code); + + assertEquals(result.vfModules.map((module) => module.path), []); + assertEquals(result.relative.map((module) => module.path), []); + }); }); describe("hasUnresolvedImports", () => { @@ -141,6 +153,20 @@ import { bar } from "./local.js"; assertEquals(result.count, 0); assertEquals(result.paths, []); }); + + it("treats malformed escaped import specifiers as unresolved evidence", () => { + const result = hasUnresolvedImports(`import bad from "/_vf_modules/bad\\xZZ";`); + + assertEquals(result.count, 1); + assertEquals(result.paths, [""]); + }); + + it("treats raw line terminators in import specifiers as unresolved evidence", () => { + const result = hasUnresolvedImports('import bad from "/_vf_modules/bad\nmodule.js";'); + + assertEquals(result.count, 1); + assertEquals(result.paths, [""]); + }); }); describe("resolveNestedModuleImports", () => { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 7329656b70..f6e3473ec1 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -13,9 +13,10 @@ import { findStaticSideEffectImportSpans, replaceSourceSpans, type SourceSpanReplacement, + type StaticImportSpan, } from "../utils/source-spans.ts"; import { buildMissingModuleError } from "../missing-module.ts"; -import { splitSpecifierSuffix } from "../../../shared/specifier-suffix.ts"; +import { splitSpecifierSuffix } from "#veryfront/transforms/shared/specifier-suffix.ts"; import type { Logger } from "#veryfront/utils"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { Semaphore } from "#veryfront/modules/react-loader/ssr-module-loader/concurrency/semaphore.ts"; @@ -39,6 +40,23 @@ type NestedImportSpan = { isSideEffect?: boolean; }; +const MALFORMED_IMPORT_SPECIFIER = ""; + +function isMalformedSpecifierSyntaxError(error: unknown): boolean { + return error instanceof SyntaxError && error.message.includes("module specifier"); +} + +function scanImportSpans( + scan: () => StaticImportSpan[], +): { spans: StaticImportSpan[]; malformed: boolean } { + try { + return { spans: scan(), malformed: false }; + } catch (error) { + if (!isMalformedSpecifierSyntaxError(error)) throw error; + return { spans: [], malformed: true }; + } +} + /** * Serialize a resolved module URL as a JavaScript string literal. * @@ -63,13 +81,51 @@ export function findNestedImports( } { const vfModules: NestedImportSpan[] = []; const relative: NestedImportSpan[] = []; - - for ( - const { original, path: rawPath, start, end } of findStaticImportFromSpans( + const staticVfModuleSpans = scanImportSpans(() => + findStaticImportFromSpans( moduleCode, matchUnresolvedVfModuleSpecifier, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ) + ).spans; + const dynamicVfModuleSpans = scanImportSpans(() => + findDynamicImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ).spans; + const sideEffectVfModuleSpans = scanImportSpans(() => + findStaticSideEffectImportSpans( + moduleCode, + matchUnresolvedVfModuleSpecifier, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ).spans; + const staticRelativeSpans = scanImportSpans(() => + findStaticImportFromSpans( + moduleCode, + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ).spans; + const dynamicRelativeSpans = scanImportSpans(() => + findDynamicImportSpans( + moduleCode, + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ).spans; + const sideEffectRelativeSpans = scanImportSpans(() => + findStaticSideEffectImportSpans( + moduleCode, + (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ) + ).spans; + + for ( + const { original, path: rawPath, start, end } of staticVfModuleSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path @@ -83,11 +139,7 @@ export function findNestedImports( } for ( - const { original, path: rawPath, start, end } of findDynamicImportSpans( - moduleCode, - matchUnresolvedVfModuleSpecifier, - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ) + const { original, path: rawPath, start, end } of dynamicVfModuleSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path @@ -102,11 +154,7 @@ export function findNestedImports( } for ( - const { original, path: rawPath, start, end } of findStaticSideEffectImportSpans( - moduleCode, - matchUnresolvedVfModuleSpecifier, - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ) + const { original, path: rawPath, start, end } of sideEffectVfModuleSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath.replace(/^(?:file:\/\/)?\/+/, "")); // Strip file:// prefix and leading slashes to get clean _vf_modules/... path @@ -121,11 +169,7 @@ export function findNestedImports( } for ( - const { original, path: rawPath, start, end } of findStaticImportFromSpans( - moduleCode, - (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ) + const { original, path: rawPath, start, end } of staticRelativeSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ @@ -138,11 +182,7 @@ export function findNestedImports( } for ( - const { original, path: rawPath, start, end } of findDynamicImportSpans( - moduleCode, - (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ) + const { original, path: rawPath, start, end } of dynamicRelativeSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ @@ -156,11 +196,7 @@ export function findNestedImports( } for ( - const { original, path: rawPath, start, end } of findStaticSideEffectImportSpans( - moduleCode, - (specifier) => specifier.match(/^(\.\.?\/.+)$/)?.[1], - MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ) + const { original, path: rawPath, start, end } of sideEffectRelativeSpans ) { const { path, suffix } = splitSpecifierSuffix(rawPath); relative.push({ @@ -180,26 +216,40 @@ export function findNestedImports( * Check for unresolved /_vf_modules/ imports. */ export function hasUnresolvedImports(moduleCode: string): { count: number; paths: string[] } { - const matches = [ - ...findStaticImportFromSpans( + const staticMatches = scanImportSpans(() => + findStaticImportFromSpans( moduleCode, matchUnresolvedVfModuleSpecifier, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ), - ...findStaticSideEffectImportSpans( + ) + ); + const sideEffectMatches = scanImportSpans(() => + findStaticSideEffectImportSpans( moduleCode, matchUnresolvedVfModuleSpecifier, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ), - ...findDynamicImportSpans( + ) + ); + const dynamicMatches = scanImportSpans(() => + findDynamicImportSpans( moduleCode, matchUnresolvedVfModuleSpecifier, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, - ), + ) + ); + const matches = [ + ...staticMatches.spans, + ...sideEffectMatches.spans, + ...dynamicMatches.spans, ]; + const malformedCount = [staticMatches, sideEffectMatches, dynamicMatches] + .filter((result) => result.malformed).length; return { - count: matches.length, - paths: matches.map((match) => match.path).slice(0, 5), + count: matches.length + malformedCount, + paths: [ + ...matches.map((match) => match.path), + ...Array.from({ length: malformedCount }, () => MALFORMED_IMPORT_SPECIFIER), + ].slice(0, 5), }; } From 7d237025173ad1d14bc07c82e12cf7af4e7b821e Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 11:35:52 +0200 Subject: [PATCH 063/104] Avoid reusing artifacts without attribution evidence A failed unresolved-import sidecar write leaves the transformed artifact usable for the current request, but reusing that artifact after restart would lose the tenant attribution evidence that made the artifact identity meaningful. Gate reusable cache publication on sidecar durability while keeping the current-request return path available. Constraint: CodeRabbit flagged exact-head evidence that ENOSPC sidecar failures still published module and path cache entries.\nRejected: Re-throw sidecar write failures | would regress current-request availability for optional attribution metadata.\nConfidence: high\nScope-risk: narrow\nDirective: Do not publish reusable module-loader cache pointers for artifacts whose required attribution sidecar could not be written.\nTested: Red-first module-persistence regression; deno fmt --check on touched files; deno lint on touched files; deno check on touched files; focused module-persistence and index tests; full module-loader tests; module and dependency boundary lint; git diff --check.\nNot-tested: Exact-head GitHub CI until pushed. --- .../module-loader/module-persistence.test.ts | 10 ++++++++-- .../orchestrator/module-loader/module-persistence.ts | 10 +++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 2e35eaf6bb..158630fe5e 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -295,7 +295,7 @@ describe("module-loader/module-persistence", () => { } }); - it("does not fail persistence when unresolved-import evidence cannot be written", async () => { + it("keeps the artifact available without caching when unresolved-import evidence cannot be written", async () => { const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); const localAdapter = await getLocalAdapter(); @@ -324,12 +324,18 @@ describe("module-loader/module-persistence", () => { localAdapter: stubAdapter, moduleCache, cacheKey: "evidence", + contentSourceId: "preview-main", + reactVersion: "19.1.1", unresolvedSpecifiers: ["./missing"], }); assertEquals(await Deno.readTextFile(result), transformedCode); - assertEquals(moduleCache.get("evidence"), result); + assertEquals(moduleCache.has("evidence"), false); assertEquals(await readPersistedUnresolvedSpecifiers(result, stubAdapter), []); + + const pathCache = await getModulePathCache(tmpDir); + const mdxCacheKey = buildMdxEsmPathCacheKey("_vf_modules/lib/evidence.js", "19.1.1"); + assertEquals(pathCache.has(mdxCacheKey), false); } finally { await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index fe74451e91..09d6921a20 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -227,6 +227,7 @@ export async function persistTransformedModule( // Publish the path cache only after its tenant-attribution evidence is // durable. A new worker can otherwise reuse the transformed artifact from // _index.json without knowing which authored imports remained unresolved. + let shouldPublishReusableCache = true; if (unresolvedSpecifiers.length > 0) { try { await input.localAdapter.fs.writeFile( @@ -234,6 +235,7 @@ export async function persistTransformedModule( serializedUnresolvedSpecifiers, ); } catch (error) { + shouldPublishReusableCache = false; logger.warn("Failed to persist unresolved-import evidence", { filePath: input.filePath.slice(-40), error: error instanceof Error ? error.message : String(error), @@ -241,7 +243,7 @@ export async function persistTransformedModule( } } - if (input.contentSourceId) { + if (shouldPublishReusableCache && input.contentSourceId) { const normalizedPath = `_vf_modules/${relativePath.replace(/\.(tsx?|jsx|mdx)$/, ".js")}`; const mdxCacheKey = buildMdxEsmPathCacheKey( normalizedPath, @@ -265,9 +267,11 @@ export async function persistTransformedModule( }); } - input.moduleCache.set(input.cacheKey, tempFilePath); + if (shouldPublishReusableCache) { + input.moduleCache.set(input.cacheKey, tempFilePath); + } - if (input.isCycleTarget) { + if (shouldPublishReusableCache && input.isCycleTarget) { const hashedFileName = jsPath.slice(jsPath.lastIndexOf("/") + 1); await writeCycleTargetAlias(input, outputRelativePath, hashedFileName); } From 8237167cff8ee4f235c24cc91d03f8a02f35ad84 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 11:57:57 +0200 Subject: [PATCH 064/104] Prevent stale cache evidence from hiding tenant imports Sidecar attribution made reusable module pointers conditional, but exact-head review found two follow-up edges: current-request cycle aliases still need to exist when sidecar persistence fails, and disk path-cache entries from before attribution sidecars can be replayed after a worker restart without evidence. This keeps the sidecar durability gate on reusable moduleCache and MDX path-cache publication, restores cycle alias publication for the active graph, and bumps the MDX ESM cache namespace schema so pre-sidecar _index.json entries miss and rebuild with current attribution evidence. Constraint: Exact-head Codex review reported stale path-cache attribution loss and sidecar-failure cycle alias loss after commit 7d2370251. Rejected: Treat every missing sidecar as a cache miss | current no-unresolved artifacts intentionally do not write sidecars. Confidence: high Scope-risk: moderate Directive: Keep reusable cache publication gated on required attribution evidence, but keep current-request graph aliases independent from reusable cache pointers. Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/ Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/cache-format.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/cache-keys.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/index.test.ts Tested: deno check touched files; deno lint touched files; deno fmt --check touched files; git diff --check; deno task lint:module-boundaries; deno task lint:dependency-boundaries Not-tested: GitHub exact-head CI until pushed. --- .../orchestrator/module-loader/index.test.ts | 54 ++++++++++++++++++- .../module-loader/module-persistence.test.ts | 3 ++ .../module-loader/module-persistence.ts | 2 +- .../mdx/esm-module-loader/cache-format.ts | 2 + 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index b0f5fc0b47..cd8082d47a 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -10,7 +10,8 @@ import { import { afterAll, describe, it } from "#veryfront/testing/bdd.ts"; import { getLocalAdapter } from "#veryfront/platform/adapters/registry.ts"; import { basename, dirname, join } from "#veryfront/compat/path/index.ts"; -import { runWithCacheDir } from "#veryfront/utils/cache-dir.ts"; +import { getMdxEsmCacheDir, runWithCacheDir } from "#veryfront/utils/cache-dir.ts"; +import { buildMdxEsmPathCacheKey } from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; import { isMissingModuleError, isUnresolvedTenantImport, @@ -571,6 +572,57 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("ignores legacy disk cache entries that predate unresolved-import sidecars", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "./dep";`, + `export default function Page() { return label; }`, + ].join("\n"), + "app/dep.tsx": [ + `import { gone } from "./gone";`, + `export const label = gone;`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const diskConfig = { + ...config, + projectId: "legacy-cache-project", + contentSourceId: "main", + }; + const legacyCacheDir = join( + getMdxEsmCacheDir(), + encodeURIComponent(diskConfig.projectId), + encodeURIComponent(diskConfig.contentSourceId), + ); + await Deno.mkdir(join(legacyCacheDir, "app"), { recursive: true }); + const legacyDepArtifact = join(legacyCacheDir, "app/dep.legacy.js"); + await Deno.writeTextFile( + legacyDepArtifact, + [`import { gone } from "./gone";`, `export const label = gone;`].join("\n"), + ); + const legacyPathKey = `mdx-esm-ec841873:19.1.1:_vf_modules/app/dep.js`; + assertEquals( + legacyPathKey === buildMdxEsmPathCacheKey("_vf_modules/app/dep.js", "19.1.1"), + false, + ); + await Deno.writeTextFile( + join(legacyCacheDir, "_index.json"), + JSON.stringify({ [legacyPathKey]: legacyDepArtifact }), + ); + + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), diskConfig), + Error, + ); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + it("attributes an executed dynamic dependency that failed to transform", async () => { await withModuleLoaderFixture( { diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 158630fe5e..9084c829ad 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -327,9 +327,12 @@ describe("module-loader/module-persistence", () => { contentSourceId: "preview-main", reactVersion: "19.1.1", unresolvedSpecifiers: ["./missing"], + isCycleTarget: true, }); assertEquals(await Deno.readTextFile(result), transformedCode); + const aliasCode = await Deno.readTextFile(join(tmpDir, "lib/evidence.js")); + assertStringIncludes(aliasCode, `export * from "./${basename(result)}";`); assertEquals(moduleCache.has("evidence"), false); assertEquals(await readPersistedUnresolvedSpecifiers(result, stubAdapter), []); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 09d6921a20..ca6f348218 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -271,7 +271,7 @@ export async function persistTransformedModule( input.moduleCache.set(input.cacheKey, tempFilePath); } - if (shouldPublishReusableCache && input.isCycleTarget) { + if (input.isCycleTarget) { const hashedFileName = jsPath.slice(jsPath.lastIndexOf("/") + 1); await writeCycleTargetAlias(input, outputRelativePath, hashedFileName); } diff --git a/src/transforms/mdx/esm-module-loader/cache-format.ts b/src/transforms/mdx/esm-module-loader/cache-format.ts index fb88b5fad1..5c24775bd0 100644 --- a/src/transforms/mdx/esm-module-loader/cache-format.ts +++ b/src/transforms/mdx/esm-module-loader/cache-format.ts @@ -13,6 +13,7 @@ const ALL_FILE_URL_PATTERN_SOURCE = /file:\/\/([^"'\s]+)/.source; const MJS_FILE_URL_PATTERN_SOURCE = /file:\/\/([^"'\s]+\.mjs)/.source; const CACHE_NAMESPACE_SENTINEL = "__vf_cache_namespace__"; export const UNRESOLVED_IMPORTS_SIDECAR_SUFFIX = ".unresolved-imports.json"; +const MDX_ESM_PATH_CACHE_ATTRIBUTION_SCHEMA = "unresolved-import-sidecars-v1"; const PUBLIC_RUNTIME_SPECIFIERS = [ "veryfront/head", "veryfront/router", @@ -116,6 +117,7 @@ function buildMdxEsmCacheSchemaSample() { unresolvedVfModulesPattern: UNRESOLVED_VF_MODULES_PATTERN.source, allFileUrlPattern: ALL_FILE_URL_PATTERN_SOURCE, mjsFileUrlPattern: MJS_FILE_URL_PATTERN_SOURCE, + pathCacheAttributionSchema: MDX_ESM_PATH_CACHE_ATTRIBUTION_SCHEMA, sourceHashing: [ hashString("_vf_modules/pages/index.jsexport default 1;"), hashString("/tmp/project/Button.tsx\0export default function Button() {}"), From 44ccaa96be4032f11a938a58d6c2a9dd6d126f38 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 12:45:37 +0200 Subject: [PATCH 065/104] Keep Markdown compiler regressions type-checkable The Markdown compiler tests exercise the production content processor path, but the PR added test coverage using a non-existent compilation mode and an incomplete ContentProcessor stub. Align the tests with the declared content processor contract while preserving the assertions that catch tenant Markdown failures and heading extraction behavior. Constraint: CompilationMode is limited to development or production. Rejected: Cast test inputs through unknown | That would hide the contract regression instead of proving the typed API remains usable. Confidence: high Scope-risk: narrow Directive: Keep compiler tests on real CompilationMode values so deno check can catch API drift. Tested: deno fmt --check src/transforms/md/compiler/md-compiler.test.ts Tested: deno check src/transforms/md/compiler/md-compiler.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/md/compiler/md-compiler.test.ts Tested: deno check over all PR-changed TypeScript files --- .../md/compiler/md-compiler.test.ts | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/transforms/md/compiler/md-compiler.test.ts b/src/transforms/md/compiler/md-compiler.test.ts index 854672debd..53c3256fad 100644 --- a/src/transforms/md/compiler/md-compiler.test.ts +++ b/src/transforms/md/compiler/md-compiler.test.ts @@ -15,6 +15,8 @@ import { } from "#veryfront/extensions/parser/yaml-parser.ts"; import { compileMarkdownRuntime } from "./md-compiler.ts"; +const markdownCompilationMode = "production"; + async function withYamlSyntaxErrorProvider(body: () => Promise): Promise { const previous = tryResolveContract(YamlParserProviderName); registerContract( @@ -41,7 +43,7 @@ describe( describe("compileMarkdownRuntime", () => { it("compiles simple markdown to a React component", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Hello World\n\nSome paragraph text.", ); @@ -52,7 +54,7 @@ describe( it("returns frontmatter object", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "---\ntitle: Test\nauthor: Jane\n---\n# Content", ); @@ -65,7 +67,7 @@ describe( const error = await assertRejects( () => compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "---\ntitle: [unterminated\n---\n# Content", undefined, @@ -84,7 +86,7 @@ describe( const error = await assertRejects( () => compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "---\ntitle: broken\n---\n# Content", undefined, @@ -110,13 +112,19 @@ describe( compileMarkdown() { throw new SyntaxError("YAML backend unavailable at line 1, column 1"); }, + getRemarkPlugins() { + return []; + }, + getRehypePlugins() { + return []; + }, } satisfies ContentProcessor, ); try { const error = await assertRejects(() => compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Content", undefined, @@ -137,21 +145,22 @@ describe( it("extracts headings", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# First\n## Second\n### Third", ); assertEquals(Array.isArray(result.headings), true); - assertEquals(result.headings.length, 3); - assertEquals(result.headings[0]!.text, "First"); - assertEquals(result.headings[0]!.level, 1); - assertEquals(result.headings[1]!.text, "Second"); - assertEquals(result.headings[1]!.level, 2); + const headings = result.headings!; + assertEquals(headings.length, 3); + assertEquals(headings[0]!.text, "First"); + assertEquals(headings[0]!.level, 1); + assertEquals(headings[1]!.text, "Second"); + assertEquals(headings[1]!.level, 2); }); it("returns rawHtml", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Hello", ); @@ -161,7 +170,7 @@ describe( it("handles empty content", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "", ); @@ -171,7 +180,7 @@ describe( it("passes frontmatter through when provided as parameter", async () => { const fm = { title: "Override", custom: "value" }; const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Content", fm, @@ -187,7 +196,7 @@ describe( | Cell 1 | Cell 2 | `; const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", markdown, ); @@ -196,17 +205,18 @@ describe( it("generates heading IDs (slugs)", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Hello World", ); - assertEquals(result.headings[0]!.id, "hello-world"); + const headings = result.headings!; + assertEquals(headings[0]!.id, "hello-world"); }); it("compiles code blocks with syntax highlighting", async () => { const markdown = "```js\nconst x = 1;\n```"; const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", markdown, ); @@ -216,7 +226,7 @@ describe( it("uses preview wrapper for non-routable files", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Readme Content", undefined, @@ -227,7 +237,7 @@ describe( it("uses standard wrapper for pages/ files", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Page Content", undefined, @@ -240,7 +250,7 @@ describe( describe("HTML sanitization", () => { it("strips script tags from markdown", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", '# Title\n\n\n\nSafe text.', ); @@ -251,7 +261,7 @@ describe( it("strips onclick event handlers from HTML", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", '
Click me
', ); @@ -260,7 +270,7 @@ describe( it("strips iframe tags", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", '\n\nSafe text.', ); @@ -270,7 +280,7 @@ describe( it("strips javascript: URLs from links", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "[click me](javascript:alert(1))", ); @@ -279,7 +289,7 @@ describe( it("preserves safe HTML elements", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "**bold** and *italic* and [link](https://example.com)", ); @@ -290,7 +300,7 @@ describe( it("preserves images with safe src", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", '![alt text](https://example.com/img.png "title")', ); @@ -303,7 +313,7 @@ describe( it("preserves safe embedded HTML like details/summary", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "
Click\n\nHidden content\n\n
", ); @@ -314,7 +324,7 @@ describe( it("strips style tags", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Title\n\n\n\nVisible text.", ); @@ -324,7 +334,7 @@ describe( it("preserves data-node attributes in studio embed mode", async () => { const result = await compileMarkdownRuntime( - "runtime", + markdownCompilationMode, "/tmp/project", "# Hello\n\nSome paragraph.", undefined, From ca698aadcc11f3f59d407f3a5971e976598d4180 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:01:01 +0200 Subject: [PATCH 066/104] Keep regex-looking imports out of dependency resolution Arrow function bodies can terminate by ASI before a regex literal. Treat that newline boundary as regex-valid so import-looking text inside the literal never enters strict dependency resolution. Remove the newly clean Markdown compiler test from the typecheck grandfather list so CI locks in its repaired contract. Constraint: Same-line slash tokens after arrow bodies remain expression continuations and must stay classified as division. Rejected: Treat every arrow-body closing brace as a regex boundary | misclassifies valid same-line division expressions. Confidence: high Scope-risk: narrow Directive: Preserve the line-terminator check when extending arrow-body scanning. Tested: source-spans suite 95 steps; test typecheck baseline; targeted format, lint, check, and diff checks. --- scripts/lint/test-typecheck-baseline.json | 1 - .../utils/source-spans.test.ts | 9 +++++++ .../esm-module-loader/utils/source-spans.ts | 26 ++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/scripts/lint/test-typecheck-baseline.json b/scripts/lint/test-typecheck-baseline.json index 9671836a3e..71f1004446 100644 --- a/scripts/lint/test-typecheck-baseline.json +++ b/scripts/lint/test-typecheck-baseline.json @@ -46,7 +46,6 @@ "src/runs/schemas.test.ts", "src/server/build-service-worker.test.ts", "src/transforms/import-rewriter/strategies/import-map-strategy.test.ts", - "src/transforms/md/compiler/md-compiler.test.ts", "src/transforms/mdx/compiler/index.test.ts", "src/transforms/mdx/esm-module-loader/loader.test.ts", "src/workflow/api/workflow-client.test.ts" diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 13d520600f..4a8589ba73 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -792,6 +792,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { } }); + it("recognizes regex literals after arrow function bodies at ASI boundaries", () => { + assertEquals( + vfModuleSpecifiers( + 'const load = () => {}\n/import("\\/_vf_modules\\/fake.js")/.test(value);', + ), + [], + ); + }); + it("ignores an import-looking string or comment", () => { assertEquals(specifiers(`const s = 'import("./foo.js")';`), []); assertEquals(specifiers(`// import("./foo.js")\nconst x = 1;`), []); diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 1de89ad6b7..2ae50a25ed 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -517,6 +517,24 @@ function isPlainStatementBlockCloseBrace( return beforeLabel < rangeStart || source[beforeLabel] === ";" || source[beforeLabel] === "}"; } +function isArrowFunctionBodyCloseBraceAtAsiBoundary( + source: string, + index: number, + nextTokenIndex: number, + matchingOpenBraces: ReadonlyMap, +): boolean { + const openBrace = matchingOpenBraces.get(index); + if (openBrace === undefined || source[openBrace.previousTokenIndex] !== ">") return false; + + const beforeArrow = previousSignificantIndex(source, openBrace.previousTokenIndex); + if (source[beforeArrow] !== "=") return false; + + for (let cursor = index + 1; cursor < nextTokenIndex; cursor++) { + if (isLineTerminator(source[cursor]!)) return true; + } + return false; +} + function isForOfKeywordBefore( source: string, rangeStart: number, @@ -572,7 +590,13 @@ function canStartRegexLiteral( ) || isDeclarationBlockCloseBrace(source, previous, matchingOpenBraces) || isStatementBlockCloseBrace(source, previous, matchingOpenBraces) || - isPlainStatementBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces)) + isPlainStatementBlockCloseBrace(source, previous, rangeStart, matchingOpenBraces) || + isArrowFunctionBodyCloseBraceAtAsiBoundary( + source, + previous, + index, + matchingOpenBraces, + )) ) return true; if ( (char === "+" || char === "-") && From e26388f4192a835872103b91d033b7ca102ffee5 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:14:26 +0200 Subject: [PATCH 067/104] Preserve tenant attribution for missing alias imports A missing project alias survives the module transform as a /_vf_modules runtime target, so the retry classifier must compare against that rewritten shape instead of only the authored @/ specifier. The matcher now derives the same alias runtime target the transform emits, while preserving the exact-target guard that prevents unrelated framework misses from being downgraded. Constraint: PR #3723 must resolve the exact-head CodeRabbit P2 without widening retry classification back to any unresolved specifier. Rejected: Classify every non-empty unresolved set as tenant | reintroduces the framework-cache false positive this PR already fixed. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/index.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/ Tested: deno fmt --check src/rendering/orchestrator/module-loader/index.ts src/rendering/orchestrator/module-loader/index.test.ts Tested: deno lint src/rendering/orchestrator/module-loader/index.ts src/rendering/orchestrator/module-loader/index.test.ts Tested: deno check --no-lock src/rendering/orchestrator/module-loader/index.ts src/rendering/orchestrator/module-loader/index.test.ts --- .../orchestrator/module-loader/index.test.ts | 35 +++++++++++++++++++ .../orchestrator/module-loader/index.ts | 19 ++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index cd8082d47a..932d94d932 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -390,6 +390,29 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("tags a missing project alias import as a tenant build failure", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "@/components/Missing";`, + `export default function Page() { return label; }`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + it("tags missing side-effect imports in every legal declaration position", async () => { for ( const source of [ @@ -705,6 +728,18 @@ describe("module-loader/isUnresolvedTenantImport", () => { assertEquals(isUnresolvedTenantImport(missing(), new Set(["./missing"])), true); }); + it("classifies a dropped project alias after the runtime reports its rewritten target", () => { + const aliasMissing = Object.assign( + new TypeError( + 'Module not found "file:///tmp/out/veryfront-modules/proj-a/_vf_modules/components/Missing.js".\n' + + ` at file://${REBUILT}:1:23`, + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals(isUnresolvedTenantImport(aliasMissing, new Set(["@/components/Missing"])), true); + }); + it("does not classify an unrelated missing target alongside a dropped specifier", () => { const unrelated = Object.assign( new TypeError( diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index c972119fba..50a3dc0e8a 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -428,10 +428,23 @@ function normalizeUnresolvedSpecifier(specifier: string): string { .replace(/^\/+/, ""); } -function missingTargetMatchesSpecifier(target: string, specifier: string): boolean { +function unresolvedSpecifierRuntimeTargets(specifier: string): string[] { const normalizedSpecifier = normalizeUnresolvedSpecifier(specifier); - if (!normalizedSpecifier) return false; - return target === normalizedSpecifier || target.endsWith(`/${normalizedSpecifier}`); + const targets = normalizedSpecifier ? [normalizedSpecifier] : []; + + if (specifier.startsWith("@/")) { + const aliasPath = specifier.replace(/[?#].*$/, "").slice(2); + const jsPath = aliasPath.endsWith(".js") ? aliasPath : `${aliasPath}.js`; + targets.push(`_vf_modules/${jsPath}`); + } + + return [...new Set(targets)]; +} + +function missingTargetMatchesSpecifier(target: string, specifier: string): boolean { + return unresolvedSpecifierRuntimeTargets(specifier).some((candidate) => + target === candidate || target.endsWith(`/${candidate}`) + ); } export function isUnresolvedTenantImport( From 4ed33d41df718404e5d4539a861b65f64dfdeff0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:17:01 +0200 Subject: [PATCH 068/104] Keep rewritten aliases attributable to tenant source Missing project aliases are recorded in authored form but fail at runtime in the canonical SSR module form. Share that rewrite rule with failure matching so tenant alias typos stay warning-classified without matching unrelated cache or framework misses. Constraint: Attribution must compare the actual missing target, not the importer or the full runtime message. Rejected: Match only the authored alias suffix | it drifts from extension and module-prefix rewriting and misses the runtime target. Confidence: high Scope-risk: narrow Directive: Keep tenant import attribution aligned with AliasStrategy SSR rewrites. Tested: module-loader and alias-strategy suites, 66 combined steps; targeted format, lint, check, and diff checks. --- .../orchestrator/module-loader/index.test.ts | 15 +++++++++++++++ .../orchestrator/module-loader/index.ts | 6 +++--- .../strategies/alias-strategy.ts | 17 +++++++++++------ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index cd8082d47a..bdf7e309e2 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -705,6 +705,21 @@ describe("module-loader/isUnresolvedTenantImport", () => { assertEquals(isUnresolvedTenantImport(missing(), new Set(["./missing"])), true); }); + it("classifies a dropped project alias after its SSR rewrite", () => { + const aliasMissing = Object.assign( + new TypeError( + 'Module not found "file:///tmp/out/veryfront-modules/proj-a/_vf_modules/components/Foo.js".\n' + + ` at file://${REBUILT}:1:23`, + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals( + isUnresolvedTenantImport(aliasMissing, new Set(["@/components/Foo"]), REBUILT), + true, + ); + }); + it("does not classify an unrelated missing target alongside a dropped specifier", () => { const unrelated = Object.assign( new TypeError( diff --git a/src/rendering/orchestrator/module-loader/index.ts b/src/rendering/orchestrator/module-loader/index.ts index c972119fba..43a0530d4f 100644 --- a/src/rendering/orchestrator/module-loader/index.ts +++ b/src/rendering/orchestrator/module-loader/index.ts @@ -34,6 +34,7 @@ import type { TransformProgressListener } from "#veryfront/transforms/progress.t import type { DependencyPinningSourceInput } from "#veryfront/transforms/esm/package-registry.ts"; import { MODULE_CACHE_MAX_ENTRIES } from "#veryfront/utils/constants/cache.ts"; import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; +import { rewriteSsrProjectAliasSpecifier } from "#veryfront/transforms/import-rewriter/strategies/alias-strategy.ts"; export { isBuildFailure } from "./build-failure.ts"; @@ -421,9 +422,8 @@ function normalizeMissingModuleTarget(message: string): string { } function normalizeUnresolvedSpecifier(specifier: string): string { - return specifier - .replace(/[?#].*$/, "") - .replace(/^@\//, "") + const withoutSuffix = specifier.replace(/[?#].*$/, ""); + return (rewriteSsrProjectAliasSpecifier(withoutSuffix) ?? withoutSuffix) .replace(/^(\.\/|\.\.\/)+/, "") .replace(/^\/+/, ""); } diff --git a/src/transforms/import-rewriter/strategies/alias-strategy.ts b/src/transforms/import-rewriter/strategies/alias-strategy.ts index d6a6884737..9f7b714ae2 100644 --- a/src/transforms/import-rewriter/strategies/alias-strategy.ts +++ b/src/transforms/import-rewriter/strategies/alias-strategy.ts @@ -7,6 +7,16 @@ import type { import { appendDependencyPinningPathKey, normalizeExtension } from "../url-builder.ts"; import { getProjectRelativePath } from "../project-paths.ts"; +/** Rewrite a project alias through the canonical SSR module-path rule. */ +export function rewriteSsrProjectAliasSpecifier(specifier: string): string | null { + if (!specifier.startsWith("@/")) return null; + let normalizedPath = normalizeExtension(specifier.slice(2)); + if (!/\.(tsx?|jsx?|mjs|cjs|mdx|css)$/.test(normalizedPath)) { + normalizedPath = `${normalizedPath}.js`; + } + return `/_vf_modules/${normalizedPath}`; +} + export class AliasStrategy implements ImportRewriteStrategy { readonly name = "alias"; readonly priority = 1; @@ -20,15 +30,10 @@ export class AliasStrategy implements ImportRewriteStrategy { // SSR uses /_vf_modules/ paths for HTTP module resolution if (ctx.target === "ssr") { - let normalizedPath = normalizeExtension(path); - // Add .js if no extension present - if (!/\.(tsx?|jsx?|mjs|cjs|mdx|css)$/.test(normalizedPath)) { - normalizedPath = `${normalizedPath}.js`; - } // The SSR adapter adds `ssr`, routing, cache-buster, and dependency // snapshot params together after this strategy runs. Keeping this URL // query-free ensures its `.js` matcher still sees the edge. - return { specifier: `/_vf_modules/${normalizedPath}` }; + return { specifier: rewriteSsrProjectAliasSpecifier(info.specifier) ?? info.specifier }; } // Browser: Use /_vf_modules/ absolute paths when moduleServerUrl is configured. From 7edc6624f9b5fe24606b2ed3f961d44ed85765bb Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:34:23 +0200 Subject: [PATCH 069/104] Keep cycle aliases from reading text as default exports Cycle alias generation only needs to re-export a default when the transformed module exposes one. The previous regex scanned raw code and treated string contents such as "Set as default" as an exported default, so the alias could emit an invalid default re-export for modules that only expose named values. Constraint: The branch already contains the remote source-span and typecheck-baseline fixes at 2fcce2ee Rejected: Keep the raw regex scan | it cannot distinguish export syntax from text contents Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/module-persistence.test.ts Tested: deno check --no-lock src/rendering/orchestrator/module-loader/module-persistence.ts src/rendering/orchestrator/module-loader/module-persistence.test.ts Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno check --no-lock src/transforms/md/compiler/md-compiler.test.ts Not-tested: Full local test-typecheck ratchet was cancelled after running for more than 25 minutes without producing a result; GitHub ci (lint) remains the full gate --- .../module-loader/module-persistence.test.ts | 78 ++++++++ .../module-loader/module-persistence.ts | 171 +++++++++++++++++- 2 files changed, 246 insertions(+), 3 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 9084c829ad..0e1312640d 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -114,6 +114,84 @@ describe("module-loader/module-persistence", () => { } }); + it("does not infer a default cycle alias from string contents", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); + const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); + const localAdapter = await getLocalAdapter(); + const filePath = join(projectDir, "app/page.ts"); + const moduleCache = new Map(); + + try { + const result = await persistTransformedModule({ + filePath, + projectDir, + tmpDir, + transformedCode: `export const label = "Set as default";`, + localAdapter, + moduleCache, + cacheKey: "string-default", + isCycleTarget: true, + }); + + const aliasCode = await Deno.readTextFile(join(dirname(result), "page.js")); + assertEquals(aliasCode, `export * from "./${basename(result)}";`); + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); + await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); + } + }); + + it("writes default cycle aliases only when an export exposes default", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); + const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); + const localAdapter = await getLocalAdapter(); + const moduleCache = new Map(); + + const cases = [ + { + path: "app/default-declaration.ts", + transformedCode: `export default function Page() { return null; }`, + exposesDefault: true, + }, + { + path: "app/named-as-default.ts", + transformedCode: `const Page = () => null;\nexport { Page as default };`, + exposesDefault: true, + }, + { + path: "app/default-as-named.ts", + transformedCode: `export { default as Page } from "./component.js";`, + exposesDefault: false, + }, + ] as const; + + try { + for (const testCase of cases) { + const result = await persistTransformedModule({ + filePath: join(projectDir, testCase.path), + projectDir, + tmpDir, + transformedCode: testCase.transformedCode, + localAdapter, + moduleCache, + cacheKey: testCase.path, + isCycleTarget: true, + }); + + const aliasCode = await Deno.readTextFile( + join(dirname(result), basename(testCase.path).replace(/\.ts$/, ".js")), + ); + assertEquals( + aliasCode.includes(`export { default } from "./${basename(result)}";`), + testCase.exposesDefault, + ); + } + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); + await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); + } + }); + it("recreates the output directory when it disappears after being cached", async () => { const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index ca6f348218..f361b24e95 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -117,9 +117,174 @@ export async function readPersistedUnresolvedSpecifiers( * `export { default } from …` forms. */ function hasDefaultExport(code: string): boolean { - return /\bexport\s+default\b/.test(code) || - /\bas\s+default\b/.test(code) || - /\bexport\s*\{[^}]*\bdefault\b[^}]*\}/.test(code); + for (let index = 0; index < code.length;) { + index = skipTrivia(code, index); + if (index >= code.length) break; + + if (startsIdentifier(code, index, "export")) { + const exportIndex = index; + index = skipTrivia(code, index + "export".length); + if (startsIdentifier(code, index, "default")) return true; + if (code[index] === "{" && exportListExposesDefault(code, index)) return true; + if (code[index] === "*") { + index = skipTrivia(code, index + 1); + if (startsIdentifier(code, index, "as")) { + index = skipTrivia(code, index + "as".length); + if (startsIdentifier(code, index, "default")) return true; + } + } + index = exportIndex + "export".length; + continue; + } + + const next = skipTextToken(code, index); + index = next === index ? index + 1 : next; + } + + return false; +} + +function exportListExposesDefault(code: string, openBraceIndex: number): boolean { + const closeBraceIndex = findExportListCloseBrace(code, openBraceIndex); + if (closeBraceIndex === -1) return false; + + const specifiers = splitExportSpecifiers(code.slice(openBraceIndex + 1, closeBraceIndex)); + return specifiers.some((specifier) => exportedName(specifier) === "default"); +} + +function findExportListCloseBrace(code: string, openBraceIndex: number): number { + for (let index = openBraceIndex + 1; index < code.length;) { + const next = skipTextToken(code, index); + if (next !== index) { + index = next; + continue; + } + if (code[index] === "}") return index; + index++; + } + return -1; +} + +function splitExportSpecifiers(list: string): string[] { + const specifiers: string[] = []; + let start = 0; + + for (let index = 0; index < list.length;) { + const next = skipTextToken(list, index); + if (next !== index) { + index = next; + continue; + } + if (list[index] === ",") { + specifiers.push(list.slice(start, index)); + start = index + 1; + } + index++; + } + + specifiers.push(list.slice(start)); + return specifiers; +} + +function exportedName(specifier: string): string | undefined { + const tokens = identifierTokens(specifier); + if (tokens.length === 0) return undefined; + + for (let index = tokens.length - 2; index >= 0; index--) { + if (tokens[index] === "as") return tokens[index + 1]; + } + + return tokens.length === 1 ? tokens[0] : undefined; +} + +function identifierTokens(source: string): string[] { + const tokens: string[] = []; + + for (let index = 0; index < source.length;) { + const next = skipTextToken(source, index); + if (next !== index) { + index = next; + continue; + } + if (isIdentifierStart(source[index])) { + const start = index; + index++; + while (index < source.length && isIdentifierPart(source[index])) index++; + tokens.push(source.slice(start, index)); + continue; + } + index++; + } + + return tokens; +} + +function skipTrivia(source: string, index: number): number { + while (index < source.length) { + const char = source[index]; + if (char === " " || char === "\t" || char === "\n" || char === "\r" || char === "\f") { + index++; + continue; + } + + const next = skipComment(source, index); + if (next !== index) { + index = next; + continue; + } + + break; + } + return index; +} + +function skipTextToken(source: string, index: number): number { + const commentEnd = skipComment(source, index); + if (commentEnd !== index) return commentEnd; + + const char = source[index]; + if (char !== '"' && char !== "'" && char !== "`") return index; + + for (index++; index < source.length; index++) { + if (source[index] === "\\") { + index++; + continue; + } + if (source[index] === char) return index + 1; + } + + return source.length; +} + +function skipComment(source: string, index: number): number { + if (source[index] !== "/" || index + 1 >= source.length) return index; + if (source[index + 1] === "/") { + const newlineIndex = source.indexOf("\n", index + 2); + return newlineIndex === -1 ? source.length : newlineIndex + 1; + } + if (source[index + 1] === "*") { + const closeIndex = source.indexOf("*/", index + 2); + return closeIndex === -1 ? source.length : closeIndex + 2; + } + return index; +} + +function startsIdentifier(source: string, index: number, identifier: string): boolean { + if (source.slice(index, index + identifier.length) !== identifier) return false; + const before = index > 0 ? source[index - 1] : ""; + const after = source[index + identifier.length] ?? ""; + return !isIdentifierPart(before) && !isIdentifierPart(after); +} + +function isIdentifierStart(char: string | undefined): boolean { + if (char === undefined) return false; + return char === "$" || char === "_" || + (char >= "A" && char <= "Z") || + (char >= "a" && char <= "z"); +} + +function isIdentifierPart(char: string | undefined): boolean { + return isIdentifierStart(char) || (char !== undefined && char >= "0" && char <= "9"); } /** From a0aec6b645e15e814f8bbffc642d4be6a80329d5 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:38:31 +0200 Subject: [PATCH 070/104] Pin JavaScript cycle targets to their authored path The persistence hash rewrite intentionally excludes .js inputs, so their emitted artifact already occupies the stable path a cycle edge imports. Record that invariant directly to prevent an unnecessary self-alias from being introduced. Constraint: Cycle aliases are needed only when source-extension normalization or hashing changes the authored runtime path. Rejected: Emit page.js as an alias to itself | the artifact already has that path and an alias would overwrite it. Confidence: high Scope-risk: narrow Directive: Check the emitted artifact path before adding a cycle alias. Tested: module-persistence suite, 8 steps; targeted format, lint, check, and diff checks. --- .../module-loader/module-persistence.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 9084c829ad..f78725d813 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -114,6 +114,33 @@ describe("module-loader/module-persistence", () => { } }); + it("writes a JavaScript cycle target at the authored stable path", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); + const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); + const localAdapter = await getLocalAdapter(); + const filePath = join(projectDir, "app/page.js"); + const transformedCode = "export const page = 1;"; + + try { + const result = await persistTransformedModule({ + filePath, + projectDir, + tmpDir, + transformedCode, + localAdapter, + moduleCache: new Map(), + cacheKey: "javascript-cycle-target", + isCycleTarget: true, + }); + + assertEquals(result, join(tmpDir, "app/page.js")); + assertEquals(await Deno.readTextFile(result), transformedCode); + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); + await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); + } + }); + it("recreates the output directory when it disappears after being cached", async () => { const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); From 417d8b46127a6c07fefc92bbb1b203c6704d03d6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:44:39 +0200 Subject: [PATCH 071/104] Defer missing dynamic MDX imports Nested MDX import materialization should keep resolvable dynamic imports cache-local, but a missing dynamic edge must not fail page preparation before that branch executes. Catch missing-module failures only for dynamic spans and leave those import expressions unchanged in strict mode, while keeping strict static imports fail-fast.\n\nAlso add the explicit @/ source-extension regression requested by the prior alias-attribution review thread.\n\nConstraint: Exact-head Codex review on PR #3723 reported eager missing dynamic imports and requested an untaken-branch regression.\nConfidence: high\nScope-risk: moderate\nTested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts\nTested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/\nTested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/index.test.ts\nTested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/\nTested: deno fmt --check src/rendering/orchestrator/module-loader/index.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts\nTested: deno lint src/rendering/orchestrator/module-loader/index.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts\nTested: deno check --no-lock src/rendering/orchestrator/module-loader/index.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts\nTested: git diff --check --- .../orchestrator/module-loader/index.test.ts | 38 +++++++++++++++ .../module-fetcher/nested-imports.test.ts | 48 +++++++++++++++++++ .../module-fetcher/nested-imports.ts | 42 +++++++++++----- 3 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index b52712e9bb..1db7a7896e 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -413,6 +413,29 @@ describe("module-loader/loadModule build-failure tagging", () => { ); }); + it("tags a missing project alias import with an explicit source extension", async () => { + await withModuleLoaderFixture( + { + "app/page.tsx": [ + `import { label } from "@/components/Missing.tsx";`, + `export default function Page() { return label; }`, + ].join("\n"), + }, + async ({ projectDir, tmpDir, config }) => { + await runWithCacheDir(tmpDir, async () => { + const error = await assertRejects( + () => loadModule(join(projectDir, "app/page.tsx"), config), + Error, + ); + + assertEquals(isMissingModuleError(error), true); + assertEquals(isBuildFailure(error), true); + assertEquals(isTenantBuildFailure(error), true); + }); + }, + ); + }); + it("tags missing side-effect imports in every legal declaration position", async () => { for ( const source of [ @@ -743,6 +766,21 @@ describe("module-loader/isUnresolvedTenantImport", () => { ); }); + it("classifies an explicit project alias source extension after its SSR rewrite", () => { + const aliasMissing = Object.assign( + new TypeError( + 'Module not found "file:///tmp/out/veryfront-modules/proj-a/_vf_modules/components/Missing.js".\n' + + ` at file://${REBUILT}:1:23`, + ), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + assertEquals( + isUnresolvedTenantImport(aliasMissing, new Set(["@/components/Missing.tsx"]), REBUILT), + true, + ); + }); + it("does not classify an unrelated missing target alongside a dropped specifier", () => { const unrelated = Object.assign( new TypeError( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index ed57f276f7..a3abbbcda2 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -13,6 +13,7 @@ import { MAX_MDX_MODULE_TRANSFORM_CONCURRENCY, ModuleImportLimitError, } from "./limits.ts"; +import { buildMissingModuleError } from "../missing-module.ts"; describe("transforms/mdx/esm-module-loader/module-fetcher/nested-imports", () => { describe("findNestedImports", () => { @@ -388,6 +389,53 @@ import { bar } from "./local.js"; } }); + it("defers missing dynamic imports in strict mode until they execute", async () => { + const source = [ + `export async function loadOptional(enabled) {`, + ` if (enabled) return await import("./optional.js");`, + ` return null;`, + `}`, + ].join("\n"); + const calls: string[] = []; + + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path) => { + calls.push(path); + throw buildMissingModuleError({ + modulePath: path, + importer: "_vf_modules/pages/index.js", + importStatement: `import("./optional.js")`, + code: source, + projectSlug: "docs", + }); + }, + }); + + assertEquals(calls, ["./optional.js"]); + assertEquals(result, source); + }); + + it("still rejects missing static imports in strict mode", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `import optional from "./optional.js";\nexport default optional;`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => Promise.resolve(null), + }), + Error, + "[MDX] Missing module: ./optional.js.", + ); + }); + it("resolves admitted fan-out with bounded concurrency", async () => { const importCount = MAX_MDX_MODULE_TRANSFORM_CONCURRENCY + 4; const moduleCode = Array.from( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index f6e3473ec1..1240053155 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -57,6 +57,10 @@ function scanImportSpans( } } +function isMissingModuleError(error: unknown): boolean { + return error instanceof Error && error.name === "MissingModuleError"; +} + /** * Serialize a resolved module URL as a JavaScript string literal. * @@ -296,6 +300,8 @@ export async function processNestedImports( const modulePath = nestedPath || relativePath || ""; if (strictMissingModules) { + if (isDynamic) continue; + throw buildMissingModuleError({ modulePath, importer: parentModulePath, @@ -414,19 +420,29 @@ export async function resolveNestedModuleImports( const nestedResults: NestedImportResult[] = await parallelMap( allImports, - async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => ({ - original, - start, - end, - suffix, - isDynamic, - isSideEffect, - nestedFilePath: await input.fetchAndCacheModule( - path, - input.parentBasePath ?? input.normalizedPath, - ), - [key]: path, - }), + async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => { + let nestedFilePath: string | null; + try { + nestedFilePath = await input.fetchAndCacheModule( + path, + input.parentBasePath ?? input.normalizedPath, + ); + } catch (error) { + if (!isDynamic || !isMissingModuleError(error)) throw error; + nestedFilePath = null; + } + + return { + original, + start, + end, + suffix, + isDynamic, + isSideEffect, + nestedFilePath, + [key]: path, + }; + }, { semaphore: new Semaphore(MAX_MDX_MODULE_TRANSFORM_CONCURRENCY), }, From bdc50123a523a4b1c0596740502c9071b16391a3 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:50:32 +0200 Subject: [PATCH 072/104] Defer strict missing imports until execution Strict MDX preparation must not fail for an optional dynamic branch, but the missing module still needs to fail deterministically when that branch executes. Materialize a cache-local fail-on-import stub so relative and virtual module specifiers retain correct runtime behavior without being resolved from the cache directory. Constraint: Cached MDX modules cannot safely retain unresolved relative or /_vf_modules dynamic specifiers. Rejected: Leave the original dynamic import unchanged | relative specifiers would resolve from the cache path and virtual specifiers remain unresolved. Confidence: high Scope-risk: moderate Directive: Keep strict static imports fail-fast and defer only missing dynamic edges. Tested: focused nested import, module fetcher, and stub module suites; targeted format, lint, typecheck, and diff checks Not-tested: browser execution outside the Deno module-runtime regression --- .../module-fetcher/nested-imports.test.ts | 47 ++++++++++++++++++ .../module-fetcher/nested-imports.ts | 19 +++++++ .../esm-module-loader/utils/stub-module.ts | 49 +++++++++++++------ 3 files changed, 101 insertions(+), 14 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index ed57f276f7..7ca9f2f28d 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -2,6 +2,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; +import { join, toFileUrl } from "#veryfront/compat/path/index.ts"; import { findNestedImports, hasUnresolvedImports, @@ -388,6 +389,52 @@ import { bar } from "./local.js"; } }); + it("defers a missing strict dynamic import until the branch executes", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-missing-cache-" }); + + try { + const result = await resolveNestedModuleImports({ + moduleCode: + `export const load = (enabled) => enabled ? import("./optional.js") : Promise.resolve("skipped");`, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => Promise.resolve(null), + }); + const parentPath = join(esmCacheDir, "dynamic-parent.mjs"); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + await assertRejects( + () => loaded.load(true), + Error, + "Missing module: ./optional.js", + ); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + + it("keeps strict static imports fail-fast", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `import value from "./missing.js"; export { value };`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => Promise.resolve(null), + }), + Error, + "Missing module: ./missing.js", + ); + }); + it("resolves admitted fan-out with bounded concurrency", async () => { const importCount = MAX_MDX_MODULE_TRANSFORM_CONCURRENCY + 4; const moduleCode = Array.from( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index f6e3473ec1..8ac3090c02 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -295,6 +295,25 @@ export async function processNestedImports( } const modulePath = nestedPath || relativePath || ""; + if (isDynamic) { + const deferredPath = await createStubModule( + modulePath, + moduleCode, + original, + esmCacheDir, + { failOnImport: strictMissingModules }, + ); + if (deferredPath) { + replacements.push({ + start, + end, + expected: original, + replacement: toImportStringLiteral(`file://${deferredPath}${suffix ?? ""}`), + }); + continue; + } + } + if (strictMissingModules) { throw buildMissingModuleError({ modulePath, diff --git a/src/transforms/mdx/esm-module-loader/utils/stub-module.ts b/src/transforms/mdx/esm-module-loader/utils/stub-module.ts index 76c1dad50f..269b84e91a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/stub-module.ts +++ b/src/transforms/mdx/esm-module-loader/utils/stub-module.ts @@ -69,37 +69,58 @@ ${namedExports} `; } +function generateDeferredMissingModuleCode(modulePath: string): string { + const message = JSON.stringify( + `[Veryfront] Missing module: ${modulePath}. This module or file does not exist in your project.`, + ); + return `const error = new Error(${message}); +error.name = "MissingModuleError"; +throw error; +`; +} + +export interface CreateStubModuleOptions { + /** Reject a dynamic import when it executes instead of exporting fallback values. */ + failOnImport?: boolean; +} + export async function createStubModule( modulePath: string, code: string, importStatement: string, esmCacheDir: string, + options: CreateStubModuleOptions = {}, ): Promise { const namedImports = extractNamedImports(code, importStatement); - const stubHash = hashString(`stub:${modulePath}:${namedImports.join(",")}`); + const behavior = options.failOnImport ? "fail-on-import" : "fallback"; + const stubHash = hashString(`stub:${behavior}:${modulePath}:${namedImports.join(",")}`); const stubPath = join(esmCacheDir, `stub-${stubHash}.mjs`); - const stubCode = generateStubCode(modulePath, namedImports); + const stubCode = options.failOnImport + ? generateDeferredMissingModuleCode(modulePath) + : generateStubCode(modulePath, namedImports); try { await getLocalFs().writeTextFile(stubPath, stubCode); - const errorMessage = namedImports.length - ? `Missing module: ${modulePath} (imports: ${namedImports.join(", ")})` - : `Missing module: ${modulePath}`; + if (!options.failOnImport) { + const errorMessage = namedImports.length + ? `Missing module: ${modulePath} (imports: ${namedImports.join(", ")})` + : `Missing module: ${modulePath}`; - try { - getErrorCollector().addModuleError(errorMessage, modulePath, { + try { + getErrorCollector().addModuleError(errorMessage, modulePath, { + namedImports, + importStatement, + }); + } catch (_) { + /* expected: error collector may not be initialized in all contexts */ + } + + logger.error(`${LOG_PREFIX_MDX_LOADER} Missing module: ${modulePath}`, { namedImports, - importStatement, }); - } catch (_) { - /* expected: error collector may not be initialized in all contexts */ } - logger.error(`${LOG_PREFIX_MDX_LOADER} Missing module: ${modulePath}`, { - namedImports, - }); - return stubPath; } catch (error) { logger.error( From dc507655ef80a73e4bd684adde7ac87d8e640589 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 13:58:15 +0200 Subject: [PATCH 073/104] Keep HTTP fallback branches lazy Local HTTP module fallback resolves nested imports through a separate loop, so it must materialize the same cache-local deferred failure module used by direct MDX loading. Pass the cache and strictness context into that loop and share the loader-specific missing-error discriminator. Constraint: Optional dynamic imports must not fail page preparation, including modules obtained from the local HTTP fallback. Rejected: Preserve the unresolved import expression | cached relative and virtual specifiers do not retain their authored resolution context. Confidence: high Scope-risk: moderate Directive: Catch only loader-produced missing-module errors on dynamic spans; all other failures remain eager. Tested: red-green HTTP runtime regression; 3 focused suites and 61 steps; targeted format, lint, typecheck, and diff checks --- .../mdx/esm-module-loader/missing-module.ts | 5 ++ .../module-fetcher/http-fallback.ts | 6 ++- .../module-fetcher/http-fetcher.test.ts | 48 +++++++++++++++++++ .../module-fetcher/http-fetcher.ts | 23 ++++++++- .../module-fetcher/nested-imports.ts | 7 +-- 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/missing-module.ts b/src/transforms/mdx/esm-module-loader/missing-module.ts index 0135d609ac..d23ecd4df2 100644 --- a/src/transforms/mdx/esm-module-loader/missing-module.ts +++ b/src/transforms/mdx/esm-module-loader/missing-module.ts @@ -55,3 +55,8 @@ export function buildMissingModuleError(ctx: MissingModuleContext): Error { return error; } + +/** Return true only for missing-module errors produced by this loader. */ +export function isMdxMissingModuleError(error: unknown): boolean { + return error instanceof Error && error.name === "MissingModuleError"; +} diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fallback.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fallback.ts index 29eba6d4f8..c437b2f1fb 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fallback.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fallback.ts @@ -48,7 +48,11 @@ export async function resolveUnresolvedModuleViaHttpFallback( input.projectSlug, input.isLocalProject, input.dependencyPinningCacheKey, - { moduleServerOrigin: input.moduleServerOrigin }, + { + esmCacheDir: input.esmCacheDir, + moduleServerOrigin: input.moduleServerOrigin, + strictMissingModules: input.strictMissingModules ?? true, + }, ); if (moduleCode) { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts index ed46ef7f06..5ae2ff21c7 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts @@ -6,6 +6,9 @@ import type { Logger } from "#veryfront/utils/logger/logger.ts"; import { fetchModuleViaHTTP } from "./http-fetcher.ts"; import { MAX_MDX_MODULE_CODE_BYTES, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY } from "./limits.ts"; import { HttpModuleBodyTooLargeError } from "../../../shared/http-module-response.ts"; +import { makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; +import { join, toFileUrl } from "#veryfront/compat/path/index.ts"; +import { buildMissingModuleError } from "../missing-module.ts"; describe("module-fetcher/http-fetcher", () => { it("falls back to bare localhost, carrying the project slug, when the subdomain will not resolve", async () => { @@ -187,6 +190,51 @@ describe("module-fetcher/http-fetcher", () => { ); }); + it("defers a missing dynamic import fetched through the HTTP fallback", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-http-dynamic-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./optional.js") : Promise.resolve("skipped");`; + + try { + const result = await fetchModuleViaHTTP( + "_vf_modules/pages/index.js", + { env: { get: () => undefined } } as unknown as RuntimeAdapter, + (path) => { + throw buildMissingModuleError({ + modulePath: path, + importer: "_vf_modules/pages/index.js", + importStatement: `import("./optional.js")`, + code: source, + projectSlug: "docs", + }); + }, + { debug: () => {}, warn: () => {} } as unknown as Logger, + "docs", + true, + undefined, + { + esmCacheDir, + fetchFn: (() => Promise.resolve(new Response(source))) as typeof fetch, + strictMissingModules: true, + }, + ); + const parentPath = join(esmCacheDir, "http-parent.mjs"); + await Deno.writeTextFile(parentPath, result!); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + await assertRejects( + () => loaded.load(true), + Error, + "Missing module: ./optional.js", + ); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + // A single-quoted specifier may legally contain a double quote, and a cache // path may contain a backslash. Interpolating either into a hand-written // double-quoted literal emits a module that fails to parse, which takes down diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index d202eed8de..f0be72446d 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -22,10 +22,14 @@ import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/constants/limits.ts"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { Semaphore } from "#veryfront/modules/react-loader/ssr-module-loader/concurrency/semaphore.ts"; import { assertMdxModuleImportCount, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY } from "./limits.ts"; +import { createStubModule } from "../utils/stub-module.ts"; +import { isMdxMissingModuleError } from "../missing-module.ts"; export interface FetchModuleViaHttpOptions { + esmCacheDir?: string; fetchFn?: typeof fetch; moduleServerOrigin?: string; + strictMissingModules?: boolean; timeoutMs?: number; } @@ -263,7 +267,24 @@ export async function fetchModuleViaHTTP( const results = await parallelMap( allImports, async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => { - const nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); + let nestedFilePath: string | null; + try { + nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); + } catch (error) { + if (!isDynamic || !options.esmCacheDir || !isMdxMissingModuleError(error)) throw error; + nestedFilePath = null; + } + + if (!nestedFilePath && isDynamic && options.esmCacheDir) { + nestedFilePath = await createStubModule( + path, + moduleCode, + original, + options.esmCacheDir, + { failOnImport: options.strictMissingModules ?? true }, + ); + } + return { original, start, diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 3b6e391036..80587d3e91 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -16,6 +16,7 @@ import { type StaticImportSpan, } from "../utils/source-spans.ts"; import { buildMissingModuleError } from "../missing-module.ts"; +import { isMdxMissingModuleError } from "../missing-module.ts"; import { splitSpecifierSuffix } from "#veryfront/transforms/shared/specifier-suffix.ts"; import type { Logger } from "#veryfront/utils"; import { parallelMap } from "#veryfront/utils/parallel.ts"; @@ -57,10 +58,6 @@ function scanImportSpans( } } -function isMissingModuleError(error: unknown): boolean { - return error instanceof Error && error.name === "MissingModuleError"; -} - /** * Serialize a resolved module URL as a JavaScript string literal. * @@ -445,7 +442,7 @@ export async function resolveNestedModuleImports( input.parentBasePath ?? input.normalizedPath, ); } catch (error) { - if (!isDynamic || !isMissingModuleError(error)) throw error; + if (!isDynamic || !isMdxMissingModuleError(error)) throw error; nestedFilePath = null; } From 002ed83766c96ad4d7b6594d1e4cea3d8d663239 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 14:13:36 +0200 Subject: [PATCH 074/104] Defer root MDX dynamic alias failures Root MDX modules rewrite project aliases before root vf-module processing, so an untaken dynamic alias can still become an unresolved _vf_modules import and fail page preparation. Teach the root vf-module pass to recognize dynamic import spans and materialize loader-produced missing modules as execution-time failures, matching nested module behavior. Constraint: Strict missing modules must still fail fast for static root imports. Rejected: Exempt dynamic _vf_modules imports from the unresolved guard | it would leave runtime imports without the loader-owned missing-module error surface. Confidence: high Scope-risk: moderate Directive: Keep root and nested dynamic missing-module handling aligned; only loader-produced missing-module errors are deferred. Tested: Red focused root dynamic regression, then targeted MDX loader suites, touched-file fmt, lint, typecheck, and diff checks. --- .../mdx/esm-module-loader/loader-helpers.ts | 74 ++++++++++++++++--- .../esm-module-loader/module-writer.test.ts | 44 +++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/loader-helpers.ts b/src/transforms/mdx/esm-module-loader/loader-helpers.ts index 015a28008a..b48aa88824 100644 --- a/src/transforms/mdx/esm-module-loader/loader-helpers.ts +++ b/src/transforms/mdx/esm-module-loader/loader-helpers.ts @@ -19,12 +19,14 @@ import { LOG_PREFIX_MDX_LOADER } from "./constants.ts"; import { getLocalFs } from "./cache/index.ts"; import { createStubModule } from "./utils/stub-module.ts"; import { + findDynamicImportSpans, findStaticImportFromSpans, replaceSourceSpans, type SourceSpanReplacement, } from "./utils/source-spans.ts"; import { createModuleFetcherContext, fetchAndCacheModule } from "./module-fetcher/index.ts"; -import { buildMissingModuleError } from "./missing-module.ts"; +import { buildMissingModuleError, isMdxMissingModuleError } from "./missing-module.ts"; +import { toImportStringLiteral } from "./module-fetcher/nested-imports.ts"; import type { ESMLoaderContext } from "./types.ts"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { @@ -108,12 +110,27 @@ export async function initializeCacheDir(context: ESMLoaderContext): Promise { - return findStaticImportFromSpans( +): Array<{ + original: string; + path: string; + start: number; + end: number; + isDynamic?: boolean; +}> { + const matchVfModule = (specifier: string): string | null => + specifier.match(/^\/?(_vf_modules\/[^?]+)(?:\?.*)?$/)?.[1] ?? null; + const staticImports = findStaticImportFromSpans( code, - (specifier) => specifier.match(/^\/?(_vf_modules\/[^?]+)(?:\?.*)?$/)?.[1], + matchVfModule, MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ); + const dynamicImports = findDynamicImportSpans( + code, + matchVfModule, + MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, + ).map((importSpan) => ({ ...importSpan, isDynamic: true })); + + return [...staticImports, ...dynamicImports].sort((left, right) => left.start - right.start); } /** @@ -121,7 +138,13 @@ export function findVfModuleImports( */ export async function processVfModuleImports( code: string, - imports: Array<{ original: string; path: string; start: number; end: number }>, + imports: Array<{ + original: string; + path: string; + start: number; + end: number; + isDynamic?: boolean; + }>, context: ESMLoaderContext, projectDir: string, strictMissingModules: boolean, @@ -180,7 +203,7 @@ export async function processVfModuleImports( const results = await parallelMap( imports, - async ({ original, path, start, end }, index) => { + async ({ original, path, start, end, isDynamic }, index) => { return await withSpan( SpanNames.MDX_FETCH_MODULE, async () => { @@ -190,14 +213,20 @@ export async function processVfModuleImports( index, path, }); - const filePath = await fetchAndCacheModule(path, fetcherContext); + let filePath: string | null; + try { + filePath = await fetchAndCacheModule(path, fetcherContext); + } catch (error) { + if (!isDynamic || !isMdxMissingModuleError(error)) throw error; + filePath = null; + } logger.debug(`${LOG_PREFIX_MDX_LOADER} Fetching module DONE`, { projectSlug, index, path, durationMs: (performance.now() - moduleStart).toFixed(1), }); - return { original, start, end, filePath, path }; + return { original, start, end, filePath, path, isDynamic }; }, { "mdx.module_path": path, @@ -216,17 +245,38 @@ export async function processVfModuleImports( }); const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, filePath, path } of results) { + for (const { original, start, end, filePath, path, isDynamic } of results) { if (filePath) { replacements.push({ start, end, expected: original, - replacement: `from "file://${filePath}"`, + replacement: isDynamic + ? toImportStringLiteral(`file://${filePath}`) + : `from "file://${filePath}"`, }); continue; } + if (isDynamic) { + const deferredPath = await createStubModule( + path, + code, + original, + context.esmCacheDir!, + { failOnImport: strictMissingModules }, + ); + if (deferredPath) { + replacements.push({ + start, + end, + expected: original, + replacement: toImportStringLiteral(`file://${deferredPath}`), + }); + continue; + } + } + if (strictMissingModules) { throw buildMissingModuleError({ modulePath: path, @@ -243,7 +293,9 @@ export async function processVfModuleImports( start, end, expected: original, - replacement: `from "file://${stubPath}"`, + replacement: isDynamic + ? toImportStringLiteral(`file://${stubPath}`) + : `from "file://${stubPath}"`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/module-writer.test.ts b/src/transforms/mdx/esm-module-loader/module-writer.test.ts index 1f01141dc1..2703e77776 100644 --- a/src/transforms/mdx/esm-module-loader/module-writer.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-writer.test.ts @@ -164,6 +164,50 @@ describe("MDX root module cache identity", () => { }); }); +describe("MDX root dynamic imports", () => { + it("defers a missing strict alias import until its branch executes", async () => { + const missingModule = `MissingRoot-${crypto.randomUUID()}`; + const projectDir = await Deno.makeTempDir({ prefix: "vf-mdx-root-dynamic-" }); + + try { + const mod = await withMockFetch( + () => Promise.resolve(new Response("missing", { status: 404 })), + () => + mdxRenderer.loadModuleESM( + `export async function loadOptional(enabled) { + if (!enabled) return "SKIPPED"; + return (await import("@/${missingModule}")).default; + } + export default function Root() { return null; }`, + { + adapter: denoAdapter, + projectId: `project-${crypto.randomUUID()}`, + projectDir, + projectSlug: "root-dynamic", + contentSourceId: `source-${crypto.randomUUID()}`, + isLocalProject: true, + }, + ), + ); + const loadOptional = (mod as unknown as { + loadOptional(enabled: boolean): Promise; + }).loadOptional; + + assertEquals(await loadOptional(false), "SKIPPED"); + await assertRejects( + () => loadOptional(true), + Error, + `Missing module: _vf_modules/${missingModule}.js`, + ); + } finally { + mdxRenderer.clearCache(); + await Deno.remove(projectDir, { recursive: true }); + const esbuild = await import("veryfront/extensions/bundler"); + await esbuild.stop(); + } + }); +}); + describe("verifyMdxCacheFile", () => { const { verifyMdxCacheFile } = __moduleWriterInternals; From 5af0193bc5a299db4f7e98eb5704397aa575da1a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 14:36:59 +0200 Subject: [PATCH 075/104] Keep missing authored packages in tenant build telemetry An HTTP fetch failure alone cannot establish ownership, but the bare-specifier resolver knows when project source selected a package. Preserve the upstream status as structured context and add tenant classification only at that authored-package seam. Constraint: Network, cache, HTML, and direct HTTP failures must remain framework-severity errors. Rejected: Classify every esm.sh 404 as tenant-owned | direct framework HTTP fetches do not carry authored-package provenance. Confidence: high Scope-risk: narrow Directive: Keep tenant classification at source-aware resolver seams; do not infer ownership from BUILD_FAILED alone. Tested: HTTP cache 77 steps; specifier resolver and module-loader 80 steps; focused fmt, lint, source typecheck, and diff check. Not-tested: Live esm.sh package registry response. --- src/transforms/esm/http-cache.test.ts | 21 ++++++++++++++++++ src/transforms/esm/http-cache.ts | 5 ++++- src/transforms/esm/specifier-resolver.ts | 27 +++++++++++++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 5527470b52..25bc9acea9 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -37,6 +37,7 @@ import { MAX_BUNDLE_CHUNK_SIZE_BYTES } from "#veryfront/utils/constants/buffers. import { HTTP_MODULE_FETCH_TIMEOUT_MS } from "#veryfront/utils/constants/http.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; import { MODULE_LOAD_TIMEOUT_MS } from "#veryfront/rendering/orchestrator/module-collection.ts"; +import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; import { FakeTime } from "#std/testing/time"; import { __getMaxInFlightHttpFetchWaiterCountForTests, @@ -1239,6 +1240,26 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, }); }); + it("classifies an authored missing bare package without classifying direct HTTP failures", async () => { + const mockFetch = (() => + Promise.resolve(new Response("not found", { status: 404 }))) as typeof fetch; + + await withIsolatedHttpCache("vf-esm-missing-package-", mockFetch, async (tempDir) => { + const options = { cacheDir: tempDir, importMap: { imports: {}, scopes: {} } }; + const packageError = await assertRejects( + () => cacheHttpImportsToLocal('import "missing-tenant-package";', options), + Error, + ); + const directHttpError = await assertRejects( + () => cacheModuleToLocal("https://esm.sh/missing-framework-module", tempDir), + Error, + ); + + assertEquals(isTenantSourceBuildError(packageError), true); + assertEquals(isTenantSourceBuildError(directHttpError), false); + }); + }); + it("retries failures while reading an HTTP module body", async () => { let fetchCount = 0; diff --git a/src/transforms/esm/http-cache.ts b/src/transforms/esm/http-cache.ts index 447948eddb..e6798911f6 100644 --- a/src/transforms/esm/http-cache.ts +++ b/src/transforms/esm/http-cache.ts @@ -309,7 +309,10 @@ async function fetchHttpModule( ); } catch (error) { if (error instanceof HttpModuleResponseError) { - throw BUILD_FAILED.create({ detail: `Failed to fetch ${safeUrl}: ${error.status}` }); + throw BUILD_FAILED.create({ + detail: `Failed to fetch ${safeUrl}: ${error.status}`, + context: { httpStatus: error.status }, + }); } if (error instanceof HttpModuleRequestError) { throw BUILD_FAILED.create({ diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index c606c0aedf..72b7644df1 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -10,6 +10,8 @@ import { basename } from "#veryfront/compat/path/index.ts"; import { resolveImport } from "#veryfront/modules/import-map/resolver.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; +import { BUILD_FAILED } from "#veryfront/errors"; +import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; import { appendSameOriginSSRDependencyPinningKey, normalizeExtension, @@ -42,6 +44,25 @@ function stringStartsWith(value: string, search: string): boolean { return ReflectApply(StringStartsWith, value, [search]) as boolean; } +function classifyAuthoredPackageFetchError(error: unknown): unknown { + const snapshot = snapshotVeryfrontError(error); + const context = snapshot?.context; + if ( + snapshot?.slug !== BUILD_FAILED.slug || + typeof context !== "object" || context === null || + (context as { httpStatus?: unknown }).httpStatus !== 404 + ) { + return error; + } + + return BUILD_FAILED.create({ + message: snapshot.message, + detail: snapshot.detail, + cause: error, + context: { httpStatus: 404, tenantBuildFailure: true }, + }); +} + /** Function signature for caching an HTTP module and returning its local path. */ export type CacheHttpModuleFn = (url: string, options: CacheOptions) => Promise; @@ -200,7 +221,11 @@ async function resolveSpecifier( if (mapped === specifier) return null; if (isLocalMappedSpecifier(mapped)) return mapped; - return resolveSpecifier(mapped, baseUrl, options, cacheHttpModule); + try { + return await resolveSpecifier(mapped, baseUrl, options, cacheHttpModule); + } catch (error) { + throw classifyAuthoredPackageFetchError(error); + } } /** Complete specifier replacements for one module. */ From 7aeda41d6f7a997faa21ddb22bf07c30190e44b8 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 14:38:23 +0200 Subject: [PATCH 076/104] Classify missing imports as tenant build failures Missing authored package imports surface as import-resolution errors after the resolver tries the package URL path. Treating those as framework errors makes tenant source mistakes look like platform failures in application error reporting. Constraint: Keep the observability boundary dependent only on shared error classification. Rejected: Tag this only at one MDX loader call site | classification already has a single shared owner for tenant-source verdicts. Confidence: high Scope-risk: narrow Directive: Add future tenant-source build slugs in tenant-classification before touching reporting-specific code. Tested: DENO_TESTING=1 deno test --preload=src/testing/preload.ts --no-check --allow-all src/observability/application-errors.test.ts Tested: deno fmt --check src/errors/tenant-classification.ts src/observability/application-errors.test.ts Tested: deno lint src/errors/tenant-classification.ts src/observability/application-errors.test.ts Tested: deno check src/errors/tenant-classification.ts src/observability/application-errors.test.ts Tested: git diff --check --- src/errors/tenant-classification.ts | 6 ++++-- src/observability/application-errors.test.ts | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/errors/tenant-classification.ts b/src/errors/tenant-classification.ts index 5e9a86652d..e1d32db066 100644 --- a/src/errors/tenant-classification.ts +++ b/src/errors/tenant-classification.ts @@ -12,7 +12,7 @@ import { snapshotVeryfrontError } from "./types.ts"; /** - * BUILD registry slugs that describe tenant source failing to compile, as + * Registry slugs that describe tenant source failing to compile or resolve, as * opposed to framework cache/bundle/asset infrastructure failing in the same * phase. */ @@ -20,6 +20,7 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", + "import-resolution-error", ]); /** @@ -45,5 +46,6 @@ export function isTenantSourceBuildError(error: unknown): boolean { ) { return true; } - return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); + return (snapshot.category === "BUILD" || snapshot.category === "MODULE") && + TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); } diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 9a2a920051..085717c9e4 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -20,6 +20,7 @@ import { COMPILATION_ERROR, CONFIG_PARSE_ERROR, createError, + IMPORT_RESOLUTION_ERROR, INITIALIZATION_ERROR, MARKDOWN_COMPILE_ERROR, MDX_COMPILE_ERROR, @@ -180,6 +181,9 @@ it("application error reporter downgrades tenant build errors to tagged warnings detail: "TypeScript syntax failed in /pages/index.tsx", context: { tenantBuildFailure: true }, }); + const missingPackageError = IMPORT_RESOLUTION_ERROR.create({ + detail: "Could not resolve authored package import: missing-authored-package", + }); const frameworkError = INITIALIZATION_ERROR.create({ detail: "renderer failed to initialize", }); @@ -221,6 +225,10 @@ it("application error reporter downgrades tenant build errors to tagged warnings captureApplicationError(sourceCompilationError, { boundary: "ssr.render" }), "event-id", ); + assertEquals( + captureApplicationError(missingPackageError, { boundary: "ssr.render" }), + "event-id", + ); assertEquals( captureApplicationError(frameworkError, { boundary: "ssr.render" }), "event-id", @@ -262,7 +270,7 @@ it("application error reporter downgrades tenant build errors to tagged warnings "event-id", ); - assertEquals(captures.length, 14); + assertEquals(captures.length, 15); // Tenant build/content failures stay visible for escalation analysis, but // are tagged and downgraded so they stop surfacing as error-level issues. assertEquals(captures[0]?.context.errorClass, "tenant-build"); @@ -275,9 +283,9 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[3]?.context.level, "warning"); assertEquals(captures[4]?.context.errorClass, "tenant-build"); assertEquals(captures[4]?.context.level, "warning"); + assertEquals(captures[5]?.context.errorClass, "tenant-build"); + assertEquals(captures[5]?.context.level, "warning"); // Genuine framework failures keep their default error-level capture. - assertEquals(captures[5]?.context.errorClass, undefined); - assertEquals(captures[5]?.context.level, undefined); assertEquals(captures[6]?.context.errorClass, undefined); assertEquals(captures[6]?.context.level, undefined); assertEquals(captures[7]?.context.errorClass, undefined); @@ -294,6 +302,8 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[12]?.context.level, undefined); assertEquals(captures[13]?.context.errorClass, undefined); assertEquals(captures[13]?.context.level, undefined); + assertEquals(captures[14]?.context.errorClass, undefined); + assertEquals(captures[14]?.context.level, undefined); }); it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { From 97647eaa2cff07519d756c2e7a7e4ec124301957 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 14:48:05 +0200 Subject: [PATCH 077/104] Keep framework import failures at error severity A concurrent follow-up broadened tenant classification to every import-resolution error, but the same slug also represents missing Veryfront framework and relative framework imports. Restore the source-aware boundary: authored package 404s carry explicit tenant context, while generic resolution errors remain error-level. Constraint: The import-resolution-error slug is shared by tenant-facing and framework-internal seams. Rejected: Whitelist import-resolution-error globally | it downgrades missing #veryfront and framework-relative modules. Confidence: high Scope-risk: narrow Directive: Require explicit tenantBuildFailure context whenever a shared error slug has mixed ownership. Tested: application-error and HTTP-cache suites, 77 steps; focused fmt, lint, source typecheck, and diff check; production-equivalent parent passed full pre-push with 3820 tests and 28666 steps. Not-tested: Live esm.sh package registry response. --- src/errors/tenant-classification.ts | 6 ++---- src/observability/application-errors.test.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/errors/tenant-classification.ts b/src/errors/tenant-classification.ts index e1d32db066..5e9a86652d 100644 --- a/src/errors/tenant-classification.ts +++ b/src/errors/tenant-classification.ts @@ -12,7 +12,7 @@ import { snapshotVeryfrontError } from "./types.ts"; /** - * Registry slugs that describe tenant source failing to compile or resolve, as + * BUILD registry slugs that describe tenant source failing to compile, as * opposed to framework cache/bundle/asset infrastructure failing in the same * phase. */ @@ -20,7 +20,6 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "typescript-error", "mdx-compile-error", "markdown-compile-error", - "import-resolution-error", ]); /** @@ -46,6 +45,5 @@ export function isTenantSourceBuildError(error: unknown): boolean { ) { return true; } - return (snapshot.category === "BUILD" || snapshot.category === "MODULE") && - TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); + return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); } diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 085717c9e4..f219033542 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -181,8 +181,8 @@ it("application error reporter downgrades tenant build errors to tagged warnings detail: "TypeScript syntax failed in /pages/index.tsx", context: { tenantBuildFailure: true }, }); - const missingPackageError = IMPORT_RESOLUTION_ERROR.create({ - detail: "Could not resolve authored package import: missing-authored-package", + const frameworkImportError = IMPORT_RESOLUTION_ERROR.create({ + detail: "Could not resolve framework import: #veryfront/missing", }); const frameworkError = INITIALIZATION_ERROR.create({ detail: "renderer failed to initialize", @@ -226,7 +226,7 @@ it("application error reporter downgrades tenant build errors to tagged warnings "event-id", ); assertEquals( - captureApplicationError(missingPackageError, { boundary: "ssr.render" }), + captureApplicationError(frameworkImportError, { boundary: "ssr.render" }), "event-id", ); assertEquals( @@ -283,9 +283,11 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[3]?.context.level, "warning"); assertEquals(captures[4]?.context.errorClass, "tenant-build"); assertEquals(captures[4]?.context.level, "warning"); - assertEquals(captures[5]?.context.errorClass, "tenant-build"); - assertEquals(captures[5]?.context.level, "warning"); - // Genuine framework failures keep their default error-level capture. + // Generic import-resolution and other framework failures keep their default + // error-level capture. Source-aware resolver seams add tenant context when + // project code is actually responsible. + assertEquals(captures[5]?.context.errorClass, undefined); + assertEquals(captures[5]?.context.level, undefined); assertEquals(captures[6]?.context.errorClass, undefined); assertEquals(captures[6]?.context.level, undefined); assertEquals(captures[7]?.context.errorClass, undefined); From 1d6f809e24c91f39be6fad99e86e8e8983c26918 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 14:59:40 +0200 Subject: [PATCH 078/104] Keep descendant CDN failures at error severity A successful authored package can fail while recursively caching one of its HTTP dependencies. Correlate the structured 404 URL with the exact root package request before applying tenant build attribution, so descendant CDN failures remain framework-level. Constraint: URL evidence is sanitized before entering error context. Rejected: Classify every 404 escaping a bare-package resolution | recursive dependency failures share the same error path. Confidence: high Scope-risk: narrow Directive: Do not weaken the root-request URL equality check without a package-200 and descendant-404 regression. Tested: Red-first descendant 404 regression; HTTP Bundle Cache suite (79 steps); pinned Deno 2.7.7 format, lint, typecheck, generation, 3845 unit tests (28727 steps), 10 cwd tests (197 steps), and 2 cwd-exclusion tests. Not-tested: Exact-head GitHub CI. --- src/transforms/esm/http-cache.test.ts | 33 ++++++++++++++++++++++++ src/transforms/esm/http-cache.ts | 4 +-- src/transforms/esm/specifier-resolver.ts | 25 ++++++++++++++---- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 31a5fed417..270c1b028d 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -1261,6 +1261,39 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, }); }); + it("does not classify a missing dependency of an existing bare package as tenant source", async () => { + const packageUrl = "https://esm.sh/package-with-missing-dependency"; + const dependencyUrl = "https://esm.sh/missing-package-dependency.js"; + const mockFetch = ((input: string | URL | Request) => { + const url = String(input); + if (url.startsWith(packageUrl)) { + return Promise.resolve( + new Response(`import "${dependencyUrl}"; export const loaded = true;`, { + headers: { "content-type": "application/javascript" }, + }), + ); + } + return Promise.resolve(new Response("not found", { status: 404 })); + }) as typeof fetch; + + await withIsolatedHttpCache( + "vf-esm-missing-package-dependency-", + mockFetch, + async (tempDir) => { + const error = await assertRejects( + () => + cacheHttpImportsToLocal('import "package-with-missing-dependency";', { + cacheDir: tempDir, + importMap: { imports: {}, scopes: {} }, + }), + Error, + ); + + assertEquals(isTenantSourceBuildError(error), false); + }, + ); + }); + it("retries failures while reading an HTTP module body", async () => { let fetchCount = 0; diff --git a/src/transforms/esm/http-cache.ts b/src/transforms/esm/http-cache.ts index dd7277ef87..f7339156d9 100644 --- a/src/transforms/esm/http-cache.ts +++ b/src/transforms/esm/http-cache.ts @@ -183,7 +183,7 @@ interface HttpModuleFetchResult { function terminalHttpModuleFetchError( detail: string, - context: { httpStatus?: number } = {}, + context: { httpStatus?: number; httpModuleUrl?: string } = {}, ): VeryfrontError { return BUILD_FAILED.create({ detail, @@ -321,7 +321,7 @@ async function fetchHttpModule( if (error instanceof HttpModuleResponseError) { throw terminalHttpModuleFetchError( `Failed to fetch ${safeUrl}: ${error.status}`, - { httpStatus: error.status }, + { httpStatus: error.status, httpModuleUrl: safeUrl }, ); } if (error instanceof HttpModuleRequestError) { diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 72b7644df1..a43ea9fe94 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -12,6 +12,7 @@ import { resolveImport } from "#veryfront/modules/import-map/resolver.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; import { BUILD_FAILED } from "#veryfront/errors"; import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; +import { sanitizeUrlForSpan } from "#veryfront/utils/logger/redact.ts"; import { appendSameOriginSSRDependencyPinningKey, normalizeExtension, @@ -23,12 +24,14 @@ import { parseImports, replaceSpecifiers } from "./lexer.ts"; import { type CacheOptions, + getEffectiveHttpCacheRequest, isCanonicalReactEsmUrl, isExternalScheme, isHttpUrl, isInternalBare, isParentHttpModule, isRelative, + normalizeHttpUrl, resolveBareSpecifier, } from "./http-cache-helpers.ts"; @@ -44,13 +47,18 @@ function stringStartsWith(value: string, search: string): boolean { return ReflectApply(StringStartsWith, value, [search]) as boolean; } -function classifyAuthoredPackageFetchError(error: unknown): unknown { +function classifyAuthoredPackageFetchError( + error: unknown, + requestedPackageUrl: string | undefined, +): unknown { const snapshot = snapshotVeryfrontError(error); const context = snapshot?.context; if ( snapshot?.slug !== BUILD_FAILED.slug || typeof context !== "object" || context === null || - (context as { httpStatus?: unknown }).httpStatus !== 404 + typeof requestedPackageUrl !== "string" || + (context as { httpStatus?: unknown }).httpStatus !== 404 || + (context as { httpModuleUrl?: unknown }).httpModuleUrl !== requestedPackageUrl ) { return error; } @@ -59,7 +67,7 @@ function classifyAuthoredPackageFetchError(error: unknown): unknown { message: snapshot.message, detail: snapshot.detail, cause: error, - context: { httpStatus: 404, tenantBuildFailure: true }, + context: { httpStatus: 404, httpModuleUrl: requestedPackageUrl, tenantBuildFailure: true }, }); } @@ -221,10 +229,17 @@ async function resolveSpecifier( if (mapped === specifier) return null; if (isLocalMappedSpecifier(mapped)) return mapped; + let requestedPackageUrl: string | undefined; + const cacheAuthoredPackage: CacheHttpModuleFn = async (url, cacheOptions) => { + const effective = getEffectiveHttpCacheRequest(url, cacheOptions); + requestedPackageUrl = sanitizeUrlForSpan(normalizeHttpUrl(effective.url)); + return await cacheHttpModule(url, cacheOptions); + }; + try { - return await resolveSpecifier(mapped, baseUrl, options, cacheHttpModule); + return await resolveSpecifier(mapped, baseUrl, options, cacheAuthoredPackage); } catch (error) { - throw classifyAuthoredPackageFetchError(error); + throw classifyAuthoredPackageFetchError(error, requestedPackageUrl); } } From f6783d77d68f15b2a0bc62b68946933e5ae439bb Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 15:12:40 +0200 Subject: [PATCH 079/104] Keep URL correlation lossless without exposing requests Tenant package attribution must compare the exact authored module request with the failed fetch even when diagnostics redact query details. Correlate the first effective package request through an opaque namespaced SHA-256 fingerprint and keep the display URL sanitized. Constraint: Raw module request URLs can contain sensitive query values and cannot enter error context. Rejected: Compare sanitized URLs | distinct requests at the same path collide after query redaction. Confidence: high Scope-risk: narrow Directive: Keep request identity separate from user-visible URL diagnostics. Tested: HTTP cache and helper suites, 156 steps; targeted format, lint, typecheck, and diff check. Not-tested: Live CDN requests. --- src/transforms/esm/http-cache-helpers.ts | 6 ++++ src/transforms/esm/http-cache.test.ts | 36 ++++++++++++++++++++++++ src/transforms/esm/http-cache.ts | 13 +++++++-- src/transforms/esm/specifier-resolver.ts | 22 ++++++++++----- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 2a5940f9dd..5a07cb28a6 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -284,6 +284,7 @@ interface HttpCacheRequestIdentityContext { } const HTTP_CACHE_REQUEST_IDENTITY_CONTEXT = Symbol("http-cache-request-identity-context"); +const HTTP_MODULE_REQUEST_FINGERPRINT_NAMESPACE = "veryfront:http-module-request:v1"; type HttpCacheRequestIdentityCarrier = { [HTTP_CACHE_REQUEST_IDENTITY_CONTEXT]?: HttpCacheRequestIdentityContext; @@ -675,6 +676,11 @@ export function normalizeHttpUrl(raw: string): string { } } +/** Build an opaque identity for correlating one exact HTTP module request. */ +export function fingerprintHttpModuleRequest(rawUrl: string): Promise { + return computeHash(`${HTTP_MODULE_REQUEST_FINGERPRINT_NAMESPACE}\0${normalizeHttpUrl(rawUrl)}`); +} + export function resolveBareSpecifier( specifier: string, importMap: ImportMapConfig, diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 270c1b028d..4abaa74da2 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -1294,6 +1294,42 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, ); }); + it("distinguishes a package from a missing dependency at the same sanitized URL", async () => { + const packageUrl = "https://esm.sh/same-path-module.js?entry=root"; + const dependencyUrl = "https://esm.sh/same-path-module.js?entry=dependency"; + const mockFetch = ((input: string | URL | Request) => { + const url = String(input); + if (new URL(url).searchParams.get("entry") === "root") { + return Promise.resolve( + new Response(`import "${dependencyUrl}"; export const loaded = true;`, { + headers: { "content-type": "application/javascript" }, + }), + ); + } + return Promise.resolve(new Response("not found", { status: 404 })); + }) as typeof fetch; + + await withIsolatedHttpCache( + "vf-esm-same-sanitized-package-url-", + mockFetch, + async (tempDir) => { + const error = await assertRejects( + () => + cacheHttpImportsToLocal('import "same-path-package";', { + cacheDir: tempDir, + importMap: { + imports: { "same-path-package": packageUrl }, + scopes: {}, + }, + }), + Error, + ); + + assertEquals(isTenantSourceBuildError(error), false); + }, + ); + }); + it("retries failures while reading an HTTP module body", async () => { let fetchCount = 0; diff --git a/src/transforms/esm/http-cache.ts b/src/transforms/esm/http-cache.ts index f7339156d9..643577fefc 100644 --- a/src/transforms/esm/http-cache.ts +++ b/src/transforms/esm/http-cache.ts @@ -53,6 +53,7 @@ import { describeHtmlModuleResponse, ensureAbsoluteDir, ensurePreparedHttpCacheRequestOptions, + fingerprintHttpModuleRequest, getEffectiveHttpCacheRequest, hashHttpCacheIdentity, hasIncompatibleFilePaths, @@ -183,7 +184,11 @@ interface HttpModuleFetchResult { function terminalHttpModuleFetchError( detail: string, - context: { httpStatus?: number; httpModuleUrl?: string } = {}, + context: { + httpStatus?: number; + httpModuleUrl?: string; + httpModuleRequestFingerprint?: string; + } = {}, ): VeryfrontError { return BUILD_FAILED.create({ detail, @@ -321,7 +326,11 @@ async function fetchHttpModule( if (error instanceof HttpModuleResponseError) { throw terminalHttpModuleFetchError( `Failed to fetch ${safeUrl}: ${error.status}`, - { httpStatus: error.status, httpModuleUrl: safeUrl }, + { + httpStatus: error.status, + httpModuleUrl: safeUrl, + httpModuleRequestFingerprint: await fingerprintHttpModuleRequest(url), + }, ); } if (error instanceof HttpModuleRequestError) { diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index a43ea9fe94..28eaff38fe 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -12,7 +12,6 @@ import { resolveImport } from "#veryfront/modules/import-map/resolver.ts"; import { OutboundRequestBlockedError } from "#veryfront/security/http/outbound-fetch.ts"; import { BUILD_FAILED } from "#veryfront/errors"; import { snapshotVeryfrontError } from "#veryfront/errors/types.ts"; -import { sanitizeUrlForSpan } from "#veryfront/utils/logger/redact.ts"; import { appendSameOriginSSRDependencyPinningKey, normalizeExtension, @@ -24,6 +23,7 @@ import { parseImports, replaceSpecifiers } from "./lexer.ts"; import { type CacheOptions, + fingerprintHttpModuleRequest, getEffectiveHttpCacheRequest, isCanonicalReactEsmUrl, isExternalScheme, @@ -49,16 +49,17 @@ function stringStartsWith(value: string, search: string): boolean { function classifyAuthoredPackageFetchError( error: unknown, - requestedPackageUrl: string | undefined, + requestedPackageFingerprint: string | undefined, ): unknown { const snapshot = snapshotVeryfrontError(error); const context = snapshot?.context; if ( snapshot?.slug !== BUILD_FAILED.slug || typeof context !== "object" || context === null || - typeof requestedPackageUrl !== "string" || + typeof requestedPackageFingerprint !== "string" || (context as { httpStatus?: unknown }).httpStatus !== 404 || - (context as { httpModuleUrl?: unknown }).httpModuleUrl !== requestedPackageUrl + (context as { httpModuleRequestFingerprint?: unknown }).httpModuleRequestFingerprint !== + requestedPackageFingerprint ) { return error; } @@ -67,7 +68,11 @@ function classifyAuthoredPackageFetchError( message: snapshot.message, detail: snapshot.detail, cause: error, - context: { httpStatus: 404, httpModuleUrl: requestedPackageUrl, tenantBuildFailure: true }, + context: { + httpStatus: 404, + httpModuleRequestFingerprint: requestedPackageFingerprint, + tenantBuildFailure: true, + }, }); } @@ -232,14 +237,17 @@ async function resolveSpecifier( let requestedPackageUrl: string | undefined; const cacheAuthoredPackage: CacheHttpModuleFn = async (url, cacheOptions) => { const effective = getEffectiveHttpCacheRequest(url, cacheOptions); - requestedPackageUrl = sanitizeUrlForSpan(normalizeHttpUrl(effective.url)); + requestedPackageUrl ??= normalizeHttpUrl(effective.url); return await cacheHttpModule(url, cacheOptions); }; try { return await resolveSpecifier(mapped, baseUrl, options, cacheAuthoredPackage); } catch (error) { - throw classifyAuthoredPackageFetchError(error, requestedPackageUrl); + const requestedPackageFingerprint = requestedPackageUrl === undefined + ? undefined + : await fingerprintHttpModuleRequest(requestedPackageUrl); + throw classifyAuthoredPackageFetchError(error, requestedPackageFingerprint); } } From d97073bb7a68f88e1262bb5fe0a47e6750d1727a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 15:27:58 +0200 Subject: [PATCH 080/104] Respect ASI boundaries while scanning MDX imports The source-span scanner treated exported declaration blocks differently from local declaration blocks, so regex literals at the following ASI boundary could leak import-like regex contents as dynamic imports. Static side-effect scanning also kept a non-start state across block comments even when the comment contained a line terminator, hiding a valid following import statement. Constraint: Preserve lightweight scanner behavior without adding parser dependencies. Rejected: Parse MDX module JavaScript with a full AST parser | too broad for exact review findings and existing scanner design. Confidence: high Scope-risk: narrow Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno lint src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: deno check src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: git diff --check -- src/transforms/mdx/esm-module-loader/utils/source-spans.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Not-tested: Full repository suite; root owns full prepush validation before pushing. --- .../utils/source-spans.test.ts | 38 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 24 ++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 4a8589ba73..39161be4a1 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -484,6 +484,33 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after exported declarations at ASI boundaries", () => { + assertEquals( + vfModuleSpecifiers( + 'export function f() {}\n/import("\\/_vf_modules\\/fake-function.js")/.test(value);', + ), + [], + ); + assertEquals( + vfModuleSpecifiers( + 'export class C {}\n/import("\\/_vf_modules\\/fake-class.js")/.test(value);', + ), + [], + ); + assertEquals( + vfModuleSpecifiers( + 'export default function () {}\n/import("\\/_vf_modules\\/fake-default-function.js")/.test(value);', + ), + [], + ); + assertEquals( + vfModuleSpecifiers( + 'export default class {}\n/import("\\/_vf_modules\\/fake-default-class.js")/.test(value);', + ), + [], + ); + }); + it("recognizes Unicode line terminators in declaration comments", () => { for (const lineTerminator of ["\u2028", "\u2029"]) { assertEquals( @@ -990,6 +1017,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { } }); + it("finds side-effect imports after block comments with line terminators", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'const ready = true /* note\n */ import "/_vf_modules/after-comment.js";', + (specifier) => specifier.startsWith("/_vf_modules/") ? specifier : null, + UNBOUNDED, + ).map((span) => span.path), + ["/_vf_modules/after-comment.js"], + ); + }); + it("ignores side-effect import text inside regex literals", () => { assertEquals( findStaticSideEffectImportSpans( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 2ae50a25ed..3041be704a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -221,6 +221,13 @@ function isLineTerminator(char: string): boolean { return char === "\r" || char === "\n" || char === "\u2028" || char === "\u2029"; } +function containsLineTerminator(source: string, start: number, end: number): boolean { + for (let cursor = start; cursor < end; cursor++) { + if (isLineTerminator(source[cursor]!)) return true; + } + return false; +} + function decodeLiteralContents( source: string, start: number, @@ -478,8 +485,13 @@ function isDeclarationBlockCloseBrace( /\/\*[\s\S]*?\*\/|\/\/[^\r\n\u2028\u2029]*/g, " ", ); - return /^(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/.test(prefix) || - /^class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test(prefix); + return /^(?:export\s+(?:default\s+)?)?(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/ + .test( + prefix, + ) || + /^(?:export\s+(?:default\s+)?)?class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test( + prefix, + ); } function isStatementBlockCloseBrace( @@ -918,7 +930,9 @@ export function findStaticImportFromSpans( ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; - else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; + else if (char === "/" && source[cursor + 1] === "*") { + atStatementStart = atStatementStart || containsLineTerminator(source, cursor, skipped); + } else atStatementStart = false; previousTokenIndex = tokenIndexAfterIgnored( source, cursor, @@ -1288,7 +1302,9 @@ export function findStaticSideEffectImportSpans( ); if (skipped !== cursor) { if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; - else if (!(char === "/" && source[cursor + 1] === "*")) atStatementStart = false; + else if (char === "/" && source[cursor + 1] === "*") { + atStatementStart = atStatementStart || containsLineTerminator(source, cursor, skipped); + } else atStatementStart = false; previousTokenIndex = tokenIndexAfterIgnored( source, cursor, From b625b6d0c6c2f42fc2f7e807e6bdeae4fd7aebda Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 15:45:13 +0200 Subject: [PATCH 081/104] Keep async iteration syntax out of import discovery The lightweight source scanner treated the parenthesis in for-await-of headers as an ordinary await expression. That made a regex after the loop look like division and exposed import-shaped regex text to dependency resolution. Constraint: Preserve comment trivia between for and await without adding a parser dependency. Rejected: Special-case the trailing slash after the loop | the parenthesis context is the reusable syntax boundary. Confidence: high Scope-risk: narrow Directive: Keep for-await headers aligned with classic for headers in regex and semicolon context. Tested: Red-green source-span regression, focused suite, format, lint, typecheck, diff check. Not-tested: Full repository suite before this commit. --- .../utils/source-spans.test.ts | 15 ++++++++++ .../esm-module-loader/utils/source-spans.ts | 28 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 39161be4a1..d6ebf91de3 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -562,6 +562,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after for-await loops", () => { + assertEquals( + specifiers( + 'for await (const value of []) {}\n/import("\\.\\/fake.js")/.test(value);', + ), + [], + ); + assertEquals( + specifiers( + 'for /* stream */ await (const value of []) {}\n/import("\\.\\/commented-fake.js")/.test(value);', + ), + [], + ); + }); + it("ignores import-looking regex text inside template substitutions", () => { assertEquals( specifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 3041be704a..251f13631b 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -427,17 +427,41 @@ function keywordBefore( return source.slice(start, end); } +function isForAwaitHeader( + source: string, + previousTokenIndex: number, +): boolean { + const awaitStart = previousTokenIndex - "await".length + 1; + let forStart = source.lastIndexOf("for", awaitStart - 1); + + while (forStart >= 0) { + const isStandaloneKeyword = !isIdentifierPartAt(source, forStart - 1) && + source[forStart - 1] !== "." && + !isIdentifierPartAt(source, forStart + "for".length); + if ( + isStandaloneKeyword && + skipWhitespaceAndComments(source, forStart + "for".length) === awaitStart + ) return true; + + forStart = source.lastIndexOf("for", forStart - 1); + } + + return false; +} + function openParenContext( source: string, index: number, previousTokenIndex: number, ): OpenParenContext { const keyword = keywordBefore(source, index, previousTokenIndex); + const isForHeader = keyword === "for" || + (keyword === "await" && isForAwaitHeader(source, previousTokenIndex)); return { index, - isControlCondition: keyword === "if" || keyword === "while" || keyword === "for" || + isControlCondition: keyword === "if" || keyword === "while" || isForHeader || keyword === "with" || keyword === "switch" || keyword === "catch", - isForHeader: keyword === "for", + isForHeader, hasSemicolon: false, }; } From f7386176afa109600307abcdd52af5ee62a75d57 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Sat, 15 Aug 2026 15:57:23 +0200 Subject: [PATCH 082/104] fix(transforms): keep commented for out of for-await detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The for-await header check searches raw source text for the preceding `for`, so it also finds one that is not code. A block comment cannot fool it — the `*/` terminator stops the adjacency scan — but a line comment ends at a newline, which that scan treats as ordinary whitespace and walks straight through. A comment whose last word is `for` sitting above a top-level `await (…)` therefore read as a for-await header. The `/` that actually divides was then taken as a regex opening, hiding a real dynamic import inside it from dependency discovery. Guard the search with a line-comment check that lexes only the current line, which is sufficient because a line comment cannot start on an earlier line. The check tracks quotes: a `//` inside a string on the same line is not a comment start, and treating it as one would stop a genuine for-await header from being recognised and put the phantom-import back. --- .../utils/source-spans.test.ts | 32 +++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 41 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index d6ebf91de3..08ad429ed7 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -577,6 +577,38 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + // The for-await search runs over raw text, so it also finds a `for` that is + // not code. A block comment cannot fool it — the `*/` terminator stops the + // adjacency scan — but a line comment ends at a newline, which that scan + // treats as ordinary whitespace and walks straight through. A comment whose + // last word is `for` sitting above a top-level `await (…)` therefore read as + // a for-await header, and the `/` that actually divides was taken as a regex + // opening, hiding a real dynamic import inside it. + it("does not read a line comment ending in for as a for-await header", () => { + assertEquals( + specifiers('// for\nawait (ready)\n/import(".\\/after-line-comment.js")/.source;'), + ["./after-line-comment.js"], + ); + assertEquals( + specifiers( + '// what we are waiting for\nawait (ready)\n/import(".\\/after-prose-comment.js")/.source;', + ), + ["./after-prose-comment.js"], + ); + }); + + // The converse over-correction: `//` inside a string is not a comment, so a + // URL on the same line must not stop a genuine for-await header from being + // recognised — that would put the phantom-import bug straight back. + it("still reads a for-await header on a line holding a url string", () => { + assertEquals( + specifiers( + 'const origin = "http://example.test"; for await (const value of source) {}\n/import(".\\/url-line-fake.js")/.test(value);', + ), + [], + ); + }); + it("ignores import-looking regex text inside template substitutions", () => { assertEquals( specifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 251f13631b..5e35a31847 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -427,6 +427,43 @@ function keywordBefore( return source.slice(start, end); } +/** + * Whether `index` sits inside a `//` line comment. + * + * Only line comments need this, and the asymmetry is structural. A block + * comment cannot leak into the for-await check: its `*` + `/` terminator stops + * `skipWhitespaceAndComments`, so a `for` written inside one never reads as + * adjacent to a later `await`. A line comment ends at a newline, which that + * same scan treats as ordinary whitespace and walks straight through — so the + * commented word is read as code. + * + * Lexing only the current line is both sufficient and bounded: a line comment + * cannot have started on an earlier line. The quote tracking matters because a + * `//` inside a string on the same line (a URL, say) is not a comment start, + * and treating it as one would fail to recognise a real `for await` header. + */ +function isInsideLineComment(source: string, index: number): boolean { + let cursor = index; + while (cursor > 0 && !isLineTerminator(source[cursor - 1] ?? "")) cursor--; + + let quote: string | null = null; + for (; cursor < index; cursor++) { + const char = source[cursor]!; + if (quote !== null) { + if (char === "\\") cursor++; + else if (char === quote) quote = null; + continue; + } + if (char === '"' || char === "'" || char === "`") { + quote = char; + continue; + } + if (char === "/" && source[cursor + 1] === "/") return true; + } + + return false; +} + function isForAwaitHeader( source: string, previousTokenIndex: number, @@ -440,6 +477,10 @@ function isForAwaitHeader( !isIdentifierPartAt(source, forStart + "for".length); if ( isStandaloneKeyword && + // The search runs over raw text, so it also finds a `for` that is not + // code. Everything but a line comment is already excluded by the + // adjacency check below — see `isInsideLineComment`. + !isInsideLineComment(source, forStart) && skipWhitespaceAndComments(source, forStart + "for".length) === awaitStart ) return true; From d6f2b3130f53642287f5e247d9eb11946e685cd8 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 16:03:43 +0200 Subject: [PATCH 083/104] Keep scanner guidance within repository copy rules The concurrent for-await hardening used non-ASCII punctuation in public source comments. Reword the explanation without changing scanner behavior. Constraint: Public comments use ASCII punctuation. Confidence: high Scope-risk: narrow Tested: Combined source-span suite, format, lint, typecheck, diff check. Not-tested: Runtime behavior change, because this commit changes comments only. --- .../mdx/esm-module-loader/utils/source-spans.test.ts | 8 ++++---- .../mdx/esm-module-loader/utils/source-spans.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 08ad429ed7..9411f5e7cd 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -578,10 +578,10 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { }); // The for-await search runs over raw text, so it also finds a `for` that is - // not code. A block comment cannot fool it — the `*/` terminator stops the - // adjacency scan — but a line comment ends at a newline, which that scan + // not code. A block comment cannot fool it because the `*/` terminator + // stops the adjacency scan. A line comment ends at a newline, which the scan // treats as ordinary whitespace and walks straight through. A comment whose - // last word is `for` sitting above a top-level `await (…)` therefore read as + // last word is `for` sitting above a top-level `await (...)` therefore read as // a for-await header, and the `/` that actually divides was taken as a regex // opening, hiding a real dynamic import inside it. it("does not read a line comment ending in for as a for-await header", () => { @@ -599,7 +599,7 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { // The converse over-correction: `//` inside a string is not a comment, so a // URL on the same line must not stop a genuine for-await header from being - // recognised — that would put the phantom-import bug straight back. + // recognized. Otherwise, the phantom-import bug would return. it("still reads a for-await header on a line holding a url string", () => { assertEquals( specifiers( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 5e35a31847..e4f35f022e 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -434,7 +434,7 @@ function keywordBefore( * comment cannot leak into the for-await check: its `*` + `/` terminator stops * `skipWhitespaceAndComments`, so a `for` written inside one never reads as * adjacent to a later `await`. A line comment ends at a newline, which that - * same scan treats as ordinary whitespace and walks straight through — so the + * same scan treats as ordinary whitespace and walks straight through, so the * commented word is read as code. * * Lexing only the current line is both sufficient and bounded: a line comment @@ -479,7 +479,7 @@ function isForAwaitHeader( isStandaloneKeyword && // The search runs over raw text, so it also finds a `for` that is not // code. Everything but a line comment is already excluded by the - // adjacency check below — see `isInsideLineComment`. + // adjacency check below (see `isInsideLineComment`). !isInsideLineComment(source, forStart) && skipWhitespaceAndComments(source, forStart + "for".length) === awaitStart ) return true; From f584aab477a7e55b689bc05cad2525c452ecb5e0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 16:16:55 +0200 Subject: [PATCH 084/104] Keep dynamic dependency failures lazy Dynamic imports should not block parent module preparation when their child dependency has an authored source, transform, or cycle failure. The loader now turns only known deferable dependency failures into typed fail-on-import stubs, while static imports and infrastructure failures still fail during preparation. Strict false dynamic imports continue to use fallback stubs, so non-strict rendering keeps its existing recovery behavior. Constraint: PR review required strict dynamic imports to stay lazy for existing child source, transform, and cycle failures without swallowing framework or infrastructure failures. Rejected: Catch every dynamic fetch error | would hide cache, pinning, timeout, and platform failures that must remain prepare-time errors. Confidence: high Scope-risk: moderate Directive: Add new deferable dynamic failure classes to dynamicDependencyFailure with a sanitized runtime message; do not widen the catch to all errors. Tested: deno fmt --check on touched dynamic files Tested: deno lint on touched dynamic files Tested: deno check on touched dynamic files Tested: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Not-tested: full repository suite --- .../module-fetcher/nested-imports.test.ts | 254 ++++++++++++++++++ .../module-fetcher/nested-imports.ts | 59 +++- src/transforms/mdx/esm-module-loader/types.ts | 2 + .../esm-module-loader/utils/stub-module.ts | 27 +- 4 files changed, 334 insertions(+), 8 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 69b44234a2..75ee6296b9 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -3,6 +3,7 @@ import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; import { join, toFileUrl } from "#veryfront/compat/path/index.ts"; +import { MDX_COMPILE_ERROR } from "#veryfront/errors"; import { findNestedImports, hasUnresolvedImports, @@ -13,6 +14,7 @@ import { MAX_MDX_MODULE_IMPORTS_PER_FILE, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY, ModuleImportLimitError, + ModuleSourceLimitError, } from "./limits.ts"; import { buildMissingModuleError } from "../missing-module.ts"; @@ -432,6 +434,180 @@ import { bar } from "./local.js"; } }); + it("defers strict dynamic child source failures until the branch executes", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-source-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./oversized.js") : Promise.resolve("skipped");`; + + try { + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path) => { + throw new ModuleSourceLimitError(path, 2048, 1024); + }, + }); + const parentPath = join(esmCacheDir, "dynamic-source-parent.mjs"); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + const error = await assertRejects( + () => loaded.load(true), + Error, + "module source exceeds the allowed size", + ); + if (!(error instanceof Error)) throw new Error("expected Error"); + assertEquals(error.name, "ModuleSourceLimitError"); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + + it("defers strict dynamic child transform failures with sanitized runtime errors", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-transform-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./broken.mdx") : Promise.resolve("skipped");`; + + try { + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + throw MDX_COMPILE_ERROR.create({ + detail: "MDX compilation error: | file: /broken.mdx", + }); + }, + }); + const parentPath = join(esmCacheDir, "dynamic-transform-parent.mjs"); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + const error = await assertRejects( + () => loaded.load(true), + Error, + "MDX compilation failed", + ); + if (!(error instanceof Error)) throw new Error("expected Error"); + assertEquals(error.name, "MdxCompileError"); + assertEquals(error.message.includes(""), false); + assertEquals(error.message.includes(""), false); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + + it("defers strict dynamic child cycles until the branch executes", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-cycle-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./cycle.js") : Promise.resolve("skipped");`; + + try { + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + const error = new Error( + "Circular module dependency detected: _vf_modules/pages/index.js -> ./cycle.js", + ); + error.name = "CircularModuleDependencyError"; + throw error; + }, + }); + const parentPath = join(esmCacheDir, "dynamic-cycle-parent.mjs"); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + const error = await assertRejects( + () => loaded.load(true), + Error, + "circular module dependency", + ); + if (!(error instanceof Error)) throw new Error("expected Error"); + assertEquals(error.name, "CircularModuleDependencyError"); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + + it("uses fallback stubs for non-strict dynamic child dependency failures", async () => { + const cases = [ + { + name: "source", + path: "./oversized.js", + error: () => new ModuleSourceLimitError("./oversized.js", 2048, 1024), + }, + { + name: "transform", + path: "./broken.mdx", + error: () => + MDX_COMPILE_ERROR.create({ + detail: "MDX compilation error: | file: /broken.mdx", + }), + }, + { + name: "cycle", + path: "./cycle.js", + error: () => { + const error = new Error( + "Circular module dependency detected: _vf_modules/pages/index.js -> ./cycle.js", + ); + error.name = "CircularModuleDependencyError"; + return error; + }, + }, + ]; + + for (const testCase of cases) { + const esmCacheDir = await makeTempDir({ + prefix: `vf-mdx-dynamic-${testCase.name}-fallback-cache-`, + }); + const source = + `export const load = (enabled) => enabled ? import("${testCase.path}") : Promise.resolve("skipped");`; + + try { + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: false, + fetchAndCacheModule: () => { + throw testCase.error(); + }, + }); + const parentPath = join(esmCacheDir, `dynamic-${testCase.name}-fallback-parent.mjs`); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + const loadedFallback = await loaded.load(true); + assertEquals(typeof loadedFallback, "object"); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + } + }); + it("keeps strict static imports fail-fast", async () => { await assertRejects( () => @@ -448,6 +624,84 @@ import { bar } from "./local.js"; ); }); + it("keeps strict static child source failures fail-fast", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `import value from "./oversized.js"; export { value };`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: (path) => { + throw new ModuleSourceLimitError(path, 2048, 1024); + }, + }), + ModuleSourceLimitError, + "exceeds the source-size limit", + ); + }); + + it("keeps strict static child transform failures fail-fast", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `import value from "./broken.mdx"; export { value };`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + throw MDX_COMPILE_ERROR.create({ + detail: "MDX compilation error: | file: /broken.mdx", + }); + }, + }), + Error, + "MDX compilation error", + ); + }); + + it("keeps strict static child cycles fail-fast", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `import value from "./cycle.js"; export { value };`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + const error = new Error( + "Circular module dependency detected: _vf_modules/pages/index.js -> ./cycle.js", + ); + error.name = "CircularModuleDependencyError"; + throw error; + }, + }), + Error, + "Circular module dependency detected", + ); + }); + + it("keeps dynamic infrastructure failures fail-fast", async () => { + await assertRejects( + () => + resolveNestedModuleImports({ + moduleCode: `export const load = () => import("./later.js");`, + esmCacheDir: "/tmp/veryfront-unused", + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + throw new Error("cache backend unavailable"); + }, + }), + Error, + "cache backend unavailable", + ); + }); + it("resolves admitted fan-out with bounded concurrency", async () => { const importCount = MAX_MDX_MODULE_TRANSFORM_CONCURRENCY + 4; const moduleCode = Array.from( diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 80587d3e91..9e5ac33c27 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -6,7 +6,7 @@ import { LOG_PREFIX_MDX_LOADER } from "../constants.ts"; import type { NestedImportResult } from "../types.ts"; -import { createStubModule } from "../utils/stub-module.ts"; +import { createStubModule, type DeferredImportErrorDescriptor } from "../utils/stub-module.ts"; import { findDynamicImportSpans, findStaticImportFromSpans, @@ -26,6 +26,7 @@ import { MAX_MDX_MODULE_IMPORTS_PER_FILE, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY, } from "./limits.ts"; +import { VeryfrontError } from "#veryfront/errors"; function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/.+)$/)?.[1] ?? null; @@ -47,6 +48,45 @@ function isMalformedSpecifierSyntaxError(error: unknown): boolean { return error instanceof SyntaxError && error.message.includes("module specifier"); } +function dynamicDependencyFailure( + modulePath: string, + error: unknown, +): DeferredImportErrorDescriptor | null { + if (!(error instanceof Error)) return null; + + if (isMdxMissingModuleError(error)) { + return { + name: "MissingModuleError", + message: + `[Veryfront] Missing module: ${modulePath}. This module or file does not exist in your project.`, + }; + } + + if (error.name === "CircularModuleDependencyError") { + return { + name: "CircularModuleDependencyError", + message: `[Veryfront] Dynamic import failed for ${modulePath}: circular module dependency.`, + }; + } + + if (error.name === "ModuleSourceLimitError") { + return { + name: "ModuleSourceLimitError", + message: + `[Veryfront] Dynamic import failed for ${modulePath}: module source exceeds the allowed size.`, + }; + } + + if (error instanceof VeryfrontError && error.slug === "mdx-compile-error") { + return { + name: "MdxCompileError", + message: `[Veryfront] Dynamic import failed for ${modulePath}: MDX compilation failed.`, + }; + } + + return null; +} + function scanImportSpans( scan: () => StaticImportSpan[], ): { spans: StaticImportSpan[]; malformed: boolean } { @@ -276,6 +316,7 @@ export async function processNestedImports( isDynamic, isSideEffect, nestedFilePath, + deferredError, nestedPath, relativePath, } of results @@ -302,7 +343,7 @@ export async function processNestedImports( moduleCode, original, esmCacheDir, - { failOnImport: strictMissingModules }, + { failOnImport: strictMissingModules, deferredError }, ); if (deferredPath) { replacements.push({ @@ -442,8 +483,20 @@ export async function resolveNestedModuleImports( input.parentBasePath ?? input.normalizedPath, ); } catch (error) { - if (!isDynamic || !isMdxMissingModuleError(error)) throw error; + const deferredError = isDynamic ? dynamicDependencyFailure(path, error) : null; + if (!deferredError) throw error; nestedFilePath = null; + return { + original, + start, + end, + suffix, + isDynamic, + isSideEffect, + nestedFilePath, + deferredError, + [key]: path, + }; } return { diff --git a/src/transforms/mdx/esm-module-loader/types.ts b/src/transforms/mdx/esm-module-loader/types.ts index 21c1a52fdb..f485d5b6bb 100644 --- a/src/transforms/mdx/esm-module-loader/types.ts +++ b/src/transforms/mdx/esm-module-loader/types.ts @@ -3,6 +3,7 @@ import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import type { Logger } from "#veryfront/utils"; import type { MDXModule } from "../types.ts"; import type { DependencyPinningSourceInput } from "#veryfront/transforms/esm/package-registry.ts"; +import type { DeferredImportErrorDescriptor } from "./utils/stub-module.ts"; export interface ESMLoaderContext { esmCacheDir?: string; @@ -60,6 +61,7 @@ export interface NestedImportResult { isSideEffect?: boolean; suffix?: string; nestedFilePath: string | null; + deferredError?: DeferredImportErrorDescriptor; nestedPath?: string; relativePath?: string; } diff --git a/src/transforms/mdx/esm-module-loader/utils/stub-module.ts b/src/transforms/mdx/esm-module-loader/utils/stub-module.ts index 269b84e91a..6c8b512379 100644 --- a/src/transforms/mdx/esm-module-loader/utils/stub-module.ts +++ b/src/transforms/mdx/esm-module-loader/utils/stub-module.ts @@ -69,12 +69,22 @@ ${namedExports} `; } -function generateDeferredMissingModuleCode(modulePath: string): string { +export interface DeferredImportErrorDescriptor { + name: string; + message: string; +} + +function generateDeferredImportFailureCode( + modulePath: string, + deferredError?: DeferredImportErrorDescriptor, +): string { const message = JSON.stringify( - `[Veryfront] Missing module: ${modulePath}. This module or file does not exist in your project.`, + deferredError?.message ?? + `[Veryfront] Missing module: ${modulePath}. This module or file does not exist in your project.`, ); + const name = JSON.stringify(deferredError?.name ?? "MissingModuleError"); return `const error = new Error(${message}); -error.name = "MissingModuleError"; +error.name = ${name}; throw error; `; } @@ -82,6 +92,8 @@ throw error; export interface CreateStubModuleOptions { /** Reject a dynamic import when it executes instead of exporting fallback values. */ failOnImport?: boolean; + /** Sanitized typed error to throw when a strict dynamic import executes. */ + deferredError?: DeferredImportErrorDescriptor; } export async function createStubModule( @@ -93,10 +105,15 @@ export async function createStubModule( ): Promise { const namedImports = extractNamedImports(code, importStatement); const behavior = options.failOnImport ? "fail-on-import" : "fallback"; - const stubHash = hashString(`stub:${behavior}:${modulePath}:${namedImports.join(",")}`); + const deferredIdentity = options.deferredError + ? `${options.deferredError.name}:${options.deferredError.message}` + : ""; + const stubHash = hashString( + `stub:${behavior}:${modulePath}:${namedImports.join(",")}:${deferredIdentity}`, + ); const stubPath = join(esmCacheDir, `stub-${stubHash}.mjs`); const stubCode = options.failOnImport - ? generateDeferredMissingModuleCode(modulePath) + ? generateDeferredImportFailureCode(modulePath, options.deferredError) : generateStubCode(modulePath, namedImports); try { From 042ea1c9bec187c1f455d108e77ada8126cba9f6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 16:02:41 +0200 Subject: [PATCH 085/104] Preserve concurrent scanner hardening The remote branch added line-comment discrimination for for-await detection while the local branch broadened declaration names. Both close independent scanner false positives and are retained without rewriting shared history. Constraint: Remote head advanced after the local Unicode fix was committed. Rejected: Rebase or force update | shared branch history must remain non-destructive. Confidence: high Scope-risk: narrow Tested: Each parent passed its focused red-green scanner suite before integration. Not-tested: Combined focused and full suites after merge. --- .../utils/source-spans.test.ts | 21 ++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 22 +++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 9411f5e7cd..714f29868a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -511,6 +511,27 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after Unicode declaration names", () => { + assertEquals( + specifiers('function λ() {}\n/import("\\.\\/fake-function.js")/.test(value);'), + [], + ); + assertEquals( + specifiers('class Ω {}\n/import("\\.\\/fake-class.js")/.test(value);'), + [], + ); + assertEquals( + specifiers( + 'function \\u0061() {}\n/import("\\.\\/fake-escaped-function.js")/.test(value);', + ), + [], + ); + assertEquals( + specifiers('class \\u{41} {}\n/import("\\.\\/fake-escaped-class.js")/.test(value);'), + [], + ); + }); + it("recognizes Unicode line terminators in declaration comments", () => { for (const lineTerminator of ["\u2028", "\u2029"]) { assertEquals( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index e4f35f022e..297c413da2 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -37,6 +37,19 @@ interface OpenBraceContext { const MAX_TEMPLATE_LITERAL_DEPTH = 512; const StringFromCodePoint = String.fromCodePoint; const IDENTIFIER_PART_PATTERN = /^[$_\p{ID_Continue}\u200C\u200D]$/u; +const IDENTIFIER_ESCAPE_SOURCE = String.raw`\\u(?:[0-9A-Fa-f]{4}|\{[0-9A-Fa-f]+\})`; +const IDENTIFIER_NAME_SOURCE = String + .raw`(?:[$_\p{ID_Start}]|${IDENTIFIER_ESCAPE_SOURCE})(?:[$\p{ID_Continue}\u200C\u200D]|${IDENTIFIER_ESCAPE_SOURCE})*`; +const FUNCTION_DECLARATION_BLOCK_PREFIX_PATTERN = new RegExp( + String + .raw`^(?:export\s+(?:default\s+)?)?(?:async\s+)?function(?:\s*\*)?(?:\s+${IDENTIFIER_NAME_SOURCE})?\s*\(`, + "u", +); +const CLASS_DECLARATION_BLOCK_PREFIX_PATTERN = new RegExp( + String + .raw`^(?:export\s+(?:default\s+)?)?class(?:\s+${IDENTIFIER_NAME_SOURCE})?(?:\s+extends\s+[\s\S]+)?\s*$`, + "u", +); function assertTemplateLiteralDepth(depth: number): void { if (depth > MAX_TEMPLATE_LITERAL_DEPTH) { @@ -550,13 +563,8 @@ function isDeclarationBlockCloseBrace( /\/\*[\s\S]*?\*\/|\/\/[^\r\n\u2028\u2029]*/g, " ", ); - return /^(?:export\s+(?:default\s+)?)?(?:async\s+)?function(?:\s*\*)?(?:\s+[$A-Za-z_][$\w]*)?\s*\(/ - .test( - prefix, - ) || - /^(?:export\s+(?:default\s+)?)?class(?:\s+[$A-Za-z_][$\w]*)?(?:\s+extends\s+[\s\S]+)?\s*$/.test( - prefix, - ); + return FUNCTION_DECLARATION_BLOCK_PREFIX_PATTERN.test(prefix) || + CLASS_DECLARATION_BLOCK_PREFIX_PATTERN.test(prefix); } function isStatementBlockCloseBrace( From 6d99c28ffccab452ed79e195963a5a1c0c0c934c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 16:35:14 +0200 Subject: [PATCH 086/104] Keep optional module failures lazy on every fetch path The source scanner now recognizes export lists as completed statements at ASI boundaries, and the HTTP fallback reuses the same sanitized dynamic dependency classification as direct module resolution. Constraint: Static imports and infrastructure failures must remain fail-fast Rejected: Catch every dynamic child error | would hide infrastructure and programming failures Confidence: high Scope-risk: narrow Directive: Keep direct and HTTP fallback dynamic deferral classifications identical Tested: Focused scanner, resolver, and HTTP fallback suites; formatting, lint, typecheck, and diff checks Not-tested: Browser execution through a live local development server --- .../module-fetcher/http-fetcher.test.ts | 41 +++++++++++++++++++ .../module-fetcher/http-fetcher.ts | 17 +++++--- .../module-fetcher/nested-imports.test.ts | 13 ++++++ .../module-fetcher/nested-imports.ts | 3 +- .../utils/source-spans.test.ts | 15 +++++++ .../esm-module-loader/utils/source-spans.ts | 27 +++++++++++- 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts index 5ae2ff21c7..f421bd2846 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts @@ -235,6 +235,47 @@ describe("module-fetcher/http-fetcher", () => { } }); + it("defers a typed dynamic child failure fetched through the HTTP fallback", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-http-dynamic-failure-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./oversized.js") : Promise.resolve("skipped");`; + + try { + const result = await fetchModuleViaHTTP( + "_vf_modules/pages/index.js", + { env: { get: () => undefined } } as unknown as RuntimeAdapter, + () => { + const error = new Error("private source detail"); + error.name = "ModuleSourceLimitError"; + throw error; + }, + { debug: () => {}, warn: () => {} } as unknown as Logger, + "docs", + true, + undefined, + { + esmCacheDir, + fetchFn: (() => Promise.resolve(new Response(source))) as typeof fetch, + strictMissingModules: true, + }, + ); + const parentPath = join(esmCacheDir, "http-typed-failure-parent.mjs"); + await Deno.writeTextFile(parentPath, result!); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + await assertRejects( + () => loaded.load(true), + Error, + "Dynamic import failed for ./oversized.js: module source exceeds the allowed size", + ); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + // A single-quoted specifier may legally contain a double quote, and a cache // path may contain a backslash. Interpolating either into a hand-written // double-quoted literal emits a module that fails to parse, which takes down diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts index f0be72446d..0f328c366c 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.ts @@ -13,7 +13,11 @@ import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { LOG_PREFIX_MDX_LOADER } from "../constants.ts"; import { rewriteVeryfrontImports } from "./import-rewriter.ts"; -import { findNestedImports, toImportStringLiteral } from "./nested-imports.ts"; +import { + dynamicDependencyFailure, + findNestedImports, + toImportStringLiteral, +} from "./nested-imports.ts"; import { replaceSourceSpans, type SourceSpanReplacement } from "../utils/source-spans.ts"; import { HTTP_FETCH_TIMEOUT_MS } from "#veryfront/utils/constants/http.ts"; import { readHttpModuleText } from "../../../shared/http-module-response.ts"; @@ -22,8 +26,7 @@ import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/constants/limits.ts"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { Semaphore } from "#veryfront/modules/react-loader/ssr-module-loader/concurrency/semaphore.ts"; import { assertMdxModuleImportCount, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY } from "./limits.ts"; -import { createStubModule } from "../utils/stub-module.ts"; -import { isMdxMissingModuleError } from "../missing-module.ts"; +import { createStubModule, type DeferredImportErrorDescriptor } from "../utils/stub-module.ts"; export interface FetchModuleViaHttpOptions { esmCacheDir?: string; @@ -268,10 +271,14 @@ export async function fetchModuleViaHTTP( allImports, async ({ original, path, suffix, start, end, isDynamic, isSideEffect, key }) => { let nestedFilePath: string | null; + let deferredError: DeferredImportErrorDescriptor | undefined; try { nestedFilePath = await fetchAndCacheModuleFn(path, normalizedPath); } catch (error) { - if (!isDynamic || !options.esmCacheDir || !isMdxMissingModuleError(error)) throw error; + deferredError = isDynamic + ? dynamicDependencyFailure(path, error) ?? undefined + : undefined; + if (!deferredError || !options.esmCacheDir) throw error; nestedFilePath = null; } @@ -281,7 +288,7 @@ export async function fetchModuleViaHTTP( moduleCode, original, options.esmCacheDir, - { failOnImport: options.strictMissingModules ?? true }, + { failOnImport: options.strictMissingModules ?? true, deferredError }, ); } diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 75ee6296b9..347d9b52b7 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -80,6 +80,19 @@ import { bar } from "./local.js"; assertEquals(result.vfModules.map((module) => module.path), []); assertEquals(result.relative.map((module) => module.path), []); }); + + it("does not resolve import-looking regex text after an export list", () => { + const code = [ + `const value = 1;`, + `export { value }`, + `/import("\\.\\/optional.js")/.test(input);`, + ].join("\n"); + + const result = findNestedImports(code); + + assertEquals(result.vfModules, []); + assertEquals(result.relative, []); + }); }); describe("hasUnresolvedImports", () => { diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index 9e5ac33c27..ab70313a8a 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -48,7 +48,8 @@ function isMalformedSpecifierSyntaxError(error: unknown): boolean { return error instanceof SyntaxError && error.message.includes("module specifier"); } -function dynamicDependencyFailure( +/** Return the sanitized runtime failure for a dependency error that can stay lazy. */ +export function dynamicDependencyFailure( modulePath: string, error: unknown, ): DeferredImportErrorDescriptor | null { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 714f29868a..5c57bc2263 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -511,6 +511,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-looking regex text after export lists at ASI boundaries", () => { + assertEquals( + specifiers( + 'const value = 1; export { value }\n/import("\\.\\/fake-export-list.js")/.test(input);', + ), + [], + ); + assertEquals( + vfModuleSpecifiers( + 'const value = 1; export { value }\n/import("\\/_vf_modules\\/fake-export-list.js")/.test(input);', + ), + [], + ); + }); + it("ignores import-looking regex text after Unicode declaration names", () => { assertEquals( specifiers('function λ() {}\n/import("\\.\\/fake-function.js")/.test(value);'), diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 297c413da2..e08dcafbe9 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -614,12 +614,31 @@ function isArrowFunctionBodyCloseBraceAtAsiBoundary( const beforeArrow = previousSignificantIndex(source, openBrace.previousTokenIndex); if (source[beforeArrow] !== "=") return false; - for (let cursor = index + 1; cursor < nextTokenIndex; cursor++) { + return hasLineTerminator(source, index + 1, nextTokenIndex); +} + +function hasLineTerminator(source: string, start: number, end: number): boolean { + for (let cursor = start; cursor < end; cursor++) { if (isLineTerminator(source[cursor]!)) return true; } return false; } +function isExportListCloseBraceAtAsiBoundary( + source: string, + index: number, + nextTokenIndex: number, + matchingOpenBraces: ReadonlyMap, +): boolean { + const openBrace = matchingOpenBraces.get(index); + if ( + openBrace === undefined || + keywordBefore(source, openBrace.index, openBrace.previousTokenIndex) !== "export" + ) return false; + + return hasLineTerminator(source, index + 1, nextTokenIndex); +} + function isForOfKeywordBefore( source: string, rangeStart: number, @@ -681,6 +700,12 @@ function canStartRegexLiteral( previous, index, matchingOpenBraces, + ) || + isExportListCloseBraceAtAsiBoundary( + source, + previous, + index, + matchingOpenBraces, )) ) return true; if ( From 498c067c5bf99c999f09c72401f4b264a9776ed5 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 16:55:52 +0200 Subject: [PATCH 087/104] Keep keyword-named properties from swallowing tenant imports Reserved words are valid member names. Treating the slash after mod.default or metrics.in as a regex opener hides every later dynamic import from module materialization. A single member-name gate now covers direct, optional-chain, and private-field access while preserving genuine keyword regex positions. Constraint: PR 3723 can merge independently of the sibling scanner branch. Rejected: Wait for PR 3721 to merge first | both branches are independently mergeable and currently carry scanner changes. Confidence: high Scope-risk: narrow Directive: Keep the keyword matrix synchronized with regex-prefix classification. Tested: source-spans red-green matrix; nested-imports and HTTP fallback suites; fmt, lint, typecheck, diff-check. --- .../utils/source-spans.test.ts | 87 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 18 ++++ 2 files changed, 105 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 5c57bc2263..fa09c3db5a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -613,6 +613,93 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("finds the tenant alias import after division by a default property", () => { + const spans = findDynamicImportSpans( + 'const half = mod.default / 2; const L = lazy(() => import("@/components/Chart"));', + (specifier) => specifier.startsWith("@/") ? specifier : null, + UNBOUNDED, + ); + + assertEquals(spans.map((span) => span.path), ["@/components/Chart"]); + }); + + const REGEX_PREFIX_KEYWORDS = [ + "of", + "case", + "default", + "delete", + "do", + "else", + "extends", + "in", + "instanceof", + "new", + "await", + "break", + "continue", + "debugger", + "return", + "throw", + "typeof", + "void", + "yield", + ]; + + for (const keyword of REGEX_PREFIX_KEYWORDS) { + it(`divides after a \`.${keyword}\` property instead of opening a regex`, () => { + assertEquals( + specifiers( + `const ratio = metrics.${keyword} / 2; import("./after-${keyword}-property.js");`, + ), + [`./after-${keyword}-property.js`], + ); + }); + + it(`divides after an optionally chained \`?.${keyword}\` property`, () => { + assertEquals( + specifiers( + `const ratio = metrics?.${keyword} / 2; import("./after-${keyword}-optional.js");`, + ), + [`./after-${keyword}-optional.js`], + ); + }); + + it(`divides after a \`#${keyword}\` private field`, () => { + assertEquals( + specifiers( + `class C { #${keyword} = 1; m() { const r = this.#${keyword} / 2; ` + + `return import("./after-${keyword}-private.js"); } }`, + ), + [`./after-${keyword}-private.js`], + ); + }); + } + + it("still treats genuine keyword positions as regex prefixes", () => { + assertEquals( + specifiers('const t = typeof /re/; import("./after-typeof-keyword.js");'), + ["./after-typeof-keyword.js"], + ); + assertEquals( + specifiers( + 'function f() { return /re/.test(x); } import("./after-return-keyword.js");', + ), + ["./after-return-keyword.js"], + ); + assertEquals( + specifiers( + 'switch (v) { case /re/.source: break; } import("./after-case-keyword.js");', + ), + ["./after-case-keyword.js"], + ); + assertEquals( + specifiers( + 'for (const x of /re/.exec(s) ?? []) {} import("./after-for-of-regex.js");', + ), + ["./after-for-of-regex.js"], + ); + }); + // The for-await search runs over raw text, so it also finds a `for` that is // not code. A block comment cannot fool it because the `*/` terminator // stops the adjacency scan. A line comment ends at a newline, which the scan diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index e08dcafbe9..1d4d62528a 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -440,6 +440,23 @@ function keywordBefore( return source.slice(start, end); } +/** Whether the word before a slash is a member name rather than a keyword. */ +function isMemberNameBefore( + source: string, + previousTokenIndex: number, +): boolean { + const end = previousTokenIndex + 1; + let start = end; + while (start > 0 && /[A-Za-z_$]/.test(source[start - 1] ?? "")) start--; + if (start === end) return false; + + const before = previousSignificantIndex(source, start); + if (before < 0) return false; + + const char = source[before]; + return char === "." || char === "#"; +} + /** * Whether `index` sits inside a `//` line comment. * @@ -726,6 +743,7 @@ function canStartRegexLiteral( if (char !== undefined && "([{=,:;!~?&|+-*%^<>".includes(char)) return true; const keyword = keywordBefore(source, index, previous); + if (keyword !== null && isMemberNameBefore(source, previous)) return false; if (keyword === "of") { return isForOfKeywordBefore(source, rangeStart, currentParen, previous); } From 5b670404660637648745ced6b8ec0e34b6040031 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 17:10:42 +0200 Subject: [PATCH 088/104] Keep display text and regexes out of module metadata The module scanners consumed import and export-shaped text that was not executable syntax. Side-effect imports now require a declaration terminator, and cycle alias export detection skips regex literals while retaining division and control-statement behavior. Constraint: Preserve the synchronous persistence interface and avoid new parser dependencies Rejected: Match raw export text with another regular expression | it cannot distinguish executable syntax from regex contents Confidence: high Scope-risk: narrow Directive: Keep source scanners conservative when syntax is ambiguous Tested: focused module loader and source span suites, 4 tests and 187 steps Tested: targeted format, lint, typecheck, and diff checks Not-tested: full repository pre-push gate before this commit --- .../module-loader/dependency-resolver.test.ts | 29 ++++ .../module-loader/module-persistence.test.ts | 40 ++++++ .../module-loader/module-persistence.ts | 130 +++++++++++++++++- .../esm-module-loader/utils/source-spans.ts | 33 +++++ 4 files changed, 229 insertions(+), 3 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts index e3a9a3611e..165a0e2230 100644 --- a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts +++ b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts @@ -187,6 +187,35 @@ describe("module-loader/dependency-resolver", () => { ); }); + it("does not resolve import-looking JSX display text", async () => { + await withDependencyFixture( + { + "app/page.tsx": [ + `const label = "Example: ";`, + `export default function Page() {`, + ` return {label}import "./example";`, + `}`, + ].join("\n"), + "app/example.ts": `export const example = true;`, + }, + async ({ projectDir }) => { + const adapter = await getLocalAdapter(); + const filePath = join(projectDir, "app/page.tsx"); + const fileContent = await Deno.readTextFile(filePath); + + const deps = await resolveModuleDependencies({ + adapter, + fileContent, + filePath, + projectDir, + }); + + assertEquals(deps, []); + assertEquals(rewriteResolvedDependencyImports(fileContent, deps), fileContent); + }, + ); + }); + it("resolves alias and relative imports while ignoring already transformed file imports", async () => { await withDependencyFixture( { diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index a2be3ba3cf..68d6090dbe 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -167,6 +167,41 @@ describe("module-loader/module-persistence", () => { } }); + it("does not infer a default cycle alias from regex contents", async () => { + const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); + const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); + const localAdapter = await getLocalAdapter(); + + const cases = [ + `export const pattern = /export default/;`, + `if (enabled) /export default/.test(source); export const value = 1;`, + `function read() { return /* keep the comment */ /export default/.source; }`, + `function read() { return // keep the comment\n/export default/.source; }`, + ] as const; + + try { + for (const [index, transformedCode] of cases.entries()) { + const filePath = join(projectDir, `app/page-${index}.ts`); + const result = await persistTransformedModule({ + filePath, + projectDir, + tmpDir, + transformedCode, + localAdapter, + moduleCache: new Map(), + cacheKey: `regex-default-${index}`, + isCycleTarget: true, + }); + + const aliasCode = await Deno.readTextFile(join(dirname(result), `page-${index}.js`)); + assertEquals(aliasCode, `export * from "./${basename(result)}";`); + } + } finally { + await Deno.remove(projectDir, { recursive: true }).catch(() => undefined); + await Deno.remove(tmpDir, { recursive: true }).catch(() => undefined); + } + }); + it("writes default cycle aliases only when an export exposes default", async () => { const projectDir = await Deno.makeTempDir({ prefix: "vf-module-persist-project-" }); const tmpDir = await Deno.makeTempDir({ prefix: "vf-module-persist-out-" }); @@ -179,6 +214,11 @@ describe("module-loader/module-persistence", () => { transformedCode: `export default function Page() { return null; }`, exposesDefault: true, }, + { + path: "app/division-before-default.ts", + transformedCode: `const ratio = total / count; export default ratio;`, + exposesDefault: true, + }, { path: "app/named-as-default.ts", transformedCode: `const Page = () => null;\nexport { Page as default };`, diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index f361b24e95..346229717a 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -117,6 +117,10 @@ export async function readPersistedUnresolvedSpecifiers( * `export { default } from …` forms. */ function hasDefaultExport(code: string): boolean { + let previousTokenIndex = -1; + const controlConditionCloseParens = new Set(); + const openParens: boolean[] = []; + for (let index = 0; index < code.length;) { index = skipTrivia(code, index); if (index >= code.length) break; @@ -137,8 +141,35 @@ function hasDefaultExport(code: string): boolean { continue; } - const next = skipTextToken(code, index); - index = next === index ? index + 1 : next; + const next = skipTextToken(code, index, { + previousTokenIndex, + controlConditionCloseParens, + }); + if (next !== index) { + previousTokenIndex = next - 1; + index = next; + continue; + } + + if (isIdentifierStart(code[index])) { + index++; + while (index < code.length && isIdentifierPart(code[index])) index++; + previousTokenIndex = index - 1; + continue; + } + + if (code[index] === "(") { + const keyword = identifierBefore(code, previousTokenIndex); + openParens.push( + keyword === "if" || keyword === "while" || keyword === "for" || + keyword === "with" || keyword === "switch" || keyword === "catch", + ); + } else if (code[index] === ")" && openParens.pop() === true) { + controlConditionCloseParens.add(index); + } + + previousTokenIndex = index; + index++; } return false; @@ -238,10 +269,22 @@ function skipTrivia(source: string, index: number): number { return index; } -function skipTextToken(source: string, index: number): number { +interface RegexScanContext { + previousTokenIndex: number; + controlConditionCloseParens: ReadonlySet; +} + +function skipTextToken( + source: string, + index: number, + context?: RegexScanContext, +): number { const commentEnd = skipComment(source, index); if (commentEnd !== index) return commentEnd; + const regexEnd = skipRegexToken(source, index, context); + if (regexEnd !== index) return regexEnd; + const char = source[index]; if (char !== '"' && char !== "'" && char !== "`") return index; @@ -256,6 +299,87 @@ function skipTextToken(source: string, index: number): number { return source.length; } +function skipRegexToken( + source: string, + index: number, + context?: RegexScanContext, +): number { + if (source[index] !== "/" || source[index + 1] === "/" || source[index + 1] === "*") { + return index; + } + + const previous = context?.previousTokenIndex ?? previousSignificantIndex(source, index); + if (previous >= 0) { + const char = source[previous]!; + if (char === ")" && context?.controlConditionCloseParens.has(previous)) { + // A statement can start with a regex immediately after a control + // condition, for example `if (ready) /pattern/.test(value)`. + } else if (!"([{=,:;!~?&|+-*%^<>".includes(char)) { + const keyword = identifierBefore(source, previous); + if ( + ![ + "case", + "delete", + "do", + "else", + "extends", + "in", + "instanceof", + "new", + "await", + "return", + "throw", + "typeof", + "void", + "yield", + ].includes(keyword ?? "") + ) return index; + } + } + + let cursor = index + 1; + let inCharacterClass = false; + while (cursor < source.length) { + const char = source[cursor]!; + if (char === "\\") { + cursor += 2; + continue; + } + if (char === "[" && !inCharacterClass) { + inCharacterClass = true; + cursor++; + continue; + } + if (char === "]" && inCharacterClass) { + inCharacterClass = false; + cursor++; + continue; + } + if (char === "/" && !inCharacterClass) { + cursor++; + while (isIdentifierPart(source[cursor])) cursor++; + return cursor; + } + if (char === "\n" || char === "\r") return index; + cursor++; + } + + return index; +} + +function previousSignificantIndex(source: string, index: number): number { + let cursor = index - 1; + while (cursor >= 0 && /\s/.test(source[cursor] ?? "")) cursor--; + return cursor; +} + +function identifierBefore(source: string, endIndex: number): string | null { + const end = endIndex + 1; + let start = end; + while (start > 0 && isIdentifierPart(source[start - 1])) start--; + return start === end ? null : source.slice(start, end); +} + function skipComment(source: string, index: number): number { if (source[index] !== "/" || index + 1 >= source.length) return index; if (source[index + 1] === "/") { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 1d4d62528a..00fcb0aeea 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -207,6 +207,32 @@ function nextStatementCursor(source: string, index: number): number { return source.length; } +function isSideEffectImportTerminated(source: string, index: number): boolean { + let cursor = index; + + while (cursor < source.length) { + const char = source[cursor]!; + if (isLineTerminator(char)) return true; + if (char === " " || char === "\t" || char === "\f") { + cursor++; + continue; + } + if (char === ";") return true; + if (char === "/" && source[cursor + 1] === "/") return true; + if (char === "/" && source[cursor + 1] === "*") { + const commentEnd = skipIgnored(source, cursor); + if (containsLineTerminator(source, cursor, commentEnd)) return true; + cursor = commentEnd; + continue; + } + // Import attributes are part of the same declaration and follow the + // specifier before its terminator. + return source.startsWith("with", cursor) || source.startsWith("assert", cursor); + } + + return true; +} + function hexDigitValue(char: string | undefined): number { if (char === undefined) return -1; const code = char.charCodeAt(0); @@ -1491,6 +1517,13 @@ export function findStaticSideEffectImportSpans( continue; } + if (!isSideEffectImportTerminated(source, literal.end)) { + atStatementStart = false; + previousTokenIndex = literal.end - 1; + cursor = literal.end; + continue; + } + const matchedPath = matcher(literal.specifier); if (matchedPath) { spans.push({ From 81b5efaa13f41b09977afbee499407a906a65d2d Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 17:17:40 +0200 Subject: [PATCH 089/104] Keep reserved member names distinct from regex prefixes The source scanner now walks backward across legal block-comment trivia before recognizing member access and excludes ellipsis dots from that classification. This preserves division after reserved property names without turning spread-prefixed keyword regexes into dependencies. Constraint: Keep the scanner dependency-free and within its existing bounded runtime Rejected: Make every dot a member operator | the final ellipsis dot precedes an expression, not a property name Confidence: high Scope-risk: narrow Directive: Preserve the long-input runtime regressions when changing backward token inspection Tested: source-span suite, 162 steps including direct and optional-chain comment cases, spread regex, and bounded scans Tested: targeted format, lint, typecheck, and diff checks Not-tested: full repository pre-push gate before this commit --- .../utils/source-spans.test.ts | 21 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 19 +++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index fa09c3db5a..4fd376405e 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -623,6 +623,21 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { assertEquals(spans.map((span) => span.path), ["@/components/Chart"]); }); + it("finds imports after commented reserved-name member divisions", () => { + assertEquals( + specifiers( + 'const direct = mod./* note */default / 2; import("./after-direct.js");', + ), + ["./after-direct.js"], + ); + assertEquals( + specifiers( + 'const optional = mod?./* note */default / 2; import("./after-optional.js");', + ), + ["./after-optional.js"], + ); + }); + const REGEX_PREFIX_KEYWORDS = [ "of", "case", @@ -686,6 +701,12 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ), ["./after-return-keyword.js"], ); + assertEquals( + specifiers( + '[...typeof /import(".\\/fake-spread.js")/]; import("./after-spread.js");', + ), + ["./after-spread.js"], + ); assertEquals( specifiers( 'switch (v) { case /re/.source: break; } import("./after-case-keyword.js");', diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 00fcb0aeea..0c4555b478 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -454,6 +454,18 @@ function previousSignificantIndex(source: string, index: number): number { return cursor; } +function previousSignificantIndexAcrossComments(source: string, index: number): number { + let cursor = previousSignificantIndex(source, index); + + while (cursor >= 1 && source[cursor] === "/" && source[cursor - 1] === "*") { + const commentStart = source.lastIndexOf("/*", cursor - 1); + if (commentStart < 0) break; + cursor = previousSignificantIndex(source, commentStart); + } + + return cursor; +} + function keywordBefore( source: string, index: number, @@ -476,11 +488,14 @@ function isMemberNameBefore( while (start > 0 && /[A-Za-z_$]/.test(source[start - 1] ?? "")) start--; if (start === end) return false; - const before = previousSignificantIndex(source, start); + const before = previousSignificantIndexAcrossComments(source, start); if (before < 0) return false; const char = source[before]; - return char === "." || char === "#"; + if (char === "#") return true; + if (char !== ".") return false; + + return source[before - 1] !== "." || source[before - 2] !== "."; } /** From 9f970a32a5a5862c5194f28aa36b6536f2acfd39 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 17:41:46 +0200 Subject: [PATCH 090/104] Keep source-like text from becoming executable dependencies The bounded scanners now carry JSX text context, cross line-comment trivia for keyword-named members, and share the member-access discriminator with default-export detection. This keeps rendered examples inert without losing imports that follow TypeScript assertions or reserved-name division. Constraint: Preserve synchronous bounded scanning without adding a parser dependency Rejected: Treat any semicolon-terminated import-shaped text as code | JSX children can contain the same display text Confidence: high Scope-risk: moderate Directive: Keep JSX text, TypeScript assertion, and bounded-runtime regressions together when changing source scanning Tested: Focused scanner, dependency resolver, cycle persistence, and source-import suites; full pinned pre-push Not-tested: Manual runtime rendering in a browser --- .../module-loader/module-persistence.test.ts | 5 + .../module-loader/module-persistence.ts | 2 + .../utils/source-spans.test.ts | 34 ++++ .../esm-module-loader/utils/source-spans.ts | 175 +++++++++++++++++- 4 files changed, 212 insertions(+), 4 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 68d6090dbe..6d80f857e7 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -219,6 +219,11 @@ describe("module-loader/module-persistence", () => { transformedCode: `const ratio = total / count; export default ratio;`, exposesDefault: true, }, + { + path: "app/member-keyword-division-before-default.ts", + transformedCode: `const ratio = mod.typeof / 2; export { default } from "./component.js";`, + exposesDefault: true, + }, { path: "app/named-as-default.ts", transformedCode: `const Page = () => null;\nexport { Page as default };`, diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 346229717a..c0ce2063fb 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -18,6 +18,7 @@ import { buildMdxEsmPathCacheKey, UNRESOLVED_IMPORTS_SIDECAR_SUFFIX, } from "#veryfront/transforms/mdx/esm-module-loader/cache-format.ts"; +import { isMemberNameBefore } from "#veryfront/transforms/mdx/esm-module-loader/utils/source-spans.ts"; import { buildModuleTransformCacheVariant } from "./module-cache-lookup.ts"; const logger = rendererLogger.component("module-loader"); @@ -316,6 +317,7 @@ function skipRegexToken( // condition, for example `if (ready) /pattern/.test(value)`. } else if (!"([{=,:;!~?&|+-*%^<>".includes(char)) { const keyword = identifierBefore(source, previous); + if (keyword !== null && isMemberNameBefore(source, previous)) return index; if ( ![ "case", diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 4fd376405e..4798e1dd45 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -636,6 +636,18 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ), ["./after-optional.js"], ); + assertEquals( + specifiers( + 'const direct = mod.// note\ntypeof / 2; import("./after-line-direct.js");', + ), + ["./after-line-direct.js"], + ); + assertEquals( + specifiers( + 'const optional = mod?.// note\ntypeof / 2; import("./after-line-optional.js");', + ), + ["./after-line-optional.js"], + ); }); const REGEX_PREFIX_KEYWORDS = [ @@ -1230,6 +1242,28 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores semicolon-terminated side-effect import text in JSX children", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'export function Example() { return {label}import "./example.js";; } import "./real.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./real.js"], + ); + }); + + it("keeps scanning after a TypeScript angle-bracket assertion", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'const value = ""; import "./after-assertion.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-assertion.js"], + ); + }); + it("ignores side-effect import text in regex literals after comments", () => { assertEquals( findStaticSideEffectImportSpans( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 0c4555b478..f52c27190b 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -233,6 +233,90 @@ function isSideEffectImportTerminated(source: string, index: number): boolean { return true; } +interface JsxTagEnd { + end: number; + name: string; + selfClosing: boolean; +} + +function skipJsxTag(source: string, index: number): JsxTagEnd | null { + let nameStart = index + 1; + if (source[nameStart] === "/") nameStart++; + let nameEnd = nameStart; + while (/[A-Za-z0-9_$.-]/.test(source[nameEnd] ?? "")) nameEnd++; + + let cursor = index + 1; + let expressionDepth = 0; + + while (cursor < source.length) { + const char = source[cursor]!; + if (char === '"' || char === "'") { + cursor = skipIgnored(source, cursor); + continue; + } + if (char === "`") { + cursor = skipFullTemplateLiteral(source, cursor); + continue; + } + if (char === "{") { + expressionDepth++; + cursor++; + continue; + } + if (char === "}" && expressionDepth > 0) { + expressionDepth--; + cursor++; + continue; + } + if (char === ">" && expressionDepth === 0) { + const before = previousSignificantIndex(source, cursor); + return { + end: cursor + 1, + name: source.slice(nameStart, nameEnd), + selfClosing: source[before] === "/", + }; + } + cursor++; + } + + return null; +} + +function hasClosingJsxTag(source: string, index: number, name: string): boolean { + const prefix = name === "" ? "" : `]/.test(source[cursor + prefix.length] ?? "")) + ) return true; + cursor++; + } + + return false; +} + +function canStartJsxElement( + source: string, + index: number, + previousTokenIndex: number, +): boolean { + const next = source[index + 1]; + if (next !== ">" && !/[A-Za-z_$]/.test(next ?? "")) return false; + if (previousTokenIndex < 0) return true; + + const previous = source[previousTokenIndex]!; + if ("([{=,:;!?&|+-*%^<>".includes(previous)) return true; + + const keyword = keywordBefore(source, index, previousTokenIndex); + return keyword === "case" || keyword === "default" || keyword === "return" || + keyword === "yield"; +} + function hexDigitValue(char: string | undefined): number { if (char === undefined) return -1; const code = char.charCodeAt(0); @@ -455,17 +539,50 @@ function previousSignificantIndex(source: string, index: number): number { } function previousSignificantIndexAcrossComments(source: string, index: number): number { + let scanEnd = index; let cursor = previousSignificantIndex(source, index); - while (cursor >= 1 && source[cursor] === "/" && source[cursor - 1] === "*") { - const commentStart = source.lastIndexOf("/*", cursor - 1); - if (commentStart < 0) break; + while (cursor >= 0) { + if (cursor >= 1 && source[cursor] === "/" && source[cursor - 1] === "*") { + const commentStart = source.lastIndexOf("/*", cursor - 1); + if (commentStart < 0) break; + scanEnd = commentStart; + cursor = previousSignificantIndex(source, commentStart); + continue; + } + + if (!containsLineTerminator(source, cursor + 1, scanEnd)) break; + const commentStart = lineCommentStart(source, cursor); + if (commentStart === null) break; + scanEnd = commentStart; cursor = previousSignificantIndex(source, commentStart); } return cursor; } +function lineCommentStart(source: string, index: number): number | null { + let cursor = index; + while (cursor > 0 && !isLineTerminator(source[cursor - 1] ?? "")) cursor--; + + let quote: string | null = null; + for (; cursor <= index; cursor++) { + const char = source[cursor]!; + if (quote !== null) { + if (char === "\\") cursor++; + else if (char === quote) quote = null; + continue; + } + if (char === '"' || char === "'" || char === "`") { + quote = char; + continue; + } + if (char === "/" && source[cursor + 1] === "/") return cursor; + } + + return null; +} + function keywordBefore( source: string, index: number, @@ -479,7 +596,7 @@ function keywordBefore( } /** Whether the word before a slash is a member name rather than a keyword. */ -function isMemberNameBefore( +export function isMemberNameBefore( source: string, previousTokenIndex: number, ): boolean { @@ -1444,9 +1561,49 @@ export function findStaticSideEffectImportSpans( const openParens: OpenParenContext[] = []; const matchingOpenParens = new Map(); let previousTokenIndex = -1; + let jsxDepth = 0; + let inJsxText = false; + const jsxExpressionStack: Array<{ braceDepth: number; parentDepth: number }> = []; while (cursor < source.length) { const char = source[cursor]; + + if (inJsxText) { + if (char === "<") { + const tag = skipJsxTag(source, cursor); + if (tag !== null) { + const closing = source[cursor + 1] === "/"; + if (closing) jsxDepth = Math.max(0, jsxDepth - 1); + else if (!tag.selfClosing) jsxDepth++; + const expressionParentDepth = jsxExpressionStack.at(-1)?.parentDepth ?? 0; + inJsxText = jsxDepth > expressionParentDepth; + atStatementStart = false; + previousTokenIndex = tag.end - 1; + cursor = tag.end; + continue; + } + } + if (char === "{") { + jsxExpressionStack.push({ braceDepth: 0, parentDepth: jsxDepth }); + inJsxText = false; + } else { + cursor++; + continue; + } + } else if (char === "<" && canStartJsxElement(source, cursor, previousTokenIndex)) { + const tag = skipJsxTag(source, cursor); + if (tag !== null && (tag.selfClosing || hasClosingJsxTag(source, tag.end, tag.name))) { + if (!tag.selfClosing) { + jsxDepth++; + inJsxText = true; + } + atStatementStart = false; + previousTokenIndex = tag.end - 1; + cursor = tag.end; + continue; + } + } + const skipped = skipExpressionIgnored( source, cursor, @@ -1473,6 +1630,8 @@ export function findStaticSideEffectImportSpans( } if (char === "{") { + const expression = jsxExpressionStack.at(-1); + if (expression !== undefined) expression.braceDepth++; openBraces.push({ index: cursor, previousTokenIndex }); atStatementStart = false; previousTokenIndex = cursor; @@ -1480,6 +1639,14 @@ export function findStaticSideEffectImportSpans( continue; } if (char === "}") { + const expression = jsxExpressionStack.at(-1); + if (expression !== undefined) { + expression.braceDepth--; + if (expression.braceDepth === 0) { + jsxExpressionStack.pop(); + inJsxText = jsxDepth > 0; + } + } const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); atStatementStart = true; From e0c12efc8b06145ca9181aea80873100cf97c168 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 17:59:50 +0200 Subject: [PATCH 091/104] Keep source-like JSX syntax out of dependency resolution Namespaced JSX closing tags were scanned with only the prefix before the colon, while a regex after a TypeScript angle assertion could impersonate a closing tag. The scanner now accepts the complete namespaced tag name and rejects only lexically complete regex lookalikes with valid flags and expression continuations. Constraint: The bounded source scanner must remain parser-free and preserve existing JSX text behavior. Rejected: Treat every matching closing-tag prefix as JSX | regex syntax can then hide executable imports after TypeScript assertions. Confidence: high Scope-risk: narrow Directive: Keep JSX-name and closing-tag lookahead regressions paired when extending source syntax. Tested: focused source-spans red-green; five related suites 193 steps; fmt; lint; check; dependency boundaries; diff check; full pre-push 3845 tests and 28811 steps plus cwd suites. Not-tested: Full JavaScript and JSX grammar parsing beyond the bounded scanner contract. --- .../utils/source-spans.test.ts | 22 +++++++++++++ .../esm-module-loader/utils/source-spans.ts | 32 +++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 4798e1dd45..1843972580 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -1253,6 +1253,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores side-effect import text in namespaced JSX children", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'export function Example() { return {label}import "./example.js";; } import "./real.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./real.js"], + ); + }); + it("keeps scanning after a TypeScript angle-bracket assertion", () => { assertEquals( findStaticSideEffectImportSpans( @@ -1264,6 +1275,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("does not treat regex syntax as an assertion closing JSX tag", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'const value = thing; import "./after-assertion.js"; const ok = x foo/.test(source);', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-assertion.js"], + ); + }); + it("ignores side-effect import text in regex literals after comments", () => { assertEquals( findStaticSideEffectImportSpans( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index f52c27190b..21c6d89ee4 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -243,7 +243,7 @@ function skipJsxTag(source: string, index: number): JsxTagEnd | null { let nameStart = index + 1; if (source[nameStart] === "/") nameStart++; let nameEnd = nameStart; - while (/[A-Za-z0-9_$.-]/.test(source[nameEnd] ?? "")) nameEnd++; + while (/[A-Za-z0-9_$.:.-]/.test(source[nameEnd] ?? "")) nameEnd++; let cursor = index + 1; let expressionDepth = 0; @@ -293,13 +293,41 @@ function hasClosingJsxTag(source: string, index: number, name: string): boolean if ( source.startsWith(prefix, cursor) && (name === "" || /[\s>]/.test(source[cursor + prefix.length] ?? "")) - ) return true; + ) { + if (name !== "" && isRegexClosingTagLookalike(source, cursor)) { + cursor = skipRegexLiteral(source, cursor + 1); + continue; + } + return true; + } cursor++; } return false; } +function isRegexClosingTagLookalike(source: string, index: number): boolean { + const regexStart = index + 1; + const regexEnd = skipRegexLiteral(source, regexStart); + let closingSlash = regexEnd - 1; + while (/[A-Za-z]/.test(source[closingSlash] ?? "")) closingSlash--; + + if (closingSlash <= regexStart || source[closingSlash] !== "/") return false; + if (containsLineTerminator(source, regexStart, closingSlash)) return false; + + const flags = source.slice(closingSlash + 1, regexEnd); + const uniqueFlags = new Set(flags); + if ( + uniqueFlags.size !== flags.length || + [...uniqueFlags].some((flag) => !"dgimsuvy".includes(flag)) || + (uniqueFlags.has("u") && uniqueFlags.has("v")) + ) return false; + + const afterRegex = skipWhitespaceAndComments(source, regexEnd); + const next = source[afterRegex]; + return next === undefined || ".([?;,)]}:".includes(next); +} + function canStartJsxElement( source: string, index: number, From a4d3667c10b562e6247e46fe719bba280ab787fc Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 18:13:30 +0200 Subject: [PATCH 092/104] Keep source-like Unicode JSX out of dependency resolution The scanner must distinguish Unicode JSX tag names and regular-expression operator continuations from TypeScript assertion syntax before resolving side-effect imports. Reuse the existing Unicode identifier reader for tag names and accept punctuator continuations after complete regex literals. Constraint: Preserve lightweight bounded source scanning without adding a parser dependency. Rejected: Expand the ASCII character class | valid JSX names can contain Unicode identifier characters. Confidence: high Scope-risk: narrow Directive: Keep JSX tag-name recognition aligned with the scanner Unicode identifier helpers. Tested: Source-span regressions, related resolver matrix, format, lint, typecheck, diff check, and full pre-push suite. Not-tested: Full TypeScript and JSX grammar equivalence beyond the covered scanner boundaries. --- .../utils/source-spans.test.ts | 24 +++++++++++++++++++ .../esm-module-loader/utils/source-spans.ts | 13 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 1843972580..bacf6284f4 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -1264,6 +1264,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores side-effect import text in Unicode namespaced JSX children", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'export function Example() { return {label}import "./example.js";; } import "./real.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./real.js"], + ); + }); + it("keeps scanning after a TypeScript angle-bracket assertion", () => { assertEquals( findStaticSideEffectImportSpans( @@ -1286,6 +1297,19 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("recognizes regex syntax followed by binary operators", () => { + for (const continuation of ["&& ready", "+ offset", "=== expected"]) { + assertEquals( + findStaticSideEffectImportSpans( + `const value = thing; import "./after-assertion.js"; const ok = x foo/ ${continuation};`, + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./after-assertion.js"], + ); + } + }); + it("ignores side-effect import text in regex literals after comments", () => { assertEquals( findStaticSideEffectImportSpans( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 21c6d89ee4..0b2fe4e917 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -239,11 +239,20 @@ interface JsxTagEnd { selfClosing: boolean; } +function jsxTagNameCharacterLength(source: string, index: number): number { + const character = identifierCharacterAt(source, index); + if (character !== undefined && isIdentifierChar(character)) return character.length; + return ".:-".includes(source[index] ?? "") ? 1 : 0; +} + function skipJsxTag(source: string, index: number): JsxTagEnd | null { let nameStart = index + 1; if (source[nameStart] === "/") nameStart++; let nameEnd = nameStart; - while (/[A-Za-z0-9_$.:.-]/.test(source[nameEnd] ?? "")) nameEnd++; + for (let length = jsxTagNameCharacterLength(source, nameEnd); length > 0;) { + nameEnd += length; + length = jsxTagNameCharacterLength(source, nameEnd); + } let cursor = index + 1; let expressionDepth = 0; @@ -325,7 +334,7 @@ function isRegexClosingTagLookalike(source: string, index: number): boolean { const afterRegex = skipWhitespaceAndComments(source, regexEnd); const next = source[afterRegex]; - return next === undefined || ".([?;,)]}:".includes(next); + return next === undefined || ".([?;,)]}:+-*/%<>=!&|^~".includes(next); } function canStartJsxElement( From 49da2c17a267a789f47945d847f534865863db7f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 18:28:00 +0200 Subject: [PATCH 093/104] Keep optional tenant modules lazy without hiding infrastructure faults Dynamic imports now defer only classified tenant-facing dependency failures, while operational failures remain fail-fast. The source scanner also recognizes Unicode JSX names and keyword operators after regex literals so import-looking text is classified in the correct lexical context. Constraint: Static imports and infrastructure failures must fail during module preparation. Rejected: Defer every dynamic import error | this would hide control-plane and filesystem outages. Confidence: high Scope-risk: moderate Directive: Add new deferred error classes through the shared sanitized descriptor boundary. Tested: Focused 9 tests/274 steps; pinned full pre-push 3845 tests/28816 steps plus cwd and exclusion suites. --- .../mdx/esm-module-loader/loader-helpers.ts | 22 +++++--- .../module-fetcher/nested-imports.test.ts | 42 +++++++++++++++- .../module-fetcher/nested-imports.ts | 17 ++++++- .../esm-module-loader/module-writer.test.ts | 50 +++++++++++++++++++ .../utils/source-spans.test.ts | 21 +++++++- .../esm-module-loader/utils/source-spans.ts | 7 ++- 6 files changed, 147 insertions(+), 12 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/loader-helpers.ts b/src/transforms/mdx/esm-module-loader/loader-helpers.ts index b48aa88824..cdd8479ab4 100644 --- a/src/transforms/mdx/esm-module-loader/loader-helpers.ts +++ b/src/transforms/mdx/esm-module-loader/loader-helpers.ts @@ -17,7 +17,7 @@ import { getMdxEsmCacheDir } from "#veryfront/utils/cache-dir.ts"; import { exists as fsExists } from "#veryfront/platform/compat/fs.ts"; import { LOG_PREFIX_MDX_LOADER } from "./constants.ts"; import { getLocalFs } from "./cache/index.ts"; -import { createStubModule } from "./utils/stub-module.ts"; +import { createStubModule, type DeferredImportErrorDescriptor } from "./utils/stub-module.ts"; import { findDynamicImportSpans, findStaticImportFromSpans, @@ -25,8 +25,11 @@ import { type SourceSpanReplacement, } from "./utils/source-spans.ts"; import { createModuleFetcherContext, fetchAndCacheModule } from "./module-fetcher/index.ts"; -import { buildMissingModuleError, isMdxMissingModuleError } from "./missing-module.ts"; -import { toImportStringLiteral } from "./module-fetcher/nested-imports.ts"; +import { buildMissingModuleError } from "./missing-module.ts"; +import { + dynamicDependencyFailure, + toImportStringLiteral, +} from "./module-fetcher/nested-imports.ts"; import type { ESMLoaderContext } from "./types.ts"; import { parallelMap } from "#veryfront/utils/parallel.ts"; import { @@ -214,10 +217,13 @@ export async function processVfModuleImports( path, }); let filePath: string | null; + let deferredError: DeferredImportErrorDescriptor | undefined; try { filePath = await fetchAndCacheModule(path, fetcherContext); } catch (error) { - if (!isDynamic || !isMdxMissingModuleError(error)) throw error; + if (!isDynamic) throw error; + deferredError = dynamicDependencyFailure(path, error) ?? undefined; + if (!deferredError) throw error; filePath = null; } logger.debug(`${LOG_PREFIX_MDX_LOADER} Fetching module DONE`, { @@ -226,7 +232,7 @@ export async function processVfModuleImports( path, durationMs: (performance.now() - moduleStart).toFixed(1), }); - return { original, start, end, filePath, path, isDynamic }; + return { original, start, end, filePath, path, isDynamic, deferredError }; }, { "mdx.module_path": path, @@ -245,7 +251,9 @@ export async function processVfModuleImports( }); const replacements: SourceSpanReplacement[] = []; - for (const { original, start, end, filePath, path, isDynamic } of results) { + for ( + const { original, start, end, filePath, path, isDynamic, deferredError } of results + ) { if (filePath) { replacements.push({ start, @@ -264,7 +272,7 @@ export async function processVfModuleImports( code, original, context.esmCacheDir!, - { failOnImport: strictMissingModules }, + { failOnImport: strictMissingModules, deferredError }, ); if (deferredPath) { replacements.push({ diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts index 347d9b52b7..407c7463e4 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts @@ -3,7 +3,7 @@ import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { makeTempDir, remove } from "#veryfront/testing/deno-compat.ts"; import { join, toFileUrl } from "#veryfront/compat/path/index.ts"; -import { MDX_COMPILE_ERROR } from "#veryfront/errors"; +import { COMPILATION_ERROR, MDX_COMPILE_ERROR } from "#veryfront/errors"; import { findNestedImports, hasUnresolvedImports, @@ -521,6 +521,46 @@ import { bar } from "./local.js"; } }); + it("defers tenant TypeScript compilation failures until the branch executes", async () => { + const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-ts-cache-" }); + const source = + `export const load = (enabled) => enabled ? import("./broken.ts") : Promise.resolve("skipped");`; + + try { + const result = await resolveNestedModuleImports({ + moduleCode: source, + esmCacheDir, + normalizedPath: "_vf_modules/pages/index.js", + projectSlug: "docs", + strictMissingModules: true, + fetchAndCacheModule: () => { + throw COMPILATION_ERROR.create({ + detail: "ESM transform failed for /broken.ts: ", + context: { tenantBuildFailure: true }, + }); + }, + }); + const parentPath = join(esmCacheDir, "dynamic-ts-parent.mjs"); + await Deno.writeTextFile(parentPath, result); + const loaded = await import( + `${toFileUrl(parentPath).href}?test=${crypto.randomUUID()}` + ) as { load(enabled: boolean): Promise }; + + assertEquals(await loaded.load(false), "skipped"); + const error = await assertRejects( + () => loaded.load(true), + Error, + "TypeScript compilation failed", + ); + if (!(error instanceof Error)) throw new Error("expected Error"); + assertEquals(error.name, "CompilationError"); + assertEquals(error.message.includes(""), false); + assertEquals(error.message.includes(""), false); + } finally { + await remove(esmCacheDir, { recursive: true }); + } + }); + it("defers strict dynamic child cycles until the branch executes", async () => { const esmCacheDir = await makeTempDir({ prefix: "vf-mdx-dynamic-cycle-cache-" }); const source = diff --git a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts index ab70313a8a..e9f922ff1b 100644 --- a/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts +++ b/src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.ts @@ -27,6 +27,7 @@ import { MAX_MDX_MODULE_TRANSFORM_CONCURRENCY, } from "./limits.ts"; import { VeryfrontError } from "#veryfront/errors"; +import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; function matchUnresolvedVfModuleSpecifier(specifier: string): string | null { return specifier.match(/^((?:file:\/\/)?\/?\/?_vf_modules\/.+)$/)?.[1] ?? null; @@ -70,7 +71,10 @@ export function dynamicDependencyFailure( }; } - if (error.name === "ModuleSourceLimitError") { + if ( + error.name === "ModuleSourceLimitError" || + error.name === "HttpModuleBodyTooLargeError" + ) { return { name: "ModuleSourceLimitError", message: @@ -85,6 +89,17 @@ export function dynamicDependencyFailure( }; } + if ( + error instanceof VeryfrontError && error.slug === "compilation-error" && + isTenantSourceBuildError(error) + ) { + return { + name: "CompilationError", + message: + `[Veryfront] Dynamic import failed for ${modulePath}: TypeScript compilation failed.`, + }; + } + return null; } diff --git a/src/transforms/mdx/esm-module-loader/module-writer.test.ts b/src/transforms/mdx/esm-module-loader/module-writer.test.ts index 2703e77776..842207de2d 100644 --- a/src/transforms/mdx/esm-module-loader/module-writer.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-writer.test.ts @@ -12,6 +12,7 @@ import type { FileSystem } from "#veryfront/platform/compat/fs.ts"; import { VeryfrontError } from "#veryfront/errors"; import { LRUCache } from "#veryfront/utils/lru-wrapper.ts"; import type { MDXModule } from "../types.ts"; +import { MAX_MDX_MODULE_CODE_BYTES } from "./module-fetcher/limits.ts"; function cacheKeyForDependencies( dependencies: Readonly>, @@ -206,6 +207,55 @@ describe("MDX root dynamic imports", () => { await esbuild.stop(); } }); + + it("defers a typed dependency failure until the root import executes", async () => { + const oversizedModule = `OversizedRoot-${crypto.randomUUID()}.js`; + const projectDir = await Deno.makeTempDir({ prefix: "vf-mdx-root-dynamic-limit-" }); + + try { + const mod = await withMockFetch( + () => + Promise.resolve( + new Response("x".repeat(MAX_MDX_MODULE_CODE_BYTES + 1), { + headers: { "content-type": "application/javascript" }, + }), + ), + () => + mdxRenderer.loadModuleESM( + `export async function loadOptional(enabled) { + if (!enabled) return "SKIPPED"; + return await import("@/${oversizedModule}"); + } + export default function Root() { return null; }`, + { + adapter: denoAdapter, + projectId: `project-${crypto.randomUUID()}`, + projectDir, + projectSlug: "root-dynamic-limit", + contentSourceId: `source-${crypto.randomUUID()}`, + isLocalProject: true, + }, + ), + ); + const loadOptional = (mod as unknown as { + loadOptional(enabled: boolean): Promise; + }).loadOptional; + + assertEquals(await loadOptional(false), "SKIPPED"); + const error = await assertRejects( + () => loadOptional(true), + Error, + "module source exceeds the allowed size", + ); + if (!(error instanceof Error)) throw new Error("expected Error"); + assertEquals(error.name, "ModuleSourceLimitError"); + } finally { + mdxRenderer.clearCache(); + await Deno.remove(projectDir, { recursive: true }); + const esbuild = await import("veryfront/extensions/bundler"); + await esbuild.stop(); + } + }); }); describe("verifyMdxCacheFile", () => { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index bacf6284f4..7c63138591 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -1275,6 +1275,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores side-effect import text in Unicode-leading JSX children", () => { + assertEquals( + findStaticSideEffectImportSpans( + 'export function Example() { return <路径>{label}import "./example.js";; } import "./real.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./real.js"], + ); + }); + it("keeps scanning after a TypeScript angle-bracket assertion", () => { assertEquals( findStaticSideEffectImportSpans( @@ -1298,7 +1309,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { }); it("recognizes regex syntax followed by binary operators", () => { - for (const continuation of ["&& ready", "+ offset", "=== expected"]) { + for ( + const continuation of [ + "&& ready", + "+ offset", + "=== expected", + "in expressions", + "instanceof RegExp", + ] + ) { assertEquals( findStaticSideEffectImportSpans( `const value = thing; import "./after-assertion.js"; const ok = x foo/ ${continuation};`, diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 0b2fe4e917..68211757d5 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -334,7 +334,10 @@ function isRegexClosingTagLookalike(source: string, index: number): boolean { const afterRegex = skipWhitespaceAndComments(source, regexEnd); const next = source[afterRegex]; - return next === undefined || ".([?;,)]}:+-*/%<>=!&|^~".includes(next); + return next === undefined || ".([?;,)]}:+-*/%<>=!&|^~".includes(next) || + (source.startsWith("in", afterRegex) && !isIdentifierPartAt(source, afterRegex + 2)) || + (source.startsWith("instanceof", afterRegex) && + !isIdentifierPartAt(source, afterRegex + "instanceof".length)); } function canStartJsxElement( @@ -343,7 +346,7 @@ function canStartJsxElement( previousTokenIndex: number, ): boolean { const next = source[index + 1]; - if (next !== ">" && !/[A-Za-z_$]/.test(next ?? "")) return false; + if (next !== ">" && jsxTagNameCharacterLength(source, index + 1) === 0) return false; if (previousTokenIndex < 0) return true; const previous = source[previousTokenIndex]!; From 1144890a67d251c687985e35d55ae96b5b5d684f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 18:47:33 +0200 Subject: [PATCH 094/104] Keep declaration and control boundaries out of regex text Nested class heritage expressions and completed control blocks can both precede regex literals. Preserve their syntactic boundaries so source scanners do not promote regex text into imports or default exports. Constraint: The scanners remain dependency-free and operate without a full JavaScript parser. Rejected: Parse every module with a third-party AST | adds dependency and hot-path overhead outside this focused repair. Confidence: high Scope-risk: narrow Directive: Keep new token-boundary cases covered in both scanners before broadening their heuristics. Tested: Focused scanner and persistence suites, fmt, lint, typecheck, diff check, and full pre-push suite. Not-tested: Browser runtime execution of arbitrary tenant modules. --- .../module-loader/module-persistence.test.ts | 1 + .../module-loader/module-persistence.ts | 14 ++++++++++++++ .../utils/source-spans.test.ts | 9 +++++++++ .../esm-module-loader/utils/source-spans.ts | 19 ++++++++++++++----- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index 6d80f857e7..d3c361c0e0 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -175,6 +175,7 @@ describe("module-loader/module-persistence", () => { const cases = [ `export const pattern = /export default/;`, `if (enabled) /export default/.test(source); export const value = 1;`, + `if (enabled) {} /export default/.test(source); export const value = 1;`, `function read() { return /* keep the comment */ /export default/.source; }`, `function read() { return // keep the comment\n/export default/.source; }`, ] as const; diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index c0ce2063fb..9246907f10 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -120,7 +120,9 @@ export async function readPersistedUnresolvedSpecifiers( function hasDefaultExport(code: string): boolean { let previousTokenIndex = -1; const controlConditionCloseParens = new Set(); + const controlBlockCloseBraces = new Set(); const openParens: boolean[] = []; + const openBraces: boolean[] = []; for (let index = 0; index < code.length;) { index = skipTrivia(code, index); @@ -145,6 +147,7 @@ function hasDefaultExport(code: string): boolean { const next = skipTextToken(code, index, { previousTokenIndex, controlConditionCloseParens, + controlBlockCloseBraces, }); if (next !== index) { previousTokenIndex = next - 1; @@ -167,6 +170,13 @@ function hasDefaultExport(code: string): boolean { ); } else if (code[index] === ")" && openParens.pop() === true) { controlConditionCloseParens.add(index); + } else if (code[index] === "{") { + openBraces.push( + code[previousTokenIndex] === ")" && + controlConditionCloseParens.has(previousTokenIndex), + ); + } else if (code[index] === "}" && openBraces.pop() === true) { + controlBlockCloseBraces.add(index); } previousTokenIndex = index; @@ -273,6 +283,7 @@ function skipTrivia(source: string, index: number): number { interface RegexScanContext { previousTokenIndex: number; controlConditionCloseParens: ReadonlySet; + controlBlockCloseBraces: ReadonlySet; } function skipTextToken( @@ -315,6 +326,9 @@ function skipRegexToken( if (char === ")" && context?.controlConditionCloseParens.has(previous)) { // A statement can start with a regex immediately after a control // condition, for example `if (ready) /pattern/.test(value)`. + } else if (char === "}" && context?.controlBlockCloseBraces.has(previous)) { + // The same is true after the braced form, for example + // `if (ready) {} /pattern/.test(value)`. } else if (!"([{=,:;!~?&|+-*%^<>".includes(char)) { const keyword = identifierBefore(source, previous); if (keyword !== null && isMemberNameBefore(source, previous)) return index; diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 7c63138591..3208ecc57c 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -997,6 +997,15 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { } }); + it("keeps class context across nested extends braces", () => { + assertEquals( + vfModuleSpecifiers( + 'class Loader extends mixin({}) {} /import("\\/_vf_modules\\/fake.js")/.test(value);', + ), + [], + ); + }); + it("treats Unicode identifier parts as import boundaries", () => { for ( const source of [ diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 68211757d5..29482dc080 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -769,11 +769,20 @@ function isDeclarationBlockCloseBrace( const openBrace = matchingOpenBraces.get(index); if (openBrace === undefined) return false; - const declarationStart = Math.max( - source.lastIndexOf(";", openBrace.index - 1), - source.lastIndexOf("{", openBrace.index - 1), - source.lastIndexOf("}", openBrace.index - 1), - ) + 1; + let declarationStart = 0; + for (let cursor = openBrace.index - 1; cursor >= 0; cursor--) { + if (source[cursor] === "}") { + const nestedBrace = matchingOpenBraces.get(cursor); + if (nestedBrace !== undefined) { + cursor = nestedBrace.index; + continue; + } + } + if (source[cursor] === ";" || source[cursor] === "{" || source[cursor] === "}") { + declarationStart = cursor + 1; + break; + } + } const prefix = source.slice(declarationStart, openBrace.index).trimStart().replace( /\/\*[\s\S]*?\*\/|\/\/[^\r\n\u2028\u2029]*/g, " ", From 91e21cb75a7f05414614c58f6b4dd33703d83987 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 19:20:55 +0200 Subject: [PATCH 095/104] Keep authored failures distinct at syntax and package boundaries Declaration bodies can legally be followed by regex statements, while explicit npm imports can fail at the authored root or inside a transitive dependency. Track declaration-body delimiters in the lightweight export scanner and fingerprint explicit npm roots before assigning tenant ownership. Constraint: Preserve infrastructure failures and transitive package failures at error severity. Rejected: Treat every closing brace as a regex boundary | object expressions require division semantics. Rejected: Mark every npm fetch failure as tenant-authored | transitive dependency failures are framework or upstream concerns. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep tenant classification tied to the exact authored request fingerprint. Tested: Focused 106-step module/dependency/HTTP cache suite, targeted fmt/lint/check, lint:ci typecheck ratchet, pinned docs check, and full pre-push. Not-tested: Live esm.sh failure behavior outside the mocked 404 and transitive dependency matrix. --- .../module-loader/dependency-resolver.test.ts | 2 +- .../module-loader/module-persistence.test.ts | 4 + .../module-loader/module-persistence.ts | 75 ++++++++++++++++--- src/transforms/esm/http-cache.test.ts | 42 +++++++---- src/transforms/esm/specifier-resolver.ts | 12 ++- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts index 165a0e2230..718da7363b 100644 --- a/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts +++ b/src/rendering/orchestrator/module-loader/dependency-resolver.test.ts @@ -211,7 +211,7 @@ describe("module-loader/dependency-resolver", () => { }); assertEquals(deps, []); - assertEquals(rewriteResolvedDependencyImports(fileContent, deps), fileContent); + assertEquals(rewriteResolvedDependencyImports(fileContent, []), fileContent); }, ); }); diff --git a/src/rendering/orchestrator/module-loader/module-persistence.test.ts b/src/rendering/orchestrator/module-loader/module-persistence.test.ts index d3c361c0e0..fb163389f9 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.test.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.test.ts @@ -176,6 +176,10 @@ describe("module-loader/module-persistence", () => { `export const pattern = /export default/;`, `if (enabled) /export default/.test(source); export const value = 1;`, `if (enabled) {} /export default/.test(source); export const value = 1;`, + `function setup() {} /export default/.test(source); export const value = 1;`, + `function setup({ nested: {} } = {}) {} /export default/.test(source); export const value = 1;`, + `class Setup {} /export default/.test(source); export const value = 1;`, + `class Setup extends mixin({}) {} /export default/.test(source); export const value = 1;`, `function read() { return /* keep the comment */ /export default/.source; }`, `function read() { return // keep the comment\n/export default/.source; }`, ] as const; diff --git a/src/rendering/orchestrator/module-loader/module-persistence.ts b/src/rendering/orchestrator/module-loader/module-persistence.ts index 9246907f10..aaf3b8bf29 100644 --- a/src/rendering/orchestrator/module-loader/module-persistence.ts +++ b/src/rendering/orchestrator/module-loader/module-persistence.ts @@ -120,9 +120,19 @@ export async function readPersistedUnresolvedSpecifiers( function hasDefaultExport(code: string): boolean { let previousTokenIndex = -1; const controlConditionCloseParens = new Set(); - const controlBlockCloseBraces = new Set(); + const statementBlockCloseBraces = new Set(); const openParens: boolean[] = []; const openBraces: boolean[] = []; + let openBracketCount = 0; + let pendingDeclaration: + | { + kind: "class" | "function"; + braceDepth: number; + parenDepth: number; + bracketDepth: number; + parameterListClosed: boolean; + } + | undefined; for (let index = 0; index < code.length;) { index = skipTrivia(code, index); @@ -147,7 +157,7 @@ function hasDefaultExport(code: string): boolean { const next = skipTextToken(code, index, { previousTokenIndex, controlConditionCloseParens, - controlBlockCloseBraces, + statementBlockCloseBraces, }); if (next !== index) { previousTokenIndex = next - 1; @@ -156,8 +166,22 @@ function hasDefaultExport(code: string): boolean { } if (isIdentifierStart(code[index])) { + const identifierStart = index; index++; while (index < code.length && isIdentifierPart(code[index])) index++; + const identifier = code.slice(identifierStart, index); + if ( + (identifier === "function" || identifier === "class") && + startsDeclaration(code, previousTokenIndex, controlConditionCloseParens) + ) { + pendingDeclaration = { + kind: identifier, + braceDepth: openBraces.length, + parenDepth: openParens.length, + bracketDepth: openBracketCount, + parameterListClosed: false, + }; + } previousTokenIndex = index - 1; continue; } @@ -168,15 +192,32 @@ function hasDefaultExport(code: string): boolean { keyword === "if" || keyword === "while" || keyword === "for" || keyword === "with" || keyword === "switch" || keyword === "catch", ); - } else if (code[index] === ")" && openParens.pop() === true) { - controlConditionCloseParens.add(index); + } else if (code[index] === ")") { + if (openParens.pop() === true) controlConditionCloseParens.add(index); + if ( + pendingDeclaration?.kind === "function" && + openParens.length === pendingDeclaration.parenDepth + ) { + pendingDeclaration.parameterListClosed = true; + } + } else if (code[index] === "[") { + openBracketCount++; + } else if (code[index] === "]") { + openBracketCount = Math.max(0, openBracketCount - 1); } else if (code[index] === "{") { + const opensDeclarationBody = pendingDeclaration !== undefined && + openBraces.length === pendingDeclaration.braceDepth && + openParens.length === pendingDeclaration.parenDepth && + openBracketCount === pendingDeclaration.bracketDepth && + (pendingDeclaration.kind === "class" || pendingDeclaration.parameterListClosed); openBraces.push( - code[previousTokenIndex] === ")" && - controlConditionCloseParens.has(previousTokenIndex), + opensDeclarationBody || + code[previousTokenIndex] === ")" && + controlConditionCloseParens.has(previousTokenIndex), ); + if (opensDeclarationBody) pendingDeclaration = undefined; } else if (code[index] === "}" && openBraces.pop() === true) { - controlBlockCloseBraces.add(index); + statementBlockCloseBraces.add(index); } previousTokenIndex = index; @@ -186,6 +227,19 @@ function hasDefaultExport(code: string): boolean { return false; } +function startsDeclaration( + code: string, + previousTokenIndex: number, + controlConditionCloseParens: ReadonlySet, +): boolean { + if (previousTokenIndex < 0) return true; + if (";{}:".includes(code[previousTokenIndex] ?? "")) return true; + if (controlConditionCloseParens.has(previousTokenIndex)) return true; + return ["async", "default", "export"].includes( + identifierBefore(code, previousTokenIndex) ?? "", + ); +} + function exportListExposesDefault(code: string, openBraceIndex: number): boolean { const closeBraceIndex = findExportListCloseBrace(code, openBraceIndex); if (closeBraceIndex === -1) return false; @@ -283,7 +337,7 @@ function skipTrivia(source: string, index: number): number { interface RegexScanContext { previousTokenIndex: number; controlConditionCloseParens: ReadonlySet; - controlBlockCloseBraces: ReadonlySet; + statementBlockCloseBraces: ReadonlySet; } function skipTextToken( @@ -326,9 +380,10 @@ function skipRegexToken( if (char === ")" && context?.controlConditionCloseParens.has(previous)) { // A statement can start with a regex immediately after a control // condition, for example `if (ready) /pattern/.test(value)`. - } else if (char === "}" && context?.controlBlockCloseBraces.has(previous)) { + } else if (char === "}" && context?.statementBlockCloseBraces.has(previous)) { // The same is true after the braced form, for example - // `if (ready) {} /pattern/.test(value)`. + // `if (ready) {} /pattern/.test(value)` or + // `function ready() {} /pattern/.test(value)`. } else if (!"([{=,:;!~?&|+-*%^<>".includes(char)) { const keyword = identifierBefore(source, previous); if (keyword !== null && isMemberNameBefore(source, previous)) return index; diff --git a/src/transforms/esm/http-cache.test.ts b/src/transforms/esm/http-cache.test.ts index 4abaa74da2..87c42675dd 100644 --- a/src/transforms/esm/http-cache.test.ts +++ b/src/transforms/esm/http-cache.test.ts @@ -1251,12 +1251,17 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, () => cacheHttpImportsToLocal('import "missing-tenant-package";', options), Error, ); + const explicitPackageError = await assertRejects( + () => cacheHttpImportsToLocal('import "npm:missing-tenant-package";', options), + Error, + ); const directHttpError = await assertRejects( () => cacheModuleToLocal("https://esm.sh/missing-framework-module", tempDir), Error, ); assertEquals(isTenantSourceBuildError(packageError), true); + assertEquals(isTenantSourceBuildError(explicitPackageError), true); assertEquals(isTenantSourceBuildError(directHttpError), false); }); }); @@ -1276,22 +1281,29 @@ describe("HTTP Bundle Cache", { sanitizeResources: false, sanitizeOps: false }, return Promise.resolve(new Response("not found", { status: 404 })); }) as typeof fetch; - await withIsolatedHttpCache( - "vf-esm-missing-package-dependency-", - mockFetch, - async (tempDir) => { - const error = await assertRejects( - () => - cacheHttpImportsToLocal('import "package-with-missing-dependency";', { - cacheDir: tempDir, - importMap: { imports: {}, scopes: {} }, - }), - Error, - ); + for ( + const specifier of [ + "package-with-missing-dependency", + "npm:package-with-missing-dependency", + ] + ) { + await withIsolatedHttpCache( + "vf-esm-missing-package-dependency-", + mockFetch, + async (tempDir) => { + const error = await assertRejects( + () => + cacheHttpImportsToLocal(`import ${JSON.stringify(specifier)};`, { + cacheDir: tempDir, + importMap: { imports: {}, scopes: {} }, + }), + Error, + ); - assertEquals(isTenantSourceBuildError(error), false); - }, - ); + assertEquals(isTenantSourceBuildError(error), false, specifier); + }, + ); + } }); it("distinguishes a package from a missing dependency at the same sanitized URL", async () => { diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index 28eaff38fe..09d8d36b76 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -181,7 +181,17 @@ async function resolveSpecifier( if (stringStartsWith(specifier, "npm:")) { const bareSpecifier = stringSlice(specifier, 4); - const cached = await cacheHttpModule(`https://esm.sh/${bareSpecifier}`, options); + const requestedPackageUrl = `https://esm.sh/${bareSpecifier}`; + let cached: string | null; + try { + cached = await cacheHttpModule(requestedPackageUrl, options); + } catch (error) { + const effective = getEffectiveHttpCacheRequest(requestedPackageUrl, options); + const requestedPackageFingerprint = await fingerprintHttpModuleRequest( + normalizeHttpUrl(effective.url), + ); + throw classifyAuthoredPackageFetchError(error, requestedPackageFingerprint); + } if (!cached) return bareSpecifier; if (isParentHttpModule(baseUrl)) { From 769ca30d108d3a13530141942b67d903e508cf0c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 19:51:29 +0200 Subject: [PATCH 096/104] Keep JSX examples out of static import resolution The MDX source scanner treated import-from prose inside JSX text as executable source whenever an expression preceded the text. Track JSX text and expression boundaries in the static import scanner, and restrict export from-clause scanning to actual re-export forms. Constraint: The scanner must remain dependency-free and preserve TypeScript value and type re-exports. Rejected: Parse every module with a full JavaScript parser | the existing bounded scanner is used on tolerant and partially transformed source. Confidence: high Scope-risk: narrow Directive: Keep JSX text handling aligned across static import scanner variants. Tested: Focused source-span suite, five related MDX suites, format, lint, typecheck, diff check, and full pre-push (3,852 tests / 28,887 steps plus CWD suites). Not-tested: Browser execution of a tenant MDX document containing the exact prose example. --- .../utils/source-spans.test.ts | 25 +++++++ .../esm-module-loader/utils/source-spans.ts | 70 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index 3208ecc57c..b938ca2b7f 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -144,6 +144,17 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("keeps value and type re-export forms eligible for from clauses", () => { + assertEquals( + findStaticImportFromSpans( + 'export * from "./all.js"; export type { Value } from "./types.js";', + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./all.js", "./types.js"], + ); + }); + it("finds static imports after top-level block declarations", () => { assertEquals( findStaticImportFromSpans( @@ -155,6 +166,20 @@ describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { ); }); + it("ignores import-from examples in JSX text after an expression", () => { + assertEquals( + findStaticImportFromSpans( + `export function Example() { + return {label}import value from /* note */ "./example.js";; +} +import real from "./real.js";`, + matchRelative, + UNBOUNDED, + ).map((span) => span.path), + ["./real.js"], + ); + }); + it("recognizes every ECMAScript line terminator", () => { for (const lineTerminator of ["\r", "\u2028", "\u2029"]) { assertEquals( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 29482dc080..f3810d6b83 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -1206,6 +1206,19 @@ function findFromSpan( return null; } +function canExportHaveFromClause(source: string, statementStart: number): boolean { + let cursor = skipWhitespaceAndComments(source, statementStart); + if ( + source.startsWith("type", cursor) && + !isIdentifierPartAt(source, cursor - 1) && + !isIdentifierPartAt(source, cursor + "type".length) + ) { + cursor = skipWhitespaceAndComments(source, cursor + "type".length); + } + + return source[cursor] === "*" || source[cursor] === "{"; +} + /** * Validate the match bound every scanner requires. * @@ -1238,9 +1251,49 @@ export function findStaticImportFromSpans( const openParens: OpenParenContext[] = []; const matchingOpenParens = new Map(); let previousTokenIndex = -1; + let jsxDepth = 0; + let inJsxText = false; + const jsxExpressionStack: Array<{ braceDepth: number; parentDepth: number }> = []; while (cursor < source.length) { const char = source[cursor]; + + if (inJsxText) { + if (char === "<") { + const tag = skipJsxTag(source, cursor); + if (tag !== null) { + const closing = source[cursor + 1] === "/"; + if (closing) jsxDepth = Math.max(0, jsxDepth - 1); + else if (!tag.selfClosing) jsxDepth++; + const expressionParentDepth = jsxExpressionStack.at(-1)?.parentDepth ?? 0; + inJsxText = jsxDepth > expressionParentDepth; + atStatementStart = false; + previousTokenIndex = tag.end - 1; + cursor = tag.end; + continue; + } + } + if (char === "{") { + jsxExpressionStack.push({ braceDepth: 0, parentDepth: jsxDepth }); + inJsxText = false; + } else { + cursor++; + continue; + } + } else if (char === "<" && canStartJsxElement(source, cursor, previousTokenIndex)) { + const tag = skipJsxTag(source, cursor); + if (tag !== null && (tag.selfClosing || hasClosingJsxTag(source, tag.end, tag.name))) { + if (!tag.selfClosing) { + jsxDepth++; + inJsxText = true; + } + atStatementStart = false; + previousTokenIndex = tag.end - 1; + cursor = tag.end; + continue; + } + } + const skipped = skipExpressionIgnored( source, cursor, @@ -1267,6 +1320,8 @@ export function findStaticImportFromSpans( } if (char === "{") { + const expression = jsxExpressionStack.at(-1); + if (expression !== undefined) expression.braceDepth++; openBraces.push({ index: cursor, previousTokenIndex }); atStatementStart = false; previousTokenIndex = cursor; @@ -1274,6 +1329,14 @@ export function findStaticImportFromSpans( continue; } if (char === "}") { + const expression = jsxExpressionStack.at(-1); + if (expression !== undefined) { + expression.braceDepth--; + if (expression.braceDepth === 0) { + jsxExpressionStack.pop(); + inJsxText = jsxDepth > 0; + } + } const openBrace = openBraces.pop(); if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); atStatementStart = true; @@ -1334,6 +1397,13 @@ export function findStaticImportFromSpans( continue; } + if (isExport && !canExportHaveFromClause(source, afterKeyword)) { + atStatementStart = false; + previousTokenIndex = cursor + keywordLength - 1; + cursor = afterKeyword; + continue; + } + const span = findFromSpan(source, afterKeyword, matcher); if (span) { spans.push(span); From 2c1e6b2952cf3121c08aa45166094eb63fb80243 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 21:22:02 +0200 Subject: [PATCH 097/104] Keep tenant build diagnostics resistant to poisoned errors PR review found two reviewer-blocking edges: symbol tags could be inherited from Error.prototype or accessor-backed, and static import scanning repeatedly walked the remaining source while deciding whether JSX-looking angle assertions had a closing tag. The fix keeps tag reads to own data descriptors, indexes JSX closing tags once per static scan, and preserves the CLI/public export baseline repairs already present in the worktree. Constraint: PR #3723 review requires Symbol.for tenant-build-failure reads to require own data value true without invoking accessors Constraint: JSX closing-tag detection must be bounded for repeated JSX-looking TypeScript angle assertions Rejected: Keep wall-clock performance tests | machine-speed thresholds do not prove the quadratic path was removed Confidence: high Scope-risk: moderate Reversibility: clean Directive: Do not replace descriptor-based symbol reads with property access without re-running the inherited and accessor poisoning regressions Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH VF_DISABLE_LRU_INTERVAL=1 deno test --preload=src/testing/preload.ts --no-check --allow-all src/rendering/orchestrator/module-loader/index.test.ts src/observability/application-errors.test.ts src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task fmt:check Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task lint Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task typecheck Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task docs:api-reference:check Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task test:unit Tested: PATH=/private/tmp/deno-2.7.7-aarch64-apple-darwin:$PATH deno task verify:quick Not-tested: Playwright/e2e binary smoke path --- cli/commands/build/error-handler.ts | 2 +- cli/commands/generate/command.ts | 101 ++++- docs/api-reference/veryfront/errors.md | 93 ++--- docs/api-reference/veryfront/observability.md | 8 +- src/errors/index.ts | 2 + src/observability/README.md | 7 + src/observability/application-errors.test.ts | 55 +++ src/observability/application-errors.ts | 12 +- .../module-loader/build-failure.ts | 15 +- .../orchestrator/module-loader/index.test.ts | 58 +++ .../utils/source-spans.test.ts | 65 ++++ .../esm-module-loader/utils/source-spans.ts | 354 +++++++++--------- 12 files changed, 519 insertions(+), 253 deletions(-) diff --git a/cli/commands/build/error-handler.ts b/cli/commands/build/error-handler.ts index 5e05679eb2..511b5f4502 100644 --- a/cli/commands/build/error-handler.ts +++ b/cli/commands/build/error-handler.ts @@ -1,6 +1,6 @@ import { brand, dim } from "#cli/ui"; import { cliLogger, isVerbose, logError } from "#cli/utils"; -import { sanitizeTerminalDiagnosticText } from "#veryfront/errors/safe-diagnostics.ts"; +import { sanitizeTerminalDiagnosticText } from "veryfront/errors"; import { exit, getStdout } from "veryfront/platform"; const STACK_FRAME_WITH_PARENS = diff --git a/cli/commands/generate/command.ts b/cli/commands/generate/command.ts index 510c66c3a4..45d27ba607 100644 --- a/cli/commands/generate/command.ts +++ b/cli/commands/generate/command.ts @@ -1,11 +1,9 @@ import { getConfig } from "veryfront/config"; import { cliLogger } from "#cli/utils"; import { createError, toError } from "veryfront/errors"; +import { exists, join, readTextFile } from "veryfront/fs"; import { generateIntegration } from "./integration-generator.ts"; import { isScaffoldType, scaffoldProjectFile } from "../../scaffold/engine.ts"; -import { exists, readTextFile } from "#veryfront/compat/fs.ts"; -import { join } from "#veryfront/compat/path"; -import { parseExtensionManifest } from "#veryfront/extensions/manifest-reader.ts"; const PROJECT_MARKERS = [ "veryfront.config.ts", @@ -26,6 +24,97 @@ const PROJECT_MANIFESTS = [ { name: "deno.jsonc", syntax: "jsonc" }, ] as const; +function stripProjectJsoncComments(source: string): string { + let output = ""; + let inString = false; + let escaped = false; + + for (let index = 0; index < source.length; index++) { + const char = source[index]!; + if (inString) { + output += char; + if (escaped) escaped = false; + else if (char === "\\") escaped = true; + else if (char === '"') inString = false; + continue; + } + if (char === '"') { + inString = true; + output += char; + continue; + } + if (char === "/" && source[index + 1] === "/") { + output += " "; + index += 2; + while (index < source.length && !/[\r\n\u2028\u2029]/.test(source[index]!)) { + output += " "; + index++; + } + if (index < source.length) output += source[index]!; + continue; + } + if (char === "/" && source[index + 1] === "*") { + output += " "; + index += 2; + while (index < source.length) { + if (source[index] === "*" && source[index + 1] === "/") { + output += " "; + index++; + break; + } + output += /[\r\n\u2028\u2029]/.test(source[index]!) ? source[index]! : " "; + index++; + } + continue; + } + output += char; + } + + return output; +} + +function stripProjectJsoncTrailingCommas(source: string): string { + let output = ""; + let inString = false; + let escaped = false; + + for (let index = 0; index < source.length; index++) { + const char = source[index]!; + if (inString) { + output += char; + if (escaped) escaped = false; + else if (char === "\\") escaped = true; + else if (char === '"') inString = false; + continue; + } + if (char === '"') { + inString = true; + output += char; + continue; + } + if (char === ",") { + let next = index + 1; + while (/\s/.test(source[next] ?? "")) next++; + output += source[next] === "}" || source[next] === "]" ? " " : ","; + continue; + } + output += char; + } + + return output; +} + +function parseProjectManifest(source: string, syntax: "json" | "jsonc"): { + dependencies?: Record; + devDependencies?: Record; + imports?: Record; +} { + const json = syntax === "jsonc" + ? stripProjectJsoncTrailingCommas(stripProjectJsoncComments(source)) + : source; + return JSON.parse(json); +} + /** * `generate` writes into whatever directory it is invoked from, so running it * one level above the project (or in the wrong terminal tab) silently produces @@ -41,11 +130,7 @@ async function looksLikeVeryfrontProject(projectDir: string): Promise { const path = join(projectDir, manifest.name); if (!(await exists(path))) continue; try { - const parsed = parseExtensionManifest<{ - dependencies?: Record; - devDependencies?: Record; - imports?: Record; - }>(await readTextFile(path), manifest.syntax, manifest.name); + const parsed = parseProjectManifest(await readTextFile(path), manifest.syntax); const specifiers = [ ...Object.keys(parsed.dependencies ?? {}), ...Object.keys(parsed.devDependencies ?? {}), diff --git a/docs/api-reference/veryfront/errors.md b/docs/api-reference/veryfront/errors.md index 3f9f2ead7b..a2ce57c872 100644 --- a/docs/api-reference/veryfront/errors.md +++ b/docs/api-reference/veryfront/errors.md @@ -166,52 +166,53 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." }); ### Functions -| Name | Description | Source | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| `attachErrorToActiveSpan` | Attach error to the currently active span (if any) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/tracing.ts#L87) | -| `attachErrorToSpan` | Attach error metadata to an OpenTelemetry span | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/tracing.ts#L39) | -| `cliErrorBoundary` | CLI error boundary - wraps a handler function and catches errors | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L183) | -| `cliErrorBoundarySync` | Synchronous version of CLI error boundary | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L209) | -| `createError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L105) | -| `createErrorHandler` | Express/Hono-style error handler middleware factory | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L126) | -| `createErrorResponse` | Create an RFC 9457 compliant error Response | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L46) | -| `createErrorResponseFromDefinition` | Create an RFC 9457 error Response from a registered error definition | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L55) | -| `createErrorScope` | Create a scoped error context helper for multiple related operations | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L179) | -| `createErrorSolution` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/factory.ts#L11) | -| `createProblemResponse` | Create an RFC 9457 error Response from raw parameters | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L70) | -| `createSimpleError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/factory.ts#L29) | -| `defineError` | Define an error in the registry | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/types.ts#L98) | -| `ensureError` | Ensure a value is an Error while preserving the established identity contract for ordinary Error instances. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L837) | -| `errorToResponse` | Convert any error to an RFC 9457 Response | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L106) | -| `errorToRFC9457Response` | Convert any error to an RFC 9457 Response with environment-aware filtering | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L98) | -| `formatCLIError` | Format any error for CLI output | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L163) | -| `formatErrorLog` | Log format for errors (matches the plan's log format spec) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L142) | -| `formatUserError` | Format error with plain text (existing behavior) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-formatter.ts#L85) | -| `fromError` | Decode legacy Veryfront error data attached by `toError()`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/legacy-error-codec.ts#L17) | -| `getAllSlugs` | Get all registered slugs | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L72) | -| `getErrorBySlug` | Get an error definition by slug | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L58) | -| `getErrorMessage` | Extract error message from any error type | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L678) | -| `getErrorsByCategory` | Get all errors in a category | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L65) | -| `getErrorSolution` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L44) | -| `handleErrorWithFallback` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L41) | -| `handleErrorWithFallbackSync` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L54) | -| `httpErrorBoundary` | Wrap a handler with error boundary that catches all errors and converts them to RFC 9457 Problem Details responses. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L55) | -| `identifyError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-identifier.ts#L26) | -| `isVeryfrontError` | Check if an error is a VeryfrontError with slug-based identity | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L96) | -| `logError` | Log a VeryfrontError with structured formatting | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/logging.ts#L96) | -| `logErrorWithMessage` | Log an error with a custom message prefix | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/logging.ts#L148) | -| `retryWithBackoff` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L149) | -| `safeFileRead` | Safe file read with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L150) | -| `safeFileStat` | Safe file stat with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L137) | -| `safeReadDir` | Safe directory read with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L163) | -| `searchErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L48) | -| `toError` | Convert a VeryfrontErrorData (plain object) to a throwable Error instance. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L622) | -| `withErrorContext` | Execute async operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L109) | -| `withErrorContextSync` | Execute sync operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L123) | -| `wrapErrorHandler` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-wrapper.ts#L26) | -| `wrapHandlerWithErrorBoundary` | Wrap a complete Handler object with error boundary | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L85) | -| `wrapUnknownError` | Return a detached VeryfrontError, preserving safe identity fields from valid VeryfrontError inputs | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/wrap-unknown.ts#L51) | -| `wrapWithContext` | Wrap an error with additional context | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/wrap-unknown.ts#L101) | +| Name | Description | Source | +| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `attachErrorToActiveSpan` | Attach error to the currently active span (if any) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/tracing.ts#L87) | +| `attachErrorToSpan` | Attach error metadata to an OpenTelemetry span | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/tracing.ts#L39) | +| `cliErrorBoundary` | CLI error boundary - wraps a handler function and catches errors | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L183) | +| `cliErrorBoundarySync` | Synchronous version of CLI error boundary | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L209) | +| `createError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L105) | +| `createErrorHandler` | Express/Hono-style error handler middleware factory | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L126) | +| `createErrorResponse` | Create an RFC 9457 compliant error Response | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L46) | +| `createErrorResponseFromDefinition` | Create an RFC 9457 error Response from a registered error definition | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L55) | +| `createErrorScope` | Create a scoped error context helper for multiple related operations | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L179) | +| `createErrorSolution` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/factory.ts#L11) | +| `createProblemResponse` | Create an RFC 9457 error Response from raw parameters | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L70) | +| `createSimpleError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/factory.ts#L29) | +| `defineError` | Define an error in the registry | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/types.ts#L98) | +| `ensureError` | Ensure a value is an Error while preserving the established identity contract for ordinary Error instances. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L837) | +| `errorToResponse` | Convert any error to an RFC 9457 Response | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L106) | +| `errorToRFC9457Response` | Convert any error to an RFC 9457 Response with environment-aware filtering | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L98) | +| `formatCLIError` | Format any error for CLI output | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/cli-error-boundary.ts#L163) | +| `formatErrorLog` | Log format for errors (matches the plan's log format spec) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L142) | +| `formatUserError` | Format error with plain text (existing behavior) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-formatter.ts#L85) | +| `fromError` | Decode legacy Veryfront error data attached by `toError()`. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/legacy-error-codec.ts#L17) | +| `getAllSlugs` | Get all registered slugs | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L72) | +| `getErrorBySlug` | Get an error definition by slug | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L58) | +| `getErrorMessage` | Extract error message from any error type | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L678) | +| `getErrorsByCategory` | Get all errors in a category | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry.ts#L65) | +| `getErrorSolution` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L44) | +| `handleErrorWithFallback` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L41) | +| `handleErrorWithFallbackSync` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L54) | +| `httpErrorBoundary` | Wrap a handler with error boundary that catches all errors and converts them to RFC 9457 Problem Details responses. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L55) | +| `identifyError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-identifier.ts#L26) | +| `isVeryfrontError` | Check if an error is a VeryfrontError with slug-based identity | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/http-error.ts#L96) | +| `logError` | Log a VeryfrontError with structured formatting | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/logging.ts#L96) | +| `logErrorWithMessage` | Log an error with a custom message prefix | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/logging.ts#L148) | +| `retryWithBackoff` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-handlers.ts#L149) | +| `safeFileRead` | Safe file read with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L150) | +| `safeFileStat` | Safe file stat with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L137) | +| `safeReadDir` | Safe directory read with logging | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L163) | +| `sanitizeTerminalDiagnosticText` | Prepare one untrusted diagnostic field for terminal or plain-text output. Apply framework-owned ANSI styling only after this sanitizer returns. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/safe-diagnostics.ts#L97) | +| `searchErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/index.ts#L48) | +| `toError` | Convert a VeryfrontErrorData (plain object) to a throwable Error instance. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/veryfront-error.ts#L622) | +| `withErrorContext` | Execute async operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L109) | +| `withErrorContextSync` | Execute sync operation with error logging and fallback | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-context.ts#L123) | +| `wrapErrorHandler` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/user-friendly/error-wrapper.ts#L26) | +| `wrapHandlerWithErrorBoundary` | Wrap a complete Handler object with error boundary | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/http-error-boundary.ts#L85) | +| `wrapUnknownError` | Return a detached VeryfrontError, preserving safe identity fields from valid VeryfrontError inputs | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/wrap-unknown.ts#L51) | +| `wrapWithContext` | Wrap an error with additional context | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/middleware/wrap-unknown.ts#L101) | ### Classes diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 228c2e8b5b..2bba39ca19 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L260) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L262) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L288) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L290) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L260) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L288) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L262) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L290) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | diff --git a/src/errors/index.ts b/src/errors/index.ts index 3d8dcd763d..48970736d8 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -206,6 +206,8 @@ export { withErrorContextSync, } from "./error-context.ts"; +export { sanitizeTerminalDiagnosticText } from "./safe-diagnostics.ts"; + export type { ErrorContext, ErrorHandlingOptions, LogLevel } from "./error-context.ts"; export { diff --git a/src/observability/README.md b/src/observability/README.md index a86b0ba6c0..0a12ac76b0 100644 --- a/src/observability/README.md +++ b/src/observability/README.md @@ -216,6 +216,13 @@ context values do not replace application control flow. for timeout, rejection, exceptions, or an invalid timeout; it never waits for a non-cooperative reporter after the deadline. +Reporter context is sanitized before capture. Public fields include +`boundary`, `method`, `processRole`, `requestId`, `spanId`, `traceId`, +`errorClass`, `level`, and scalar `attributes`. Tenant-authored build and +content compile failures are still captured, but Veryfront tags them with +`errorClass: "tenant-build"` and downgrades the default `level` to `"warning"`. +Sentry integrations consume that class as the `veryfront.error_class` tag. + Concrete reporters are separate extension packages. Sentry configuration and runtime setup are documented by `@veryfront/ext-observability-sentry`. diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index f219033542..79a161eabd 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -307,6 +307,61 @@ it("application error reporter downgrades tenant build errors to tagged warnings assertEquals(captures[14]?.context.errorClass, undefined); assertEquals(captures[14]?.context.level, undefined); }); + +it("application error reporter ignores inherited tenant build tags", () => { + const tenantBuildFailureTag = Symbol.for("veryfront.module-loader.tenant-build-failure"); + const previousDescriptor = Object.getOwnPropertyDescriptor( + Error.prototype, + tenantBuildFailureTag, + ); + Object.defineProperty(Error.prototype, tenantBuildFailureTag, { + configurable: true, + value: true, + }); + + try { + const captures: Array<{ error: unknown; context: SharedApplicationErrorContext }> = []; + setApplicationErrorReporter({ + capture(error, context) { + captures.push({ error, context }); + return "event-id"; + }, + flush: () => Promise.resolve(true), + }); + + assertEquals( + captureApplicationError(new Error("framework failed"), { boundary: "ssr.render" }), + "event-id", + ); + assertEquals(captures[0]?.context.errorClass, undefined); + assertEquals(captures[0]?.context.level, undefined); + + const accessorTagError = new Error("framework failed"); + let getterRead = false; + Object.defineProperty(accessorTagError, tenantBuildFailureTag, { + configurable: true, + get() { + getterRead = true; + return true; + }, + }); + + assertEquals( + captureApplicationError(accessorTagError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals(getterRead, false); + assertEquals(captures[1]?.context.errorClass, undefined); + assertEquals(captures[1]?.context.level, undefined); + } finally { + if (previousDescriptor) { + Object.defineProperty(Error.prototype, tenantBuildFailureTag, previousDescriptor); + } else { + delete (Error.prototype as { [tenantBuildFailureTag]?: unknown })[tenantBuildFailureTag]; + } + } +}); + it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { getPrototypeOf() { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index e6edae2bd0..5ce8790333 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -233,6 +233,12 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; */ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-build-failure"); +function hasOwnTrueSymbol(value: object, key: symbol): boolean { + const descriptor = Reflect.getOwnPropertyDescriptor(value, key); + return descriptor !== undefined && !descriptor.get && !descriptor.set && + "value" in descriptor && descriptor.value === true; +} + /** * Whether `error` describes tenant build/content failing to compile (a page * that does not build, MDX that does not parse) rather than a framework fault. @@ -245,11 +251,7 @@ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-buil function isTenantBuildError(error: unknown): boolean { try { if (error instanceof Error) { - if ( - (error as { [TENANT_BUILD_FAILURE_TAG]?: unknown })[TENANT_BUILD_FAILURE_TAG] === true - ) { - return true; - } + if (hasOwnTrueSymbol(error, TENANT_BUILD_FAILURE_TAG)) return true; } return isTenantSourceBuildError(error); } catch { diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index 95b726bd43..f8ba6c4587 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -19,11 +19,6 @@ import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classificatio const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); const TENANT_BUILD_FAILURE = Symbol.for("veryfront.module-loader.tenant-build-failure"); -type TaggedError = Error & { - [BUILD_FAILURE]?: true; - [TENANT_BUILD_FAILURE]?: true; -}; - /** * Modules are strict mode, so a plain assignment onto a frozen error throws. * These taggers run inside `catch` blocks, where a throw would replace the @@ -38,6 +33,12 @@ function defineTag(error: Error, tag: symbol): void { } } +function hasOwnTrueTag(error: Error, tag: symbol): boolean { + const descriptor = Reflect.getOwnPropertyDescriptor(error, tag); + return descriptor !== undefined && !descriptor.get && !descriptor.set && + "value" in descriptor && descriptor.value === true; +} + /** Tag `error` as a build failure and return it. */ export function markBuildFailure(error: unknown): unknown { if (error instanceof Error) { @@ -64,10 +65,10 @@ export function markTenantBuildFailure(error: unknown): unknown { /** True when `error` was raised while compiling or resolving project source. */ export function isBuildFailure(error: unknown): boolean { - return error instanceof Error && (error as TaggedError)[BUILD_FAILURE] === true; + return error instanceof Error && hasOwnTrueTag(error, BUILD_FAILURE); } /** True only for a build failure explicitly classified as tenant source. */ export function isTenantBuildFailure(error: unknown): boolean { - return error instanceof Error && (error as TaggedError)[TENANT_BUILD_FAILURE] === true; + return error instanceof Error && hasOwnTrueTag(error, TENANT_BUILD_FAILURE); } diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 1db7a7896e..c325ae6867 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -285,6 +285,64 @@ describe("module-loader/transformModuleWithDeps", () => { }); describe("module-loader/loadModule build-failure tagging", () => { + it("ignores inherited build-failure tags", () => { + const buildFailureTag = Symbol.for("veryfront.module-loader.build-failure"); + const tenantBuildFailureTag = Symbol.for("veryfront.module-loader.tenant-build-failure"); + const previousBuildDescriptor = Object.getOwnPropertyDescriptor( + Error.prototype, + buildFailureTag, + ); + const previousTenantDescriptor = Object.getOwnPropertyDescriptor( + Error.prototype, + tenantBuildFailureTag, + ); + Object.defineProperty(Error.prototype, buildFailureTag, { configurable: true, value: true }); + Object.defineProperty(Error.prototype, tenantBuildFailureTag, { + configurable: true, + value: true, + }); + + try { + const frameworkError = new Error("framework failed"); + assertEquals(isBuildFailure(frameworkError), false); + assertEquals(isTenantBuildFailure(frameworkError), false); + + const accessorTagError = new Error("framework failed"); + let buildGetterRead = false; + let tenantGetterRead = false; + Object.defineProperty(accessorTagError, buildFailureTag, { + configurable: true, + get() { + buildGetterRead = true; + return true; + }, + }); + Object.defineProperty(accessorTagError, tenantBuildFailureTag, { + configurable: true, + get() { + tenantGetterRead = true; + return true; + }, + }); + + assertEquals(isBuildFailure(accessorTagError), false); + assertEquals(isTenantBuildFailure(accessorTagError), false); + assertEquals(buildGetterRead, false); + assertEquals(tenantGetterRead, false); + } finally { + if (previousBuildDescriptor) { + Object.defineProperty(Error.prototype, buildFailureTag, previousBuildDescriptor); + } else { + delete (Error.prototype as { [buildFailureTag]?: unknown })[buildFailureTag]; + } + if (previousTenantDescriptor) { + Object.defineProperty(Error.prototype, tenantBuildFailureTag, previousTenantDescriptor); + } else { + delete (Error.prototype as { [tenantBuildFailureTag]?: unknown })[tenantBuildFailureTag]; + } + } + }); + // Compiling a real page module starts esbuild's child process; stop it so the // test does not leak the handle rather than opting out of the sanitizer. afterAll(async () => { diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts index b938ca2b7f..3d569d333d 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts @@ -12,6 +12,29 @@ import { // it collects, opt out of the bound explicitly. const UNBOUNDED = Number.MAX_SAFE_INTEGER; +function countStartsWithCalls(callback: () => void): number { + const original = String.prototype.startsWith; + let calls = 0; + Object.defineProperty(String.prototype, "startsWith", { + configurable: true, + writable: true, + value(this: string, searchString: string, position?: number) { + calls++; + return original.call(this, searchString, position); + }, + }); + try { + callback(); + } finally { + Object.defineProperty(String.prototype, "startsWith", { + configurable: true, + writable: true, + value: original, + }); + } + return calls; +} + describe("transforms/mdx/esm-module-loader/utils/source-spans", () => { describe("replaceSourceSpans", () => { it("replaces a single span", () => { @@ -180,6 +203,48 @@ import real from "./real.js";`, ); }); + it("keeps JSX closing-tag checks linear for repeated angle assertions", () => { + const repeatedAssertions = Array.from( + { length: 3_000 }, + (_, index) => `const value${index} = input${index};`, + ).join("\n"); + const source = `${repeatedAssertions}\nimport real from "./real.js";`; + let paths: string[] = []; + + const startsWithCalls = countStartsWithCalls(() => { + paths = findStaticImportFromSpans(source, matchRelative, UNBOUNDED) + .map((span) => span.path); + }); + + assertEquals(paths, ["./real.js"]); + assert( + startsWithCalls < source.length * 3, + `Expected a linear static import scan, got ${startsWithCalls} startsWith calls ` + + `for ${source.length} source characters`, + ); + }); + + it("keeps side-effect JSX closing-tag checks linear for repeated angle assertions", () => { + const repeatedAssertions = Array.from( + { length: 3_000 }, + (_, index) => `const value${index} = input${index};`, + ).join("\n"); + const source = `${repeatedAssertions}\nimport "./real.js";`; + let paths: string[] = []; + + const startsWithCalls = countStartsWithCalls(() => { + paths = findStaticSideEffectImportSpans(source, matchRelative, UNBOUNDED) + .map((span) => span.path); + }); + + assertEquals(paths, ["./real.js"]); + assert( + startsWithCalls < source.length * 3, + `Expected a linear side-effect import scan, got ${startsWithCalls} startsWith calls ` + + `for ${source.length} source characters`, + ); + }); + it("recognizes every ECMAScript line terminator", () => { for (const lineTerminator of ["\r", "\u2028", "\u2029"]) { assertEquals( diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index f3810d6b83..30610d99db 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -239,6 +239,8 @@ interface JsxTagEnd { selfClosing: boolean; } +type JsxClosingTagIndex = ReadonlyMap; + function jsxTagNameCharacterLength(source: string, index: number): number { const character = identifierCharacterAt(source, index); if (character !== undefined && isIdentifierChar(character)) return character.length; @@ -291,28 +293,85 @@ function skipJsxTag(source: string, index: number): JsxTagEnd | null { return null; } -function hasClosingJsxTag(source: string, index: number, name: string): boolean { - const prefix = name === "" ? "" : `(); + + for (let cursor = 0; cursor < source.length;) { const skipped = skipIgnored(source, cursor); if (skipped !== cursor) { cursor = skipped; continue; } - if ( - source.startsWith(prefix, cursor) && - (name === "" || /[\s>]/.test(source[cursor + prefix.length] ?? "")) - ) { - if (name !== "" && isRegexClosingTagLookalike(source, cursor)) { + + if (source[cursor] === "<" && source[cursor + 1] === "/") { + const tag = skipJsxTag(source, cursor); + if (tag !== null) { + if (tag.name !== "" && isRegexClosingTagLookalike(source, cursor)) { + cursor = skipRegexLiteral(source, cursor + 1); + continue; + } + const positions = tags.get(tag.name); + if (positions === undefined) tags.set(tag.name, [cursor]); + else positions.push(cursor); + cursor = tag.end; + continue; + } + + if (source.startsWith("", cursor)) { + const positions = tags.get(""); + if (positions === undefined) tags.set("", [cursor]); + else positions.push(cursor); + cursor += "".length; + continue; + } + + const nameStart = cursor + 2; + let nameEnd = nameStart; + for (let length = jsxTagNameCharacterLength(source, nameEnd); length > 0;) { + nameEnd += length; + length = jsxTagNameCharacterLength(source, nameEnd); + } + const name = source.slice(nameStart, nameEnd); + if (name !== "" && /[\s>]/.test(source[nameEnd] ?? "")) { + if (isRegexClosingTagLookalike(source, cursor)) { + cursor = skipRegexLiteral(source, cursor + 1); + continue; + } + const positions = tags.get(name); + if (positions === undefined) tags.set(name, [cursor]); + else positions.push(cursor); + cursor = nameEnd; + continue; + } + + if (isRegexClosingTagLookalike(source, cursor)) { cursor = skipRegexLiteral(source, cursor + 1); continue; } - return true; } cursor++; } - return false; + return tags; } function isRegexClosingTagLookalike(source: string, index: number): boolean { @@ -1236,14 +1295,26 @@ function assertMaxMatches(maxMatches: number): void { } } -export function findStaticImportFromSpans( - source: string, - matcher: SpecifierMatcher, - maxMatches: number, -): StaticImportSpan[] { - assertMaxMatches(maxMatches); +type StaticStatementScanContext = { + cursor: number; + isImport: boolean; + isExport: boolean; + afterKeyword: number; + keywordLength: number; + openParens: OpenParenContext[]; +}; + +type StaticStatementScanAction = { + cursor: number; + atStatementStart: boolean; + previousTokenIndex: number; + done?: boolean; +}; - const spans: StaticImportSpan[] = []; +function scanStaticStatementKeywords( + source: string, + onStatementKeyword: (context: StaticStatementScanContext) => StaticStatementScanAction, +): void { let cursor = 0; let atStatementStart = true; const openBraces: OpenBraceContext[] = []; @@ -1254,6 +1325,7 @@ export function findStaticImportFromSpans( let jsxDepth = 0; let inJsxText = false; const jsxExpressionStack: Array<{ braceDepth: number; parentDepth: number }> = []; + const jsxClosingTags = indexClosingJsxTags(source); while (cursor < source.length) { const char = source[cursor]; @@ -1282,7 +1354,10 @@ export function findStaticImportFromSpans( } } else if (char === "<" && canStartJsxElement(source, cursor, previousTokenIndex)) { const tag = skipJsxTag(source, cursor); - if (tag !== null && (tag.selfClosing || hasClosingJsxTag(source, tag.end, tag.name))) { + if ( + tag !== null && + (tag.selfClosing || hasClosingJsxTag(tag.end, tag.name, jsxClosingTags)) + ) { if (!tag.selfClosing) { jsxDepth++; inJsxText = true; @@ -1384,40 +1459,77 @@ export function findStaticImportFromSpans( const keywordLength = isImport ? "import".length : "export".length; const afterKeyword = skipWhitespaceAndComments(source, cursor + keywordLength); + const action = onStatementKeyword({ + cursor, + isImport, + isExport, + afterKeyword, + keywordLength, + openParens, + }); + cursor = action.cursor; + atStatementStart = action.atStatementStart; + previousTokenIndex = action.previousTokenIndex; + if (action.done) return; + } +} + +export function findStaticImportFromSpans( + source: string, + matcher: SpecifierMatcher, + maxMatches: number, +): StaticImportSpan[] { + assertMaxMatches(maxMatches); + + const spans: StaticImportSpan[] = []; + scanStaticStatementKeywords(source, ({ + cursor, + isImport, + isExport, + afterKeyword, + keywordLength, + openParens, + }) => { if (isImport && source[afterKeyword] === "(") { - atStatementStart = false; openParens.push({ index: afterKeyword, isControlCondition: false, isForHeader: false, hasSemicolon: false, }); - previousTokenIndex = afterKeyword; - cursor = afterKeyword + 1; - continue; + return { + cursor: afterKeyword + 1, + atStatementStart: false, + previousTokenIndex: afterKeyword, + }; } if (isExport && !canExportHaveFromClause(source, afterKeyword)) { - atStatementStart = false; - previousTokenIndex = cursor + keywordLength - 1; - cursor = afterKeyword; - continue; + return { + cursor: afterKeyword, + atStatementStart: false, + previousTokenIndex: cursor + keywordLength - 1, + }; } const span = findFromSpan(source, afterKeyword, matcher); if (span) { spans.push(span); - if (spans.length >= maxMatches) return spans; - atStatementStart = false; - previousTokenIndex = span.end - 1; - cursor = span.end; - continue; + return { + cursor: span.end, + atStatementStart: false, + previousTokenIndex: span.end - 1, + done: spans.length >= maxMatches, + }; } - atStatementStart = true; - cursor = nextStatementCursor(source, afterKeyword); - previousTokenIndex = Math.max(previousTokenIndex, cursor - 1); - } + const nextCursor = nextStatementCursor(source, afterKeyword); + return { + cursor: nextCursor, + atStatementStart: true, + previousTokenIndex: Math.max(cursor + keywordLength - 1, nextCursor - 1), + }; + }); return spans; } @@ -1673,156 +1785,32 @@ export function findStaticSideEffectImportSpans( assertMaxMatches(maxMatches); const spans: StaticImportSpan[] = []; - let cursor = 0; - let atStatementStart = true; - const openBraces: OpenBraceContext[] = []; - const matchingOpenBraces = new Map(); - const openParens: OpenParenContext[] = []; - const matchingOpenParens = new Map(); - let previousTokenIndex = -1; - let jsxDepth = 0; - let inJsxText = false; - const jsxExpressionStack: Array<{ braceDepth: number; parentDepth: number }> = []; - - while (cursor < source.length) { - const char = source[cursor]; - - if (inJsxText) { - if (char === "<") { - const tag = skipJsxTag(source, cursor); - if (tag !== null) { - const closing = source[cursor + 1] === "/"; - if (closing) jsxDepth = Math.max(0, jsxDepth - 1); - else if (!tag.selfClosing) jsxDepth++; - const expressionParentDepth = jsxExpressionStack.at(-1)?.parentDepth ?? 0; - inJsxText = jsxDepth > expressionParentDepth; - atStatementStart = false; - previousTokenIndex = tag.end - 1; - cursor = tag.end; - continue; - } - } - if (char === "{") { - jsxExpressionStack.push({ braceDepth: 0, parentDepth: jsxDepth }); - inJsxText = false; - } else { - cursor++; - continue; - } - } else if (char === "<" && canStartJsxElement(source, cursor, previousTokenIndex)) { - const tag = skipJsxTag(source, cursor); - if (tag !== null && (tag.selfClosing || hasClosingJsxTag(source, tag.end, tag.name))) { - if (!tag.selfClosing) { - jsxDepth++; - inJsxText = true; - } - atStatementStart = false; - previousTokenIndex = tag.end - 1; - cursor = tag.end; - continue; - } - } - - const skipped = skipExpressionIgnored( - source, - cursor, - 0, - 0, - matchingOpenBraces, - matchingOpenParens, - openParens.at(-1), - previousTokenIndex, - ); - if (skipped !== cursor) { - if (char === "/" && source[cursor + 1] === "/") atStatementStart = true; - else if (char === "/" && source[cursor + 1] === "*") { - atStatementStart = atStatementStart || containsLineTerminator(source, cursor, skipped); - } else atStatementStart = false; - previousTokenIndex = tokenIndexAfterIgnored( - source, - cursor, - skipped, - previousTokenIndex, - ); - cursor = skipped; - continue; - } - - if (char === "{") { - const expression = jsxExpressionStack.at(-1); - if (expression !== undefined) expression.braceDepth++; - openBraces.push({ index: cursor, previousTokenIndex }); - atStatementStart = false; - previousTokenIndex = cursor; - cursor++; - continue; - } - if (char === "}") { - const expression = jsxExpressionStack.at(-1); - if (expression !== undefined) { - expression.braceDepth--; - if (expression.braceDepth === 0) { - jsxExpressionStack.pop(); - inJsxText = jsxDepth > 0; - } - } - const openBrace = openBraces.pop(); - if (openBrace !== undefined) matchingOpenBraces.set(cursor, openBrace); - atStatementStart = true; - previousTokenIndex = cursor; - cursor++; - continue; - } - if (char === "(") { - openParens.push(openParenContext(source, cursor, previousTokenIndex)); - atStatementStart = false; - previousTokenIndex = cursor; - cursor++; - continue; - } - if (char === ")") { - const openParen = openParens.pop(); - if (openParen !== undefined) matchingOpenParens.set(cursor, openParen); - atStatementStart = false; - previousTokenIndex = cursor; - cursor++; - continue; - } - if (char === ";" && openParens.at(-1)?.isForHeader) { - openParens.at(-1)!.hasSemicolon = true; - } - if (char === ";" || (char !== undefined && isLineTerminator(char))) { - atStatementStart = true; - if (char === ";") previousTokenIndex = cursor; - cursor++; - continue; - } - if (/\s/.test(char ?? "")) { - cursor++; - continue; - } - - if (!isStatementKeywordAt(source, cursor, "import", atStatementStart)) { - atStatementStart = false; - previousTokenIndex = cursor; - cursor++; - continue; + scanStaticStatementKeywords(source, ({ cursor, isImport }) => { + if (!isImport) { + return { + cursor: cursor + 1, + atStatementStart: false, + previousTokenIndex: cursor, + }; } const literalIndex = skipWhitespaceAndComments(source, cursor + "import".length); const literal = readLiteralSpecifier(source, literalIndex); if (!literal) { - atStatementStart = true; - cursor = nextStatementCursor(source, literalIndex); - previousTokenIndex = Math.max(previousTokenIndex, cursor - 1); - continue; + const nextCursor = nextStatementCursor(source, literalIndex); + return { + cursor: nextCursor, + atStatementStart: true, + previousTokenIndex: Math.max(cursor + "import".length - 1, nextCursor - 1), + }; } if (!isSideEffectImportTerminated(source, literal.end)) { - atStatementStart = false; - previousTokenIndex = literal.end - 1; - cursor = literal.end; - continue; + return { + cursor: literal.end, + atStatementStart: false, + previousTokenIndex: literal.end - 1, + }; } const matchedPath = matcher(literal.specifier); @@ -1833,13 +1821,15 @@ export function findStaticSideEffectImportSpans( start: cursor, end: literal.end, }); - if (spans.length >= maxMatches) return spans; } - atStatementStart = false; - previousTokenIndex = literal.end - 1; - cursor = literal.end; - } + return { + cursor: literal.end, + atStatementStart: false, + previousTokenIndex: literal.end - 1, + done: spans.length >= maxMatches, + }; + }); return spans; } From 0e25fadc1e3a371661f9a973dea710494e226e45 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 21:58:09 +0200 Subject: [PATCH 098/104] Keep tenant build classification honest after intrinsic poisoning Tenant modules can mutate process globals before later framework error classification runs. Capture the Reflect.getOwnPropertyDescriptor intrinsic inside both tag-reader modules so false own tag descriptors synthesized from a poisoned global cannot convert untagged framework errors into tenant build failures. The tag writers continue using Object.defineProperty because the regression only proves read-time descriptor poisoning creates the false-positive downgrade path. Constraint: Tenant-authored modules can mutate process globals before later error classification. Rejected: Capture Object.defineProperty in tag writers | regressions prove read-time descriptor poisoning is sufficient for the false positives covered here. Confidence: high Scope-risk: narrow Tested: poisoned Reflect red-first regressions, focused touched tests, changed-file deno check, deno fmt --check, deno task lint, direct typecheck entrypoints, deno task test:unit:parallel Not-tested: deno task typecheck wrapper remains blocked before typechecking by stale templates/manifest.generated.ts in the exact PR head --- src/observability/application-errors.test.ts | 39 +++++++++++++++++++ src/observability/application-errors.ts | 3 +- .../module-loader/build-failure.ts | 4 +- .../orchestrator/module-loader/index.test.ts | 25 ++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index 79a161eabd..a29196b015 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -362,6 +362,45 @@ it("application error reporter ignores inherited tenant build tags", () => { } }); +it("application error reporter ignores poisoned Reflect descriptor lookups for tenant tags", () => { + const previousDescriptor = Object.getOwnPropertyDescriptor( + Reflect, + "getOwnPropertyDescriptor", + ); + if (!previousDescriptor || typeof previousDescriptor.value !== "function") { + throw new Error("Expected Reflect.getOwnPropertyDescriptor descriptor"); + } + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", { + ...previousDescriptor, + value: () => ({ + configurable: true, + enumerable: false, + value: true, + writable: false, + }), + }); + + try { + const captures: Array<{ error: unknown; context: SharedApplicationErrorContext }> = []; + setApplicationErrorReporter({ + capture(error, context) { + captures.push({ error, context }); + return "event-id"; + }, + flush: () => Promise.resolve(true), + }); + + assertEquals( + captureApplicationError(new Error("framework failed"), { boundary: "ssr.render" }), + "event-id", + ); + assertEquals(captures[0]?.context.errorClass, undefined); + assertEquals(captures[0]?.context.level, undefined); + } finally { + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", previousDescriptor); + } +}); + it("application error capture failures never replace application control flow", () => { const hostile = new Proxy({}, { getPrototypeOf() { diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 5ce8790333..5501eedd2a 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -34,6 +34,7 @@ export type ApplicationErrorReporterLifecycle = { }; const MAX_APPLICATION_ERROR_SERVICE_NAME_LENGTH = 255; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; let reporter: ApplicationErrorReporter | undefined; let reporterOwner: symbol | undefined; @@ -234,7 +235,7 @@ const TENANT_BUILD_ERROR_CLASS = "tenant-build"; const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-build-failure"); function hasOwnTrueSymbol(value: object, key: symbol): boolean { - const descriptor = Reflect.getOwnPropertyDescriptor(value, key); + const descriptor = ReflectGetOwnPropertyDescriptor(value, key); return descriptor !== undefined && !descriptor.get && !descriptor.set && "value" in descriptor && descriptor.value === true; } diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index f8ba6c4587..890bbca37d 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -16,6 +16,8 @@ import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; + const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); const TENANT_BUILD_FAILURE = Symbol.for("veryfront.module-loader.tenant-build-failure"); @@ -34,7 +36,7 @@ function defineTag(error: Error, tag: symbol): void { } function hasOwnTrueTag(error: Error, tag: symbol): boolean { - const descriptor = Reflect.getOwnPropertyDescriptor(error, tag); + const descriptor = ReflectGetOwnPropertyDescriptor(error, tag); return descriptor !== undefined && !descriptor.get && !descriptor.set && "value" in descriptor && descriptor.value === true; } diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index c325ae6867..43cd69412b 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -343,6 +343,31 @@ describe("module-loader/loadModule build-failure tagging", () => { } }); + it("ignores poisoned Reflect descriptor lookups when reading build-failure tags", () => { + const previousDescriptor = Object.getOwnPropertyDescriptor( + Reflect, + "getOwnPropertyDescriptor", + ); + assert(previousDescriptor); + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", { + ...previousDescriptor, + value: () => ({ + configurable: true, + enumerable: false, + value: true, + writable: false, + }), + }); + + try { + const frameworkError = new Error("framework failed"); + assertEquals(isBuildFailure(frameworkError), false); + assertEquals(isTenantBuildFailure(frameworkError), false); + } finally { + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", previousDescriptor); + } + }); + // Compiling a real page module starts esbuild's child process; stop it so the // test does not leak the handle rather than opting out of the sanitizer. afterAll(async () => { From ab0b645f0feb37f75d2e691892f42637167caebb Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 22:20:32 +0200 Subject: [PATCH 099/104] Keep observability references aligned with hardened classification The captured intrinsic shifts public source anchors by one line. Regenerating the API reference preserves the exact generated contract required by the lint gate. Constraint: Generated API references must be produced with pinned Deno 2.7.7 on PATH. Confidence: high Scope-risk: narrow Directive: Regenerate public references whenever observability source anchors move. Tested: Pinned docs generation, API-reference freshness check for all 44 files, and git diff check. Not-tested: No additional runtime behavior; the parent exact head already passed the full pre-push suite. --- docs/api-reference/veryfront/observability.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 2bba39ca19..7f6eddb6d4 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L262) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L263) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L290) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L291) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -58,7 +58,7 @@ const result = await withSpan("load-data", async () => { | `getMetricsState` | State for get metrics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L38) | | `getTraceContext` | Context for get trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L500) | | `initAutoInstrumentation` | Initialize automatic instrumentation wrappers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/orchestrator.ts#L15) | -| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L150) | +| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L151) | | `initializeOTLP` | Initialize OTLP tracing export. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L113) | | `initMetrics` | Initialize metrics collection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L20) | | `initTracing` | Initialize tracing for the current runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L18) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L262) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L290) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L263) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L291) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | From 9048ec507cceffb04d7a7448e813d7d26fa8b3e0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sat, 15 Aug 2026 23:29:28 +0200 Subject: [PATCH 100/104] Preserve root dynamic import suffixes in MDX modules Root VF module imports now split query and fragment suffixes with the shared specifier suffix helper before fetch and cache resolution, then append the suffix back onto the generated file import. This keeps root dynamic imports aligned with the nested import path behavior without changing the fetched module path. Constraint: PR #3723 review thread identified root dynamic import query and fragment suffixes as the remaining unresolved path. Rejected: Treating query or fragment suffixes as part of the cached module path | fragments were fetched as literal filenames and queries were not restored on the runtime import. Confidence: high Scope-risk: narrow Tested: Deno 2.7.7 focused loader/root writer tests: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/loader-helpers.test.ts src/transforms/mdx/esm-module-loader/module-writer.test.ts Tested: Deno 2.7.7 scanner and nested fetcher tests: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/utils/source-spans.test.ts src/transforms/mdx/esm-module-loader/module-fetcher/nested-imports.test.ts Tested: Deno 2.7.7 HTTP fetcher and alias/static import tests: deno test --preload=src/testing/preload.ts --no-check --allow-all src/transforms/mdx/esm-module-loader/module-fetcher/http-fetcher.test.ts src/transforms/mdx/esm-module-loader/transforms/alias-imports.test.ts Tested: Deno 2.7.7 fmt check for touched files Tested: Deno 2.7.7 lint for touched files Tested: git diff --check Tested: Deno 2.7.7 .husky/pre-push Not-tested: Playwright E2E was not run; this change is isolated to MDX module import rewriting and covered by loader/fetcher unit tests plus the repository pre-push hook. --- .../mdx/esm-module-loader/loader-helpers.ts | 30 ++++++---- .../esm-module-loader/module-writer.test.ts | 60 +++++++++++++++++++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/transforms/mdx/esm-module-loader/loader-helpers.ts b/src/transforms/mdx/esm-module-loader/loader-helpers.ts index cdd8479ab4..ca42f65bb8 100644 --- a/src/transforms/mdx/esm-module-loader/loader-helpers.ts +++ b/src/transforms/mdx/esm-module-loader/loader-helpers.ts @@ -37,6 +37,7 @@ import { MAX_MDX_MODULE_IMPORTS_PER_FILE, MAX_MDX_MODULE_TRANSFORM_CONCURRENCY, } from "./module-fetcher/limits.ts"; +import { splitSpecifierSuffix } from "#veryfront/transforms/shared/specifier-suffix.ts"; /** * Check which framework bundles are missing from disk. @@ -116,12 +117,13 @@ export function findVfModuleImports( ): Array<{ original: string; path: string; + suffix: string; start: number; end: number; isDynamic?: boolean; }> { const matchVfModule = (specifier: string): string | null => - specifier.match(/^\/?(_vf_modules\/[^?]+)(?:\?.*)?$/)?.[1] ?? null; + specifier.match(/^\/?(_vf_modules\/.+)$/)?.[1] ?? null; const staticImports = findStaticImportFromSpans( code, matchVfModule, @@ -133,7 +135,12 @@ export function findVfModuleImports( MAX_MDX_MODULE_IMPORTS_PER_FILE + 1, ).map((importSpan) => ({ ...importSpan, isDynamic: true })); - return [...staticImports, ...dynamicImports].sort((left, right) => left.start - right.start); + return [...staticImports, ...dynamicImports] + .map((importSpan) => { + const { path, suffix } = splitSpecifierSuffix(importSpan.path); + return { ...importSpan, path, suffix }; + }) + .sort((left, right) => left.start - right.start); } /** @@ -144,6 +151,7 @@ export async function processVfModuleImports( imports: Array<{ original: string; path: string; + suffix?: string; start: number; end: number; isDynamic?: boolean; @@ -206,7 +214,7 @@ export async function processVfModuleImports( const results = await parallelMap( imports, - async ({ original, path, start, end, isDynamic }, index) => { + async ({ original, path, suffix, start, end, isDynamic }, index) => { return await withSpan( SpanNames.MDX_FETCH_MODULE, async () => { @@ -232,7 +240,7 @@ export async function processVfModuleImports( path, durationMs: (performance.now() - moduleStart).toFixed(1), }); - return { original, start, end, filePath, path, isDynamic, deferredError }; + return { original, start, end, filePath, path, suffix, isDynamic, deferredError }; }, { "mdx.module_path": path, @@ -252,16 +260,15 @@ export async function processVfModuleImports( const replacements: SourceSpanReplacement[] = []; for ( - const { original, start, end, filePath, path, isDynamic, deferredError } of results + const { original, start, end, filePath, path, suffix, isDynamic, deferredError } of results ) { if (filePath) { + const importTarget = toImportStringLiteral(`file://${filePath}${suffix ?? ""}`); replacements.push({ start, end, expected: original, - replacement: isDynamic - ? toImportStringLiteral(`file://${filePath}`) - : `from "file://${filePath}"`, + replacement: isDynamic ? importTarget : `from ${importTarget}`, }); continue; } @@ -279,7 +286,7 @@ export async function processVfModuleImports( start, end, expected: original, - replacement: toImportStringLiteral(`file://${deferredPath}`), + replacement: toImportStringLiteral(`file://${deferredPath}${suffix ?? ""}`), }); continue; } @@ -297,13 +304,12 @@ export async function processVfModuleImports( const stubPath = await createStubModule(path, code, original, context.esmCacheDir!); if (stubPath) { + const importTarget = toImportStringLiteral(`file://${stubPath}${suffix ?? ""}`); replacements.push({ start, end, expected: original, - replacement: isDynamic - ? toImportStringLiteral(`file://${stubPath}`) - : `from "file://${stubPath}"`, + replacement: isDynamic ? importTarget : `from ${importTarget}`, }); } } diff --git a/src/transforms/mdx/esm-module-loader/module-writer.test.ts b/src/transforms/mdx/esm-module-loader/module-writer.test.ts index 842207de2d..b625bf43d9 100644 --- a/src/transforms/mdx/esm-module-loader/module-writer.test.ts +++ b/src/transforms/mdx/esm-module-loader/module-writer.test.ts @@ -166,6 +166,66 @@ describe("MDX root module cache identity", () => { }); describe("MDX root dynamic imports", () => { + for ( + const { label, suffix } of [ + { label: "query", suffix: "?raw" }, + { label: "fragment", suffix: "#variant" }, + { label: "query and fragment", suffix: "?raw#variant" }, + ] as const + ) { + it(`preserves a root dynamic import ${label} suffix`, async () => { + const moduleName = `RootSuffix-${label.replaceAll(" ", "-")}-${crypto.randomUUID()}.js`; + const expectedPath = `/_vf_modules/${moduleName}`; + const projectDir = await Deno.makeTempDir({ prefix: "vf-mdx-root-dynamic-suffix-" }); + const fetchedPaths: string[] = []; + + try { + const mod = await withMockFetch( + (input, init) => { + const request = new Request(input, init); + const url = new URL(request.url); + fetchedPaths.push(url.pathname); + if (url.pathname !== expectedPath || url.searchParams.get("ssr") !== "true") { + return Promise.resolve(new Response("missing", { status: 404 })); + } + return Promise.resolve( + new Response("export default import.meta.url;", { + headers: { "content-type": "application/javascript" }, + }), + ); + }, + () => + mdxRenderer.loadModuleESM( + `export async function loadVariant() { + return (await import("${expectedPath}${suffix}")).default; + } + export default function Root() { return null; }`, + { + adapter: denoAdapter, + projectId: `project-${crypto.randomUUID()}`, + projectDir, + projectSlug: "root-dynamic-suffix", + contentSourceId: `source-${crypto.randomUUID()}`, + isLocalProject: true, + }, + ), + ); + const loadVariant = (mod as unknown as { + loadVariant(): Promise; + }).loadVariant; + + const importedUrl = await loadVariant(); + assertEquals(fetchedPaths, [expectedPath]); + assertEquals(importedUrl.endsWith(suffix), true); + } finally { + mdxRenderer.clearCache(); + await Deno.remove(projectDir, { recursive: true }); + const esbuild = await import("veryfront/extensions/bundler"); + await esbuild.stop(); + } + }); + } + it("defers a missing strict alias import until its branch executes", async () => { const missingModule = `MissingRoot-${crypto.randomUUID()}`; const projectDir = await Deno.makeTempDir({ prefix: "vf-mdx-root-dynamic-" }); From 3ca25c24d7f2dc6f7dfbaead2d659ecaecc7998e Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 00:11:45 +0200 Subject: [PATCH 101/104] Keep tenant build markers honest after primordial poisoning Tenant code can mutate shared globals and error prototypes before framework failures are classified. Read frontmatter and tenant-context markers only from own data descriptors, capture marker and Set intrinsics at module initialization, and fail closed when proxy inspection throws. Constraint: Framework failures must not be downgraded to tenant warnings by prototype pollution or poisoned globals Rejected: Keep direct property and collection method calls | inherited values and mutable primordials can forge tenant classification Confidence: high Scope-risk: narrow Reversibility: clean Directive: Preserve captured intrinsic and own-data checks at marker classification seams Tested: Focused compiler, observability, Sentry policy, module-loader, and root suffix suites, 78 passed with 86 steps Tested: Changed-file format, lint, typecheck, and diff checks Tested: Full pinned pre-push suite, 3855 unit tests with 28904 steps, 10 cwd tests with 197 steps, and 2 cwd exclusion tests with 2 steps Not-tested: Hosted CI on the pushed commit --- src/errors/tenant-classification.test.ts | 86 ++++++++++++ src/errors/tenant-classification.ts | 21 ++- .../module-loader/build-failure.ts | 3 +- .../orchestrator/module-loader/index.test.ts | 37 ++++- .../compiler/frontmatter-extractor.test.ts | 128 +++++++++++++++++- .../mdx/compiler/frontmatter-extractor.ts | 17 ++- .../mdx/compiler/mdx-compiler.test.ts | 58 +++++++- 7 files changed, 340 insertions(+), 10 deletions(-) create mode 100644 src/errors/tenant-classification.test.ts diff --git a/src/errors/tenant-classification.test.ts b/src/errors/tenant-classification.test.ts new file mode 100644 index 0000000000..485d91831b --- /dev/null +++ b/src/errors/tenant-classification.test.ts @@ -0,0 +1,86 @@ +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { BUILD_FAILED, COMPILATION_ERROR, MDX_COMPILE_ERROR } from "./error-registry/build.ts"; +import { isTenantSourceBuildError } from "./tenant-classification.ts"; + +describe("errors/tenant-classification", () => { + it("uses the Set membership intrinsic captured during module initialization", () => { + const previous = Object.getOwnPropertyDescriptor(Set.prototype, "has"); + if (!previous || typeof previous.value !== "function") { + throw new Error("Expected Set.prototype.has descriptor"); + } + Object.defineProperty(Set.prototype, "has", { + ...previous, + value: () => true, + }); + + try { + assertEquals(isTenantSourceBuildError(BUILD_FAILED.create()), false); + assertEquals(isTenantSourceBuildError(MDX_COMPILE_ERROR.create()), true); + } finally { + Object.defineProperty(Set.prototype, "has", previous); + } + }); + + it("requires an own data context marker without invoking accessors", () => { + const inheritedContext = Object.create({ tenantBuildFailure: true }); + assertEquals( + isTenantSourceBuildError(COMPILATION_ERROR.create({ context: inheritedContext })), + false, + ); + + let getterRead = false; + const accessorContext = Object.defineProperty({}, "tenantBuildFailure", { + configurable: true, + get() { + getterRead = true; + return true; + }, + }); + const previousDescriptorValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + Object.defineProperty(Object.prototype, "value", { + configurable: true, + value: true, + }); + try { + assertEquals( + isTenantSourceBuildError(COMPILATION_ERROR.create({ context: accessorContext })), + false, + ); + assertEquals(getterRead, false); + } finally { + if (previousDescriptorValue) { + Object.defineProperty(Object.prototype, "value", previousDescriptorValue); + } else { + delete (Object.prototype as { value?: unknown }).value; + } + } + }); + + it("ignores Object prototype pollution while preserving explicit context markers", () => { + const previous = Object.getOwnPropertyDescriptor(Object.prototype, "tenantBuildFailure"); + Object.defineProperty(Object.prototype, "tenantBuildFailure", { + configurable: true, + value: true, + }); + + try { + assertEquals( + isTenantSourceBuildError(COMPILATION_ERROR.create({ context: {} })), + false, + ); + assertEquals( + isTenantSourceBuildError( + COMPILATION_ERROR.create({ context: { tenantBuildFailure: true } }), + ), + true, + ); + } finally { + if (previous) { + Object.defineProperty(Object.prototype, "tenantBuildFailure", previous); + } else { + delete (Object.prototype as { tenantBuildFailure?: unknown }).tenantBuildFailure; + } + } + }); +}); diff --git a/src/errors/tenant-classification.ts b/src/errors/tenant-classification.ts index 5e9a86652d..7742a07b9e 100644 --- a/src/errors/tenant-classification.ts +++ b/src/errors/tenant-classification.ts @@ -11,6 +11,11 @@ import { snapshotVeryfrontError } from "./types.ts"; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; +const SetPrototypeHas = Set.prototype.has; + /** * BUILD registry slugs that describe tenant source failing to compile, as * opposed to framework cache/bundle/asset infrastructure failing in the same @@ -22,6 +27,17 @@ const TENANT_BUILD_ERROR_SLUGS = new Set([ "markdown-compile-error", ]); +function hasOwnTrueDataProperty(value: object, key: PropertyKey): boolean { + try { + const descriptor = ReflectGetOwnPropertyDescriptor(value, key); + return descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true && + descriptor.value === true; + } catch { + return false; + } +} + /** * Whether `error` describes tenant source or content failing to build (a page * that does not compile, MDX that does not parse) rather than a framework @@ -41,9 +57,10 @@ export function isTenantSourceBuildError(error: unknown): boolean { const errorContext = snapshot.context; if ( typeof errorContext === "object" && errorContext !== null && - (errorContext as { tenantBuildFailure?: unknown }).tenantBuildFailure === true + hasOwnTrueDataProperty(errorContext, "tenantBuildFailure") ) { return true; } - return snapshot.category === "BUILD" && TENANT_BUILD_ERROR_SLUGS.has(snapshot.slug); + return snapshot.category === "BUILD" && + ReflectApply(SetPrototypeHas, TENANT_BUILD_ERROR_SLUGS, [snapshot.slug]) === true; } diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index 890bbca37d..a79f6ec048 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -16,6 +16,7 @@ import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; +const ObjectDefineProperty = Object.defineProperty; const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); @@ -28,7 +29,7 @@ const TENANT_BUILD_FAILURE = Symbol.for("veryfront.module-loader.tenant-build-fa */ function defineTag(error: Error, tag: symbol): void { try { - Object.defineProperty(error, tag, { value: true, configurable: true }); + ObjectDefineProperty(error, tag, { value: true, configurable: true }); } catch { // Sealed or non-configurable: the error stays untagged, which degrades to // the pre-classification behavior rather than destroying the error. diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 43cd69412b..2b675eb0ed 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -20,7 +20,12 @@ import { transformModuleWithDeps, } from "./index.ts"; import { buildModuleTransformCacheVariant, getModuleCacheKey } from "./module-cache-lookup.ts"; -import { isBuildFailure, isTenantBuildFailure } from "./build-failure.ts"; +import { + isBuildFailure, + isTenantBuildFailure, + markBuildFailure, + markTenantBuildFailure, +} from "./build-failure.ts"; async function withModuleLoaderFixture( files: Record, @@ -368,6 +373,36 @@ describe("module-loader/loadModule build-failure tagging", () => { } }); + it("uses the definition intrinsic captured during module initialization", () => { + const tenantBuildFailureTag = Symbol.for("veryfront.module-loader.tenant-build-failure"); + const defineProperty = Object.defineProperty; + const previous = Object.getOwnPropertyDescriptor(Object, "defineProperty"); + if (!previous || typeof previous.value !== "function") { + throw new Error("Expected Object.defineProperty descriptor"); + } + defineProperty(Object, "defineProperty", { + ...previous, + value: (target: object, tag: PropertyKey, descriptor: PropertyDescriptor) => { + defineProperty(target, tenantBuildFailureTag, { configurable: true, value: true }); + return defineProperty(target, tag, descriptor); + }, + }); + + try { + const frameworkError = new Error("framework failed"); + assertStrictEquals(markBuildFailure(frameworkError), frameworkError); + assertEquals(isBuildFailure(frameworkError), true); + assertEquals(isTenantBuildFailure(frameworkError), false); + + const tenantError = new Error("tenant failed"); + assertStrictEquals(markTenantBuildFailure(tenantError), tenantError); + assertEquals(isBuildFailure(tenantError), true); + assertEquals(isTenantBuildFailure(tenantError), true); + } finally { + defineProperty(Object, "defineProperty", previous); + } + }); + // Compiling a real page module starts esbuild's child process; stop it so the // test does not leak the handle rather than opting out of the sanitizer. afterAll(async () => { diff --git a/src/transforms/mdx/compiler/frontmatter-extractor.test.ts b/src/transforms/mdx/compiler/frontmatter-extractor.test.ts index 0d388705ec..d677022053 100644 --- a/src/transforms/mdx/compiler/frontmatter-extractor.test.ts +++ b/src/transforms/mdx/compiler/frontmatter-extractor.test.ts @@ -1,7 +1,9 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertThrows } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; -import { extractFrontmatter } from "./frontmatter-extractor.ts"; +import { extractFrontmatter, isFrontmatterSyntaxError } from "./frontmatter-extractor.ts"; + +const FRONTMATTER_SYNTAX_ERROR = Symbol.for("veryfront.transforms.mdx.frontmatter-syntax-error"); describe("transforms/mdx/compiler/frontmatter-extractor", () => { describe("extractFrontmatter", () => { @@ -99,5 +101,127 @@ export const title = "Override"; assertEquals(result.body, ""); assertEquals(result.frontmatter, {}); }); + + it("marks frontmatter syntax failures with an own data property", () => { + const error = assertThrows( + () => extractFrontmatter("---\ntitle: [unterminated\n---"), + SyntaxError, + ); + + assertEquals(isFrontmatterSyntaxError(error), true); + }); + + it("requires an own data marker without invoking accessors", () => { + const previous = Object.getOwnPropertyDescriptor( + SyntaxError.prototype, + FRONTMATTER_SYNTAX_ERROR, + ); + const previousDescriptorValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + let inheritedGetterRead = false; + let ownGetterRead = false; + + try { + Object.defineProperty(SyntaxError.prototype, FRONTMATTER_SYNTAX_ERROR, { + configurable: true, + value: true, + }); + assertEquals(isFrontmatterSyntaxError(new SyntaxError("framework failed")), false); + + Object.defineProperty(SyntaxError.prototype, FRONTMATTER_SYNTAX_ERROR, { + configurable: true, + get() { + inheritedGetterRead = true; + return true; + }, + }); + assertEquals(isFrontmatterSyntaxError(new SyntaxError("framework failed")), false); + + const accessorBacked = new SyntaxError("framework failed"); + Object.defineProperty(accessorBacked, FRONTMATTER_SYNTAX_ERROR, { + configurable: true, + get() { + ownGetterRead = true; + return true; + }, + }); + Object.defineProperty(Object.prototype, "value", { + configurable: true, + value: true, + }); + assertEquals(isFrontmatterSyntaxError(accessorBacked), false); + assertEquals(inheritedGetterRead, false); + assertEquals(ownGetterRead, false); + } finally { + if (previous) { + Object.defineProperty(SyntaxError.prototype, FRONTMATTER_SYNTAX_ERROR, previous); + } else { + delete (SyntaxError.prototype as { [FRONTMATTER_SYNTAX_ERROR]?: unknown })[ + FRONTMATTER_SYNTAX_ERROR + ]; + } + if (previousDescriptorValue) { + Object.defineProperty(Object.prototype, "value", previousDescriptorValue); + } else { + delete (Object.prototype as { value?: unknown }).value; + } + } + }); + + it("fails closed when a proxy throws during marker inspection", () => { + const hostileDescriptors = new Proxy(new SyntaxError("framework failed"), { + getOwnPropertyDescriptor() { + throw new Error("marker descriptor invoked proxy code"); + }, + }); + const hostilePrototype = new Proxy(new SyntaxError("framework failed"), { + getPrototypeOf() { + throw new Error("prototype inspection invoked proxy code"); + }, + }); + + assertEquals(isFrontmatterSyntaxError(hostileDescriptors), false); + assertEquals(isFrontmatterSyntaxError(hostilePrototype), false); + }); + + it("uses the descriptor intrinsic captured during module initialization", () => { + const previous = Object.getOwnPropertyDescriptor(Reflect, "getOwnPropertyDescriptor"); + if (!previous || typeof previous.value !== "function") { + throw new Error("Expected Reflect.getOwnPropertyDescriptor descriptor"); + } + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", { + ...previous, + value: () => ({ value: true }), + }); + + try { + assertEquals(isFrontmatterSyntaxError(new SyntaxError("framework failed")), false); + } finally { + Object.defineProperty(Reflect, "getOwnPropertyDescriptor", previous); + } + }); + + it("uses the definition intrinsic captured during module initialization", () => { + const defineProperty = Object.defineProperty; + const previous = Object.getOwnPropertyDescriptor(Object, "defineProperty"); + if (!previous || typeof previous.value !== "function") { + throw new Error("Expected Object.defineProperty descriptor"); + } + defineProperty(Object, "defineProperty", { + ...previous, + value: () => { + throw new Error("poisoned marker definition"); + }, + }); + + try { + const error = assertThrows( + () => extractFrontmatter("---\ntitle: [unterminated\n---"), + SyntaxError, + ); + assertEquals(isFrontmatterSyntaxError(error), true); + } finally { + defineProperty(Object, "defineProperty", previous); + } + }); }); }); diff --git a/src/transforms/mdx/compiler/frontmatter-extractor.ts b/src/transforms/mdx/compiler/frontmatter-extractor.ts index ad7e6192e2..ef51542933 100644 --- a/src/transforms/mdx/compiler/frontmatter-extractor.ts +++ b/src/transforms/mdx/compiler/frontmatter-extractor.ts @@ -7,16 +7,27 @@ export interface FrontmatterExtractionResult { } const FRONTMATTER_SYNTAX_ERROR = Symbol.for("veryfront.transforms.mdx.frontmatter-syntax-error"); +const ObjectDefineProperty = Object.defineProperty; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; /** Return true when an error came from MDX or Markdown YAML frontmatter parsing. */ export function isFrontmatterSyntaxError(error: unknown): error is SyntaxError { - return error instanceof SyntaxError && - (error as { [FRONTMATTER_SYNTAX_ERROR]?: unknown })[FRONTMATTER_SYNTAX_ERROR] === true; + try { + if (!(error instanceof SyntaxError)) return false; + const descriptor = ReflectGetOwnPropertyDescriptor(error, FRONTMATTER_SYNTAX_ERROR); + return descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true && + descriptor.value === true; + } catch { + return false; + } } function createFrontmatterSyntaxError(cause: SyntaxError): SyntaxError { const error = new SyntaxError(`Invalid YAML frontmatter: ${cause.message}`, { cause }); - Object.defineProperty(error, FRONTMATTER_SYNTAX_ERROR, { value: true }); + ObjectDefineProperty(error, FRONTMATTER_SYNTAX_ERROR, { value: true }); return error; } diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 0d74928b53..9d4b3d646a 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -1,6 +1,11 @@ import "#veryfront/schemas/_test-setup.ts"; import "./__tests__/content-processor-setup.ts"; -import { assertEquals, assertInstanceOf, assertRejects } from "#veryfront/testing/assert.ts"; +import { + assertEquals, + assertInstanceOf, + assertRejects, + assertStrictEquals, +} from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { VeryfrontError } from "#veryfront/errors"; import { @@ -199,5 +204,56 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { registerContract("ContentProcessor", previous); } }); + + it("preserves framework SyntaxErrors when the frontmatter prototype is polluted", async () => { + const marker = Symbol.for("veryfront.transforms.mdx.frontmatter-syntax-error"); + const previousMarker = Object.getOwnPropertyDescriptor(SyntaxError.prototype, marker); + const previousProcessor = tryResolveContract("ContentProcessor"); + const frameworkFailure = new SyntaxError("Expected ContentProcessor to initialize"); + Object.defineProperty(SyntaxError.prototype, marker, { + configurable: true, + value: true, + }); + registerContract( + "ContentProcessor", + { + compileMdx() { + throw frameworkFailure; + }, + compileMarkdown() { + throw new Error("not used"); + }, + getRemarkPlugins() { + return []; + }, + getRehypePlugins() { + return []; + }, + } satisfies ContentProcessor, + ); + + try { + const error = await assertRejects( + () => + compileMDXRuntime( + "production", + "/project", + "# Hello", + undefined, + "framework-failure.mdx", + "server", + ), + SyntaxError, + ); + assertStrictEquals(error, frameworkFailure); + } finally { + registerContract("ContentProcessor", previousProcessor); + if (previousMarker) { + Object.defineProperty(SyntaxError.prototype, marker, previousMarker); + } else { + delete (SyntaxError.prototype as { [marker]?: unknown })[marker]; + } + } + }); }); }); From d8edd7827cc9981316e22ffb1a57bd9f149db7e1 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 00:47:24 +0200 Subject: [PATCH 102/104] Prevent poisoned diagnostic shapes from hiding framework failures Framework severity must depend on provenance stamped at trusted compiler seams, not inherited fields or accessors supplied by an arbitrary error. Normalize esbuild source evidence inside its adapter, then require captured own-data descriptor reads for downstream markers and MDX parser metadata. Constraint: Framework failures must remain error-level under prototype and primordial poisoning Rejected: Read esbuild diagnostic accessors in the compile stage | arbitrary bundler failures could execute getters and forge tenant ownership Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep esbuild accessor reads inside the trusted adapter and require own-data exact-true markers downstream Tested: Focused compiler, observability, Sentry, module-loader, root-suffix, pipeline, and esbuild suites, 88 tests with 150 steps Tested: Changed-file format, lint, typecheck, and diff checks Tested: Full pinned pre-push suite, 3856 unit tests with 28910 steps, 10 cwd tests with 197 steps, and 2 cwd exclusion tests with 2 steps Not-tested: Hosted CI on the pushed commit --- .../src/esbuild-bundler.test.ts | 50 ++++++++ .../src/esbuild-bundler.ts | 82 ++++++++++++- src/observability/application-errors.test.ts | 57 +++++++++ src/observability/application-errors.ts | 7 +- .../module-loader/build-failure.ts | 7 +- .../orchestrator/module-loader/index.test.ts | 44 +++++++ .../mdx/compiler/mdx-compiler.test.ts | 113 ++++++++++++++++++ src/transforms/mdx/compiler/mdx-compiler.ts | 38 ++++-- .../pipeline/stages/compile.test.ts | 113 ++++++++++++++++++ src/transforms/pipeline/stages/compile.ts | 34 +++++- 10 files changed, 523 insertions(+), 22 deletions(-) diff --git a/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts b/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts index d3015865f6..102b27a19a 100644 --- a/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts +++ b/extensions/ext-bundler-esbuild/src/esbuild-bundler.test.ts @@ -16,6 +16,7 @@ import { createRequire } from "node:module"; import type { BuildContext } from "veryfront/extensions/bundler"; import { + __markEsbuildSourceDiagnosticForTests, __recordOwnershipErrorForTests, __resetOwnershipErrorForTests, __resetServiceRecoveryForTests, @@ -72,6 +73,55 @@ function observeEsbuildServices(): { } describe("EsbuildBundler.transform", () => { + it("normalizes only trusted esbuild diagnostic accessors to an own marker", () => { + const marker = Symbol.for("veryfront.bundler.esbuild-source-diagnostic"); + const failure = new Error("Transform failed"); + let errorsGetterReads = 0; + Object.defineProperty(failure, "errors", { + get() { + errorsGetterReads++; + return [{ location: { line: 1, column: 1 } }]; + }, + }); + const defineProperty = Object.defineProperty; + const previousDefineProperty = Object.getOwnPropertyDescriptor(Object, "defineProperty"); + assertExists(previousDefineProperty); + defineProperty(Object, "defineProperty", { + ...previousDefineProperty, + value: () => { + throw new Error("poisoned Object.defineProperty"); + }, + }); + + try { + __markEsbuildSourceDiagnosticForTests(failure); + } finally { + defineProperty(Object, "defineProperty", previousDefineProperty); + } + + assertEquals(errorsGetterReads, 1); + assertEquals(Object.getOwnPropertyDescriptor(failure, marker)?.value, true); + + let locationGetterReads = 0; + const accessorLocation = Object.defineProperty({}, "location", { + get() { + locationGetterReads++; + return { line: 1, column: 1 }; + }, + }); + const untrustedLocation = new Error("Transform failed"); + Object.defineProperty(untrustedLocation, "errors", { + get() { + return [accessorLocation]; + }, + }); + + __markEsbuildSourceDiagnosticForTests(untrustedLocation); + + assertEquals(locationGetterReads, 0); + assertEquals(Object.getOwnPropertyDescriptor(untrustedLocation, marker), undefined); + }); + it("compiles TS to JS", async () => { const bundler = new EsbuildBundler(); try { diff --git a/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts b/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts index b04773a7f1..2d4de464eb 100644 --- a/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts +++ b/extensions/ext-bundler-esbuild/src/esbuild-bundler.ts @@ -33,6 +33,14 @@ import { toEsbuildPlugin } from "./plugin-adapter.ts"; type EsbuildModule = any; const ESBUILD_STOP_TIMEOUT_MS = 5_000; +const ESBUILD_SOURCE_DIAGNOSTIC = Symbol.for( + "veryfront.bundler.esbuild-source-diagnostic", +); +const ArrayIsArray = Array.isArray; +const ObjectDefineProperty = Object.defineProperty; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; /** * Unexpected service-child deaths tolerated before the adapter gives up. * @@ -96,6 +104,68 @@ const MAX_CAUSE_DETAIL_LENGTH = 200; /** Absolute POSIX and Windows paths, reduced to a basename below. */ const ABSOLUTE_PATH_PATTERN = /(?:[A-Za-z]:)?(?:\/|\\\\)[^\s"']*/g; +function readOwnDataProperty(value: unknown, key: PropertyKey): unknown { + if ( + value === null || + (typeof value !== "object" && typeof value !== "function") + ) { + return undefined; + } + try { + const descriptor = ReflectGetOwnPropertyDescriptor(value, key); + if ( + descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true + ) { + return descriptor.value; + } + } catch { + // A hostile proxy cannot provide trusted diagnostic evidence. + } + return undefined; +} + +/** + * Read esbuild's own diagnostic collection at the direct package boundary. + * + * esbuild exposes `errors` through an own accessor. Invoking that accessor is + * safe only here, before the failure crosses into framework classification. + */ +function readTrustedEsbuildErrors(error: unknown): unknown { + if (error === null || (typeof error !== "object" && typeof error !== "function")) { + return undefined; + } + try { + const descriptor = ReflectGetOwnPropertyDescriptor(error, "errors"); + if (descriptor === undefined) return undefined; + if (ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true) { + return descriptor.value; + } + const getter = readOwnDataProperty(descriptor, "get"); + return typeof getter === "function" ? ReflectApply(getter, error, []) : undefined; + } catch { + return undefined; + } +} + +function markEsbuildSourceDiagnostic(error: unknown): void { + const diagnostics = readTrustedEsbuildErrors(error); + if (!ArrayIsArray(diagnostics)) return; + const length = readOwnDataProperty(diagnostics, "length"); + if (typeof length !== "number") return; + for (let index = 0; index < length; index++) { + const diagnostic = readOwnDataProperty(diagnostics, index); + const location = readOwnDataProperty(diagnostic, "location"); + if (typeof location !== "object" || location === null) continue; + try { + ObjectDefineProperty(error, ESBUILD_SOURCE_DIAGNOSTIC, { value: true }); + } catch { + // Non-extensible failures remain unmarked and classify as infrastructure. + } + return; + } +} + /** * Reduce a cause to a single redacted line. * @@ -508,6 +578,11 @@ export function __recordOwnershipErrorForTests(cause?: unknown): Error { return recordOwnershipError(cause); } +/** Exercise trusted esbuild diagnostic normalization without starting a service. */ +export function __markEsbuildSourceDiagnosticForTests(error: unknown): void { + markEsbuildSourceDiagnostic(error); +} + export function isLiveEsbuildServiceProcess( child: Pick, ): boolean { @@ -705,7 +780,12 @@ export class EsbuildBundler implements Bundler { return runBundlerOperation(async () => { const esbuild = await getEsbuild(); const { code, ...rest } = options; - const result = await invokeEsbuild(() => esbuild.transform(code, rest)); + const result = await invokeEsbuild(() => esbuild.transform(code, rest)).catch( + (error: unknown) => { + markEsbuildSourceDiagnostic(error); + throw error; + }, + ); return { code: result.code, map: result.map, diff --git a/src/observability/application-errors.test.ts b/src/observability/application-errors.test.ts index a29196b015..7bf1a720ec 100644 --- a/src/observability/application-errors.test.ts +++ b/src/observability/application-errors.test.ts @@ -362,6 +362,63 @@ it("application error reporter ignores inherited tenant build tags", () => { } }); +it("application error reporter rejects prototype-polluted accessor tag descriptors", () => { + const tenantBuildFailureTag = Symbol.for("veryfront.module-loader.tenant-build-failure"); + const previousDescriptorValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + const previousHasOwnProperty = Object.getOwnPropertyDescriptor( + Object.prototype, + "hasOwnProperty", + ); + if (!previousHasOwnProperty) throw new Error("Expected Object.prototype.hasOwnProperty"); + const frameworkError = new Error("framework failed"); + Object.defineProperty(frameworkError, tenantBuildFailureTag, { + configurable: true, + get: undefined, + set: undefined, + }); + const tenantError = new Error("tenant failed"); + Object.defineProperty(tenantError, tenantBuildFailureTag, { + configurable: true, + value: true, + }); + Object.defineProperty(Object.prototype, "hasOwnProperty", { + ...previousHasOwnProperty, + value: () => true, + }); + Object.defineProperty(Object.prototype, "value", { configurable: true, value: true }); + + try { + const captures: Array<{ error: unknown; context: SharedApplicationErrorContext }> = []; + setApplicationErrorReporter({ + capture(error, context) { + captures.push({ error, context }); + return "event-id"; + }, + flush: () => Promise.resolve(true), + }); + + assertEquals( + captureApplicationError(frameworkError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals(captures[0]?.context.errorClass, undefined); + assertEquals(captures[0]?.context.level, undefined); + assertEquals( + captureApplicationError(tenantError, { boundary: "ssr.render" }), + "event-id", + ); + assertEquals(captures[1]?.context.errorClass, "tenant-build"); + assertEquals(captures[1]?.context.level, "warning"); + } finally { + if (previousDescriptorValue) { + Object.defineProperty(Object.prototype, "value", previousDescriptorValue); + } else { + delete (Object.prototype as { value?: unknown }).value; + } + Object.defineProperty(Object.prototype, "hasOwnProperty", previousHasOwnProperty); + } +}); + it("application error reporter ignores poisoned Reflect descriptor lookups for tenant tags", () => { const previousDescriptor = Object.getOwnPropertyDescriptor( Reflect, diff --git a/src/observability/application-errors.ts b/src/observability/application-errors.ts index 5501eedd2a..f08bc9d907 100644 --- a/src/observability/application-errors.ts +++ b/src/observability/application-errors.ts @@ -34,6 +34,8 @@ export type ApplicationErrorReporterLifecycle = { }; const MAX_APPLICATION_ERROR_SERVICE_NAME_LENGTH = 255; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; let reporter: ApplicationErrorReporter | undefined; @@ -236,8 +238,9 @@ const TENANT_BUILD_FAILURE_TAG = Symbol.for("veryfront.module-loader.tenant-buil function hasOwnTrueSymbol(value: object, key: symbol): boolean { const descriptor = ReflectGetOwnPropertyDescriptor(value, key); - return descriptor !== undefined && !descriptor.get && !descriptor.set && - "value" in descriptor && descriptor.value === true; + return descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true && + descriptor.value === true; } /** diff --git a/src/rendering/orchestrator/module-loader/build-failure.ts b/src/rendering/orchestrator/module-loader/build-failure.ts index a79f6ec048..60f467095d 100644 --- a/src/rendering/orchestrator/module-loader/build-failure.ts +++ b/src/rendering/orchestrator/module-loader/build-failure.ts @@ -17,6 +17,8 @@ import { isTenantSourceBuildError } from "#veryfront/errors/tenant-classification.ts"; const ObjectDefineProperty = Object.defineProperty; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; const BUILD_FAILURE = Symbol.for("veryfront.module-loader.build-failure"); @@ -38,8 +40,9 @@ function defineTag(error: Error, tag: symbol): void { function hasOwnTrueTag(error: Error, tag: symbol): boolean { const descriptor = ReflectGetOwnPropertyDescriptor(error, tag); - return descriptor !== undefined && !descriptor.get && !descriptor.set && - "value" in descriptor && descriptor.value === true; + return descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true && + descriptor.value === true; } /** Tag `error` as a build failure and return it. */ diff --git a/src/rendering/orchestrator/module-loader/index.test.ts b/src/rendering/orchestrator/module-loader/index.test.ts index 2b675eb0ed..99ad59f834 100644 --- a/src/rendering/orchestrator/module-loader/index.test.ts +++ b/src/rendering/orchestrator/module-loader/index.test.ts @@ -312,6 +312,11 @@ describe("module-loader/loadModule build-failure tagging", () => { assertEquals(isBuildFailure(frameworkError), false); assertEquals(isTenantBuildFailure(frameworkError), false); + const tenantError = new Error("tenant failed"); + assertStrictEquals(markTenantBuildFailure(tenantError), tenantError); + assertEquals(isBuildFailure(tenantError), true); + assertEquals(isTenantBuildFailure(tenantError), true); + const accessorTagError = new Error("framework failed"); let buildGetterRead = false; let tenantGetterRead = false; @@ -348,6 +353,45 @@ describe("module-loader/loadModule build-failure tagging", () => { } }); + it("rejects prototype-polluted accessor tag descriptors", () => { + const buildFailureTag = Symbol.for("veryfront.module-loader.build-failure"); + const tenantBuildFailureTag = Symbol.for("veryfront.module-loader.tenant-build-failure"); + const previousDescriptorValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + const previousHasOwnProperty = Object.getOwnPropertyDescriptor( + Object.prototype, + "hasOwnProperty", + ); + assert(previousHasOwnProperty); + const frameworkError = new Error("framework failed"); + Object.defineProperty(frameworkError, buildFailureTag, { + configurable: true, + get: undefined, + set: undefined, + }); + Object.defineProperty(frameworkError, tenantBuildFailureTag, { + configurable: true, + get: undefined, + set: undefined, + }); + Object.defineProperty(Object.prototype, "hasOwnProperty", { + ...previousHasOwnProperty, + value: () => true, + }); + Object.defineProperty(Object.prototype, "value", { configurable: true, value: true }); + + try { + assertEquals(isBuildFailure(frameworkError), false); + assertEquals(isTenantBuildFailure(frameworkError), false); + } finally { + if (previousDescriptorValue) { + Object.defineProperty(Object.prototype, "value", previousDescriptorValue); + } else { + delete (Object.prototype as { value?: unknown }).value; + } + Object.defineProperty(Object.prototype, "hasOwnProperty", previousHasOwnProperty); + } + }); + it("ignores poisoned Reflect descriptor lookups when reading build-failure tags", () => { const previousDescriptor = Object.getOwnPropertyDescriptor( Reflect, diff --git a/src/transforms/mdx/compiler/mdx-compiler.test.ts b/src/transforms/mdx/compiler/mdx-compiler.test.ts index 9d4b3d646a..b41b944783 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.test.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.test.ts @@ -205,6 +205,119 @@ describe("transforms/mdx/compiler/mdx-compiler", () => { } }); + it("preserves processor failures when MDX source fields are inherited", async () => { + const previous = tryResolveContract("ContentProcessor"); + const inheritedFields = { + source: "remark-mdx", + ruleId: "unexpected-token", + line: 1, + column: 1, + } as const; + const previousDescriptors = new Map( + Object.keys(inheritedFields).map((key) => [ + key, + Object.getOwnPropertyDescriptor(Error.prototype, key), + ]), + ); + const frameworkFailure = new Error("Expected ContentProcessor to initialize"); + for (const [key, value] of Object.entries(inheritedFields)) { + Object.defineProperty(Error.prototype, key, { configurable: true, value }); + } + registerContract( + "ContentProcessor", + { + compileMdx() { + throw frameworkFailure; + }, + compileMarkdown() { + throw new Error("not used"); + }, + getRemarkPlugins() { + return []; + }, + getRehypePlugins() { + return []; + }, + } satisfies ContentProcessor, + ); + + try { + const error = await assertRejects(() => + compileMDXRuntime( + "production", + "/project", + "# Hello", + undefined, + "framework-failure.mdx", + "server", + ) + ); + assertStrictEquals(error, frameworkFailure); + } finally { + registerContract("ContentProcessor", previous); + for (const [key, descriptor] of previousDescriptors) { + if (descriptor) Object.defineProperty(Error.prototype, key, descriptor); + else delete (Error.prototype as unknown as Record)[key]; + } + } + }); + + it("does not invoke accessor-backed MDX source fields", async () => { + const previous = tryResolveContract("ContentProcessor"); + const frameworkFailure = new Error("Expected ContentProcessor to initialize"); + let getterReads = 0; + for ( + const [key, value] of Object.entries({ + source: "remark-mdx", + ruleId: "unexpected-token", + line: 1, + column: 1, + }) + ) { + Object.defineProperty(frameworkFailure, key, { + configurable: true, + get() { + getterReads++; + return value; + }, + }); + } + registerContract( + "ContentProcessor", + { + compileMdx() { + throw frameworkFailure; + }, + compileMarkdown() { + throw new Error("not used"); + }, + getRemarkPlugins() { + return []; + }, + getRehypePlugins() { + return []; + }, + } satisfies ContentProcessor, + ); + + try { + const error = await assertRejects(() => + compileMDXRuntime( + "production", + "/project", + "# Hello", + undefined, + "framework-failure.mdx", + "server", + ) + ); + assertStrictEquals(error, frameworkFailure); + assertEquals(getterReads, 0); + } finally { + registerContract("ContentProcessor", previous); + } + }); + it("preserves framework SyntaxErrors when the frontmatter prototype is polluted", async () => { const marker = Symbol.for("veryfront.transforms.mdx.frontmatter-syntax-error"); const previousMarker = Object.getOwnPropertyDescriptor(SyntaxError.prototype, marker); diff --git a/src/transforms/mdx/compiler/mdx-compiler.ts b/src/transforms/mdx/compiler/mdx-compiler.ts index 449123c00e..11e6ee64af 100644 --- a/src/transforms/mdx/compiler/mdx-compiler.ts +++ b/src/transforms/mdx/compiler/mdx-compiler.ts @@ -11,19 +11,35 @@ import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import { isFrontmatterSyntaxError } from "./frontmatter-extractor.ts"; const logger = rendererLogger.component("mdx-compiler"); +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; + +function readOwnDataProperty(value: object, key: PropertyKey): unknown { + try { + const descriptor = ReflectGetOwnPropertyDescriptor(value, key); + if ( + descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true + ) { + return descriptor.value; + } + } catch { + // A hostile proxy cannot provide trusted source-diagnostic evidence. + } + return undefined; +} function isMdxSourceCompileError(error: Error): boolean { - const candidate = error as Error & { - column?: unknown; - line?: unknown; - ruleId?: unknown; - source?: unknown; - }; - const isMdxParserError = typeof candidate.source === "string" && - /(?:^|-)mdx(?:-|$)|micromark|remark|recma|rehype/.test(candidate.source) && - typeof candidate.ruleId === "string" && - Number.isSafeInteger(candidate.line) && - Number.isSafeInteger(candidate.column); + const source = readOwnDataProperty(error, "source"); + const ruleId = readOwnDataProperty(error, "ruleId"); + const line = readOwnDataProperty(error, "line"); + const column = readOwnDataProperty(error, "column"); + const isMdxParserError = typeof source === "string" && + /(?:^|-)mdx(?:-|$)|micromark|remark|recma|rehype/.test(source) && + typeof ruleId === "string" && + Number.isSafeInteger(line) && + Number.isSafeInteger(column); // Frontmatter failures are identified by the symbol `extractFrontmatter` // stamps at the throw site, not by matching stack-frame paths: `extract()` is // the only frontmatter path and it tags every SyntaxError it raises. A diff --git a/src/transforms/pipeline/stages/compile.test.ts b/src/transforms/pipeline/stages/compile.test.ts index c426883ec7..fa8e7e7708 100644 --- a/src/transforms/pipeline/stages/compile.test.ts +++ b/src/transforms/pipeline/stages/compile.test.ts @@ -4,11 +4,18 @@ import { assertExists, assertInstanceOf, assertRejects, + assertStrictEquals, assertStringIncludes, } from "#veryfront/testing/assert.ts"; import { afterAll, describe, it } from "#veryfront/testing/bdd.ts"; import { stop as stopEsbuild } from "#veryfront/platform/compat/esbuild.ts"; import { VeryfrontError } from "#veryfront/errors"; +import type { Bundler } from "#veryfront/extensions/bundler/bundler.ts"; +import { + register as registerContract, + tryResolve as tryResolveContract, + unregister as unregisterContract, +} from "#veryfront/extensions/contracts.ts"; import { compilePlugin } from "./compile.ts"; import { TransformStage } from "../types.ts"; import type { TransformContext } from "../types.ts"; @@ -31,6 +38,26 @@ function createContext(code: string, filePath = "/project/lib/x.ts"): TransformC } as TransformContext; } +async function transformWithBundlerFailure(cause: Error): Promise { + const previous = tryResolveContract("Bundler"); + registerContract("Bundler", { + bundle: () => Promise.reject(new Error("not used")), + transform: () => Promise.reject(cause), + }); + + try { + const error = await assertRejects( + async () => await compilePlugin.transform(createContext("export const value = 1;")), + VeryfrontError, + ); + assertInstanceOf(error, VeryfrontError); + return error; + } finally { + if (previous) registerContract("Bundler", previous); + else unregisterContract("Bundler"); + } +} + describe("transforms/pipeline/stages/compile", () => { afterAll(async () => { await stopEsbuild(); @@ -152,6 +179,92 @@ describe("transforms/pipeline/stages/compile", () => { ); }); + it("does not use an inherited esbuild diagnostic collection", async () => { + const marker = Symbol.for("veryfront.bundler.esbuild-source-diagnostic"); + const previousErrors = Object.getOwnPropertyDescriptor(Error.prototype, "errors"); + const previousMarker = Object.getOwnPropertyDescriptor(Error.prototype, marker); + const frameworkFailure = new Error("esbuild service stopped"); + Object.defineProperty(Error.prototype, "errors", { + configurable: true, + value: [{ location: { line: 1, column: 1 } }], + }); + Object.defineProperty(Error.prototype, marker, { configurable: true, value: true }); + + try { + const error = await transformWithBundlerFailure(frameworkFailure); + assertStrictEquals(error.cause, frameworkFailure); + assertEquals( + (error.context as { tenantBuildFailure?: unknown } | undefined)?.tenantBuildFailure, + false, + ); + } finally { + if (previousErrors) Object.defineProperty(Error.prototype, "errors", previousErrors); + else delete (Error.prototype as { errors?: unknown }).errors; + if (previousMarker) Object.defineProperty(Error.prototype, marker, previousMarker); + else delete (Error.prototype as { [marker]?: unknown })[marker]; + } + }); + + it("does not use inherited esbuild diagnostic locations", async () => { + const frameworkFailure = new Error("esbuild service stopped"); + Object.defineProperty(frameworkFailure, "errors", { + value: [Object.create({ location: { line: 1, column: 1 } })], + }); + + const error = await transformWithBundlerFailure(frameworkFailure); + assertStrictEquals(error.cause, frameworkFailure); + assertEquals( + (error.context as { tenantBuildFailure?: unknown } | undefined)?.tenantBuildFailure, + false, + ); + }); + + it("does not invoke accessor-backed esbuild diagnostic fields", async () => { + const marker = Symbol.for("veryfront.bundler.esbuild-source-diagnostic"); + let errorsGetterReads = 0; + let markerGetterReads = 0; + const accessorCollectionFailure = new Error("esbuild service stopped"); + Object.defineProperty(accessorCollectionFailure, "errors", { + get() { + errorsGetterReads++; + return [{ location: { line: 1, column: 1 } }]; + }, + }); + Object.defineProperty(accessorCollectionFailure, marker, { + get() { + markerGetterReads++; + return true; + }, + }); + + const collectionError = await transformWithBundlerFailure(accessorCollectionFailure); + assertEquals( + (collectionError.context as { tenantBuildFailure?: unknown } | undefined) + ?.tenantBuildFailure, + false, + ); + assertEquals(errorsGetterReads, 0); + assertEquals(markerGetterReads, 0); + + let locationGetterReads = 0; + const diagnostic = Object.defineProperty({}, "location", { + get() { + locationGetterReads++; + return { line: 1, column: 1 }; + }, + }); + const accessorLocationFailure = new Error("esbuild service stopped"); + Object.defineProperty(accessorLocationFailure, "errors", { value: [diagnostic] }); + + const locationError = await transformWithBundlerFailure(accessorLocationFailure); + assertEquals( + (locationError.context as { tenantBuildFailure?: unknown } | undefined) + ?.tenantBuildFailure, + false, + ); + assertEquals(locationGetterReads, 0); + }); + // By the time an `.mdx` file reaches COMPILE, PARSE has already turned the // tenant's source into JSX, so `ctx.code` is the framework's MDX-compiler // output. A remark/rehype/recma plugin emitting broken JSX still yields an diff --git a/src/transforms/pipeline/stages/compile.ts b/src/transforms/pipeline/stages/compile.ts index 581c765af4..d95e9995ed 100644 --- a/src/transforms/pipeline/stages/compile.ts +++ b/src/transforms/pipeline/stages/compile.ts @@ -7,14 +7,36 @@ import { ESBUILD_SUPPORTED_FEATURES, getLoaderFromPath } from "../../esm/transfo import { type TransformContext, type TransformPlugin, TransformStage } from "../types.ts"; const logger = rendererLogger.component("esm-transform"); +const ESBUILD_SOURCE_DIAGNOSTIC = Symbol.for( + "veryfront.bundler.esbuild-source-diagnostic", +); +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; +const ReflectGetOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; + +function readOwnDataProperty(value: unknown, key: PropertyKey): unknown { + if ( + value === null || + (typeof value !== "object" && typeof value !== "function") + ) { + return undefined; + } + try { + const descriptor = ReflectGetOwnPropertyDescriptor(value, key); + if ( + descriptor !== undefined && + ReflectApply(ObjectPrototypeHasOwnProperty, descriptor, ["value"]) === true + ) { + return descriptor.value; + } + } catch { + // A hostile proxy cannot provide trusted source-diagnostic evidence. + } + return undefined; +} function isEsbuildSourceDiagnostic(error: unknown): boolean { - const diagnostics = (error as { errors?: unknown })?.errors; - if (!Array.isArray(diagnostics)) return false; - return diagnostics.some((diagnostic) => { - const location = (diagnostic as { location?: unknown })?.location; - return typeof location === "object" && location !== null; - }); + return readOwnDataProperty(error, ESBUILD_SOURCE_DIAGNOSTIC) === true; } /** From e7f53903fc5d297cc75f413a16d30308305bbdba Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 01:03:51 +0200 Subject: [PATCH 103/104] Keep generated observability links aligned with source The hardened application-error classifier shifted public source locations, so regenerate the observability reference with the CI-pinned Deno toolchain. Constraint: Generated API references must match Deno 2.7.7 output. Rejected: Edit line links manually | regeneration is the repository source of truth. Confidence: high Scope-risk: narrow Directive: Regenerate API references after moving public observability declarations. Tested: Pinned docs:api-reference:check (43 groups, 44 files current), deno fmt --check, git diff --check. --- docs/api-reference/veryfront/observability.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-reference/veryfront/observability.md b/docs/api-reference/veryfront/observability.md index 7f6eddb6d4..1f71681116 100644 --- a/docs/api-reference/veryfront/observability.md +++ b/docs/api-reference/veryfront/observability.md @@ -43,13 +43,13 @@ const result = await withSpan("load-data", async () => { | Name | Description | Source | | ---------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `addSpanEvent` | Event emitted for add span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L70) | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L263) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L266) | | `createChildSpan` | Create child span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L79) | | `createFileLogSubscriber` | Create file log subscriber. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/file-log-subscriber.ts#L541) | | `createOpenTelemetryServiceTracer` | Create open telemetry service tracer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/service-tracer.ts#L364) | | `endSpan` | End an active tracing span. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L57) | | `extractContext` | Context for extract. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L88) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L291) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L294) | | `getActiveContext` | Context for get active. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L98) | | `getErrorCollector` | Return error collector. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/error-collector.ts#L406) | | `getGlobalMetricsAPI` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/api-shim.ts#L667) | @@ -58,7 +58,7 @@ const result = await withSpan("load-data", async () => { | `getMetricsState` | State for get metrics. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L38) | | `getTraceContext` | Context for get trace. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L500) | | `initAutoInstrumentation` | Initialize automatic instrumentation wrappers. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/auto-instrument/orchestrator.ts#L15) | -| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L151) | +| `initializeApplicationErrorReporter` | Activate an explicitly selected reporter initializer. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L153) | | `initializeOTLP` | Initialize OTLP tracing export. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/otlp-setup.ts#L113) | | `initMetrics` | Initialize metrics collection. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/metrics/index.ts#L20) | | `initTracing` | Initialize tracing for the current runtime. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/tracing/index.ts#L18) | @@ -237,8 +237,8 @@ import { | Name | Description | Source | | ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L263) | -| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L291) | +| `captureApplicationError` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L266) | +| `flushApplicationErrors` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/application-errors.ts#L294) | | `initializeSentry` | Initialize the process-wide Sentry reporter once. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L86) | | `initializeSentryFromEnv` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L70) | | `isSentryEnabled` | Return whether Sentry is explicitly enabled. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/observability/sentry.ts#L39) | From f972e7d731844161de3c5a10f45050eac8e3d6f8 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Sun, 16 Aug 2026 02:19:01 +0200 Subject: [PATCH 104/104] Keep member-name scanning linear under coverage Coverage instrumentation exposed repeated backward comment scans for plain keyword-shaped identifiers. Use the immediate non-whitespace character for the common no-comment case and retain the comment-aware fallback where adjacent trivia can contain a block or line comment. Constraint: Comment-separated member names must retain their existing parsing behavior Rejected: Raise the 750ms regression threshold | masks quadratic scanner work Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the fallback for block comments and line comments across line terminators Tested: Exact coverage shard 1/8 (397 tests, 3763 steps); focused scanner coverage (243 steps); scanner/HTTP matrix (451 steps); fmt, lint, check, diff Not-tested: Hosted CI after this commit --- .../mdx/esm-module-loader/utils/source-spans.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts index 0bd8ed375a..b533e9adc3 100644 --- a/src/transforms/mdx/esm-module-loader/utils/source-spans.ts +++ b/src/transforms/mdx/esm-module-loader/utils/source-spans.ts @@ -667,6 +667,23 @@ export function isMemberNameBefore( while (start > 0 && isIdentifierPartAt(source, start - 1)) start--; if (start === end) return false; + const immediateBefore = previousSignificantIndex(source, start); + if (immediateBefore < 0) return false; + + // Most keyword-shaped identifiers are ordinary expression operands. Avoid + // rescanning the whole line for a comment unless the adjacent trivia can + // actually contain one; doing that for every `of` makes long declarations + // quadratic under coverage instrumentation. + if ( + source[immediateBefore] !== "/" && + !hasLineTerminatorBetween(source, immediateBefore + 1, start) + ) { + const immediateChar = source[immediateBefore]; + if (immediateChar === "#") return true; + if (immediateChar !== ".") return false; + return source[immediateBefore - 1] !== "." || source[immediateBefore - 2] !== "."; + } + const before = previousSignificantIndexBeforeIgnored(source, start); if (before < 0) return false;