From c8e7c9e8bc76aaebac8dd15504e04c2a5989c46d Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 08:44:58 +0200 Subject: [PATCH 01/34] fix(modules): harden the import-map preloader against poisoning and unbounded growth Cherry-picked from codex/module-reconcile-20260723 (preloader cluster only). The import-map preloader previously kept one unbounded process-wide cache keyed only by projectId/projectDir. This replaces it with a bounded two-level LRU (projects x content-source/config variants) with lazy TTL expiry, per-variant cache identity derived from an immutable pre-await snapshot of the validated config's import map, generation-atomic explicit invalidation, and captured-primordial execution so project code running in the same realm cannot redirect dependency graphs by replacing shared built-ins. Loader output is snapshotted and deep-frozen before publication. Supporting changes, hand-ported as behavior-preserving hunks only: - loader.ts: accept an optional pre-validated config instead of re-reading it from the project source; existing no-config path unchanged. - hash-utils.ts / config-hash.ts / http-cache-helpers.ts: capture the primitives used for cache identities at module load; hash outputs are byte-identical to before. - pipeline/types.ts: add the optional TransformPlugin.cacheIdentity field. - transforms/pipeline/cache-identity.ts (+test): new module providing the descriptor-only import-map snapshot and identity primitives. --- src/cache/config-hash.ts | 4 +- src/modules/import-map/loader.ts | 35 +- .../preloader-primordial-poisoning.worker.ts | 153 ++++ src/modules/import-map/preloader.test.ts | 731 +++++++++++++++- src/modules/import-map/preloader.ts | 784 +++++++++++++++++- src/transforms/esm/http-cache-helpers.ts | 35 +- .../pipeline/cache-identity.test.ts | 353 ++++++++ src/transforms/pipeline/cache-identity.ts | 329 ++++++++ src/transforms/pipeline/types.ts | 5 + src/utils/hash-utils.ts | 43 +- 10 files changed, 2415 insertions(+), 57 deletions(-) create mode 100644 src/modules/import-map/preloader-primordial-poisoning.worker.ts create mode 100644 src/transforms/pipeline/cache-identity.test.ts create mode 100644 src/transforms/pipeline/cache-identity.ts diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 87291deda1..2ef0a6c5a9 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -14,6 +14,8 @@ import { } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts"; +const JSONStringify = JSON.stringify; + /** * Configuration that affects transform output. */ @@ -62,7 +64,7 @@ export function computeConfigHash(config: TransformConfig): Promise { tailwind: TAILWIND_VERSION, }; - return computeHash(JSON.stringify(normalized)); + return computeHash(JSONStringify(normalized)); } /** diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index 56fa4b460c..e42465207a 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -2,7 +2,7 @@ import { rendererLogger as logger } from "#veryfront/utils"; import { dirname, join } from "#veryfront/compat/path/index.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { isVirtualFilesystem } from "#veryfront/platform/adapters/fs/wrapper.ts"; -import { getConfig } from "#veryfront/config"; +import { getConfig, type VeryfrontConfig } from "#veryfront/config"; import type { ImportMapConfig } from "./types.ts"; import { getDefaultImportMap } from "./default-import-map.ts"; import { mergeImportMaps } from "./merger.ts"; @@ -135,9 +135,20 @@ async function loadDenoJsonImportMap( return null; } +function getConfigImportMap(config: VeryfrontConfig): ImportMapConfig | null { + const importMap = config.resolve?.importMap; + if (!importMap || typeof importMap !== "object") return null; + + return { + imports: importMap.imports ?? {}, + scopes: importMap.scopes ?? {}, + }; +} + export function loadImportMap( startPath: string, adapter?: RuntimeAdapter, + config?: VeryfrontConfig, ): Promise { return withSpan( "modules.importMap.load", @@ -147,19 +158,19 @@ export function loadImportMap( // First, load import map from deno.json (if exists) const denoJsonMap = await loadDenoJsonImportMap(startPath, runtimeAdapter); - // Then, try to get config's import map + // Then, try to get config's import map. A config already validated for + // the authenticated request takes precedence over re-reading it from the + // project source. let configMap: ImportMapConfig | null = null; - try { - const cfg = await getConfig(startPath, runtimeAdapter); - const importMap = cfg?.resolve?.importMap; - if (importMap && typeof importMap === "object") { - configMap = { - imports: importMap.imports ?? {}, - scopes: importMap.scopes ?? {}, - }; + if (config) { + configMap = getConfigImportMap(config); + } else { + try { + const cfg = await getConfig(startPath, runtimeAdapter); + if (cfg) configMap = getConfigImportMap(cfg); + } catch (_) { + /* expected: config not found or invalid, continue without it */ } - } catch (_) { - /* expected: config not found or invalid, continue without it */ } // Merge: defaults < deno.json < config diff --git a/src/modules/import-map/preloader-primordial-poisoning.worker.ts b/src/modules/import-map/preloader-primordial-poisoning.worker.ts new file mode 100644 index 0000000000..93b73b4e8c --- /dev/null +++ b/src/modules/import-map/preloader-primordial-poisoning.worker.ts @@ -0,0 +1,153 @@ +import type { VeryfrontConfig } from "#veryfront/config"; +import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import { ImportMapPreloader } from "./preloader.ts"; + +const adapter = { + fs: {}, + env: {}, +} as unknown as RuntimeAdapter; +const configA = { + resolve: { + importMap: { + imports: { package: "https://example.com/package-a.ts" }, + }, + }, +} as VeryfrontConfig; +const configB = { + resolve: { + importMap: { + imports: { package: "https://example.com/package-b.ts" }, + }, + }, +} as VeryfrontConfig; + +async function runPoisoningRegression() { + const original = { + arrayMap: Array.prototype.map, + arraySort: Array.prototype.sort, + dateNow: Date.now, + jsonStringify: JSON.stringify, + map: Map, + mapClear: Map.prototype.clear, + mapDelete: Map.prototype.delete, + mapForEach: Map.prototype.forEach, + mapGet: Map.prototype.get, + mapSet: Map.prototype.set, + mapSize: Object.getOwnPropertyDescriptor(Map.prototype, "size")!, + mathMin: Math.min, + numberIsFinite: Number.isFinite, + numberIsSafeInteger: Number.isSafeInteger, + objectEntries: Object.entries, + promise: Promise, + promiseResolve: Promise.resolve, + set: Set, + setAdd: Set.prototype.add, + setDelete: Set.prototype.delete, + setSize: Object.getOwnPropertyDescriptor(Set.prototype, "size")!, + }; + const poisoned = () => { + throw new Error("poisoned primordial"); + }; + let first: Awaited> | undefined; + let firstAgain: Awaited> | undefined; + let second: Awaited> | undefined; + let evicted: + | Awaited> + | undefined; + let loads = 0; + + try { + Reflect.set(Array.prototype, "map", poisoned); + Reflect.set(Array.prototype, "sort", poisoned); + Reflect.set(Date, "now", poisoned); + Reflect.set(JSON, "stringify", poisoned); + Reflect.set(globalThis, "Map", class PoisonedMap {}); + Reflect.set(original.map.prototype, "clear", poisoned); + Reflect.set(original.map.prototype, "delete", poisoned); + Reflect.set(original.map.prototype, "forEach", poisoned); + Reflect.set(original.map.prototype, "get", poisoned); + Reflect.set(original.map.prototype, "set", poisoned); + Object.defineProperty(original.map.prototype, "size", { + configurable: true, + get: poisoned, + }); + Reflect.set(Math, "min", poisoned); + Reflect.set(Number, "isFinite", poisoned); + Reflect.set(Number, "isSafeInteger", poisoned); + Reflect.set(Object, "entries", poisoned); + Reflect.set(globalThis, "Promise", class PoisonedPromise {}); + Reflect.set(original.promise, "resolve", poisoned); + Reflect.set(globalThis, "Set", class PoisonedSet {}); + Reflect.set(original.set.prototype, "add", poisoned); + Reflect.set(original.set.prototype, "delete", poisoned); + Object.defineProperty(original.set.prototype, "size", { + configurable: true, + get: poisoned, + }); + + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: async (_path, _adapter, config) => ({ + imports: { + loaded: String(++loads), + package: config?.resolve?.importMap?.imports?.package ?? "", + }, + }), + }); + const contextA = { contentSourceId: "source", config: configA }; + const contextB = { contentSourceId: "source", config: configB }; + + first = await preloader.preload("/project", adapter, "project", contextA); + firstAgain = await preloader.preload( + "/project", + adapter, + "project", + contextA, + ); + second = await preloader.preload("/project", adapter, "project", contextB); + evicted = await preloader.getCached("project", contextA); + } finally { + Reflect.set(Array.prototype, "map", original.arrayMap); + Reflect.set(Array.prototype, "sort", original.arraySort); + Reflect.set(Date, "now", original.dateNow); + Reflect.set(JSON, "stringify", original.jsonStringify); + Reflect.set(original.map.prototype, "clear", original.mapClear); + Reflect.set(original.map.prototype, "delete", original.mapDelete); + Reflect.set(original.map.prototype, "forEach", original.mapForEach); + Reflect.set(original.map.prototype, "get", original.mapGet); + Reflect.set(original.map.prototype, "set", original.mapSet); + Object.defineProperty(original.map.prototype, "size", original.mapSize); + Reflect.set(Math, "min", original.mathMin); + Reflect.set(Number, "isFinite", original.numberIsFinite); + Reflect.set(Number, "isSafeInteger", original.numberIsSafeInteger); + Reflect.set(Object, "entries", original.objectEntries); + Reflect.set(original.promise, "resolve", original.promiseResolve); + Reflect.set(original.set.prototype, "add", original.setAdd); + Reflect.set(original.set.prototype, "delete", original.setDelete); + Object.defineProperty(original.set.prototype, "size", original.setSize); + Reflect.set(globalThis, "Map", original.map); + Reflect.set(globalThis, "Promise", original.promise); + Reflect.set(globalThis, "Set", original.set); + } + + return { + firstLoaded: first?.imports?.loaded, + firstSame: firstAgain === first, + secondLoaded: second?.imports?.loaded, + secondPackage: second?.imports?.package, + evicted: evicted === undefined, + loads, + }; +} + +try { + const result = await runPoisoningRegression(); + postMessage({ ok: true, result }); +} catch (error) { + postMessage({ + ok: false, + error: error instanceof Error ? (error.stack ?? error.message) : String(error), + }); +} diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 1cc6270d05..65d0de6965 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1,14 +1,23 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertRejects, assertThrows } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; -import { clearImportMapCache, getCachedImportMap, preloadImportMap } from "./preloader.ts"; +import { + clearImportMapCache, + getCachedImportMap, + ImportMapPreloader, + preloadImportMap, +} from "./preloader.ts"; +import { validateVeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import type { ImportMapConfig } from "./types.ts"; function createMinimalAdapter(): RuntimeAdapter { return { fs: { readFile: () => { - throw new Error("not found"); + const error = new Error("not found") as Error & { code: string }; + error.code = "ENOENT"; + throw error; }, writeFile: () => {}, exists: () => false, @@ -22,7 +31,27 @@ function createMinimalAdapter(): RuntimeAdapter { env: { get: () => undefined, }, - } as RuntimeAdapter; + } as unknown as RuntimeAdapter; +} + +function createDeferred() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, reject, resolve }; +} + +async function waitForLoadCount( + loads: readonly unknown[], + expected: number, +): Promise { + for (let attempt = 0; attempt < 100 && loads.length < expected; attempt++) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + assertEquals(loads.length, expected); } describe("modules/import-map/preloader", () => { @@ -57,6 +86,257 @@ describe("modules/import-map/preloader", () => { assertEquals(typeof result1, "object"); assertEquals(typeof result2, "object"); }); + + it("isolates cache entries when one project source receives a changed config map", async () => { + clearImportMapCache(); + const adapter = createMinimalAdapter(); + const firstConfig = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { package: "https://example.com/package-v1.ts" }, + }, + }, + }); + const secondConfig = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { package: "https://example.com/package-v2.ts" }, + }, + }, + }); + const firstContext = { + contentSourceId: "release-1", + config: firstConfig, + }; + + const first = await preloadImportMap( + "/shared-project", + adapter, + "project-1", + firstContext, + ); + const firstAgain = await preloadImportMap( + "/shared-project", + adapter, + "project-1", + firstContext, + ); + const changed = await preloadImportMap( + "/shared-project", + adapter, + "project-1", + { + contentSourceId: "release-1", + config: secondConfig, + }, + ); + + assertEquals(first, firstAgain); + assertEquals(first.imports?.package, "https://example.com/package-v1.ts"); + assertEquals(changed.imports?.package, "https://example.com/package-v2.ts"); + assertEquals(first === changed, false); + }); + + it("binds variant identity and loading to one pre-await config snapshot", async () => { + const adapter = createMinimalAdapter(); + const releaseLoader = createDeferred(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + ttlMs: 1_000, + loadImportMap: async (_path, _adapter, config) => { + loads += 1; + await releaseLoader.promise; + return { + imports: { + package: config?.resolve?.importMap?.imports?.package ?? "", + }, + }; + }, + }); + const config = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { package: "https://example.com/package-a.ts" }, + }, + }, + }); + const context = { contentSourceId: "release", config }; + + const firstPromise = preloader.preload( + "/atomic-project", + adapter, + "atomic-project", + context, + ); + const mutableImports = config.resolve?.importMap?.imports as Record< + string, + string + >; + mutableImports.package = "https://example.com/package-b.ts"; + releaseLoader.resolve(); + const first = await firstPromise; + + const originalContext = { + contentSourceId: "release", + config: validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { package: "https://example.com/package-a.ts" }, + }, + }, + }), + }; + const cachedOriginal = await preloader.preload( + "/atomic-project", + adapter, + "atomic-project", + originalContext, + ); + const changed = await preloader.preload( + "/atomic-project", + adapter, + "atomic-project", + context, + ); + + assertEquals(first.imports?.package, "https://example.com/package-a.ts"); + assertEquals(cachedOriginal, first); + assertEquals(changed.imports?.package, "https://example.com/package-b.ts"); + assertEquals(loads, 2); + }); + + it("isolates cache entries across content sources with the same validated config", async () => { + clearImportMapCache(); + const adapter = createMinimalAdapter(); + const config = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { package: "https://example.com/package.ts" }, + }, + }, + }); + + const release = await preloadImportMap( + "/shared-project", + adapter, + "project-1", + { contentSourceId: "release-1", config }, + ); + const branch = await preloadImportMap( + "/shared-project", + adapter, + "project-1", + { contentSourceId: "branch-main", config }, + ); + + assertEquals(release === branch, false); + }); + + it("snapshots and deep-freezes loader output before publishing it", async () => { + const adapter = createMinimalAdapter(); + const loadedMap = { + imports: { package: "https://example.com/package-v1.ts" }, + scopes: { + "https://example.com/": { + scoped: "https://example.com/scoped-v1.ts", + }, + }, + }; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: async () => loadedMap, + }); + + const published = await preloader.preload( + "/immutable-loader-output", + adapter, + "immutable-loader-output", + ); + loadedMap.imports.package = "https://example.com/package-mutated.ts"; + loadedMap.scopes["https://example.com/"].scoped = "https://example.com/scoped-mutated.ts"; + + assertEquals(published === loadedMap, false); + assertEquals(Object.isFrozen(published), true); + assertEquals(Object.isFrozen(published.imports), true); + assertEquals(Object.isFrozen(published.scopes), true); + assertEquals( + Object.isFrozen(published.scopes?.["https://example.com/"]), + true, + ); + assertThrows( + () => { + published.imports!.package = "https://example.com/caller-mutation.ts"; + }, + TypeError, + ); + assertEquals( + published.imports?.package, + "https://example.com/package-v1.ts", + ); + assertEquals( + published.scopes?.["https://example.com/"]?.scoped, + "https://example.com/scoped-v1.ts", + ); + assertEquals( + await preloader.getCached("immutable-loader-output"), + published, + ); + assertEquals( + await preloader.preload( + "/immutable-loader-output", + adapter, + "immutable-loader-output", + ), + published, + ); + }); + + it("rejects malformed loader output before publication and permits retry", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: async () => { + loads += 1; + if (loads === 1) { + return { + imports: { package: 42 }, + } as unknown as ImportMapConfig; + } + return { + imports: { package: "https://example.com/recovered.ts" }, + }; + }, + }); + + await assertRejects( + () => + preloader.preload( + "/invalid-loader-output", + adapter, + "invalid-loader-output", + ), + TypeError, + "must be a string", + ); + const recovered = await preloader.preload( + "/invalid-loader-output", + adapter, + "invalid-loader-output", + ); + + assertEquals( + recovered.imports?.package, + "https://example.com/recovered.ts", + ); + assertEquals(loads, 2); + }); }); describe("getCachedImportMap", () => { @@ -80,6 +360,449 @@ describe("modules/import-map/preloader", () => { }); }); + describe("bounded cache lifecycle", () => { + function createTestPreloader(input: { + maxProjects?: number; + maxVariantsPerProject?: number; + ttlMs?: number; + now?: () => number; + }) { + let loads = 0; + const preloader = new ImportMapPreloader({ + ...input, + loadImportMap: () => + Promise.resolve({ + imports: { loaded: String(++loads) }, + }), + }); + return { preloader, getLoads: () => loads }; + } + + it("evicts the least-recently-used variant within one project", async () => { + const adapter = createMinimalAdapter(); + const { preloader } = createTestPreloader({ + maxProjects: 2, + maxVariantsPerProject: 2, + ttlMs: 1_000, + }); + const projectDir = "/bounded-variants"; + const projectId = "project-1"; + const sourceA = { contentSourceId: "source-a" }; + const sourceB = { contentSourceId: "source-b" }; + const sourceC = { contentSourceId: "source-c" }; + + await preloader.preload(projectDir, adapter, projectId, sourceA); + await preloader.preload(projectDir, adapter, projectId, sourceB); + await preloader.getCached(projectId, sourceA); + await preloader.preload(projectDir, adapter, projectId, sourceC); + + assertEquals(await preloader.getCached(projectId, sourceA) !== undefined, true); + assertEquals(await preloader.getCached(projectId, sourceB), undefined); + assertEquals(await preloader.getCached(projectId, sourceC) !== undefined, true); + }); + + it("evicts the least-recently-used project bucket", async () => { + const adapter = createMinimalAdapter(); + const { preloader } = createTestPreloader({ + maxProjects: 2, + maxVariantsPerProject: 2, + ttlMs: 1_000, + }); + + await preloader.preload("/project-a", adapter, "project-a"); + await preloader.preload("/project-b", adapter, "project-b"); + await preloader.getCached("project-a"); + await preloader.preload("/project-c", adapter, "project-c"); + + assertEquals(await preloader.getCached("project-a") !== undefined, true); + assertEquals(await preloader.getCached("project-b"), undefined); + assertEquals(await preloader.getCached("project-c") !== undefined, true); + }); + + it("expires settled entries against an injected clock and reloads them", async () => { + const adapter = createMinimalAdapter(); + let now = 1_000; + const { preloader, getLoads } = createTestPreloader({ + maxProjects: 2, + maxVariantsPerProject: 2, + ttlMs: 100, + now: () => now, + }); + const context = { contentSourceId: "source-a" }; + + const first = await preloader.preload("/ttl-project", adapter, "ttl-project", context); + now = 1_099; + assertEquals(await preloader.getCached("ttl-project", context), first); + now = 1_100; + assertEquals(await preloader.getCached("ttl-project", context), undefined); + + const reloaded = await preloader.preload( + "/ttl-project", + adapter, + "ttl-project", + context, + ); + assertEquals(getLoads(), 2); + assertEquals(reloaded === first, false); + }); + + it("publishes one authoritative replacement on direct preload after expiry", async () => { + const adapter = createMinimalAdapter(); + let now = 1_000; + const { preloader, getLoads } = createTestPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 100, + now: () => now, + }); + const context = { contentSourceId: "source-a" }; + + const expired = await preloader.preload( + "/direct-expiry", + adapter, + "direct-expiry", + context, + ); + now = 1_100; + const replacement = await preloader.preload( + "/direct-expiry", + adapter, + "direct-expiry", + context, + ); + const cachedReplacement = await preloader.preload( + "/direct-expiry", + adapter, + "direct-expiry", + context, + ); + + assertEquals(replacement === expired, false); + assertEquals(cachedReplacement, replacement); + assertEquals(getLoads(), 2); + }); + + it("deduplicates concurrent direct refreshes at capacity after expiry", async () => { + const adapter = createMinimalAdapter(); + let now = 1_000; + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 100, + now: () => now, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + const context = { contentSourceId: "source-a" }; + + const initialPromise = preloader.preload( + "/concurrent-direct-expiry", + adapter, + "concurrent-direct-expiry", + context, + ); + await waitForLoadCount(loads, 1); + loads[0]!.resolve({ imports: { loaded: "initial" } }); + const initial = await initialPromise; + + now = 1_100; + const replacementPromise = preloader.preload( + "/concurrent-direct-expiry", + adapter, + "concurrent-direct-expiry", + context, + ); + await waitForLoadCount(loads, 2); + const duplicatePromise = preloader.preload( + "/concurrent-direct-expiry", + adapter, + "concurrent-direct-expiry", + context, + ); + const cachedPromise = preloader.getCached( + "concurrent-direct-expiry", + context, + ); + + await Promise.resolve(); + assertEquals(loads.length, 2); + loads[1]!.resolve({ imports: { loaded: "replacement" } }); + const [replacement, duplicate, cached] = await Promise.all([ + replacementPromise, + duplicatePromise, + cachedPromise, + ]); + + assertEquals(replacement === initial, false); + assertEquals(duplicate, replacement); + assertEquals(cached, replacement); + assertEquals( + await preloader.preload( + "/concurrent-direct-expiry", + adapter, + "concurrent-direct-expiry", + context, + ), + replacement, + ); + assertEquals(loads.length, 2); + }); + + it("removes a settled entry when the injected clock throws", async () => { + const adapter = createMinimalAdapter(); + let clockReads = 0; + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 100, + now: () => { + clockReads += 1; + if (clockReads === 3) throw new Error("clock unavailable"); + return 1_000; + }, + loadImportMap: async () => ({ + imports: { loaded: String(++loads) }, + }), + }); + const context = { contentSourceId: "source-a" }; + + const first = await preloader.preload( + "/throwing-clock", + adapter, + "throwing-clock", + context, + ); + + assertEquals(first.imports?.loaded, "1"); + assertEquals( + await preloader.getCached("throwing-clock", context), + undefined, + ); + + const second = await preloader.preload( + "/throwing-clock", + adapter, + "throwing-clock", + context, + ); + assertEquals(second.imports?.loaded, "2"); + assertEquals(loads, 2); + }); + + it("preserves explicit project invalidation in a bounded cache", async () => { + const adapter = createMinimalAdapter(); + const { preloader } = createTestPreloader({ + maxProjects: 2, + maxVariantsPerProject: 2, + ttlMs: 1_000, + }); + + await preloader.preload("/project-a", adapter, "project-a"); + await preloader.preload("/project-b", adapter, "project-b"); + preloader.clear("project-a"); + + assertEquals(await preloader.getCached("project-a"), undefined); + assertEquals(await preloader.getCached("project-b") !== undefined, true); + }); + + it("does not publish pre-clear work after identity hashing resumes", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: async () => ({ + imports: { loaded: String(++loads) }, + }), + }); + const context = { contentSourceId: "source-a" }; + + const preClear = preloader.preload( + "/project-a", + adapter, + "project-a", + context, + ); + preloader.clear("project-a"); + + await assertRejects( + () => + preloader.preload( + "/project-a", + adapter, + "project-a", + context, + ), + RangeError, + "capacity is occupied by in-flight loads", + ); + const staleResult = await preClear; + assertEquals(staleResult.imports?.loaded, "1"); + assertEquals(Object.isFrozen(staleResult), true); + assertEquals(Object.isFrozen(staleResult.imports), true); + assertThrows( + () => { + staleResult.imports!.loaded = "caller-mutation"; + }, + TypeError, + ); + assertEquals(await preloader.getCached("project-a", context), undefined); + + const reloaded = await preloader.preload( + "/project-a", + adapter, + "project-a", + context, + ); + assertEquals(reloaded.imports?.loaded, "2"); + assertEquals(await preloader.getCached("project-a", context), reloaded); + }); + + it("does not publish pre-clear work into a new global generation", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: async () => ({ + imports: { loaded: String(++loads) }, + }), + }); + const context = { contentSourceId: "source-a" }; + + const preClear = preloader.preload( + "/project-a", + adapter, + "project-a", + context, + ); + preloader.clear(); + + assertEquals((await preClear).imports?.loaded, "1"); + assertEquals(await preloader.getCached("project-a", context), undefined); + }); + + it("keeps in-flight project work admitted instead of evicting and duplicating it", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const first = preloader.preload("/project-a", adapter, "project-a"); + await Promise.resolve(); + const sameKey = preloader.preload("/project-a", adapter, "project-a"); + await assertRejects( + () => preloader.preload("/project-b", adapter, "project-b"), + RangeError, + "capacity is occupied by in-flight loads", + ); + await waitForLoadCount(loads, 1); + assertEquals(loads.length, 1); + + loads[0]!.resolve({ imports: { source: "a" } }); + const firstResult = await first; + assertEquals(firstResult.imports?.source, "a"); + assertEquals(await sameKey, firstResult); + + const second = preloader.preload("/project-b", adapter, "project-b"); + await waitForLoadCount(loads, 2); + loads[1]!.resolve({ imports: { source: "b" } }); + assertEquals((await second).imports?.source, "b"); + }); + + it("bounds identity and loader work across variants and explicit invalidation", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + const sourceA = { contentSourceId: "source-a" }; + const sourceB = { contentSourceId: "source-b" }; + + const first = preloader.preload("/project", adapter, "project", sourceA); + await assertRejects( + () => preloader.preload("/project", adapter, "project", sourceB), + RangeError, + "capacity is occupied by in-flight loads", + ); + preloader.clear("project"); + await assertRejects( + () => preloader.preload("/project", adapter, "project", sourceA), + RangeError, + "capacity is occupied by in-flight loads", + ); + await waitForLoadCount(loads, 1); + assertEquals(loads.length, 1); + + loads[0]!.resolve({ imports: { source: "a" } }); + await first; + const reloaded = preloader.preload("/project", adapter, "project", sourceB); + await waitForLoadCount(loads, 2); + loads[1]!.resolve({ imports: { source: "b" } }); + assertEquals((await reloaded).imports?.source, "b"); + }); + + it("keeps variant identity and capacity deterministic after primordial poisoning", async () => { + const worker = new Worker( + new URL("./preloader-primordial-poisoning.worker.ts", import.meta.url), + { type: "module" }, + ); + try { + const result = await new Promise<{ + firstLoaded: string | undefined; + firstSame: boolean; + secondLoaded: string | undefined; + secondPackage: string | undefined; + evicted: boolean; + loads: number; + }>((resolve, reject) => { + worker.onmessage = (event) => { + const message = event.data as + | { ok: true; result: Parameters[0] } + | { ok: false; error: string }; + if (message.ok) resolve(message.result); + else reject(new Error(message.error)); + }; + worker.onerror = (event) => reject(event.error ?? new Error(event.message)); + }); + + assertEquals(result.firstLoaded, "1"); + assertEquals(result.firstSame, true); + assertEquals(result.secondLoaded, "2"); + assertEquals( + result.secondPackage, + "https://example.com/package-b.ts", + ); + assertEquals(result.evicted, true); + assertEquals(result.loads, 2); + } finally { + worker.terminate(); + } + }); + }); + describe("clearImportMapCache", () => { it("should clear cache for specific project", async () => { clearImportMapCache(); diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 8866d1e066..b4cac40bc2 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -1,47 +1,773 @@ +import type { VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import { snapshotImportMap } from "#veryfront/transforms/pipeline/cache-identity.ts"; +import { computeHash } from "#veryfront/utils/hash-utils.ts"; import type { ImportMapConfig } from "./types.ts"; import { loadImportMap } from "./loader.ts"; -const importMapCache = new Map>(); +export interface PreloadImportMapContext { + /** Immutable content source selected for this render (release, branch, or environment). */ + contentSourceId?: string; + /** Config already validated for the authenticated request. */ + config?: VeryfrontConfig; +} + +const IMPORT_MAP_CACHE_IDENTITY_NAMESPACE = "veryfront:preloaded-import-map:v2"; +const DEFAULT_MAX_IMPORT_MAP_PROJECTS = 512; +const DEFAULT_MAX_IMPORT_MAP_VARIANTS_PER_PROJECT = 16; +const DEFAULT_IMPORT_MAP_TTL_MS = 10 * 60 * 1_000; + +// Project code can execute in the same realm before a later request reaches +// this cache. Capture every primitive used for identity, admission, and +// settlement so replacing shared built-ins cannot redirect dependency graphs. +const ArrayPrototypeSort = Array.prototype.sort; +const DateNow = Date.now; +const IntrinsicMap = Map; +const IntrinsicPromise = Promise; +const IntrinsicRangeError = RangeError; +const IntrinsicSet = Set; +const JSONStringify = JSON.stringify; +const MapPrototypeClear = Map.prototype.clear; +const MapPrototypeDelete = Map.prototype.delete; +const MapPrototypeForEach = Map.prototype.forEach; +const MapPrototypeGet = Map.prototype.get; +const MapPrototypeSet = Map.prototype.set; +const MapPrototypeSize = Object.getOwnPropertyDescriptor(Map.prototype, "size")! + .get!; +const MathMin = Math.min; +const NUMBER_MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; +const NumberIsFinite = Number.isFinite; +const NumberIsSafeInteger = Number.isSafeInteger; +const ObjectEntries = Object.entries; +const ObjectFreeze = Object.freeze; +const PromisePrototypeThen = Promise.prototype.then; +const PromiseResolve = Promise.resolve; +const ReflectApply = Reflect.apply; +const SetPrototypeAdd = Set.prototype.add; +const SetPrototypeDelete = Set.prototype.delete; +const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size")! + .get!; + +function arraySort( + values: T[], + compare: (left: T, right: T) => number, +): T[] { + return ReflectApply(ArrayPrototypeSort, values, [compare]) as T[]; +} + +function mapClear(map: Map): void { + ReflectApply(MapPrototypeClear, map, []); +} + +function mapDelete(map: Map, key: K): boolean { + return ReflectApply(MapPrototypeDelete, map, [key]) as boolean; +} + +function mapForEach( + map: Map, + callback: (value: V, key: K) => void, +): void { + ReflectApply(MapPrototypeForEach, map, [callback]); +} + +function mapGet(map: Map, key: K): V | undefined { + return ReflectApply(MapPrototypeGet, map, [key]) as V | undefined; +} + +function mapSet(map: Map, key: K, value: V): void { + ReflectApply(MapPrototypeSet, map, [key, value]); +} + +function mapSize(map: Map): number { + return ReflectApply(MapPrototypeSize, map, []) as number; +} + +function promiseThen( + promise: Promise, + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, +): Promise { + return ReflectApply(PromisePrototypeThen, promise, [ + onFulfilled, + onRejected, + ]) as Promise; +} + +function resolvedPromise(): Promise { + return ReflectApply(PromiseResolve, IntrinsicPromise, []) as Promise; +} + +function setAdd(set: Set, value: T): void { + ReflectApply(SetPrototypeAdd, set, [value]); +} + +function setDelete(set: Set, value: T): boolean { + return ReflectApply(SetPrototypeDelete, set, [value]) as boolean; +} + +function setSize(set: Set): number { + return ReflectApply(SetPrototypeSize, set, []) as number; +} + +interface CachedImportMap { + readonly promise: Promise; + /** Starts when the load settles; in-flight work is never expired mid-flight. */ + expiresAt: number | null; +} + +type ProjectImportMapCache = Map; + +interface ProjectImportMapState { + readonly variants: ProjectImportMapCache; + generation: object; + /** Hashes being computed or retained while their matching load is in flight. */ + readonly identityBuilds: Map>; +} + +function createGeneration(): object { + return ObjectFreeze({}); +} + +export interface ImportMapPreloaderOptions { + /** Maximum tenant/project buckets retained by one preloader. */ + maxProjects?: number; + /** Maximum content-source/config variants retained for one project. */ + maxVariantsPerProject?: number; + /** Retention lifetime after a successful load. */ + ttlMs?: number; + /** Monotonic-enough clock seam; defaults to Date.now. */ + now?: () => number; + /** Loader seam for alternate runtimes and deterministic verification. */ + loadImportMap?: typeof loadImportMap; +} + +function compareEntries( + left: readonly [string, string], + right: readonly [string, string], +): number { + return left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0; +} + +/** + * Bind cache identity and loader input to one immutable import-map snapshot + * before the first async boundary. Validated config objects are caller-owned + * and need not remain unchanged while SHA-256 is being computed. + */ +function snapshotPreloadContext( + context?: PreloadImportMapContext, +): PreloadImportMapContext | undefined { + if (!context) return undefined; + const contentSourceId = context.contentSourceId; + const config = context.config; + if (!config) return ObjectFreeze({ contentSourceId }); + + const resolve = config.resolve; + const importMap = snapshotImportMap(resolve?.importMap ?? {}); + const exactConfig = ObjectFreeze({ + ...config, + resolve: ObjectFreeze({ + ...resolve, + importMap, + }), + }) as VeryfrontConfig; + return ObjectFreeze({ contentSourceId, config: exactConfig }); +} + +function buildVariantCanonicalIdentity( + context?: PreloadImportMapContext, +): string { + const importMap = context?.config?.resolve?.importMap; + let canonical = `${IMPORT_MAP_CACHE_IDENTITY_NAMESPACE}\0source:${ + JSONStringify(context?.contentSourceId ?? null) + }\0`; + if (!context?.config) return `${canonical}ambient`; + + canonical += "validated"; + const imports = arraySort( + ObjectEntries(importMap?.imports ?? {}), + compareEntries, + ); + for (let index = 0; index < imports.length; index++) { + const entry = imports[index]!; + // JSON stringification is applied only to primitives. That keeps escaping + // canonical without exposing identity objects to inherited toJSON hooks. + canonical += `\0import:${JSONStringify(entry[0])}:${JSONStringify(entry[1])}`; + } + + const scopes = arraySort( + ObjectEntries(importMap?.scopes ?? {}), + (left, right) => left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0, + ); + for (let scopeIndex = 0; scopeIndex < scopes.length; scopeIndex++) { + const scopeEntry = scopes[scopeIndex]!; + canonical += `\0scope:${JSONStringify(scopeEntry[0])}`; + const mappings = arraySort(ObjectEntries(scopeEntry[1]), compareEntries); + for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) { + const mapping = mappings[mappingIndex]!; + canonical += `\0mapping:${JSONStringify(mapping[0])}:${JSONStringify(mapping[1])}`; + } + } + return canonical; +} + +function readPositiveSafeInteger( + value: number | undefined, + fallback: number, + label: string, +): number { + const resolved = value ?? fallback; + if (!NumberIsSafeInteger(resolved) || resolved <= 0) { + throw new IntrinsicRangeError(`${label} must be a positive safe integer`); + } + return resolved; +} + +/** + * Bounded two-level import-map cache. + * + * Map insertion order is the LRU order at both levels. Expiry is lazy, avoiding + * a process-wide timer while still placing a hard ceiling on retained values. + */ +export class ImportMapPreloader { + private readonly projects = new IntrinsicMap(); + /** Underlying loader work remains accounted for even after explicit invalidation. */ + private readonly activeLoads = new IntrinsicSet>(); + private readonly activeIdentityBuilds = new IntrinsicSet>(); + private globalGeneration = createGeneration(); + private readonly maxProjects: number; + private readonly maxVariantsPerProject: number; + private readonly maxConcurrentLoads: number; + private readonly ttlMs: number; + private readonly now: () => number; + private readonly loader: typeof loadImportMap; + + constructor(options: ImportMapPreloaderOptions = {}) { + this.maxProjects = readPositiveSafeInteger( + options.maxProjects, + DEFAULT_MAX_IMPORT_MAP_PROJECTS, + "maxProjects", + ); + this.maxVariantsPerProject = readPositiveSafeInteger( + options.maxVariantsPerProject, + DEFAULT_MAX_IMPORT_MAP_VARIANTS_PER_PROJECT, + "maxVariantsPerProject", + ); + this.maxConcurrentLoads = MathMin( + NUMBER_MAX_SAFE_INTEGER, + this.maxProjects * this.maxVariantsPerProject, + ); + this.ttlMs = readPositiveSafeInteger( + options.ttlMs, + DEFAULT_IMPORT_MAP_TTL_MS, + "ttlMs", + ); + this.now = options.now ?? DateNow; + this.loader = options.loadImportMap ?? loadImportMap; + } + + private readNow(): number { + const now = this.now(); + if (!NumberIsFinite(now)) { + throw new IntrinsicRangeError("Import-map cache clock must be finite"); + } + return now; + } + + private touchProject(cacheKey: string, projectState: ProjectImportMapState): void { + mapDelete(this.projects, cacheKey); + mapSet(this.projects, cacheKey, projectState); + } + + private touchVariant( + projectCache: ProjectImportMapCache, + variantKey: string, + entry: CachedImportMap, + ): void { + mapDelete(projectCache, variantKey); + mapSet(projectCache, variantKey, entry); + } + + private deleteEntry( + cacheKey: string, + projectState: ProjectImportMapState, + variantKey: string, + entry: CachedImportMap, + removeEmptyProject = true, + ): void { + const projectCache = projectState.variants; + if (mapGet(projectCache, variantKey) !== entry) return; + mapDelete(projectCache, variantKey); + if ( + removeEmptyProject && + mapSize(projectCache) === 0 && + mapSize(projectState.identityBuilds) === 0 && + mapGet(this.projects, cacheKey) === projectState + ) { + mapDelete(this.projects, cacheKey); + } + } + + private getEntry( + cacheKey: string, + variantKey: string, + now: number, + preserveProjectIfEmpty = false, + ): CachedImportMap | undefined { + const projectState = mapGet(this.projects, cacheKey); + const projectCache = projectState?.variants; + const entry = projectCache ? mapGet(projectCache, variantKey) : undefined; + if (!projectState || !projectCache || !entry) return undefined; + + if (entry.expiresAt !== null && entry.expiresAt <= now) { + this.deleteEntry( + cacheKey, + projectState, + variantKey, + entry, + !preserveProjectIfEmpty, + ); + return undefined; + } + + this.touchVariant(projectCache, variantKey, entry); + this.touchProject(cacheKey, projectState); + return entry; + } + + private capacityError(scope: "projects" | "variants" | "loads"): RangeError { + return new IntrinsicRangeError( + `Import-map preloader ${scope} capacity is occupied by in-flight loads; retry after a load settles`, + ); + } + + private makeProjectRoom(now: number): void { + mapForEach(this.projects, (projectState, cacheKey) => { + const projectCache = projectState.variants; + mapForEach(projectCache, (entry, variantKey) => { + if (entry.expiresAt !== null && entry.expiresAt <= now) { + mapDelete(projectCache, variantKey); + } + }); + if ( + mapSize(projectCache) === 0 && + mapSize(projectState.identityBuilds) === 0 + ) { + mapDelete(this.projects, cacheKey); + } + }); + + while (mapSize(this.projects) >= this.maxProjects) { + let oldestSettledProject: string | undefined; + mapForEach(this.projects, (projectState, cacheKey) => { + if (oldestSettledProject !== undefined) return; + if (mapSize(projectState.identityBuilds) > 0) return; + let hasInFlightEntry = false; + mapForEach(projectState.variants, (entry) => { + if (entry.expiresAt === null) { + hasInFlightEntry = true; + } + }); + if (!hasInFlightEntry) { + oldestSettledProject = cacheKey; + } + }); + if (oldestSettledProject === undefined) throw this.capacityError("projects"); + mapDelete(this.projects, oldestSettledProject); + } + } + + private makeVariantRoom(projectCache: ProjectImportMapCache, now: number): void { + mapForEach(projectCache, (entry, variantKey) => { + if (entry.expiresAt !== null && entry.expiresAt <= now) { + mapDelete(projectCache, variantKey); + } + }); + + while (mapSize(projectCache) >= this.maxVariantsPerProject) { + let oldestSettledVariant: string | undefined; + mapForEach(projectCache, (entry, variantKey) => { + if (oldestSettledVariant !== undefined) return; + if (entry.expiresAt !== null) { + oldestSettledVariant = variantKey; + } + }); + if (oldestSettledVariant === undefined) throw this.capacityError("variants"); + mapDelete(projectCache, oldestSettledVariant); + } + } + + private trackActiveLoad(promise: Promise): void { + setAdd(this.activeLoads, promise); + promiseThen( + promise, + () => { + setDelete(this.activeLoads, promise); + }, + () => { + setDelete(this.activeLoads, promise); + }, + ); + } + + private hasActiveWorkCapacity(): boolean { + return setSize(this.activeLoads) + setSize(this.activeIdentityBuilds) < + this.maxConcurrentLoads; + } + + private releaseIdentityBuild( + projectState: ProjectImportMapState, + canonicalIdentity: string, + promise: Promise, + ): void { + if (mapGet(projectState.identityBuilds, canonicalIdentity) === promise) { + mapDelete(projectState.identityBuilds, canonicalIdentity); + } + } + + private getOrCreateIdentityBuild( + projectState: ProjectImportMapState, + canonicalIdentity: string, + ): Promise { + const existing = mapGet(projectState.identityBuilds, canonicalIdentity); + if (existing) return existing; + if (mapSize(projectState.identityBuilds) >= this.maxVariantsPerProject) { + throw this.capacityError("variants"); + } + if (!this.hasActiveWorkCapacity()) throw this.capacityError("loads"); + + const promise = computeHash(canonicalIdentity); + mapSet(projectState.identityBuilds, canonicalIdentity, promise); + setAdd(this.activeIdentityBuilds, promise); + // Hash settlement frees global hashing capacity immediately. Keep the + // resolved per-project identity until its load settles, though, so a later + // request can reach and join that in-flight entry even when load capacity + // is otherwise full. + const releaseActive = (): void => { + setDelete(this.activeIdentityBuilds, promise); + }; + promiseThen( + promise, + releaseActive, + () => { + releaseActive(); + this.releaseIdentityBuild(projectState, canonicalIdentity, promise); + }, + ); + return promise; + } + + private isCurrentGeneration( + cacheKey: string, + projectState: ProjectImportMapState, + globalGeneration: object, + projectGeneration: object, + ): boolean { + return this.globalGeneration === globalGeneration && + projectState.generation === projectGeneration && + mapGet(this.projects, cacheKey) === projectState; + } + + private removeEmptyProject( + cacheKey: string, + projectState: ProjectImportMapState, + ): void { + if ( + mapSize(projectState.identityBuilds) === 0 && + mapSize(projectState.variants) === 0 && + mapGet(this.projects, cacheKey) === projectState + ) { + mapDelete(this.projects, cacheKey); + } + } + + private startTrackedLoad( + projectDir: string, + adapter: RuntimeAdapter, + config: VeryfrontConfig | undefined, + ): Promise { + if (!this.hasActiveWorkCapacity()) { + throw this.capacityError("loads"); + } + const loaderPromise = promiseThen( + resolvedPromise(), + () => this.loader(projectDir, adapter, config), + ); + const promise = promiseThen( + loaderPromise, + (loadedImportMap) => snapshotImportMap(loadedImportMap), + ); + this.trackActiveLoad(promise); + return promise; + } + + async preload( + projectDir: string, + adapter: RuntimeAdapter, + projectId?: string, + context?: PreloadImportMapContext, + ): Promise { + const exactContext = snapshotPreloadContext(context); + const cacheKey = projectId ?? projectDir; + const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); + const admissionNow = this.readNow(); + let projectState = mapGet(this.projects, cacheKey); + if (!projectState) { + this.makeProjectRoom(admissionNow); + projectState = { + variants: new IntrinsicMap(), + generation: createGeneration(), + identityBuilds: new IntrinsicMap(), + }; + mapSet(this.projects, cacheKey, projectState); + } else { + this.touchProject(cacheKey, projectState); + } + + const globalGeneration = this.globalGeneration; + const projectGeneration = projectState.generation; + + let identityBuild: Promise | undefined; + let variantKey: string; + try { + identityBuild = this.getOrCreateIdentityBuild( + projectState, + canonicalIdentity, + ); + variantKey = await identityBuild; + } catch (error) { + if (identityBuild) { + this.releaseIdentityBuild( + projectState, + canonicalIdentity, + identityBuild, + ); + } + this.removeEmptyProject(cacheKey, projectState); + throw error; + } + const releaseIdentity = (): void => { + this.releaseIdentityBuild( + projectState, + canonicalIdentity, + identityBuild, + ); + }; + + // Explicit invalidation during asynchronous identity construction must not + // let pre-clear work enter the post-clear cache generation. The caller can + // still finish against its immutable request snapshot, but only as bounded, + // actively-accounted work. + if ( + !this.isCurrentGeneration( + cacheKey, + projectState, + globalGeneration, + projectGeneration, + ) + ) { + releaseIdentity(); + return this.startTrackedLoad( + projectDir, + adapter, + exactContext?.config, + ); + } + + let now: number; + try { + now = this.readNow(); + } catch (error) { + releaseIdentity(); + this.removeEmptyProject(cacheKey, projectState); + throw error; + } + // The injected clock is application code and can invalidate synchronously. + // Recheck after it runs so publication remains generation-atomic. + if ( + !this.isCurrentGeneration( + cacheKey, + projectState, + globalGeneration, + projectGeneration, + ) + ) { + releaseIdentity(); + return this.startTrackedLoad( + projectDir, + adapter, + exactContext?.config, + ); + } + + try { + // Direct expiry refresh must retain the authoritative project state. + // Removing the empty bucket here would publish the replacement into a + // detached Map that later callers cannot observe. + const cached = this.getEntry(cacheKey, variantKey, now, true); + if (cached) { + if (cached.expiresAt !== null) { + releaseIdentity(); + } + return cached.promise; + } + + const projectCache = projectState.variants; + this.makeVariantRoom(projectCache, now); + + const promise = this.startTrackedLoad( + projectDir, + adapter, + exactContext?.config, + ); + const entry: CachedImportMap = { promise, expiresAt: null }; + mapSet(projectCache, variantKey, entry); + + promiseThen( + promise, + () => { + releaseIdentity(); + if ( + mapGet(this.projects, cacheKey) !== projectState || + mapGet(projectCache, variantKey) !== entry + ) { + return; + } + let settledAt: number; + try { + settledAt = this.now(); + } catch (_) { + this.deleteEntry(cacheKey, projectState, variantKey, entry); + return; + } + if (!NumberIsFinite(settledAt)) { + this.deleteEntry(cacheKey, projectState, variantKey, entry); + return; + } + entry.expiresAt = MathMin( + NUMBER_MAX_SAFE_INTEGER, + settledAt + this.ttlMs, + ); + }, + () => { + releaseIdentity(); + this.deleteEntry(cacheKey, projectState, variantKey, entry); + }, + ); + + return promise; + } catch (error) { + releaseIdentity(); + this.removeEmptyProject(cacheKey, projectState); + throw error; + } + } + + async getCached( + cacheKey: string, + context?: PreloadImportMapContext, + ): Promise { + const exactContext = snapshotPreloadContext(context); + const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); + const projectState = mapGet(this.projects, cacheKey); + if (!projectState) return undefined; + const globalGeneration = this.globalGeneration; + const projectGeneration = projectState.generation; + let identityBuild: Promise | undefined; + let variantKey: string; + try { + identityBuild = this.getOrCreateIdentityBuild( + projectState, + canonicalIdentity, + ); + variantKey = await identityBuild; + } catch (error) { + if (identityBuild) { + this.releaseIdentityBuild( + projectState, + canonicalIdentity, + identityBuild, + ); + } + this.removeEmptyProject(cacheKey, projectState); + throw error; + } + const releaseIdentity = (): void => { + this.releaseIdentityBuild( + projectState, + canonicalIdentity, + identityBuild, + ); + }; + if ( + !this.isCurrentGeneration( + cacheKey, + projectState, + globalGeneration, + projectGeneration, + ) + ) { + releaseIdentity(); + return undefined; + } + let entry: CachedImportMap | undefined; + try { + entry = this.getEntry( + cacheKey, + variantKey, + this.readNow(), + ); + } catch (error) { + releaseIdentity(); + this.removeEmptyProject(cacheKey, projectState); + throw error; + } + if (!entry) { + releaseIdentity(); + this.removeEmptyProject(cacheKey, projectState); + return undefined; + } + if (entry.expiresAt !== null) { + releaseIdentity(); + } + + try { + return await entry.promise; + } catch (_) { + /* expected: the rejection handler removes failed loads */ + return undefined; + } + } + + clear(cacheKey?: string): void { + if (cacheKey !== undefined) { + const projectState = mapGet(this.projects, cacheKey); + if (projectState) projectState.generation = createGeneration(); + mapDelete(this.projects, cacheKey); + return; + } + this.globalGeneration = createGeneration(); + mapClear(this.projects); + } +} + +const defaultImportMapPreloader = new ImportMapPreloader(); export function preloadImportMap( projectDir: string, adapter: RuntimeAdapter, projectId?: string, + context?: PreloadImportMapContext, ): Promise { - const cacheKey = projectId ?? projectDir; - const cached = importMapCache.get(cacheKey); - if (cached) return cached; - - const promise = loadImportMap(projectDir, adapter); - importMapCache.set(cacheKey, promise); - - promise.catch(() => { - importMapCache.delete(cacheKey); - }); - - return promise; + return defaultImportMapPreloader.preload(projectDir, adapter, projectId, context); } -export async function getCachedImportMap( +export function getCachedImportMap( cacheKey: string, + context?: PreloadImportMapContext, ): Promise { - const cached = importMapCache.get(cacheKey); - if (!cached) return undefined; - - try { - return await cached; - } catch (_) { - /* expected: cached import map promise may have been rejected */ - return undefined; - } + return defaultImportMapPreloader.getCached(cacheKey, context); } export function clearImportMapCache(cacheKey?: string): void { - if (cacheKey) { - importMapCache.delete(cacheKey); - return; - } - - importMapCache.clear(); + defaultImportMapPreloader.clear(cacheKey); } diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 6d0bfa1616..3ebc46dd29 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -15,6 +15,11 @@ import { DEFAULT_REACT_VERSION, getReactImportMap } from "./react-cdn.ts"; import { computeHash } from "#veryfront/utils/hash-utils.ts"; const logger = rendererLogger.component("http-cache"); +const ArrayPrototypeMap = Array.prototype.map; +const ArrayPrototypeSort = Array.prototype.sort; +const JSONStringify = JSON.stringify; +const ObjectEntries = Object.entries; +const ReflectApply = Reflect.apply; /** * Cache interface for dependency injection (matches LRU essential methods). @@ -77,16 +82,32 @@ const HTTP_CACHE_FILE_HASH_NAMESPACE = "veryfront:http-module-file:v2"; /** Build an order-independent fingerprint covering imports and scoped imports. */ export function fingerprintImportMap(importMap: ImportMapConfig): Promise { - const imports = Object.entries(importMap.imports ?? {}).sort(compareImportMapKeys); - const scopes = Object.entries(importMap.scopes ?? {}) - .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0) - .map(([scope, scopedImports]) => [ + const imports = ReflectApply( + ArrayPrototypeSort, + ObjectEntries(importMap.imports ?? {}), + [compareImportMapKeys], + ) as Array<[string, string]>; + const sortedScopes = ReflectApply( + ArrayPrototypeSort, + ObjectEntries(importMap.scopes ?? {}), + [([left]: [string, Record], [right]: [string, Record]) => + left < right ? -1 : left > right ? 1 : 0], + ) as Array<[string, Record]>; + const scopes = ReflectApply( + ArrayPrototypeMap, + sortedScopes, + [([scope, scopedImports]: [string, Record]) => [ scope, - Object.entries(scopedImports).sort(compareImportMapKeys), - ]); + ReflectApply( + ArrayPrototypeSort, + ObjectEntries(scopedImports), + [compareImportMapKeys], + ), + ]], + ); return computeHash( - `${HTTP_IMPORT_MAP_FINGERPRINT_NAMESPACE}\0${JSON.stringify({ imports, scopes })}`, + `${HTTP_IMPORT_MAP_FINGERPRINT_NAMESPACE}\0${JSONStringify({ imports, scopes })}`, ); } diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts new file mode 100644 index 0000000000..7fccc300a0 --- /dev/null +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -0,0 +1,353 @@ +import "#veryfront/schemas/_test-setup.ts"; +import { + assertEquals, + assertNotEquals, + assertRejects, + assertThrows, +} from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { + computePipelineConfigIdentity, + fingerprintPipelineImportMap, + getCustomPluginCacheIdentity, + snapshotImportMap, +} from "./cache-identity.ts"; +import { type TransformPlugin, TransformStage } from "./types.ts"; + +const transform = (ctx: { code: string }): string => ctx.code; + +function identityInput( + overrides: Partial[0]> = {}, +) { + return { + reactVersion: "19.1.0", + jsxImportSource: "react", + studioEmbed: false, + dev: false, + ssr: true, + projectDir: "/project", + importMapFingerprint: "a".repeat(64), + customPlugins: [], + ...overrides, + }; +} + +describe("transform pipeline cache identity", () => { + it("snapshots import maps without invoking getters", () => { + let getterCalls = 0; + const imports = Object.create(null) as Record; + Object.defineProperty(imports, "danger", { + enumerable: true, + get() { + getterCalls++; + return "/project/danger.ts"; + }, + }); + + assertThrows( + () => snapshotImportMap({ imports }), + TypeError, + "accessor properties", + ); + assertEquals(getterCalls, 0); + }); + + it("uses an immutable import-map snapshot", () => { + const raw = { imports: { local: "/project/v1.ts" } }; + const snapshot = snapshotImportMap(raw); + raw.imports.local = "/project/v2.ts"; + + assertEquals(snapshot.imports?.local, "/project/v1.ts"); + assertEquals(Object.isFrozen(snapshot), true); + assertEquals(Object.isFrozen(snapshot.imports), true); + }); + + it("fingerprints import maps independent of insertion order", async () => { + const first = snapshotImportMap({ imports: { a: "/a.ts", b: "/b.ts" } }); + const reordered = snapshotImportMap({ imports: { b: "/b.ts", a: "/a.ts" } }); + const changed = snapshotImportMap({ imports: { a: "/a.ts", b: "/v2.ts" } }); + + assertEquals( + await fingerprintPipelineImportMap(first), + await fingerprintPipelineImportMap(reordered), + ); + assertNotEquals( + await fingerprintPipelineImportMap(first), + await fingerprintPipelineImportMap(changed), + ); + }); + + it("preserves nonempty import-map identity after array iterator poisoning", async () => { + const originalArrayIterator = Array.prototype[Symbol.iterator]; + let snapshot: ReturnType | undefined; + try { + Reflect.set( + Array.prototype, + Symbol.iterator, + () => ({ next: () => ({ done: true, value: undefined }) }), + ); + snapshot = snapshotImportMap({ + imports: { package: "https://example.com/package.ts" }, + scopes: { + "https://example.com/": { + scoped: "https://example.com/scoped.ts", + }, + }, + }); + } finally { + Reflect.set(Array.prototype, Symbol.iterator, originalArrayIterator); + } + + assertEquals(snapshot?.imports?.package, "https://example.com/package.ts"); + assertEquals( + snapshot?.scopes?.["https://example.com/"]?.scoped, + "https://example.com/scoped.ts", + ); + assertNotEquals( + await fingerprintPipelineImportMap(snapshot), + await fingerprintPipelineImportMap(snapshotImportMap({})), + ); + }); + + it("disables persistent caching for unidentified custom plugins", () => { + const plugin: TransformPlugin = { + name: "custom", + stage: TransformStage.FINALIZE, + transform, + }; + + assertEquals(getCustomPluginCacheIdentity([plugin]).cacheable, false); + plugin.cacheIdentity = "custom@1"; + assertEquals(getCustomPluginCacheIdentity([plugin]).cacheable, true); + }); + + it("rejects accessor-backed plugin identities without invoking them", () => { + let getterCalls = 0; + const plugin = { + name: "custom", + stage: TransformStage.FINALIZE, + transform, + } as TransformPlugin; + Object.defineProperty(plugin, "cacheIdentity", { + enumerable: true, + get() { + getterCalls++; + return "custom@1"; + }, + }); + + assertThrows( + () => getCustomPluginCacheIdentity([plugin]), + TypeError, + "accessor properties", + ); + assertEquals(getterCalls, 0); + }); + + it("rejects control characters in plugin names used for logs and spans", () => { + const plugin: TransformPlugin = { + name: "custom\nforged-stage", + stage: TransformStage.FINALIZE, + cacheIdentity: "custom@1", + transform, + }; + + assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid name"); + }); + + it("rejects oversized base identity fields before hashing", async () => { + await assertRejects( + () => + computePipelineConfigIdentity( + identityInput({ reactVersion: "x".repeat(64 * 1024 + 1) }), + ), + TypeError, + "React version is too large", + ); + }); + + it("changes when any output-affecting endpoint or plugin identity changes", async () => { + const baseline = await computePipelineConfigIdentity(identityInput()); + const moduleServer = await computePipelineConfigIdentity( + identityInput({ moduleServerUrl: "https://modules.example/v1" }), + ); + const api = await computePipelineConfigIdentity( + identityInput({ apiBaseUrl: "https://api.example/v1" }), + ); + const plugins = await computePipelineConfigIdentity( + identityInput({ customPlugins: [[0, "custom", TransformStage.FINALIZE, "custom@1"]] }), + ); + + assertNotEquals(moduleServer, baseline); + assertNotEquals(api, baseline); + assertNotEquals(plugins, baseline); + }); + + it("changes identity when moduleServerOrigin changes", async () => { + const baseline = await computePipelineConfigIdentity( + identityInput({ moduleServerOrigin: "https://app.example.test" }), + ); + const changed = await computePipelineConfigIdentity( + identityInput({ moduleServerOrigin: "https://preview.example.test" }), + ); + + assertNotEquals(changed, baseline); + }); + + it("changes identity when dependencyPinningCacheKey changes", async () => { + const baseline = await computePipelineConfigIdentity( + identityInput({ dependencyPinningCacheKey: "on:first" }), + ); + const changed = await computePipelineConfigIdentity( + identityInput({ dependencyPinningCacheKey: "on:second" }), + ); + + assertNotEquals(changed, baseline); + }); + + it("uses captured primordials for import-map and plugin identities", async () => { + const original = { + arrayIsArray: Array.isArray, + arrayMap: Array.prototype.map, + arrayPush: Array.prototype.push, + arraySort: Array.prototype.sort, + jsonStringify: JSON.stringify, + mathAbs: Math.abs, + numberIsFinite: Number.isFinite, + objectCreate: Object.create, + objectEntries: Object.entries, + objectFreeze: Object.freeze, + objectGetOwnPropertyDescriptor: Object.getOwnPropertyDescriptor, + objectGetPrototypeOf: Object.getPrototypeOf, + reflectOwnKeys: Reflect.ownKeys, + regexpTest: RegExp.prototype.test, + rangeError: RangeError, + stringTrim: String.prototype.trim, + textEncoderEncode: TextEncoder.prototype.encode, + typeError: TypeError, + }; + const rawImportMap = { + imports: { package: "https://example.com/package.ts" }, + scopes: { + "https://example.com/": { + scoped: "https://example.com/scoped.ts", + }, + }, + }; + const plugin: TransformPlugin = { + name: "custom", + stage: TransformStage.FINALIZE, + cacheIdentity: "custom@1", + transform, + }; + + let snapshot: ReturnType | undefined; + let fingerprint: string | undefined; + let pluginIdentity: ReturnType | undefined; + let pipelineIdentity: string | undefined; + let invalidMapError: unknown; + let oversizedPluginListError: unknown; + try { + Reflect.set(Array, "isArray", () => false); + Reflect.set(Array.prototype, "map", () => { + throw new Error("poisoned Array.prototype.map"); + }); + Reflect.set(Array.prototype, "push", () => 0); + Reflect.set(Array.prototype, "sort", () => { + throw new Error("poisoned Array.prototype.sort"); + }); + Reflect.set(JSON, "stringify", () => { + throw new Error("poisoned JSON.stringify"); + }); + Reflect.set(Math, "abs", () => Number.POSITIVE_INFINITY); + Reflect.set(Number, "isFinite", () => false); + Reflect.set(Object, "create", () => { + throw new Error("poisoned Object.create"); + }); + Reflect.set(Object, "entries", () => { + throw new Error("poisoned Object.entries"); + }); + Reflect.set(Object, "freeze", (value: T): T => value); + Reflect.set(Object, "getOwnPropertyDescriptor", () => { + throw new Error("poisoned Object.getOwnPropertyDescriptor"); + }); + Reflect.set(Object, "getPrototypeOf", () => null); + Reflect.set(Reflect, "ownKeys", () => []); + Reflect.set(RegExp.prototype, "test", () => true); + Reflect.set( + globalThis, + "RangeError", + class PoisonedRangeError extends Error {}, + ); + Reflect.set(String.prototype, "trim", () => "poisoned"); + Reflect.set(TextEncoder.prototype, "encode", () => { + throw new Error("poisoned TextEncoder.encode"); + }); + Reflect.set( + globalThis, + "TypeError", + class PoisonedTypeError extends Error {}, + ); + + snapshot = snapshotImportMap(rawImportMap); + [fingerprint, pipelineIdentity] = await Promise.all([ + fingerprintPipelineImportMap(snapshot), + computePipelineConfigIdentity(identityInput()), + ]); + pluginIdentity = getCustomPluginCacheIdentity([plugin]); + try { + snapshotImportMap(null); + } catch (error) { + invalidMapError = error; + } + try { + getCustomPluginCacheIdentity( + new Array(1_001) as TransformPlugin[], + ); + } catch (error) { + oversizedPluginListError = error; + } + } finally { + Reflect.set(Array, "isArray", original.arrayIsArray); + Reflect.set(Array.prototype, "map", original.arrayMap); + Reflect.set(Array.prototype, "push", original.arrayPush); + Reflect.set(Array.prototype, "sort", original.arraySort); + Reflect.set(JSON, "stringify", original.jsonStringify); + Reflect.set(Math, "abs", original.mathAbs); + Reflect.set(Number, "isFinite", original.numberIsFinite); + Reflect.set(Object, "create", original.objectCreate); + Reflect.set(Object, "entries", original.objectEntries); + Reflect.set(Object, "freeze", original.objectFreeze); + Reflect.set( + Object, + "getOwnPropertyDescriptor", + original.objectGetOwnPropertyDescriptor, + ); + Reflect.set(Object, "getPrototypeOf", original.objectGetPrototypeOf); + Reflect.set(Reflect, "ownKeys", original.reflectOwnKeys); + Reflect.set(RegExp.prototype, "test", original.regexpTest); + Reflect.set(globalThis, "RangeError", original.rangeError); + Reflect.set(String.prototype, "trim", original.stringTrim); + Reflect.set(TextEncoder.prototype, "encode", original.textEncoderEncode); + Reflect.set(globalThis, "TypeError", original.typeError); + } + + assertEquals(snapshot?.imports?.package, "https://example.com/package.ts"); + assertEquals(Object.isFrozen(snapshot), true); + assertEquals(Object.isFrozen(snapshot?.imports), true); + assertEquals(typeof fingerprint, "string"); + assertEquals(fingerprint?.length, 64); + assertEquals(pluginIdentity?.cacheable, true); + if (!pluginIdentity?.cacheable) { + throw new Error("Expected a cacheable plugin identity"); + } + assertEquals(pluginIdentity.identity.length, 1); + assertEquals(typeof pipelineIdentity, "string"); + assertEquals(pipelineIdentity?.length, 64); + assertEquals(invalidMapError instanceof original.typeError, true); + assertEquals( + oversizedPluginListError instanceof original.rangeError, + true, + ); + }); +}); diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts new file mode 100644 index 0000000000..03980aa754 --- /dev/null +++ b/src/transforms/pipeline/cache-identity.ts @@ -0,0 +1,329 @@ +import { computeHash } from "#veryfront/utils/hash-utils.ts"; +import { computeConfigHash } from "#veryfront/cache/config-hash.ts"; +import { fingerprintImportMap } from "../esm/http-cache-helpers.ts"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; +import type { TransformPlugin } from "./types.ts"; + +const MAX_IMPORT_MAP_ENTRIES = 20_000; +const MAX_IDENTITY_STRING_BYTES = 64 * 1024; +const MAX_IMPORT_MAP_IDENTITY_BYTES = 8 * 1024 * 1024; +const MAX_PLUGIN_IDENTITY_BYTES = 4 * 1024; +const MAX_CUSTOM_PLUGINS = 1_000; + +// Transform identities are derived after project code may have run in the +// shared realm. Keep descriptor inspection, freezing, and bounded string +// handling independent from later primordial replacement. +const ArrayIsArray = Array.isArray; +const ArrayPrototypePush = Array.prototype.push; +const IntrinsicTextEncoder = TextEncoder; +const IntrinsicRangeError = RangeError; +const IntrinsicTypeError = TypeError; +const JSONStringify = JSON.stringify; +const MathAbs = Math.abs; +const NumberIsFinite = Number.isFinite; +const ObjectCreate = Object.create; +const ObjectFreeze = Object.freeze; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectGetPrototypeOf = Object.getPrototypeOf; +const ObjectPrototype = Object.prototype; +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; +const RegExpPrototypeTest = RegExp.prototype.test; +const StringPrototypeTrim = String.prototype.trim; +const TextEncoderPrototypeEncode = TextEncoder.prototype.encode; +const controlCharacterPattern = /\p{Cc}/u; +const encoder = new IntrinsicTextEncoder(); + +interface ImportMapBudget { + entries: number; + bytes: number; +} + +function readOwnDataProperty(value: object, key: PropertyKey, label: string): unknown { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (!descriptor) return undefined; + if (descriptor.get || descriptor.set) { + throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + } + return descriptor.value; +} + +function countIdentityString( + value: string, + budget: ImportMapBudget, + label: string, + maxBytes = MAX_IDENTITY_STRING_BYTES, +): string { + const bytes = ( + ReflectApply(TextEncoderPrototypeEncode, encoder, [value]) as Uint8Array + ).byteLength; + if (bytes > maxBytes) throw new IntrinsicTypeError(`${label} is too large`); + budget.bytes += bytes; + if (budget.bytes > MAX_IMPORT_MAP_IDENTITY_BYTES) { + throw new IntrinsicTypeError("Import map cache identity exceeds its byte limit"); + } + return value; +} + +function snapshotStringRecord( + value: unknown, + label: string, + budget: ImportMapBudget, +): Readonly> { + if (value === undefined) { + return ObjectFreeze(ObjectCreate(null) as Record); + } + if (value === null || typeof value !== "object" || ArrayIsArray(value)) { + throw new IntrinsicTypeError(`${label} must be a plain object`); + } + const prototype = ObjectGetPrototypeOf(value); + if (prototype !== ObjectPrototype && prototype !== null) { + throw new IntrinsicTypeError(`${label} must be a plain object`); + } + + const snapshot = ObjectCreate(null) as Record; + const keys = ReflectOwnKeys(value); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (typeof key !== "string") { + throw new IntrinsicTypeError(`${label} cannot contain symbol keys`); + } + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (!descriptor) continue; + if (descriptor.get || descriptor.set) { + throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + } + if (!descriptor.enumerable) continue; + + budget.entries++; + if (budget.entries > MAX_IMPORT_MAP_ENTRIES) { + throw new IntrinsicTypeError("Import map cache identity contains too many entries"); + } + countIdentityString(key, budget, `${label} key`); + if (typeof descriptor.value !== "string") { + throw new IntrinsicTypeError(`${label}.${key} must be a string`); + } + snapshot[key] = countIdentityString(descriptor.value, budget, `${label}.${key}`); + } + return ObjectFreeze(snapshot); +} + +/** + * Take a descriptor-only immutable snapshot before an import map is shared by + * cache identity computation and transform stages. This prevents later caller + * mutation (or getters with side effects) from making those two views diverge. + */ +export function snapshotImportMap(value: unknown): ImportMapConfig { + if (value === null || typeof value !== "object" || ArrayIsArray(value)) { + throw new IntrinsicTypeError("Import map must be a plain object"); + } + const prototype = ObjectGetPrototypeOf(value); + if (prototype !== ObjectPrototype && prototype !== null) { + throw new IntrinsicTypeError("Import map must be a plain object"); + } + + const keys = ReflectOwnKeys(value); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (typeof key !== "string") { + throw new IntrinsicTypeError("Import map cannot contain symbol keys"); + } + if (key !== "imports" && key !== "scopes") { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor?.enumerable) { + throw new IntrinsicTypeError(`Unknown import map field: ${key}`); + } + } + } + + const budget: ImportMapBudget = { entries: 0, bytes: 0 }; + const imports = snapshotStringRecord( + readOwnDataProperty(value, "imports", "Import map"), + "Import map imports", + budget, + ); + const rawScopes = readOwnDataProperty(value, "scopes", "Import map"); + const scopes = ObjectCreate(null) as Record>>; + + if (rawScopes !== undefined) { + if (rawScopes === null || typeof rawScopes !== "object" || ArrayIsArray(rawScopes)) { + throw new IntrinsicTypeError("Import map scopes must be a plain object"); + } + const scopesPrototype = ObjectGetPrototypeOf(rawScopes); + if (scopesPrototype !== ObjectPrototype && scopesPrototype !== null) { + throw new IntrinsicTypeError("Import map scopes must be a plain object"); + } + const scopeKeys = ReflectOwnKeys(rawScopes); + for (let index = 0; index < scopeKeys.length; index++) { + const scope = scopeKeys[index]; + if (typeof scope !== "string") { + throw new IntrinsicTypeError("Import map scopes cannot contain symbol keys"); + } + const descriptor = ObjectGetOwnPropertyDescriptor(rawScopes, scope); + if (!descriptor) continue; + if (descriptor.get || descriptor.set) { + throw new IntrinsicTypeError("Import map scopes cannot contain accessor properties"); + } + if (!descriptor.enumerable) continue; + budget.entries++; + if (budget.entries > MAX_IMPORT_MAP_ENTRIES) { + throw new IntrinsicTypeError("Import map cache identity contains too many entries"); + } + countIdentityString(scope, budget, "Import map scope"); + scopes[scope] = snapshotStringRecord( + descriptor.value, + `Import map scope ${scope}`, + budget, + ); + } + } + + return ObjectFreeze({ + imports, + scopes: ObjectFreeze(scopes), + }); +} + +export function fingerprintPipelineImportMap(importMap: ImportMapConfig): Promise { + return fingerprintImportMap(importMap); +} + +export type CustomPluginCacheIdentity = + | { cacheable: true; identity: ReadonlyArray } + | { cacheable: false; reason: string }; + +/** Require explicit versioned identities for caller-supplied executable code. */ +export function getCustomPluginCacheIdentity( + plugins: readonly TransformPlugin[] | undefined, +): CustomPluginCacheIdentity { + if (!plugins || plugins.length === 0) { + return { cacheable: true, identity: ObjectFreeze([]) }; + } + if (plugins.length > MAX_CUSTOM_PLUGINS) { + throw new IntrinsicRangeError( + `Transform pipeline cannot contain more than ${MAX_CUSTOM_PLUGINS} plugins`, + ); + } + + const identity: Array = []; + for (let index = 0; index < plugins.length; index++) { + const plugin = plugins[index]; + if (plugin === null || typeof plugin !== "object") { + throw new IntrinsicTypeError(`Transform plugin at index ${index} must be an object`); + } + const name = readOwnDataProperty(plugin, "name", `Transform plugin ${index}`); + const stage = readOwnDataProperty(plugin, "stage", `Transform plugin ${index}`); + const cacheIdentity = readOwnDataProperty( + plugin, + "cacheIdentity", + `Transform plugin ${index}`, + ); + if ( + typeof name !== "string" || name.length === 0 || name.length > 256 || + (ReflectApply(StringPrototypeTrim, name, []) as string) !== name || + (ReflectApply(RegExpPrototypeTest, controlCharacterPattern, [name]) as boolean) + ) { + throw new IntrinsicTypeError(`Transform plugin at index ${index} has an invalid name`); + } + if (typeof stage !== "number" || !NumberIsFinite(stage) || MathAbs(stage) > 1_000_000) { + throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid stage`); + } + if (cacheIdentity === undefined) { + return { + cacheable: false, + reason: `custom transform plugin ${name} has no cacheIdentity`, + }; + } + if ( + typeof cacheIdentity !== "string" || cacheIdentity.length === 0 || + (ReflectApply(TextEncoderPrototypeEncode, encoder, [cacheIdentity]) as Uint8Array) + .byteLength > + MAX_PLUGIN_IDENTITY_BYTES + ) { + throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid cacheIdentity`); + } + ReflectApply( + ArrayPrototypePush, + identity, + [ObjectFreeze([index, name, stage, cacheIdentity] as const)], + ); + } + return { cacheable: true, identity: ObjectFreeze(identity) }; +} + +function boundedOption(value: string | undefined, label: string): string | null { + if (value === undefined) return null; + if (typeof value !== "string") { + throw new IntrinsicTypeError(`${label} must be a string`); + } + if ( + (ReflectApply(TextEncoderPrototypeEncode, encoder, [value]) as Uint8Array) + .byteLength > + MAX_IDENTITY_STRING_BYTES + ) { + throw new IntrinsicTypeError(`${label} is too large for transform cache identity`); + } + return value; +} + +function boundedRequiredOption(value: string, label: string): string { + const bounded = boundedOption(value, label); + if (bounded === null) throw new IntrinsicTypeError(`${label} must be a string`); + return bounded; +} + +export interface PipelineConfigIdentityInput { + reactVersion: string; + jsxImportSource: string; + studioEmbed: boolean; + dev: boolean; + ssr: boolean; + projectDir: string; + moduleServerUrl?: string; + moduleServerOrigin?: string; + vendorBundleHash?: string; + apiBaseUrl?: string; + importMapFingerprint?: string; + dependencyPinningCacheKey?: string; + customPlugins: ReadonlyArray; +} + +/** Hash every known output-affecting pipeline input using full SHA-256. */ +export async function computePipelineConfigIdentity( + input: PipelineConfigIdentityInput, +): Promise { + const reactVersion = boundedRequiredOption(input.reactVersion, "React version"); + const jsxImportSource = boundedRequiredOption(input.jsxImportSource, "JSX import source"); + const projectDir = boundedRequiredOption(input.projectDir, "Project directory"); + if ( + typeof input.studioEmbed !== "boolean" || typeof input.dev !== "boolean" || + typeof input.ssr !== "boolean" + ) { + throw new IntrinsicTypeError("Transform pipeline mode identity fields must be booleans"); + } + if (!ArrayIsArray(input.customPlugins) || input.customPlugins.length > MAX_CUSTOM_PLUGINS) { + throw new IntrinsicRangeError( + `Transform pipeline cache identity cannot contain more than ${MAX_CUSTOM_PLUGINS} plugins`, + ); + } + const baseIdentity = await computeConfigHash({ + reactVersion, + jsxImportSource, + studioEmbed: input.studioEmbed, + dev: input.dev, + }); + const identity = [ + "veryfront:transform-pipeline:v3", + baseIdentity, + input.ssr, + projectDir, + boundedOption(input.moduleServerUrl, "Module server URL"), + boundedOption(input.moduleServerOrigin, "Module server origin"), + boundedOption(input.vendorBundleHash, "Vendor bundle hash"), + boundedOption(input.apiBaseUrl, "API base URL"), + boundedOption(input.importMapFingerprint, "Import map fingerprint"), + boundedOption(input.dependencyPinningCacheKey, "Dependency pinning cache key"), + input.customPlugins, + ]; + return computeHash(JSONStringify(identity)); +} diff --git a/src/transforms/pipeline/types.ts b/src/transforms/pipeline/types.ts index 2008887e71..e1dd69daa7 100644 --- a/src/transforms/pipeline/types.ts +++ b/src/transforms/pipeline/types.ts @@ -143,6 +143,11 @@ export interface TransformPlugin { name: string; /** Stage this plugin runs at */ stage: TransformStage; + /** + * Stable, versioned identity for output-affecting custom plugin behavior. + * Custom plugins without an identity still run, but disable persistent caching. + */ + cacheIdentity?: string; /** Optional condition - if false, plugin is skipped */ condition?: (ctx: TransformContext) => boolean; /** Transform function - returns new code */ diff --git a/src/utils/hash-utils.ts b/src/utils/hash-utils.ts index 40456af148..f2e0785702 100644 --- a/src/utils/hash-utils.ts +++ b/src/utils/hash-utils.ts @@ -4,19 +4,54 @@ import { HASH_SEED_FNV1A } from "./constants/hash.ts"; /** Number of hex characters kept by shortHash (8 hex chars = 32 bits) */ const SHORT_HASH_LENGTH = 8; +// Hashes participate in cache and request identities after project modules may +// have executed in the shared realm. Capture the small set of primordials used +// by that boundary before project code can replace their implementations. +const IntrinsicTextEncoder = TextEncoder; +const IntrinsicUint8Array = Uint8Array; +const NumberPrototypeToString = Number.prototype.toString; +const ReflectApply = Reflect.apply; +const StringPrototypePadStart = String.prototype.padStart; +const SubtleCryptoDigest = crypto.subtle.digest; +const TextEncoderPrototypeEncode = TextEncoder.prototype.encode; +const cryptoSubtle = crypto.subtle; +const hashTextEncoder = new IntrinsicTextEncoder(); + function toHex(buffer: ArrayBuffer): string { - return Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, "0")).join(""); + const bytes = new IntrinsicUint8Array(buffer); + let result = ""; + for (let index = 0; index < bytes.length; index++) { + const hex = ReflectApply(NumberPrototypeToString, bytes[index], [16]) as string; + result += ReflectApply(StringPrototypePadStart, hex, [2, "0"]) as string; + } + return result; } /** Compute the lowercase hex SHA-256 digest of a UTF-8 string. */ export async function computeHash(content: string): Promise { - const data = new TextEncoder().encode(content); - return toHex(await crypto.subtle.digest("SHA-256", data)); + const data = ReflectApply( + TextEncoderPrototypeEncode, + hashTextEncoder, + [content], + ) as Uint8Array; + return toHex( + await ReflectApply( + SubtleCryptoDigest, + cryptoSubtle, + ["SHA-256", data], + ) as ArrayBuffer, + ); } /** Compute the lowercase hex SHA-256 digest of raw bytes. */ export async function computeHashBytes(bytes: BufferSource): Promise { - return toHex(await crypto.subtle.digest("SHA-256", bytes)); + return toHex( + await ReflectApply( + SubtleCryptoDigest, + cryptoSubtle, + ["SHA-256", bytes], + ) as ArrayBuffer, + ); } /** Source bundle content used for hash computation. */ export interface BundleCode { From a1cd585c27bca3c5087b92fb800a87fea9770a48 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 08:52:31 +0200 Subject: [PATCH 02/34] chore: drop unused stringifyJsonValue imports to unblock pre-push lint --- extensions/ext-llm-anthropic/src/anthropic-request-builder.ts | 1 - .../ext-llm-openai/src/openai-responses-request-builder.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/extensions/ext-llm-anthropic/src/anthropic-request-builder.ts b/extensions/ext-llm-anthropic/src/anthropic-request-builder.ts index 4d9d75f6f4..6158b89e21 100644 --- a/extensions/ext-llm-anthropic/src/anthropic-request-builder.ts +++ b/extensions/ext-llm-anthropic/src/anthropic-request-builder.ts @@ -2,7 +2,6 @@ import { jsonValuesEqual, readProviderOptions, readRecord, - stringifyJsonValue, stringifyToolResultValue, unwrapToolInputSchema, } from "veryfront/provider/shared"; diff --git a/extensions/ext-llm-openai/src/openai-responses-request-builder.ts b/extensions/ext-llm-openai/src/openai-responses-request-builder.ts index e4d358c5bb..093fa001f2 100644 --- a/extensions/ext-llm-openai/src/openai-responses-request-builder.ts +++ b/extensions/ext-llm-openai/src/openai-responses-request-builder.ts @@ -1,7 +1,6 @@ import { jsonValuesEqual, readProviderOptions, - stringifyJsonValue, stringifyToolArguments, stringifyToolResultValue, unwrapToolInputSchema, From b6db54eb6372540eaef5fc579e3e047505608fca Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 09:05:02 +0200 Subject: [PATCH 03/34] chore(lint): remove preloader.test.ts from the test-typecheck baseline The rewritten preloader.test.ts typechecks cleanly, so the CI ratchet (lint:test-typecheck) requires locking that in by removing it from the grandfathered baseline. --- scripts/lint/test-typecheck-baseline.json | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/lint/test-typecheck-baseline.json b/scripts/lint/test-typecheck-baseline.json index 4f9f093368..a5f108672d 100644 --- a/scripts/lint/test-typecheck-baseline.json +++ b/scripts/lint/test-typecheck-baseline.json @@ -35,7 +35,6 @@ "src/mcp/server.test.ts", "src/middleware/builtin/security/security-headers.test.ts", "src/middleware/core/pipeline/composer.test.ts", - "src/modules/import-map/preloader.test.ts", "src/modules/react-loader/ssr-module-loader.stress.test.ts", "src/platform/adapters/fs/veryfront/adapter-helpers.test.ts", "src/platform/adapters/fs/veryfront/directory-operations.test.ts", From 1a6e9c90c15434bffc4ce7ddef5c86fd6b0abba7 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 10:00:53 +0200 Subject: [PATCH 04/34] fix(transforms): canonicalize cache identities safely --- src/cache/config-hash.ts | 144 +++++++++---- .../pipeline/cache-identity.test.ts | 70 +++++++ src/transforms/pipeline/cache-identity.ts | 193 +++++++++++++++--- 3 files changed, 333 insertions(+), 74 deletions(-) diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 2ef0a6c5a9..024e31d9a6 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -14,7 +14,15 @@ import { } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts"; +const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; + +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} /** * Configuration that affects transform output. @@ -40,31 +48,104 @@ interface TransformConfig { dependencyPinningCacheKey?: string; } +function readOwnConfigField( + config: TransformConfig, + key: keyof TransformConfig, +): unknown { + if (config === null || typeof config !== "object") { + throw new IntrinsicTypeError("Transform config must be an object"); + } + const descriptor = ObjectGetOwnPropertyDescriptor(config, key); + if (!descriptor) return undefined; + if (!hasOwn(descriptor, "value")) { + throw new IntrinsicTypeError(`Transform config ${key} must be an own data property`); + } + return descriptor.value; +} + +function readOptionalConfigString( + config: TransformConfig, + key: keyof TransformConfig, +): string | undefined { + const value = readOwnConfigField(config, key); + if (value === undefined) return undefined; + if (typeof value !== "string") { + throw new IntrinsicTypeError(`Transform config ${key} must be a string`); + } + return value; +} + +function readOptionalConfigBoolean( + config: TransformConfig, + key: "studioEmbed" | "dev", +): boolean | undefined { + const value = readOwnConfigField(config, key); + if (value === undefined) return undefined; + if (typeof value !== "boolean") { + throw new IntrinsicTypeError(`Transform config ${key} must be a boolean`); + } + return value; +} + +function encodeNullableConfigString(value: string | null): string { + return JSONStringify(value) as string; +} + +function buildConfigIdentity(config: TransformConfig): string { + const dependencyPinningCacheKey = readOptionalConfigString( + config, + "dependencyPinningCacheKey", + ); + const moduleServerOrigin = readOptionalConfigString(config, "moduleServerOrigin"); + const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( + dependencyPinningCacheKey, + moduleServerOrigin, + ); + let identity = `v${VERSION}`; + identity += `|react=${ + encodeNullableConfigString( + readOptionalConfigString(config, "reactVersion") ?? DEFAULT_REACT_VERSION, + ) + }`; + identity += `|jsx=${ + encodeNullableConfigString( + readOptionalConfigString(config, "jsxImportSource") ?? "react", + ) + }`; + identity += `|modules=${ + encodeNullableConfigString( + readOptionalConfigString(config, "moduleServerUrl") ?? null, + ) + }`; + identity += `|vendor=${ + encodeNullableConfigString( + readOptionalConfigString(config, "vendorBundleHash") ?? null, + ) + }`; + identity += `|api=${ + encodeNullableConfigString( + readOptionalConfigString(config, "apiBaseUrl") ?? null, + ) + }`; + identity += `|studio=${readOptionalConfigBoolean(config, "studioEmbed") ?? false ? "1;" : "0;"}`; + identity += `|dev=${readOptionalConfigBoolean(config, "dev") ?? false ? "1;" : "0;"}`; + identity += `|pins=${ + encodeNullableConfigString( + dependencyPinningCacheVariant ?? null, + ) + }`; + identity += `|csstype=${encodeNullableConfigString(CSSTYPE_VERSION)}`; + identity += `|tailwind=${encodeNullableConfigString(TAILWIND_VERSION)}`; + return identity; +} + /** * Compute a hash of transform-affecting configuration. * * Changes to these values should invalidate cached transforms. */ export function computeConfigHash(config: TransformConfig): Promise { - const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( - config.dependencyPinningCacheKey, - config.moduleServerOrigin, - ); - const normalized = { - transformVersion: VERSION, - reactVersion: config.reactVersion ?? DEFAULT_REACT_VERSION, - jsxImportSource: config.jsxImportSource ?? "react", - moduleServerUrl: config.moduleServerUrl ?? null, - vendorBundleHash: config.vendorBundleHash ?? null, - apiBaseUrl: config.apiBaseUrl ?? null, - studioEmbed: config.studioEmbed ?? false, - dev: config.dev ?? false, - ...(dependencyPinningCacheVariant ? { dependencyPinningCacheVariant } : {}), - csstype: CSSTYPE_VERSION, - tailwind: TAILWIND_VERSION, - }; - - return computeHash(JSONStringify(normalized)); + return computeHash(buildConfigIdentity(config)); } /** @@ -73,28 +154,5 @@ export function computeConfigHash(config: TransformConfig): Promise { * Use this when you need a config hash but can't afford async overhead. */ export function computeConfigHashSync(config: TransformConfig): string { - const parts = [ - `v${VERSION}`, - config.reactVersion ?? DEFAULT_REACT_VERSION, - config.jsxImportSource ?? "react", - encodeConfigPart("modules", config.moduleServerUrl), - encodeConfigPart("vendor", config.vendorBundleHash), - encodeConfigPart("api", config.apiBaseUrl), - config.studioEmbed ? "studio" : "", - config.dev ? "dev" : "", - ].filter(Boolean); - const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( - config.dependencyPinningCacheKey, - config.moduleServerOrigin, - ); - if (dependencyPinningCacheVariant) { - parts.push(`pins:${dependencyPinningCacheVariant}`); - } - - return parts.join(":"); -} - -function encodeConfigPart(label: string, value: string | undefined): string { - if (!value) return ""; - return `${label}:${value.length}:${value}`; + return buildConfigIdentity(config); } diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index 7fccc300a0..9050a56f0e 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -205,6 +205,76 @@ describe("transform pipeline cache identity", () => { assertNotEquals(changed, baseline); }); + it("keeps ill-formed Unicode distinct from replacement characters", async () => { + const loneSurrogate = await computePipelineConfigIdentity( + identityInput({ projectDir: "\ud800" }), + ); + const replacementCharacter = await computePipelineConfigIdentity( + identityInput({ projectDir: "\ufffd" }), + ); + + assertNotEquals(loneSurrogate, replacementCharacter); + }); + + it("does not consult inherited toJSON hooks while hashing", async () => { + const customPlugins = [[0, "custom", TransformStage.FINALIZE, "custom@1"]] as const; + const baseline = await computePipelineConfigIdentity(identityInput({ customPlugins })); + const arrayToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + const objectToJson = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + const stringToJson = Object.getOwnPropertyDescriptor(String.prototype, "toJSON"); + let hookCalls = 0; + + try { + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return ["poisoned-array"]; + }, + writable: true, + }); + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return { poisoned: true }; + }, + writable: true, + }); + Object.defineProperty(String.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return "poisoned-string"; + }, + writable: true, + }); + + assertEquals( + await computePipelineConfigIdentity(identityInput({ customPlugins })), + baseline, + ); + } finally { + if (arrayToJson) { + Object.defineProperty(Array.prototype, "toJSON", arrayToJson); + } else { + Reflect.deleteProperty(Array.prototype, "toJSON"); + } + if (objectToJson) { + Object.defineProperty(Object.prototype, "toJSON", objectToJson); + } else { + Reflect.deleteProperty(Object.prototype, "toJSON"); + } + if (stringToJson) { + Object.defineProperty(String.prototype, "toJSON", stringToJson); + } else { + Reflect.deleteProperty(String.prototype, "toJSON"); + } + } + + assertEquals(hookCalls, 0); + }); + it("uses captured primordials for import-map and plugin identities", async () => { const original = { arrayIsArray: Array.isArray, diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 03980aa754..dc7ac7185e 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -21,10 +21,12 @@ const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; const MathAbs = Math.abs; const NumberIsFinite = Number.isFinite; +const NumberIsSafeInteger = Number.isSafeInteger; const ObjectCreate = Object.create; const ObjectFreeze = Object.freeze; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectGetPrototypeOf = Object.getPrototypeOf; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ObjectPrototype = Object.prototype; const ReflectApply = Reflect.apply; const ReflectOwnKeys = Reflect.ownKeys; @@ -34,6 +36,10 @@ const TextEncoderPrototypeEncode = TextEncoder.prototype.encode; const controlCharacterPattern = /\p{Cc}/u; const encoder = new IntrinsicTextEncoder(); +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} + interface ImportMapBudget { entries: number; bytes: number; @@ -42,7 +48,7 @@ interface ImportMapBudget { function readOwnDataProperty(value: object, key: PropertyKey, label: string): unknown { const descriptor = ObjectGetOwnPropertyDescriptor(value, key); if (!descriptor) return undefined; - if (descriptor.get || descriptor.set) { + if (!hasOwn(descriptor, "value")) { throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); } return descriptor.value; @@ -90,7 +96,7 @@ function snapshotStringRecord( } const descriptor = ObjectGetOwnPropertyDescriptor(value, key); if (!descriptor) continue; - if (descriptor.get || descriptor.set) { + if (!hasOwn(descriptor, "value")) { throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); } if (!descriptor.enumerable) continue; @@ -161,7 +167,7 @@ export function snapshotImportMap(value: unknown): ImportMapConfig { } const descriptor = ObjectGetOwnPropertyDescriptor(rawScopes, scope); if (!descriptor) continue; - if (descriptor.get || descriptor.set) { + if (!hasOwn(descriptor, "value")) { throw new IntrinsicTypeError("Import map scopes cannot contain accessor properties"); } if (!descriptor.enumerable) continue; @@ -251,7 +257,7 @@ export function getCustomPluginCacheIdentity( return { cacheable: true, identity: ObjectFreeze(identity) }; } -function boundedOption(value: string | undefined, label: string): string | null { +function boundedOption(value: unknown, label: string): string | null { if (value === undefined) return null; if (typeof value !== "string") { throw new IntrinsicTypeError(`${label} must be a string`); @@ -266,12 +272,93 @@ function boundedOption(value: string | undefined, label: string): string | null return value; } -function boundedRequiredOption(value: string, label: string): string { +function boundedRequiredOption(value: unknown, label: string): string { const bounded = boundedOption(value, label); if (bounded === null) throw new IntrinsicTypeError(`${label} must be a string`); return bounded; } +function readArrayLength(value: readonly unknown[], label: string): number { + const length = readOwnDataProperty(value, "length", label); + if ( + typeof length !== "number" || !NumberIsSafeInteger(length) || length < 0 + ) { + throw new IntrinsicTypeError(`${label} has an invalid length`); + } + return length; +} + +function readArrayElement( + value: readonly unknown[], + index: number, + label: string, +): unknown { + const descriptor = ObjectGetOwnPropertyDescriptor(value, index); + if (!descriptor || !hasOwn(descriptor, "value")) { + throw new IntrinsicTypeError(`${label} must contain own data elements`); + } + return descriptor.value; +} + +function encodeIdentityPrimitive(value: string | number | boolean | null): string { + return JSONStringify(value) as string; +} + +function encodeCustomPluginIdentities( + plugins: ReadonlyArray, +): string { + if (!ArrayIsArray(plugins)) { + throw new IntrinsicTypeError("Transform pipeline custom plugin identity must be an array"); + } + const length = readArrayLength(plugins, "Transform pipeline custom plugin identity"); + if (length > MAX_CUSTOM_PLUGINS) { + throw new IntrinsicRangeError( + `Transform pipeline cache identity cannot contain more than ${MAX_CUSTOM_PLUGINS} plugins`, + ); + } + + let encoded = `${encodeIdentityPrimitive(length)};`; + for (let index = 0; index < length; index++) { + const tuple = readArrayElement( + plugins, + index, + `Transform pipeline custom plugin identity ${index}`, + ); + if (!ArrayIsArray(tuple) || readArrayLength(tuple, `Custom plugin identity ${index}`) !== 4) { + throw new IntrinsicTypeError(`Custom plugin identity ${index} must be a four-item tuple`); + } + const pluginIndex = readArrayElement(tuple, 0, `Custom plugin identity ${index}`); + const name = readArrayElement(tuple, 1, `Custom plugin identity ${index}`); + const stage = readArrayElement(tuple, 2, `Custom plugin identity ${index}`); + const cacheIdentity = readArrayElement(tuple, 3, `Custom plugin identity ${index}`); + if (pluginIndex !== index) { + throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid index`); + } + if ( + typeof name !== "string" || name.length === 0 || name.length > 256 || + (ReflectApply(StringPrototypeTrim, name, []) as string) !== name || + (ReflectApply(RegExpPrototypeTest, controlCharacterPattern, [name]) as boolean) + ) { + throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid name`); + } + if (typeof stage !== "number" || !NumberIsFinite(stage) || MathAbs(stage) > 1_000_000) { + throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid stage`); + } + if ( + typeof cacheIdentity !== "string" || cacheIdentity.length === 0 || + (ReflectApply(TextEncoderPrototypeEncode, encoder, [cacheIdentity]) as Uint8Array) + .byteLength > MAX_PLUGIN_IDENTITY_BYTES + ) { + throw new IntrinsicTypeError( + `Custom plugin identity ${index} has an invalid cache identity`, + ); + } + encoded += `${encodeIdentityPrimitive(pluginIndex)},${encodeIdentityPrimitive(name)},`; + encoded += `${encodeIdentityPrimitive(stage)},${encodeIdentityPrimitive(cacheIdentity)};`; + } + return encoded; +} + export interface PipelineConfigIdentityInput { reactVersion: string; jsxImportSource: string; @@ -292,38 +379,82 @@ export interface PipelineConfigIdentityInput { export async function computePipelineConfigIdentity( input: PipelineConfigIdentityInput, ): Promise { - const reactVersion = boundedRequiredOption(input.reactVersion, "React version"); - const jsxImportSource = boundedRequiredOption(input.jsxImportSource, "JSX import source"); - const projectDir = boundedRequiredOption(input.projectDir, "Project directory"); + const reactVersion = boundedRequiredOption( + readOwnDataProperty(input, "reactVersion", "Transform pipeline identity"), + "React version", + ); + const jsxImportSource = boundedRequiredOption( + readOwnDataProperty(input, "jsxImportSource", "Transform pipeline identity"), + "JSX import source", + ); + const projectDir = boundedRequiredOption( + readOwnDataProperty(input, "projectDir", "Transform pipeline identity"), + "Project directory", + ); + const studioEmbed = readOwnDataProperty( + input, + "studioEmbed", + "Transform pipeline identity", + ); + const dev = readOwnDataProperty(input, "dev", "Transform pipeline identity"); + const ssr = readOwnDataProperty(input, "ssr", "Transform pipeline identity"); if ( - typeof input.studioEmbed !== "boolean" || typeof input.dev !== "boolean" || - typeof input.ssr !== "boolean" + typeof studioEmbed !== "boolean" || typeof dev !== "boolean" || + typeof ssr !== "boolean" ) { throw new IntrinsicTypeError("Transform pipeline mode identity fields must be booleans"); } - if (!ArrayIsArray(input.customPlugins) || input.customPlugins.length > MAX_CUSTOM_PLUGINS) { - throw new IntrinsicRangeError( - `Transform pipeline cache identity cannot contain more than ${MAX_CUSTOM_PLUGINS} plugins`, - ); - } + const customPlugins = encodeCustomPluginIdentities( + readOwnDataProperty( + input, + "customPlugins", + "Transform pipeline identity", + ) as ReadonlyArray, + ); const baseIdentity = await computeConfigHash({ reactVersion, jsxImportSource, - studioEmbed: input.studioEmbed, - dev: input.dev, + studioEmbed, + dev, }); - const identity = [ - "veryfront:transform-pipeline:v3", - baseIdentity, - input.ssr, - projectDir, - boundedOption(input.moduleServerUrl, "Module server URL"), - boundedOption(input.moduleServerOrigin, "Module server origin"), - boundedOption(input.vendorBundleHash, "Vendor bundle hash"), - boundedOption(input.apiBaseUrl, "API base URL"), - boundedOption(input.importMapFingerprint, "Import map fingerprint"), - boundedOption(input.dependencyPinningCacheKey, "Dependency pinning cache key"), - input.customPlugins, - ]; - return computeHash(JSONStringify(identity)); + let identity = "veryfront:transform-pipeline:v4;"; + identity += `base=${encodeIdentityPrimitive(baseIdentity)};`; + identity += `ssr=${encodeIdentityPrimitive(ssr)};`; + identity += `project=${encodeIdentityPrimitive(projectDir)};`; + const moduleServerUrl = boundedOption( + readOwnDataProperty(input, "moduleServerUrl", "Transform pipeline identity"), + "Module server URL", + ); + const moduleServerOrigin = boundedOption( + readOwnDataProperty(input, "moduleServerOrigin", "Transform pipeline identity"), + "Module server origin", + ); + const vendorBundleHash = boundedOption( + readOwnDataProperty(input, "vendorBundleHash", "Transform pipeline identity"), + "Vendor bundle hash", + ); + const apiBaseUrl = boundedOption( + readOwnDataProperty(input, "apiBaseUrl", "Transform pipeline identity"), + "API base URL", + ); + const importMapFingerprint = boundedOption( + readOwnDataProperty(input, "importMapFingerprint", "Transform pipeline identity"), + "Import map fingerprint", + ); + const dependencyPinningCacheKey = boundedOption( + readOwnDataProperty( + input, + "dependencyPinningCacheKey", + "Transform pipeline identity", + ), + "Dependency pinning cache key", + ); + identity += `module-url=${encodeIdentityPrimitive(moduleServerUrl)};`; + identity += `module-origin=${encodeIdentityPrimitive(moduleServerOrigin)};`; + identity += `vendor=${encodeIdentityPrimitive(vendorBundleHash)};`; + identity += `api=${encodeIdentityPrimitive(apiBaseUrl)};`; + identity += `import-map=${encodeIdentityPrimitive(importMapFingerprint)};`; + identity += `dependency-pins=${encodeIdentityPrimitive(dependencyPinningCacheKey)};`; + identity += `plugins=${customPlugins}`; + return computeHash(identity); } From 012630516b6b6c297f0373d7b07bbb90fd2045f6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 12:39:17 +0200 Subject: [PATCH 05/34] Stabilize import-map preloading under reviewed cache edges Review feedback showed the PR still allowed production MDX layout preloads to miss request context, allowed inherited JSON hooks and poisoned primordials to influence import-map identities, and could reject renders when bounded preloader capacity was pinned by in-flight work. The fix threads the validated render context into production import-map preloading, restores main-compatible config cache identities with golden coverage, hardens import-map identity and loader paths against the reviewed primordial hazards, and makes bounded preloader admission wait for active work with a timeout instead of surfacing capacity errors to renders. Constraint: Preserve existing public cache identity behavior except for the deliberate import-map context dimensions under review. Rejected: Keep the config hash rewrite from the PR | it changed established cache identity and throw behavior without an intentional migration. Rejected: Fail renders on occupied preloader capacity | review feedback identified this as an availability risk under hung or slow loads. Confidence: high Scope-risk: moderate Directive: Do not remove projectDir, contentSourceId, or validated config from MDX import-map preloads without rechecking release-specific cache isolation. Tested: deno check touched runtime files; deno fmt --check touched files; deno lint touched files; targeted import-map/config/hash/layout tests; adjacent import-map/cache/rendering suite. Not-tested: Repo-wide unit command fails before test execution because scripts/build/build-npm-extension-packages.ts imports #dnt outside the root deno.json import map. --- src/cache/config-hash.test.ts | 45 +++++ src/cache/config-hash.ts | 146 ++++++--------- src/modules/import-map/loader.test.ts | 55 ++++++ src/modules/import-map/loader.ts | 113 ++++++++---- src/modules/import-map/merger.ts | 16 +- src/modules/import-map/preloader.test.ts | 117 ++++++++---- src/modules/import-map/preloader.ts | 168 ++++++++++++++++-- src/rendering/layouts/layout-applicator.ts | 1 + src/rendering/layouts/utils/applicator.ts | 4 + .../layouts/utils/component-loader.ts | 12 +- src/rendering/orchestrator/layout.ts | 6 + src/transforms/esm/http-cache-helpers.test.ts | 49 +++++ src/transforms/esm/http-cache-helpers.ts | 18 +- src/transforms/import-rewriter/url-builder.ts | 30 +++- 14 files changed, 585 insertions(+), 195 deletions(-) diff --git a/src/cache/config-hash.test.ts b/src/cache/config-hash.test.ts index 6d54658930..e06ffcc6e9 100644 --- a/src/cache/config-hash.test.ts +++ b/src/cache/config-hash.test.ts @@ -8,6 +8,30 @@ const CHANGED_CANONICAL_PIN_KEY = "on:z7bg3qnfgtcc"; describe("cache/config-hash", () => { describe("computeConfigHash", () => { + it("matches the golden hash for the default transform config", async () => { + assertEquals( + await computeConfigHash({}), + "4b96519f8a12a74bbef6f1a0f92f1825e5e267c6202240b2d32825dab6f6ac6c", + ); + }); + + it("matches the golden hash for a fully scoped transform config", async () => { + assertEquals( + await computeConfigHash({ + reactVersion: "18.3.1", + jsxImportSource: "preact", + moduleServerUrl: "https://modules.example.test/_vf_modules", + moduleServerOrigin: "https://preview.example.test", + vendorBundleHash: "vendor-a", + apiBaseUrl: "https://api.example.test", + studioEmbed: true, + dev: true, + dependencyPinningCacheKey: CANONICAL_PIN_KEY, + }), + "a868c7f22dc1518f90f638c07d7d03971d6ff4510b7e706eccf631c2b0269998", + ); + }); + it("should return a 64-char hex hash", async () => { const hash = await computeConfigHash({}); assertEquals(hash.length, 64); @@ -115,6 +139,27 @@ describe("cache/config-hash", () => { }); describe("computeConfigHashSync", () => { + it("matches the golden identity for the default transform config", () => { + assertEquals(computeConfigHashSync({}), "v0.1.1186:19.2.4:react"); + }); + + it("matches the golden identity for a fully scoped transform config", () => { + assertEquals( + computeConfigHashSync({ + reactVersion: "18.3.1", + jsxImportSource: "preact", + moduleServerUrl: "https://modules.example.test/_vf_modules", + moduleServerOrigin: "https://preview.example.test", + vendorBundleHash: "vendor-a", + apiBaseUrl: "https://api.example.test", + studioEmbed: true, + dev: true, + dependencyPinningCacheKey: CANONICAL_PIN_KEY, + }), + "v0.1.1186:18.3.1:preact:modules:40:https://modules.example.test/_vf_modules:vendor:8:vendor-a:api:24:https://api.example.test:studio:dev:pins:on:z7bg3qnfgtcb:origin:aHR0cHM6Ly9wcmV2aWV3LmV4YW1wbGUudGVzdA", + ); + }); + it("should return a string", () => { const hash = computeConfigHashSync({}); assertEquals(typeof hash, "string"); diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 024e31d9a6..99f261e670 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -14,15 +14,7 @@ import { } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts"; -const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; -const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; -const ReflectApply = Reflect.apply; - -function hasOwn(object: object, key: PropertyKey): boolean { - return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; -} /** * Configuration that affects transform output. @@ -48,95 +40,73 @@ interface TransformConfig { dependencyPinningCacheKey?: string; } -function readOwnConfigField( - config: TransformConfig, - key: keyof TransformConfig, -): unknown { - if (config === null || typeof config !== "object") { - throw new IntrinsicTypeError("Transform config must be an object"); - } - const descriptor = ObjectGetOwnPropertyDescriptor(config, key); - if (!descriptor) return undefined; - if (!hasOwn(descriptor, "value")) { - throw new IntrinsicTypeError(`Transform config ${key} must be an own data property`); - } - return descriptor.value; +function encodeJsonStringProperty(key: string, value: string): string { + return `${JSONStringify(key)}:${JSONStringify(value)}`; } -function readOptionalConfigString( - config: TransformConfig, - key: keyof TransformConfig, -): string | undefined { - const value = readOwnConfigField(config, key); - if (value === undefined) return undefined; - if (typeof value !== "string") { - throw new IntrinsicTypeError(`Transform config ${key} must be a string`); - } - return value; +function encodeJsonNullableStringProperty(key: string, value: string | null): string { + return `${JSONStringify(key)}:${JSONStringify(value)}`; +} + +function encodeJsonBooleanProperty(key: string, value: boolean): string { + return `${JSONStringify(key)}:${value ? "true" : "false"}`; } -function readOptionalConfigBoolean( - config: TransformConfig, - key: "studioEmbed" | "dev", -): boolean | undefined { - const value = readOwnConfigField(config, key); - if (value === undefined) return undefined; - if (typeof value !== "boolean") { - throw new IntrinsicTypeError(`Transform config ${key} must be a boolean`); +function buildAsyncConfigIdentity(config: TransformConfig): string { + const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( + config.dependencyPinningCacheKey, + config.moduleServerOrigin, + ); + const fields = [ + encodeJsonStringProperty("transformVersion", VERSION), + encodeJsonStringProperty("reactVersion", config.reactVersion ?? DEFAULT_REACT_VERSION), + encodeJsonStringProperty("jsxImportSource", config.jsxImportSource ?? "react"), + encodeJsonNullableStringProperty("moduleServerUrl", config.moduleServerUrl ?? null), + encodeJsonNullableStringProperty("vendorBundleHash", config.vendorBundleHash ?? null), + encodeJsonNullableStringProperty("apiBaseUrl", config.apiBaseUrl ?? null), + encodeJsonBooleanProperty("studioEmbed", config.studioEmbed ?? false), + encodeJsonBooleanProperty("dev", config.dev ?? false), + ]; + if (dependencyPinningCacheVariant) { + fields.push( + encodeJsonStringProperty("dependencyPinningCacheVariant", dependencyPinningCacheVariant), + ); } - return value; + fields.push( + encodeJsonStringProperty("csstype", CSSTYPE_VERSION), + encodeJsonStringProperty("tailwind", TAILWIND_VERSION), + ); + return `{${fields.join(",")}}`; } -function encodeNullableConfigString(value: string | null): string { - return JSONStringify(value) as string; +function encodeConfigPart(label: string, value: string | undefined): string { + if (!value) return ""; + return `${label}:${value.length}:${value}`; } -function buildConfigIdentity(config: TransformConfig): string { - const dependencyPinningCacheKey = readOptionalConfigString( - config, - "dependencyPinningCacheKey", - ); - const moduleServerOrigin = readOptionalConfigString(config, "moduleServerOrigin"); +function buildSyncConfigIdentity(config: TransformConfig): string { + const parts = [ + `v${VERSION}`, + config.reactVersion ?? DEFAULT_REACT_VERSION, + config.jsxImportSource ?? "react", + ]; + const moduleServerUrlPart = encodeConfigPart("modules", config.moduleServerUrl); + if (moduleServerUrlPart) parts.push(moduleServerUrlPart); + const vendorBundleHashPart = encodeConfigPart("vendor", config.vendorBundleHash); + if (vendorBundleHashPart) parts.push(vendorBundleHashPart); + const apiBaseUrlPart = encodeConfigPart("api", config.apiBaseUrl); + if (apiBaseUrlPart) parts.push(apiBaseUrlPart); + if (config.studioEmbed) parts.push("studio"); + if (config.dev) parts.push("dev"); const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( - dependencyPinningCacheKey, - moduleServerOrigin, + config.dependencyPinningCacheKey, + config.moduleServerOrigin, ); - let identity = `v${VERSION}`; - identity += `|react=${ - encodeNullableConfigString( - readOptionalConfigString(config, "reactVersion") ?? DEFAULT_REACT_VERSION, - ) - }`; - identity += `|jsx=${ - encodeNullableConfigString( - readOptionalConfigString(config, "jsxImportSource") ?? "react", - ) - }`; - identity += `|modules=${ - encodeNullableConfigString( - readOptionalConfigString(config, "moduleServerUrl") ?? null, - ) - }`; - identity += `|vendor=${ - encodeNullableConfigString( - readOptionalConfigString(config, "vendorBundleHash") ?? null, - ) - }`; - identity += `|api=${ - encodeNullableConfigString( - readOptionalConfigString(config, "apiBaseUrl") ?? null, - ) - }`; - identity += `|studio=${readOptionalConfigBoolean(config, "studioEmbed") ?? false ? "1;" : "0;"}`; - identity += `|dev=${readOptionalConfigBoolean(config, "dev") ?? false ? "1;" : "0;"}`; - identity += `|pins=${ - encodeNullableConfigString( - dependencyPinningCacheVariant ?? null, - ) - }`; - identity += `|csstype=${encodeNullableConfigString(CSSTYPE_VERSION)}`; - identity += `|tailwind=${encodeNullableConfigString(TAILWIND_VERSION)}`; - return identity; + if (dependencyPinningCacheVariant) { + parts.push(`pins:${dependencyPinningCacheVariant}`); + } + + return parts.join(":"); } /** @@ -145,7 +115,7 @@ function buildConfigIdentity(config: TransformConfig): string { * Changes to these values should invalidate cached transforms. */ export function computeConfigHash(config: TransformConfig): Promise { - return computeHash(buildConfigIdentity(config)); + return computeHash(buildAsyncConfigIdentity(config)); } /** @@ -154,5 +124,5 @@ export function computeConfigHash(config: TransformConfig): Promise { * Use this when you need a config hash but can't afford async overhead. */ export function computeConfigHashSync(config: TransformConfig): string { - return buildConfigIdentity(config); + return buildSyncConfigIdentity(config); } diff --git a/src/modules/import-map/loader.test.ts b/src/modules/import-map/loader.test.ts index 0b1fbdb619..932c17bf66 100644 --- a/src/modules/import-map/loader.test.ts +++ b/src/modules/import-map/loader.test.ts @@ -184,5 +184,60 @@ describe("modules/import-map/loader", () => { assert(!("relative" in appScope), "relative path in scope should be filtered"); assert("absolute" in appScope, "absolute path in scope should be kept"); }); + + it("uses captured JSON and collection primordials while loading deno.json maps", async () => { + const adapter = createMockAdapter(); + adapter.fs.files.set( + "/poisoned-deno-json/deno.json", + JSON.stringify({ + imports: { + "pkg": "https://esm.sh/pkg@1", + "relative": "./local.ts", + }, + scopes: { + "/app/": { + "scoped": "https://esm.sh/scoped@1", + }, + }, + }), + ); + const original = { + jsonParse: JSON.parse, + objectEntries: Object.entries, + objectFromEntries: Object.fromEntries, + arrayFilter: Array.prototype.filter, + arrayMap: Array.prototype.map, + }; + + try { + JSON.parse = (() => { + throw new Error("poisoned JSON.parse"); + }) as typeof JSON.parse; + Object.entries = (() => { + throw new Error("poisoned Object.entries"); + }) as typeof Object.entries; + Object.fromEntries = (() => { + throw new Error("poisoned Object.fromEntries"); + }) as typeof Object.fromEntries; + Array.prototype.filter = function () { + throw new Error("poisoned Array.prototype.filter"); + }; + Array.prototype.map = function () { + throw new Error("poisoned Array.prototype.map"); + }; + + const { imports, scopes } = await loadImportMap("/poisoned-deno-json", adapter); + + assertEquals(imports?.pkg, "https://esm.sh/pkg@1"); + assertEquals(imports?.relative, undefined); + assertEquals(scopes?.["/app/"]?.scoped, "https://esm.sh/scoped@1"); + } finally { + JSON.parse = original.jsonParse; + Object.entries = original.objectEntries; + Object.fromEntries = original.objectFromEntries; + Array.prototype.filter = original.arrayFilter; + Array.prototype.map = original.arrayMap; + } + }); }); }); diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index e42465207a..756494d1d1 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -1,5 +1,5 @@ import { rendererLogger as logger } from "#veryfront/utils"; -import { dirname, join } from "#veryfront/compat/path/index.ts"; +import { dirname } from "#veryfront/compat/path/index.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { isVirtualFilesystem } from "#veryfront/platform/adapters/fs/wrapper.ts"; import { getConfig, type VeryfrontConfig } from "#veryfront/config"; @@ -9,6 +9,20 @@ import { mergeImportMaps } from "./merger.ts"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import { getReactImportMap } from "#veryfront/transforms/esm/react-cdn.ts"; +const ArrayPrototypePush = Array.prototype.push; +const JSONParse = JSON.parse; +const ObjectEntries = Object.entries; +const ObjectFromEntries = Object.fromEntries; +const ReflectApply = Reflect.apply; + +function arrayPush(values: T[], value: T): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + +function objectFromEntries(entries: Array<[string, T]>): Record { + return ReflectApply(ObjectFromEntries, Object, [entries]) as Record; +} + function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConfig { const normalizeValue = (value: string): string => { if (!value.startsWith("npm:")) return value; @@ -21,27 +35,44 @@ function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConf return query ? `${url}?${query}` : `${url}?target=es2022`; }; - let imports = importMap.imports - ? Object.fromEntries(Object.entries(importMap.imports).map(([k, v]) => [k, normalizeValue(v)])) - : undefined; + let imports: Record | undefined; + if (importMap.imports) { + const normalizedImports: Array<[string, string]> = []; + const importEntries = ObjectEntries(importMap.imports); + for (let index = 0; index < importEntries.length; index++) { + const [key, value] = importEntries[index]!; + arrayPush(normalizedImports, [key, normalizeValue(value)]); + } + imports = objectFromEntries(normalizedImports); + } - const scopes = importMap.scopes - ? Object.fromEntries( - Object.entries(importMap.scopes).map(([scope, mappings]) => [ - scope, - Object.fromEntries(Object.entries(mappings).map(([k, v]) => [k, normalizeValue(v)])), - ]), - ) - : undefined; + let scopes: Record> | undefined; + if (importMap.scopes) { + const normalizedScopes: Array<[string, Record]> = []; + const scopeEntries = ObjectEntries(importMap.scopes); + for (let scopeIndex = 0; scopeIndex < scopeEntries.length; scopeIndex++) { + const [scope, mappings] = scopeEntries[scopeIndex]!; + const normalizedMappings: Array<[string, string]> = []; + const mappingEntries = ObjectEntries(mappings); + for (let mappingIndex = 0; mappingIndex < mappingEntries.length; mappingIndex++) { + const [key, value] = mappingEntries[mappingIndex]!; + arrayPush(normalizedMappings, [key, normalizeValue(value)]); + } + arrayPush(normalizedScopes, [scope, objectFromEntries(normalizedMappings)]); + } + scopes = objectFromEntries(normalizedScopes); + } // Override React mappings AFTER all other processing to ensure single instance. // Remove any "react/" prefix match since we have explicit mappings. if (imports) { - const veryfrontSsrMap = Object.fromEntries( - Object.entries(getDefaultImportMap().imports ?? {}).filter(([key]) => - key.startsWith("veryfront/") - ), - ); + const veryfrontEntries: Array<[string, string]> = []; + const defaultEntries = ObjectEntries(getDefaultImportMap().imports ?? {}); + for (let index = 0; index < defaultEntries.length; index++) { + const [key, value] = defaultEntries[index]!; + if (key.startsWith("veryfront/")) arrayPush(veryfrontEntries, [key, value]); + } + const veryfrontSsrMap = objectFromEntries(veryfrontEntries); const reactMap = getReactImportMap(); delete imports["react/"]; imports = { ...imports, ...veryfrontSsrMap, ...reactMap }; @@ -65,11 +96,15 @@ async function getRuntimeAdapter(adapter?: RuntimeAdapter): Promise): Record { - return Object.fromEntries( - Object.entries(imports).filter(([, value]) => - !value.startsWith("./") && !value.startsWith("../") - ), - ); + const filtered: Array<[string, string]> = []; + const entries = ObjectEntries(imports); + for (let index = 0; index < entries.length; index++) { + const [key, value] = entries[index]!; + if (!value.startsWith("./") && !value.startsWith("../")) { + arrayPush(filtered, [key, value]); + } + } + return objectFromEntries(filtered); } async function loadDenoJsonImportMap( @@ -81,18 +116,12 @@ async function loadDenoJsonImportMap( if (isVirtualFilesystem(adapter.fs)) { try { const content = await adapter.fs.readFile("deno.json"); - const config = JSON.parse(content); + const config = JSONParse(content); if (config.imports || config.scopes) { logger.debug("Loaded import map from deno.json (virtual filesystem)"); const imports = config.imports ? filterRelativePaths(config.imports) : {}; - const scopes = config.scopes - ? Object.fromEntries( - Object.entries(config.scopes as Record>).map( - ([scope, mappings]) => [scope, filterRelativePaths(mappings)], - ), - ) - : {}; + const scopes = config.scopes ? filterScopeRelativePaths(config.scopes) : {}; return { imports, scopes }; } } catch (_) { @@ -105,22 +134,16 @@ async function loadDenoJsonImportMap( let currentPath = startPath; while (currentPath !== "/" && currentPath !== "") { - const denoJsonPath = join(currentPath, "deno.json"); + const denoJsonPath = currentPath === "/" ? "/deno.json" : `${currentPath}/deno.json`; try { const content = await adapter.fs.readFile(denoJsonPath); - const config = JSON.parse(content); + const config = JSONParse(content); if (config.imports || config.scopes) { logger.debug(`Loaded import map from ${denoJsonPath}`); const imports = config.imports ? filterRelativePaths(config.imports) : {}; - const scopes = config.scopes - ? Object.fromEntries( - Object.entries(config.scopes as Record>).map( - ([scope, mappings]) => [scope, filterRelativePaths(mappings)], - ), - ) - : {}; + const scopes = config.scopes ? filterScopeRelativePaths(config.scopes) : {}; return { imports, scopes }; } } catch (_) { @@ -135,6 +158,18 @@ async function loadDenoJsonImportMap( return null; } +function filterScopeRelativePaths( + scopes: Record>, +): Record> { + const filteredScopes: Array<[string, Record]> = []; + const scopeEntries = ObjectEntries(scopes); + for (let index = 0; index < scopeEntries.length; index++) { + const [scope, mappings] = scopeEntries[index]!; + arrayPush(filteredScopes, [scope, filterRelativePaths(mappings)]); + } + return objectFromEntries(filteredScopes); +} + function getConfigImportMap(config: VeryfrontConfig): ImportMapConfig | null { const importMap = config.resolve?.importMap; if (!importMap || typeof importMap !== "object") return null; diff --git a/src/modules/import-map/merger.ts b/src/modules/import-map/merger.ts index 6c1bfaa60e..ec0a320ee4 100644 --- a/src/modules/import-map/merger.ts +++ b/src/modules/import-map/merger.ts @@ -1,17 +1,27 @@ import type { ImportMapConfig } from "./types.ts"; +const ObjectAssign = Object.assign; +const ObjectEntries = Object.entries; +const ReflectApply = Reflect.apply; + +function objectAssign(target: T, source: U): T & U { + return ReflectApply(ObjectAssign, Object, [target, source]) as T & U; +} + export function mergeImportMaps(...maps: ImportMapConfig[]): ImportMapConfig { const imports: Record = {}; const scopes: Record> = {}; for (const map of maps) { - if (map.imports) Object.assign(imports, map.imports); + if (map.imports) objectAssign(imports, map.imports); if (!map.scopes) continue; - for (const [scope, scopeImports] of Object.entries(map.scopes)) { + const scopeEntries = ObjectEntries(map.scopes); + for (let index = 0; index < scopeEntries.length; index++) { + const [scope, scopeImports] = scopeEntries[index]!; scopes[scope] ??= {}; - Object.assign(scopes[scope], scopeImports); + objectAssign(scopes[scope], scopeImports); } } diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 65d0de6965..e6da8873c4 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -295,6 +295,44 @@ describe("modules/import-map/preloader", () => { ); }); + it("can isolate the same project id by explicit project directory context", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + loadImportMap: async () => ({ + imports: { loaded: String(++loads) }, + }), + }); + + const first = await preloader.preload("/release-a", adapter, "project", { + projectDir: "/release-a", + contentSourceId: "source", + }); + const second = await preloader.preload("/release-b", adapter, "project", { + projectDir: "/release-b", + contentSourceId: "source", + }); + + assertEquals(first.imports?.loaded, "1"); + assertEquals(second.imports?.loaded, "2"); + assertEquals( + await preloader.getCached("project", { + projectDir: "/release-a", + contentSourceId: "source", + }), + first, + ); + assertEquals( + await preloader.getCached("project", { + projectDir: "/release-b", + contentSourceId: "source", + }), + second, + ); + }); + it("rejects malformed loader output before publication and permits retry", async () => { const adapter = createMinimalAdapter(); let loads = 0; @@ -631,16 +669,11 @@ describe("modules/import-map/preloader", () => { ); preloader.clear("project-a"); - await assertRejects( - () => - preloader.preload( - "/project-a", - adapter, - "project-a", - context, - ), - RangeError, - "capacity is occupied by in-flight loads", + const postClear = preloader.preload( + "/project-a", + adapter, + "project-a", + context, ); const staleResult = await preClear; assertEquals(staleResult.imports?.loaded, "1"); @@ -654,12 +687,7 @@ describe("modules/import-map/preloader", () => { ); assertEquals(await preloader.getCached("project-a", context), undefined); - const reloaded = await preloader.preload( - "/project-a", - adapter, - "project-a", - context, - ); + const reloaded = await postClear; assertEquals(reloaded.imports?.loaded, "2"); assertEquals(await preloader.getCached("project-a", context), reloaded); }); @@ -689,7 +717,7 @@ describe("modules/import-map/preloader", () => { assertEquals(await preloader.getCached("project-a", context), undefined); }); - it("keeps in-flight project work admitted instead of evicting and duplicating it", async () => { + it("waits for in-flight project capacity instead of failing renders", async () => { const adapter = createMinimalAdapter(); const loads: Array>> = []; const preloader = new ImportMapPreloader({ @@ -706,11 +734,7 @@ describe("modules/import-map/preloader", () => { const first = preloader.preload("/project-a", adapter, "project-a"); await Promise.resolve(); const sameKey = preloader.preload("/project-a", adapter, "project-a"); - await assertRejects( - () => preloader.preload("/project-b", adapter, "project-b"), - RangeError, - "capacity is occupied by in-flight loads", - ); + const queued = preloader.preload("/project-b", adapter, "project-b"); await waitForLoadCount(loads, 1); assertEquals(loads.length, 1); @@ -719,13 +743,12 @@ describe("modules/import-map/preloader", () => { assertEquals(firstResult.imports?.source, "a"); assertEquals(await sameKey, firstResult); - const second = preloader.preload("/project-b", adapter, "project-b"); await waitForLoadCount(loads, 2); loads[1]!.resolve({ imports: { source: "b" } }); - assertEquals((await second).imports?.source, "b"); + assertEquals((await queued).imports?.source, "b"); }); - it("bounds identity and loader work across variants and explicit invalidation", async () => { + it("waits for in-flight variant capacity across explicit invalidation", async () => { const adapter = createMinimalAdapter(); const loads: Array>> = []; const preloader = new ImportMapPreloader({ @@ -742,26 +765,46 @@ describe("modules/import-map/preloader", () => { const sourceB = { contentSourceId: "source-b" }; const first = preloader.preload("/project", adapter, "project", sourceA); - await assertRejects( - () => preloader.preload("/project", adapter, "project", sourceB), - RangeError, - "capacity is occupied by in-flight loads", - ); + const queued = preloader.preload("/project", adapter, "project", sourceB); preloader.clear("project"); - await assertRejects( - () => preloader.preload("/project", adapter, "project", sourceA), - RangeError, - "capacity is occupied by in-flight loads", - ); await waitForLoadCount(loads, 1); assertEquals(loads.length, 1); loads[0]!.resolve({ imports: { source: "a" } }); await first; - const reloaded = preloader.preload("/project", adapter, "project", sourceB); await waitForLoadCount(loads, 2); loads[1]!.resolve({ imports: { source: "b" } }); - assertEquals((await reloaded).imports?.source, "b"); + assertEquals((await queued).imports?.source, "b"); + assertEquals(await preloader.getCached("project", sourceA), undefined); + assertEquals((await preloader.getCached("project", sourceB))?.imports?.source, "b"); + }); + + it("returns undefined from getCached when identity capacity is occupied", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const first = preloader.preload("/project", adapter, "project", { + contentSourceId: "source-a", + }); + await waitForLoadCount(loads, 1); + + assertEquals( + await preloader.getCached("project", { contentSourceId: "source-b" }), + undefined, + ); + + loads[0]!.resolve({ imports: { source: "a" } }); + await first; }); it("keeps variant identity and capacity deterministic after primordial poisoning", async () => { diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index b4cac40bc2..3685053d52 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -6,6 +6,8 @@ import type { ImportMapConfig } from "./types.ts"; import { loadImportMap } from "./loader.ts"; export interface PreloadImportMapContext { + /** On-disk project root selected for this render. */ + projectDir?: string; /** Immutable content source selected for this render (release, branch, or environment). */ contentSourceId?: string; /** Config already validated for the authenticated request. */ @@ -16,16 +18,20 @@ const IMPORT_MAP_CACHE_IDENTITY_NAMESPACE = "veryfront:preloaded-import-map:v2"; const DEFAULT_MAX_IMPORT_MAP_PROJECTS = 512; const DEFAULT_MAX_IMPORT_MAP_VARIANTS_PER_PROJECT = 16; const DEFAULT_IMPORT_MAP_TTL_MS = 10 * 60 * 1_000; +const DEFAULT_IMPORT_MAP_LOAD_TIMEOUT_MS = 30_000; +const CAPACITY_ERROR = Symbol("ImportMapPreloader.capacityError"); // Project code can execute in the same realm before a later request reaches // this cache. Capture every primitive used for identity, admission, and // settlement so replacing shared built-ins cannot redirect dependency graphs. +const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeSort = Array.prototype.sort; const DateNow = Date.now; const IntrinsicMap = Map; const IntrinsicPromise = Promise; const IntrinsicRangeError = RangeError; const IntrinsicSet = Set; +const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; const MapPrototypeClear = Map.prototype.clear; const MapPrototypeDelete = Map.prototype.delete; @@ -38,15 +44,25 @@ const MathMin = Math.min; const NUMBER_MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; const NumberIsFinite = Number.isFinite; const NumberIsSafeInteger = Number.isSafeInteger; +const ObjectDefineProperty = Object.defineProperty; const ObjectEntries = Object.entries; const ObjectFreeze = Object.freeze; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; const ReflectApply = Reflect.apply; const SetPrototypeAdd = Set.prototype.add; const SetPrototypeDelete = Set.prototype.delete; +const SetPrototypeForEach = Set.prototype.forEach; const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size")! .get!; +const SetTimeout = setTimeout; +const ClearTimeout = clearTimeout; + +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} function arraySort( values: T[], @@ -55,6 +71,10 @@ function arraySort( return ReflectApply(ArrayPrototypeSort, values, [compare]) as T[]; } +function arrayPush(values: T[], value: T): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + function mapClear(map: Map): void { ReflectApply(MapPrototypeClear, map, []); } @@ -105,6 +125,10 @@ function setDelete(set: Set, value: T): boolean { return ReflectApply(SetPrototypeDelete, set, [value]) as boolean; } +function setForEach(set: Set, callback: (value: T) => void): void { + ReflectApply(SetPrototypeForEach, set, [callback]); +} + function setSize(set: Set): number { return ReflectApply(SetPrototypeSize, set, []) as number; } @@ -135,6 +159,8 @@ export interface ImportMapPreloaderOptions { maxVariantsPerProject?: number; /** Retention lifetime after a successful load. */ ttlMs?: number; + /** Maximum time an in-flight loader can pin cache capacity. */ + loadTimeoutMs?: number; /** Monotonic-enough clock seam; defaults to Date.now. */ now?: () => number; /** Loader seam for alternate runtimes and deterministic verification. */ @@ -157,29 +183,48 @@ function snapshotPreloadContext( context?: PreloadImportMapContext, ): PreloadImportMapContext | undefined { if (!context) return undefined; - const contentSourceId = context.contentSourceId; - const config = context.config; - if (!config) return ObjectFreeze({ contentSourceId }); - - const resolve = config.resolve; - const importMap = snapshotImportMap(resolve?.importMap ?? {}); + const contentSourceId = readOptionalOwnDataProperty( + context, + "contentSourceId", + "Preload import-map context", + ); + if (contentSourceId !== undefined && typeof contentSourceId !== "string") { + throw new IntrinsicTypeError("Preload import-map contentSourceId must be a string"); + } + const projectDir = readOptionalOwnDataProperty( + context, + "projectDir", + "Preload import-map context", + ); + if (projectDir !== undefined && typeof projectDir !== "string") { + throw new IntrinsicTypeError("Preload import-map projectDir must be a string"); + } + const config = readOptionalOwnDataProperty( + context, + "config", + "Preload import-map context", + ) as VeryfrontConfig | undefined; + if (!config) return ObjectFreeze({ projectDir, contentSourceId }); + + const resolve = readOptionalOwnDataProperty(config, "resolve", "Veryfront config"); + const importMap = resolve && typeof resolve === "object" + ? readOptionalOwnDataProperty(resolve, "importMap", "Veryfront config resolve") + : undefined; const exactConfig = ObjectFreeze({ - ...config, resolve: ObjectFreeze({ - ...resolve, - importMap, + importMap: snapshotImportMap(importMap ?? {}), }), }) as VeryfrontConfig; - return ObjectFreeze({ contentSourceId, config: exactConfig }); + return ObjectFreeze({ projectDir, contentSourceId, config: exactConfig }); } function buildVariantCanonicalIdentity( context?: PreloadImportMapContext, ): string { const importMap = context?.config?.resolve?.importMap; - let canonical = `${IMPORT_MAP_CACHE_IDENTITY_NAMESPACE}\0source:${ - JSONStringify(context?.contentSourceId ?? null) - }\0`; + let canonical = `${IMPORT_MAP_CACHE_IDENTITY_NAMESPACE}\0project:${ + JSONStringify(context?.projectDir ?? null) + }\0source:${JSONStringify(context?.contentSourceId ?? null)}\0`; if (!context?.config) return `${canonical}ambient`; canonical += "validated"; @@ -210,6 +255,27 @@ function buildVariantCanonicalIdentity( return canonical; } +function racePromises(promises: Array>): Promise { + return new IntrinsicPromise((resolve, reject) => { + for (let index = 0; index < promises.length; index++) { + promiseThen(promises[index]!, resolve, reject); + } + }); +} + +function readOptionalOwnDataProperty( + value: object, + key: PropertyKey, + label: string, +): unknown { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (!descriptor) return undefined; + if (!hasOwn(descriptor, "value")) { + throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + } + return descriptor.value; +} + function readPositiveSafeInteger( value: number | undefined, fallback: number, @@ -238,6 +304,7 @@ export class ImportMapPreloader { private readonly maxVariantsPerProject: number; private readonly maxConcurrentLoads: number; private readonly ttlMs: number; + private readonly loadTimeoutMs: number; private readonly now: () => number; private readonly loader: typeof loadImportMap; @@ -261,6 +328,11 @@ export class ImportMapPreloader { DEFAULT_IMPORT_MAP_TTL_MS, "ttlMs", ); + this.loadTimeoutMs = readPositiveSafeInteger( + options.loadTimeoutMs, + DEFAULT_IMPORT_MAP_LOAD_TIMEOUT_MS, + "loadTimeoutMs", + ); this.now = options.now ?? DateNow; this.loader = options.loadImportMap ?? loadImportMap; } @@ -335,9 +407,36 @@ export class ImportMapPreloader { } private capacityError(scope: "projects" | "variants" | "loads"): RangeError { - return new IntrinsicRangeError( + const error = new IntrinsicRangeError( `Import-map preloader ${scope} capacity is occupied by in-flight loads; retry after a load settles`, ); + ObjectDefineProperty(error, CAPACITY_ERROR, { + configurable: false, + enumerable: false, + value: true, + writable: false, + }); + return error; + } + + private isCapacityError(error: unknown): boolean { + return Boolean( + error && typeof error === "object" && + (error as { [CAPACITY_ERROR]?: boolean })[CAPACITY_ERROR], + ); + } + + private waitForActiveWork(): Promise { + const activeWork: Array> = []; + setForEach(this.activeLoads, (promise) => arrayPush(activeWork, promise)); + setForEach(this.activeIdentityBuilds, (promise) => arrayPush(activeWork, promise)); + if (activeWork.length === 0) throw this.capacityError("loads"); + const raced = racePromises(activeWork); + return promiseThen( + raced, + () => resolvedPromise(), + () => resolvedPromise(), + ); } private makeProjectRoom(now: number): void { @@ -492,8 +591,25 @@ export class ImportMapPreloader { resolvedPromise(), () => this.loader(projectDir, adapter, config), ); + let timeoutId: ReturnType | undefined; + const timeoutPromise = new IntrinsicPromise((_, reject) => { + timeoutId = SetTimeout(() => { + reject(new IntrinsicRangeError("Import-map preloader load timed out")); + }, this.loadTimeoutMs); + }); + const boundedLoaderPromise = promiseThen( + racePromises([loaderPromise, timeoutPromise]), + (value) => { + if (timeoutId !== undefined) ClearTimeout(timeoutId); + return value; + }, + (error) => { + if (timeoutId !== undefined) ClearTimeout(timeoutId); + throw error; + }, + ); const promise = promiseThen( - loaderPromise, + boundedLoaderPromise, (loadedImportMap) => snapshotImportMap(loadedImportMap), ); this.trackActiveLoad(promise); @@ -505,6 +621,22 @@ export class ImportMapPreloader { adapter: RuntimeAdapter, projectId?: string, context?: PreloadImportMapContext, + ): Promise { + for (;;) { + try { + return await this.preloadOnce(projectDir, adapter, projectId, context); + } catch (error) { + if (!this.isCapacityError(error)) throw error; + await this.waitForActiveWork(); + } + } + } + + private async preloadOnce( + projectDir: string, + adapter: RuntimeAdapter, + projectId?: string, + context?: PreloadImportMapContext, ): Promise { const exactContext = snapshotPreloadContext(context); const cacheKey = projectId ?? projectDir; @@ -689,7 +821,8 @@ export class ImportMapPreloader { ); } this.removeEmptyProject(cacheKey, projectState); - throw error; + if (!this.isCapacityError(error)) throw error; + return undefined; } const releaseIdentity = (): void => { this.releaseIdentityBuild( @@ -719,7 +852,8 @@ export class ImportMapPreloader { } catch (error) { releaseIdentity(); this.removeEmptyProject(cacheKey, projectState); - throw error; + if (!this.isCapacityError(error)) throw error; + return undefined; } if (!entry) { releaseIdentity(); diff --git a/src/rendering/layouts/layout-applicator.ts b/src/rendering/layouts/layout-applicator.ts index 2b818274f1..387484c410 100644 --- a/src/rendering/layouts/layout-applicator.ts +++ b/src/rendering/layouts/layout-applicator.ts @@ -288,6 +288,7 @@ export class LayoutApplicator { this.dependencyPinningDependencies, this.dependencyPinningSource, this.requestUrl?.origin, + this.config, ); } diff --git a/src/rendering/layouts/utils/applicator.ts b/src/rendering/layouts/utils/applicator.ts index c0687bb508..e271f06778 100644 --- a/src/rendering/layouts/utils/applicator.ts +++ b/src/rendering/layouts/utils/applicator.ts @@ -1,6 +1,7 @@ import { rendererLogger } from "#veryfront/utils"; import * as BundledReact from "react"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import type { VeryfrontConfig } from "#veryfront/config"; import type { LayoutItem, MdxBundle, MDXComponents } from "#veryfront/types"; import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; import { SpanNames } from "#veryfront/observability"; @@ -33,6 +34,7 @@ export function applyLayoutsESM( dependencyPinningDependencies?: Readonly>, dependencyPinningSource?: DependencyPinningSourceInput, moduleServerOrigin?: string, + config?: VeryfrontConfig, ): Promise { return withSpan( SpanNames.LAYOUT_APPLY_LAYOUTS_ESM, @@ -83,6 +85,7 @@ export function applyLayoutsESM( dependencyPinningDependencies, dependencyPinningSource, moduleServerOrigin, + config, ), spanAttrs, ); @@ -145,6 +148,7 @@ export function applyLayoutsESM( dependencyPinningDependencies, dependencyPinningSource, moduleServerOrigin, + config, ), { "layout.kind": "mdx", "layout.type": "named" }, ); diff --git a/src/rendering/layouts/utils/component-loader.ts b/src/rendering/layouts/utils/component-loader.ts index 4f78b98fe8..9602bc1c5a 100644 --- a/src/rendering/layouts/utils/component-loader.ts +++ b/src/rendering/layouts/utils/component-loader.ts @@ -6,6 +6,7 @@ import { } from "#veryfront/utils"; import * as BundledReact from "react"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import type { VeryfrontConfig } from "#veryfront/config"; import type { LayoutItem, MdxBundle, MDXComponents, MDXModule } from "#veryfront/types"; import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; import { createError, toError } from "#veryfront/errors"; @@ -346,6 +347,7 @@ export function loadMDXLayout( dependencyPinningDependencies?: Readonly>, dependencyPinningSource?: DependencyPinningSourceInput, moduleServerOrigin?: string, + config?: VeryfrontConfig, ): Promise | undefined> { return withSpan( SpanNames.LAYOUT_LOAD_MDX, @@ -355,7 +357,11 @@ export function loadMDXLayout( hasPreloadedImportMap: !!preloadedImportMap, }); - const map = preloadedImportMap ?? (await preloadImportMap(projectDir, adapter, projectId)); + const map = preloadedImportMap ?? (await preloadImportMap(projectDir, adapter, projectId, { + projectDir, + contentSourceId, + config, + })); if (preloadedImportMap) { loadMdxLayoutLog.debug("Using preloaded import map", { projectSlug }); } @@ -408,6 +414,7 @@ export async function preloadMDXLayoutModule( dependencyPinningDependencies?: Readonly>, dependencyPinningSource?: DependencyPinningSourceInput, moduleServerOrigin?: string, + config?: VeryfrontConfig, ): Promise { await loadMDXLayout( bundle, @@ -422,6 +429,7 @@ export async function preloadMDXLayoutModule( dependencyPinningDependencies, dependencyPinningSource, moduleServerOrigin, + config, ); } @@ -508,6 +516,7 @@ export async function applyMDXLayout( dependencyPinningDependencies?: Readonly>, dependencyPinningSource?: DependencyPinningSourceInput, moduleServerOrigin?: string, + config?: VeryfrontConfig, ): Promise { const React = await getProjectReact(reactVersion); const LayoutFn = await loadMDXLayout( @@ -523,6 +532,7 @@ export async function applyMDXLayout( dependencyPinningDependencies, dependencyPinningSource, moduleServerOrigin, + config, ); if (!LayoutFn) { diff --git a/src/rendering/orchestrator/layout.ts b/src/rendering/orchestrator/layout.ts index 947bac03a3..ef669afb72 100644 --- a/src/rendering/orchestrator/layout.ts +++ b/src/rendering/orchestrator/layout.ts @@ -178,6 +178,11 @@ export class LayoutOrchestrator { this.config.projectDir, this.config.adapter, this.config.projectId, + { + projectDir: this.config.projectDir, + contentSourceId: this.config.contentSourceId, + config: this.config.config, + }, ); this._preloadedImportMap = importMap; return { type: "importMap" as const, success: true }; @@ -249,6 +254,7 @@ export class LayoutOrchestrator { dependencyPinningDependencies, dependencyPinningSource, moduleServerOrigin, + this.config.config, ); return { type: "mdx" as const, path: layout.path, success: true }; } catch (error) { diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 6216bdb683..6c36010b46 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -63,6 +63,55 @@ describe("transforms/esm/http-cache-helpers", () => { ); }); + it("does not consult inherited toJSON hooks while fingerprinting import maps", async () => { + const importMap = { + imports: { pkg: "https://modules.example.com/pkg-v1.js" }, + scopes: { + "https://app.example.com/": { + scoped: "https://modules.example.com/scoped-v1.js", + }, + }, + }; + const baseline = await fingerprintImportMap(importMap); + const arrayToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + const objectToJson = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + let hookCalls = 0; + + try { + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return []; + }, + writable: true, + }); + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return {}; + }, + writable: true, + }); + + assertEquals(await fingerprintImportMap(importMap), baseline); + } finally { + if (arrayToJson) { + Object.defineProperty(Array.prototype, "toJSON", arrayToJson); + } else { + Reflect.deleteProperty(Array.prototype, "toJSON"); + } + if (objectToJson) { + Object.defineProperty(Object.prototype, "toJSON", objectToJson); + } else { + Reflect.deleteProperty(Object.prototype, "toJSON"); + } + } + + assertEquals(hookCalls, 0); + }); + it("canonicalizes and fingerprints one import map once per prepared request graph", async () => { let importEnumerations = 0; const imports = new Proxy({ pkg: "https://modules.example.com/pkg.js" }, { diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 3ebc46dd29..54b5f809fc 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -106,9 +106,21 @@ export function fingerprintImportMap(importMap: ImportMapConfig): Promise( diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index 2097e902cc..29feba1531 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -27,6 +27,19 @@ type EsmShOptions = { deps?: Record; }; +const ArrayPrototypeJoin = Array.prototype.join; +const ArrayPrototypePush = Array.prototype.push; +const ObjectEntries = Object.entries; +const ReflectApply = Reflect.apply; + +function arrayJoin(values: string[], separator: string): string { + return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; +} + +function arrayPush(values: string[], value: string): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + /** * Build esm.sh URL with proper configuration. * @@ -44,21 +57,24 @@ export function buildEsmShUrl( const params: string[] = []; if (options?.external?.length) { - params.push(`external=${options.external.join(",")}`); + arrayPush(params, `external=${arrayJoin(options.external, ",")}`); } - params.push(`target=${options?.target ?? "es2022"}`); + arrayPush(params, `target=${options?.target ?? "es2022"}`); if (options?.deps) { - const depsStr = Object.entries(options.deps) - .map(([k, v]) => `${k}@${v}`) - .join(","); - params.push(`deps=${depsStr}`); + const deps: string[] = []; + const entries = ObjectEntries(options.deps); + for (let index = 0; index < entries.length; index++) { + const [key, value] = entries[index]!; + arrayPush(deps, `${key}@${value}`); + } + arrayPush(params, `deps=${arrayJoin(deps, ",")}`); } const versionStr = version ? `@${version}` : ""; const pathStr = subpath ?? ""; - const queryStr = params.length ? `?${params.join("&")}` : ""; + const queryStr = params.length ? `?${arrayJoin(params, "&")}` : ""; return `https://esm.sh/${pkg}${versionStr}${pathStr}${queryStr}`; } From 897a299b56b55e4fda539e716f90597cbb3faa24 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 12:45:17 +0200 Subject: [PATCH 06/34] Keep generated bundles aligned with cache hardening The import-map hardening touched code that is embedded in the prebuilt client and RSC bundles. Regenerate the tracked artifacts so check-mode bundle generation and production runtime assets match the source commit. Constraint: generate:manifests:check validates the committed client prefetch bundle Rejected: Leave generated files dirty after pre-push | CI typecheck can run bundle check against committed artifacts Confidence: high Scope-risk: narrow Directive: Regenerate these bundles when embedded import-map or prefetch code changes Tested: deno fmt --check src/build/production-build/templates.ts src/server/services/rsc/endpoints/rsc-bundles.generated.ts Tested: deno task generate:manifests:check Not-tested: Full suite rerun after generated-only commit before push hook --- 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 60d4495252..da0f1d390a 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/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\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/transforms/import-rewriter/url-builder.ts\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypePush = Array.prototype.push;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\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 2d25b377a5..193645de15 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,r)=>t in e?at(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var m=(e,t,r)=>ct(e,typeof t!="symbol"?t+"":t,r);var ut="3.2.3";function lt(e,t,r,n){let o=[];if(n?.external?.length&&o.push(`external=${n.external.join(",")}`),o.push(`target=${n?.target??"es2022"}`),n?.deps){let d=Object.entries(n.deps).map(([c,g])=>`${c}@${g}`).join(",");o.push(`deps=${d}`)}let s=t?`@${t}`:"",a=r??"",u=o.length?`?${o.join("&")}`:"";return`https://esm.sh/${e}${s}${a}${u}`}function _(e,t,r,n=!1){return lt(e,t,r,{external:n?["react"]:void 0,deps:{csstype:ut}})}var gt="19.2.4",O=gt;function Ee(e=O){return{react:_("react",e),"react-dom":_("react-dom",e,void 0,!0),"react-dom/client":_("react-dom",e,"/client",!0),"react-dom/server":_("react-dom",e,"/server",!0),"react/jsx-runtime":_("react",e,"/jsx-runtime",!0),"react/jsx-dev-runtime":_("react",e,"/jsx-dev-runtime",!0)}}function Re(e=O){return Ee(e).react}function he(e=O){return Ee(e)["react-dom/client"]}function ft(e){return e.replaceAll("+","-").replaceAll("/","_").replaceAll("=","")}function pt(e){if(typeof globalThis.btoa=="function")try{return globalThis.btoa(e)}catch{return yt(new TextEncoder().encode(e))}let t=globalThis.Buffer;if(t)return t.from(e,"utf8").toString("base64");throw new Error("Base64 encoding is not supported in this runtime")}function yt(e){let t=globalThis.Buffer;if(t)return t.from(e).toString("base64");if(typeof globalThis.btoa=="function"){let r="";for(let n of e)r+=String.fromCharCode(n);return globalThis.btoa(r)}throw new Error("Base64 encoding is not supported in this runtime")}function te(e){return ft(pt(e))}var qo=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});function v(e,t){if(!t)return!1;if(Object.prototype.hasOwnProperty.call(t,e))return!0;for(let r of Object.keys(t))if(r.endsWith("/")&&e.startsWith(r))return!0;return!1}function mt(e){try{return JSON.parse(e)?.imports??{}}catch(t){return console.warn("Failed to parse import map JSON; treating as empty",{errorName:t instanceof Error?t.name:typeof t,inputLength:e.length}),{}}}function re(e=document){let t=e.querySelector(\'script[type="importmap"]\');return t?.textContent?mt(t.textContent):{}}var Zo=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Et=5e3,Rt=1e4,ti=16*1024*1024,ht=5e3;var _t=100;var xt=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),ri=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),ni=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Et,api:3e4,ssr:Rt,hmr:3e4,sandbox:ht}),cache:Object.freeze({jit:Object.freeze({maxSize:_t,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:xt})});var p="/_veryfront",ne={RSC:`${p}/rsc/`,FS:`${p}/fs/`,MODULES:`${p}/modules/`,PAGES:`${p}/pages/`,DATA:`${p}/data/`,LIB:`${p}/lib/`,CHUNKS:`${p}/chunks/`,CLIENT:`${p}/client/`},xe={HMR_RUNTIME:`${p}/hmr-runtime.js`,HMR:`${p}/hmr.js`,ERROR_OVERLAY:`${p}/error-overlay.js`,DEV_LOADER:`${p}/dev-loader.js`,CLIENT_LOG:`${p}/log`,CLIENT_JS:`${p}/client.js`,ROUTER_JS:`${p}/router.js`,PREFETCH_JS:`${p}/prefetch.js`,MANIFEST_JSON:`${p}/manifest.json`,APP_JS:`${p}/app.js`,RSC_CLIENT:`${p}/rsc/client.js`,RSC_MANIFEST:`${p}/rsc/manifest`,RSC_STREAM:`${p}/rsc/stream`,RSC_PAYLOAD:`${p}/rsc/payload`,RSC_RENDER:`${p}/rsc/render`,RSC_PAGE:`${p}/rsc/page`,RSC_MODULE:`${p}/rsc/module`,RSC_DOM:`${p}/rsc/dom.js`,LIB_CHAT_REACT:`${p}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${p}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${p}/lib/chat/primitives.js`};var Tt={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},ii=Tt.CACHE;var si={HMR_RUNTIME:xe.HMR_RUNTIME,ERROR_OVERLAY:xe.ERROR_OVERLAY};var I=ne.RSC,Te=ne.FS;var N="rsc-root",k="x-veryfront-dependency-pins";var T=class{constructor(t,r){m(this,"prefix",t);m(this,"level",r)}log(t,r,n,...o){this.level>t||r?.(n,...o)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function St(){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 $=St(),l=new T("RSC",$),di=new T("PREFETCH",$),gi=new T("HYDRATE",$),fi=new T("VERYFRONT",$);var Ct="veryfront-hydration-data";function oe(e){try{let t=[...e.querySelectorAll(`[id="${Ct}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function S(e=document){try{let t=oe(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return l.debug("hydration data parse failed",t),null}}function V(e,t){if(!t?.startsWith("on:"))return!1;try{let r=oe(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return l.debug("hydration dependency snapshot seed failed",r),!1}}function F(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function At(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function B(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),o=r===-1?e:e.slice(0,r),s=o.indexOf("?"),a=s===-1?o:o.slice(0,s),u=new URLSearchParams(s===-1?"":o.slice(s+1));u.set("pins",t);let d=u.toString();return`${a}${d?`?${d}`:""}${n}`}function bt(e,t){return At(`${Te}${te(e)}.js`,t)}function Ot(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return B(`${I}module?rel=${encodeURIComponent(e)}${n}`,r)}function D(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[k]:t}:{}}function It(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Nt=/\\.(tsx|ts|jsx|mdx|js)$/;function Dt(e){let t=It(e),r=[e,t];return Nt.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function wt(e,t){if(!e)return null;for(let r of Dt(t)){let n=e[r];if(n)return n}return null}function G(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?B(bt(r,e.version),e.dependencyPinningCacheKey):null}let t=wt(e.releaseAssetModules,e.rel);return t||Ot(e.rel,e.version,e.dependencyPinningCacheKey)}function j(e=document,t=O){let r=re(e);return{react:v("react",r)?"react":Re(t),reactDomClient:v("react-dom/client",r)?"react-dom/client":he(t)}}function Se(e=document){let t=re(e);return v("veryfront/router",t)?"veryfront/router":null}var Mt=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Lt(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let o of t)for(let[s,a]of Object.entries(o)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${s}" must define a slug`);let u=a.slug;if(typeof u!="string")throw new Error(`${n} entry "${s}" must define a string slug`);if(u!==s)throw new Error(`${n} key "${s}" does not match entry slug "${u}"`);if(Object.hasOwn(r,s))throw new Error(`Duplicate ${e} slug "${s}"`);r[s]=a}return Object.freeze(r)}function Ce(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!Mt.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Lt("error registry",...e)}var z={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"},Ai={debug:z.gray,info:z.green,warn:z.yellow,error:z.red};var y="[REDACTED]",E=Reflect.apply;var Ae=RegExp.prototype.exec,x=RegExp.prototype[Symbol.replace],Oi=String.prototype.charCodeAt,be=String.prototype.slice,Pt=String.prototype.toLowerCase,Ht=/[^a-z0-9]/g;function ie(e){let t=E(Pt,e,[]);return E(x,Ht,[t,""])}function Y(e,t,r){return r===void 0?E(be,e,[t]):E(be,e,[t,r])}var Ut=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],vt=512,kt=128,w=new Map;function Ne(e){let t=e.length<=kt;if(t){let o=w.get(e);if(o!==void 0)return o}let r=ie(e),n=Ut.some(o=>r.includes(o));if(t){if(w.size>=vt){let o=w.keys().next().value;o!==void 0&&w.delete(o)}w.set(e,n)}return n}var $t=["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"],Vt=new Set($t.map(ie)),Ft=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Bt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Gt=3;function jt(e){return e===" "||e==="\t"||e===","||e===";"||e==="&"||e==="?"||e==="#"}function zt(e){if(!e)return!1;let t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}function De(e){return zt(e)||e==="_"||e==="$"}function Yt(e){if(!e)return!1;let t=e.charCodeAt(0);return De(e)||t>=48&&t<=57||e==="."||e==="-"}function we(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!De(e[r]))return!1;for(r++;Yt(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function Me(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||jt(e)}function Le(e,t){let r=t;for(;r=e.length||we(e,r)}function Kt(e,t){let r=t,n=!0;if(e.startsWith(y,t)){let g=t+y.length;if(Oe(e,g))return{end:g,replacement:y};r=g,n=!1}let o=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",s=!1,a=()=>o?`${o}${y}${s?o:""}`:y,u=[],d="",c=-1;for(let g=r;g0&&(f==="}"||f==="]")){if(u.at(-1)!==f)return{end:e.length,replacement:a()};if(u.pop(),g++,u.length===0&&Oe(e,g))return{end:g,replacement:a()};continue}if(u.length>0||!Me(f)){g++;continue}let R=g;if(g=Le(e,g),g>=e.length||we(e,g))return{end:R,replacement:a()}}return{end:e.length,replacement:a()}}function Ie(e,t,r,n){let o=0,s="";for(let a=E(Ae,t,[e]);a;a=E(Ae,t,[e])){let u=a[r];if(!Ne(u))continue;let d=t.lastIndex,c=n===void 0?void 0:a[n],g=d+y.length;if((c==="?"||c==="&"||c===";")&&e.startsWith(y,d)&&e[g]==="#")continue;let f=Kt(e,d);s+=Y(e,o,a.index),s+=a[0],s+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:s+Y(e,o)}function Wt(e,t,r){let n=r.search(/[ \\t]/);if(n<0)return!1;let o=`${t}:${Y(r,0,n)}`,s=e==="//"?`https://${o}`:`${e}${o}`;try{let a=new URL(s);return a.username.length===0&&a.password.length===0}catch{return!1}}function Xt(e){let t=e;for(let r=0;r{let s=o.indexOf(":");if(s===-1)return`${n}${y}@`;let a=Y(o,0,s);return`${n}${a}:${y}@`}]);return t=E(x,Bt,[t,(r,n,o,s)=>Wt(n,o,s)?r:`${n}${o}:${y}@`]),t=E(x,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[t,(r,n,o,s)=>{let a=Xt(o);return Vt.has(ie(a))||Ne(a)?`${n}${o}=${y}`:r}]),t=E(x,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[t,(r,n,o)=>`${n}${o}${y}`]),t=E(x,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[t,(r,n)=>`${n}${y}`]),t=E(x,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[t,(r,n,o)=>`${n}${o}${y}`]),t=Ie(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=Ie(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var qt=2048;var Mi=64*1024,Jt=256,Zt="https://veryfront.com/docs/errors/",Pe="...[truncated]",ae="unknown-error";function He(e,t){if(e.length<=t)return e;let r=Math.max(0,t-Pe.length);return`${Qt(e,r)}${Pe}`}function Qt(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function er(e){let t="";for(let r=0;r=55296&&n<=56319){let o=e.charCodeAt(r+1);o>=56320&&o<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function C(e){return typeof e!="string"?y:He(se(e),qt)}function tr(e){let t=typeof e=="string"?se(e):ae,r=He(t||ae,Jt),n=er(r);return n==="."||n===".."?ae:n}function K(e){let t=encodeURIComponent(tr(e));return`${Zt}${t}`}var rr=Object.freeze,nr=Object.getOwnPropertyDescriptors,Ue=Number.isFinite,ke=new WeakSet,or=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function i(e){let t={...e},r={...t,create(n){let o=n?.message,s=n?.detail,a=n?.cause,u=n?.instance,d=n?.context,c=n?.status??t.status;return new ce(o||s||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:s,cause:a,instance:u,context:d})}};return rr(r)}var ce=class extends Error{constructor(r,n){super(r);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.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=ve(this);return r?{type:K(r.slug),title:C(r.title),status:r.status,detail:r.detail===void 0?void 0:C(r.detail),instance:r.instance===void 0?void 0:C(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:C(r.suggestion),cause:typeof r.cause=="string"?C(r.cause):void 0}:{type:K("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=ve(this);return K(r?.slug??"unknown-error")}};function $e(e){return typeof e=="object"&&e!==null&&ke.has(e)}function ve(e){return $e(e)?ir(e):null}function ir(e){try{if(!$e(e))return null;let t=nr(e),r=J=>{let b=t[J];return b&&"value"in b?b.value:void 0},n=r("slug"),o=r("category"),s=r("status"),a=r("title"),u=r("message"),d=r("suggestion"),c=r("exitCode"),g=r("detail"),f=r("cause"),R=r("instance"),P=r("context"),h=r("stack");return typeof n!="string"||!or.has(o)||typeof s!="number"||!Ue(s)||typeof a!="string"||typeof u!="string"||d!==void 0&&typeof d!="string"||c!==void 0&&(typeof c!="number"||!Ue(c))||g!==void 0&&typeof g!="string"||R!==void 0&&typeof R!="string"||h!==void 0&&typeof h!="string"?null:{slug:n,category:o,status:s,title:a,message:u,suggestion:d,exitCode:c,detail:g,cause:f,instance:R,context:P,stack:h}}catch{return null}}var sr=i({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),ar=i({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),cr=i({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),ur=i({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),lr=i({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),dr=i({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),gr=i({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),fr=i({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),pr=i({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),yr=i({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),mr=i({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Ve={"config-not-found":sr,"config-invalid":ar,"config-parse-error":cr,"config-validation-error":ur,"config-type-error":lr,"import-map-invalid":dr,"cors-config-invalid":gr,"config-validation-failed":fr,"webhook-config-invalid":pr,"schedule-config-invalid":yr,"trigger-config-invalid":mr};var Er=i({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Rr=i({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),hr=i({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),_r=i({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),xr=i({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),Tr=i({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),Sr=i({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),Cr=i({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Fe={"build-failed":Er,"bundle-error":Rr,"typescript-error":hr,"mdx-compile-error":_r,"asset-optimization-error":xr,"ssg-generation-error":Tr,"sourcemap-error":Sr,"compilation-error":Cr};var Ar=i({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),br=i({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Or=i({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Ir=i({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Nr=i({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),Dr=i({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),wr=i({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),Mr=i({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),Lr=i({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Pr=i({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),Be={"hydration-mismatch":Ar,"render-error":br,"component-error":Or,"layout-not-found":Ir,"page-not-found":Nr,"api-error":Dr,"middleware-error":wr,"trigger-target-not-found":Mr,"trigger-execution-failed":Lr,"trigger-not-supported":Pr};var Hr=i({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Ur=i({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),vr=i({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),kr=i({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),$r=i({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Vr=i({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),Ge={"route-conflict":Hr,"invalid-route-file":Ur,"route-handler-invalid":vr,"dynamic-route-error":kr,"route-params-error":$r,"api-route-error":Vr};var Fr=i({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Br=i({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Gr=i({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),jr=i({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),zr=i({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Yr=i({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),je={"module-not-found":Fr,"import-resolution-error":Br,"circular-dependency":Gr,"invalid-import":jr,"dependency-missing":zr,"version-mismatch":Yr};var Kr=i({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),Wr=i({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),Xr=i({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),qr=i({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Jr=i({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Zr=i({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Qr=i({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),en=i({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),tn=i({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),rn=i({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),nn=i({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),on=i({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),sn=i({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),an=i({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),cn=i({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),un=i({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),ze={"port-in-use":Kr,"server-start-error":Wr,"cache-error":Xr,"file-watch-error":qr,"request-error":Jr,"service-overloaded":Zr,"project-execution-unavailable":Qr,"semaphore-timeout":en,"circuit-breaker-open":tn,"cache-path-mismatch":rn,"network-error":nn,"api-client-error":on,"token-storage-error":sn,"cache-invariant-violation":an,"release-not-found":cn,"fallback-exhausted":un};var ln=i({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),dn=i({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),gn=i({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),fn=i({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),pn=i({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),yn=i({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),mn=i({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),Ye={"client-boundary-violation":ln,"server-only-in-client":dn,"client-only-in-server":gn,"invalid-use-client":fn,"invalid-use-server":pn,"rsc-payload-error":yn,"ssr-output-limit-exceeded":mn};var En=i({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),Rn=i({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),hn=i({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),_n=i({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),xn=i({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),Ke={"hmr-error":En,"dev-server-error":Rn,"fast-refresh-error":hn,"error-overlay-error":_n,"source-map-error":xn};var Tn=i({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),Sn=i({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Cn=i({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),An=i({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),bn=i({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),On=i({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),In=i({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Nn=i({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),Dn=i({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),wn=i({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Mn=i({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Ln=i({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),We={"deployment-error":Tn,"platform-error":Sn,"env-var-missing":Cn,"production-build-required":An,"environment-not-found":bn,"release-missing-version":On,"release-build-timeout":In,"deployment-verification-timeout":Nn,"push-receipt-missing":Dn,"source-digest-mismatch":wn,"preview-hostname-too-long":Mn,"branch-not-found":Ln};var Pn=i({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Hn=i({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Un=i({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),vn=i({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),kn=i({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),$n=i({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Vn=i({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Xe={"agent-error":Pn,"agent-not-found":Hn,"agent-timeout":Un,"agent-intent-error":vn,"orchestration-error":kn,"cost-limit-exceeded":$n,"tool-id-conflict":Vn};var Fn=i({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Bn=i({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Gn=i({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),jn=i({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),zn=i({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Yn=i({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Kn=i({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Wn=i({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Xn=i({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),ue=i({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),qn=i({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Jn=i({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),qe={"unknown-error":Fn,"authentication-required":Bn,"permission-denied":Gn,"file-not-found":jn,"resource-not-found":zn,"invalid-argument":Yn,"timeout-error":Kn,"initialization-error":Wn,"not-supported":Xn,"security-violation":ue,"input-validation-failed":qn,"project-source-empty":Jn};var _s=Ce(Ve,Fe,Be,Ge,je,ze,Ye,Ke,We,Xe,qe);var Zn=[{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 Qn(){return Zn.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function eo(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function M(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:o=!0}=t;for(let{pattern:s,name:a}of Qn())if(!(r&&a==="inline script")&&(s.lastIndex=0,!!s.test(e)&&(o&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!eo())))throw ue.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function L(e,t){let r=t==="root"?N:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let o=e.createElement("div");return o.id=r,e.body.appendChild(o),o}function to(e,t){if(t.type!=="slot")return;let r=L(e,t.id);r.innerHTML=M(String(t.html??""))}function Je(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let o of r){let s=o.trim();if(!s)continue;let a;try{a=JSON.parse(s)}catch(d){l.debug("[client-dom] malformed NDJSON line",{line:s,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let u=a;if(u.type==="slot"){to(e,u);try{oo(e,u.id||"root")}catch(d){l.debug("[client-dom] hydration optional failed",d)}}}return n}function ro(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function Ze(e,t=document,r){let n="body"in e?e:null,o=n?.body??e;if(!o)return;n&&V(t,n.headers.get(k));let s=o.getReader(),a=new TextDecoder,u="",d=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:g,value:f}=r?await Promise.race([c,ro(r)]):await c;if(g){d=!0;break}u+=a.decode(f,{stream:!0}),u=Je(t,u)}u&&Je(t,`${u}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||l.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){d||l.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){l.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){l.debug("[client-dom] stream.cancel failed",c)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(c){l.debug("[client-dom] response.body.cancel failed",c)}}}function no(e,t){let r=L(e,t),n=[],o=s=>{let a=s;a.dataset?.clientRef&&n.push(a);for(let u of s.children)o(u)};return o(r),n}function oo(e,t){let r=no(e,t);for(let n of r){let o=n.dataset?.clientRef;o&&(n.dataset.hydrated="true",l.debug("[client-dom] marked for hydration",o))}}var io=new Set(["server","client","html","fragment"]);function Qe(e){if(!e)return[];try{let t=JSON.parse(e);return ao(t)?t.nodes:[]}catch{return[]}}async function de(e,t,r){return await Promise.all(e.map(n=>so(n,t,r)))}async function so(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await de(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let o=await r(e.component);return o?t.createElement(o,e.props??{},...n):null}function ao(e){return!le(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>et(t,0))}function et(e,t){return t>100||!le(e)||!io.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&&!le(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>et(r,t+1))}function le(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function co(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function W(e,t,r=document){try{let n=Se(r);if(!n)return e;let s=(await import(n)).wrapForHydration;return typeof s!="function"?e:s(e,{params:co(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return l.debug("router provider wrap failed",n),e}}var uo="Unknown dependency snapshot",lo="export default null; // Unknown dependency snapshot",ge="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function go(){return globalThis}async function fo(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===uo||t===lo}catch{return!1}}async function A(e,t=()=>globalThis.location.reload()){if(!await fo(e))return!1;let r=go();if(r[ge])return!0;r[ge]=!0;try{t()}catch{return delete r[ge],!1}return!0}async function X(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await A(o,r)}catch{return!1}}var po=100;function yo(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=po){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}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 r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(l.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function mo(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return l.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function Eo(e){return Qe(e.dataset?.rscChildren)}function Ro(e){return"/_veryfront/rsc/manifest"}function ho(e){return D(e)}async function _o(e=document){try{let t=S(e),r=await fetch(Ro(t),{headers:ho(t)});return r.ok?await r.json():(await A(r),null)}catch{return null}}async function rt(e,t,r,n={}){let o=xo(e,t,r,n.releaseAssetModules),s=t.moduleUrl??t.rel;if(!s)return null;let a=`${s}#${e.hash??""}`;try{let u=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(u)return u}catch(u){l.debug("hydrate: cache get failed",u)}if(!o)return null;try{let u=await(n.importModule??(d=>import(d)))(o);try{yo(a,u)}catch(d){l.debug("hydrate: cache set failed",d)}return u}catch(u){return l.debug("hydrate: failed to import module",{moduleUrl:o,error:u}),await(n.recoverSnapshotFailure??X)(o),null}}function xo(e,t,r,n){if(t.moduleUrl)return B(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(s=>s.rel===t.rel)?.path;return G({strategy:r,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function To(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let o=n.parentElement;for(;o;){if(r.has(o))return!1;o=o.parentElement}return!0})}async function nt(e=document){let t=null;try{t=await _o(e)}catch(c){l.debug("hydrate: fetch manifest failed",c)}if(!t){l.debug("hydrate: no manifest");return}let r=To(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){l.debug("hydrate: hmr hash read failed",c)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed",c)}return}let n=S(e),o=F(n),s=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){l.debug("hydrate: test mode flags failed",c)}let a=j(e,n?.reactVersion),[{default:u},{createRoot:d}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let c of r){let g=c.dataset?.clientRef??"";if(!g||c.dataset?.hydrated==="true")continue;let f=tt(g);if(!f)continue;let R=await rt(t,f,o,{releaseAssetModules:s});if(!R)continue;let P=R[f.exportName]??R.default;if(typeof P=="function")try{let h=d(c),J=mo(c),b=Eo(c),ot=await de(b,{Fragment:u.Fragment,createElement(H,Z,...U){return u.createElement(H,Z,...U)}},async H=>{let Z=t.modules.find(st=>st.id===H),U=t.components?.[H],ye=Z?.clientRef??(U?`${U}#default`:void 0);if(!ye)return null;let Q=tt(ye);if(!Q)return null;let ee=await rt(t,Q,o,{releaseAssetModules:s});if(!ee)return null;let me=ee[Q.exportName]??ee.default;return typeof me=="function"?me:null}),it=await W(u.createElement(P,J,...ot),n,e);h.render(it),c.dataset.hydrated="true"}catch(h){l.warn("hydrate: render failed",h)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed (post)",c)}}var fe="data-vf-react-head-owner";var So=2*1024*1024,Zs=So*2;var Qs=64*1024,ea=1024*1024,ta=1024*1024;var ra=new TextEncoder;async function Co(){let e=S(document),t=j(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var Ao=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function pe(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||Ao.has(e.tagName.toUpperCase())}function bo(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!pe(r))??t}function Oo(e,t){return e===t}function Io(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(o=>!pe(o));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let o of e)!pe(o)&&o.parentNode===t&&r.appendChild(o);return r}function No(e,t){for(let r of e){let n=[...r.hasAttribute(fe)?[r]:[],...r.querySelectorAll(`[${fe}]`)];for(let o of n)t.contains(o)||o.remove()}}function Do(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function wo(e,t){return t?.pagePath?!1:!!e.getElementById(N)}function Mo(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function Lo(e){return e==="rsc-module"}function Po(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function Ho(e,t,r){return G({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function Uo(e,t){try{let r=await fetch(I+"stream"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await Ze(r,document,n.signal),"success"}catch(r){return l.debug("tryStream failed",r),"failure"}}async function q(){try{await nt(document)}catch(e){l.debug("hydration failed",e)}}async function vo(e,t,r){try{let{React:n,ReactDOM:o}=await Co(),s=Ho(e,t,r);if(!s)return!1;l.debug("Loading component from:",s);let a;try{a=await import(s)}catch(R){throw await X(s),R}let u=a.default;if(typeof u!="function")return l.debug("Page component is not a function"),!1;let d=Array.from(document.body.children),c=bo(d,document.body),g=Oo(c,document.body)?Io(d,document.body):c;No(d,g);let f=await W(n.createElement(u,{}),r);return Lo(t)?o.createRoot(g).render(f):o.hydrateRoot(g,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),l.debug("Page component hydrated successfully"),!0}catch(n){return l.error("Page hydration failed",n),!1}}async function ko(e,t){try{let r=await fetch(I+"payload"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";let n=await r.json();if(V(document,n?.dependencyPinningCacheKey),n?.slots){for(let[o,s]of Object.entries(n.slots))L(document,o).innerHTML=M(String(s||""));return"success"}return L(document,N).innerHTML=M(String(n?.html||"")),"success"}catch(r){return l.debug("payload fetch failed",r),"failure"}}async function $o(){try{let e=S(document),t=Po(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(Mo()){await q();return}let r=e?.pagePath,n=F(e);if(r){if(Do(globalThis.window,e,document)){l.debug("Page renderer owns hydration");return}l.debug("Found page component in hydration data:",r),await vo(r,n,e)&&l.debug("Client component hydrated successfully");return}if(!wo(document,e))return;let o=await Uo(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await q();return}let s=await ko(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await q();return}await q()}catch(e){l.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{$o()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{$o as boot,Ho as buildPageHydrationModuleUrl,Po as buildRSCTransportQuery,No as retireAbandonedHeadOwnerMarkers,bo as selectHydrationRoot,wo as shouldAttemptRSCTransport,Mo as shouldHydrateOnly,Lo as shouldRenderPageComponent,Do as shouldUsePageRendererHydration,Oo as shouldWrapPageHydrationRoot};\n'; + 'var lt=Object.defineProperty;var dt=(e,t,r)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var m=(e,t,r)=>dt(e,typeof t!="symbol"?t+"":t,r);var gt="3.2.3",ft=Array.prototype.join,pt=Array.prototype.push,yt=Object.entries,he=Reflect.apply;function re(e,t){return he(ft,e,[t])}function v(e,t){he(pt,e,[t])}function mt(e,t,r,n){let o=[];if(n?.external?.length&&v(o,`external=${re(n.external,",")}`),v(o,`target=${n?.target??"es2022"}`),n?.deps){let d=[],c=yt(n.deps);for(let l=0;lt||r?.(n,...o)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function Nt(){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 V=Nt(),g=new T("RSC",V),Ei=new T("PREFETCH",V),Ri=new T("HYDRATE",V),hi=new T("VERYFRONT",V);var Dt="veryfront-hydration-data";function se(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function S(e=document){try{let t=se(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return g.debug("hydration data parse failed",t),null}}function F(e,t){if(!t?.startsWith("on:"))return!1;try{let r=se(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return g.debug("hydration dependency snapshot seed failed",r),!1}}function B(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function wt(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function G(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),o=r===-1?e:e.slice(0,r),s=o.indexOf("?"),a=s===-1?o:o.slice(0,s),u=new URLSearchParams(s===-1?"":o.slice(s+1));u.set("pins",t);let d=u.toString();return`${a}${d?`?${d}`:""}${n}`}function Mt(e,t){return wt(`${Ae}${ne(e)}.js`,t)}function Lt(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return G(`${I}module?rel=${encodeURIComponent(e)}${n}`,r)}function D(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[$]:t}:{}}function Pt(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ht=/\\.(tsx|ts|jsx|mdx|js)$/;function Ut(e){let t=Pt(e),r=[e,t];return Ht.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function vt(e,t){if(!e)return null;for(let r of Ut(t)){let n=e[r];if(n)return n}return null}function j(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?G(Mt(r,e.version),e.dependencyPinningCacheKey):null}let t=vt(e.releaseAssetModules,e.rel);return t||Lt(e.rel,e.version,e.dependencyPinningCacheKey)}function z(e=document,t=O){let r=oe(e);return{react:k("react",r)?"react":xe(t),reactDomClient:k("react-dom/client",r)?"react-dom/client":Te(t)}}function be(e=document){let t=oe(e);return k("veryfront/router",t)?"veryfront/router":null}var kt=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function $t(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let o of t)for(let[s,a]of Object.entries(o)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${s}" must define a slug`);let u=a.slug;if(typeof u!="string")throw new Error(`${n} entry "${s}" must define a string slug`);if(u!==s)throw new Error(`${n} key "${s}" does not match entry slug "${u}"`);if(Object.hasOwn(r,s))throw new Error(`Duplicate ${e} slug "${s}"`);r[s]=a}return Object.freeze(r)}function Oe(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!kt.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return $t("error registry",...e)}var Y={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"},wi={debug:Y.gray,info:Y.green,warn:Y.yellow,error:Y.red};var y="[REDACTED]",R=Reflect.apply;var Ie=RegExp.prototype.exec,x=RegExp.prototype[Symbol.replace],Li=String.prototype.charCodeAt,Ne=String.prototype.slice,Vt=String.prototype.toLowerCase,Ft=/[^a-z0-9]/g;function ae(e){let t=R(Vt,e,[]);return R(x,Ft,[t,""])}function K(e,t,r){return r===void 0?R(Ne,e,[t]):R(Ne,e,[t,r])}var Bt=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Gt=512,jt=128,w=new Map;function Me(e){let t=e.length<=jt;if(t){let o=w.get(e);if(o!==void 0)return o}let r=ae(e),n=Bt.some(o=>r.includes(o));if(t){if(w.size>=Gt){let o=w.keys().next().value;o!==void 0&&w.delete(o)}w.set(e,n)}return n}var zt=["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"],Yt=new Set(zt.map(ae)),Kt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Wt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Xt=3;function qt(e){return e===" "||e==="\t"||e===","||e===";"||e==="&"||e==="?"||e==="#"}function Jt(e){if(!e)return!1;let t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}function Le(e){return Jt(e)||e==="_"||e==="$"}function Zt(e){if(!e)return!1;let t=e.charCodeAt(0);return Le(e)||t>=48&&t<=57||e==="."||e==="-"}function Pe(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!Le(e[r]))return!1;for(r++;Zt(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function He(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||qt(e)}function Ue(e,t){let r=t;for(;r=e.length||Pe(e,r)}function Qt(e,t){let r=t,n=!0;if(e.startsWith(y,t)){let l=t+y.length;if(De(e,l))return{end:l,replacement:y};r=l,n=!1}let o=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",s=!1,a=()=>o?`${o}${y}${s?o:""}`:y,u=[],d="",c=-1;for(let l=r;l0&&(f==="}"||f==="]")){if(u.at(-1)!==f)return{end:e.length,replacement:a()};if(u.pop(),l++,u.length===0&&De(e,l))return{end:l,replacement:a()};continue}if(u.length>0||!He(f)){l++;continue}let E=l;if(l=Ue(e,l),l>=e.length||Pe(e,l))return{end:E,replacement:a()}}return{end:e.length,replacement:a()}}function we(e,t,r,n){let o=0,s="";for(let a=R(Ie,t,[e]);a;a=R(Ie,t,[e])){let u=a[r];if(!Me(u))continue;let d=t.lastIndex,c=n===void 0?void 0:a[n],l=d+y.length;if((c==="?"||c==="&"||c===";")&&e.startsWith(y,d)&&e[l]==="#")continue;let f=Qt(e,d);s+=K(e,o,a.index),s+=a[0],s+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:s+K(e,o)}function er(e,t,r){let n=r.search(/[ \\t]/);if(n<0)return!1;let o=`${t}:${K(r,0,n)}`,s=e==="//"?`https://${o}`:`${e}${o}`;try{let a=new URL(s);return a.username.length===0&&a.password.length===0}catch{return!1}}function tr(e){let t=e;for(let r=0;r{let s=o.indexOf(":");if(s===-1)return`${n}${y}@`;let a=K(o,0,s);return`${n}${a}:${y}@`}]);return t=R(x,Wt,[t,(r,n,o,s)=>er(n,o,s)?r:`${n}${o}:${y}@`]),t=R(x,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[t,(r,n,o,s)=>{let a=tr(o);return Yt.has(ae(a))||Me(a)?`${n}${o}=${y}`:r}]),t=R(x,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[t,(r,n,o)=>`${n}${o}${y}`]),t=R(x,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[t,(r,n)=>`${n}${y}`]),t=R(x,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[t,(r,n,o)=>`${n}${o}${y}`]),t=we(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=we(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var rr=2048;var ki=64*1024,nr=256,or="https://veryfront.com/docs/errors/",ve="...[truncated]",ue="unknown-error";function ke(e,t){if(e.length<=t)return e;let r=Math.max(0,t-ve.length);return`${ir(e,r)}${ve}`}function ir(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function sr(e){let t="";for(let r=0;r=55296&&n<=56319){let o=e.charCodeAt(r+1);o>=56320&&o<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function C(e){return typeof e!="string"?y:ke(ce(e),rr)}function ar(e){let t=typeof e=="string"?ce(e):ue,r=ke(t||ue,nr),n=sr(r);return n==="."||n===".."?ue:n}function W(e){let t=encodeURIComponent(ar(e));return`${or}${t}`}var cr=Object.freeze,ur=Object.getOwnPropertyDescriptors,$e=Number.isFinite,Fe=new WeakSet,lr=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function i(e){let t={...e},r={...t,create(n){let o=n?.message,s=n?.detail,a=n?.cause,u=n?.instance,d=n?.context,c=n?.status??t.status;return new le(o||s||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:s,cause:a,instance:u,context:d})}};return cr(r)}var le=class extends Error{constructor(r,n){super(r);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");Fe.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=Ve(this);return r?{type:W(r.slug),title:C(r.title),status:r.status,detail:r.detail===void 0?void 0:C(r.detail),instance:r.instance===void 0?void 0:C(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:C(r.suggestion),cause:typeof r.cause=="string"?C(r.cause):void 0}:{type:W("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=Ve(this);return W(r?.slug??"unknown-error")}};function Be(e){return typeof e=="object"&&e!==null&&Fe.has(e)}function Ve(e){return Be(e)?dr(e):null}function dr(e){try{if(!Be(e))return null;let t=ur(e),r=Z=>{let b=t[Z];return b&&"value"in b?b.value:void 0},n=r("slug"),o=r("category"),s=r("status"),a=r("title"),u=r("message"),d=r("suggestion"),c=r("exitCode"),l=r("detail"),f=r("cause"),E=r("instance"),P=r("context"),h=r("stack");return typeof n!="string"||!lr.has(o)||typeof s!="number"||!$e(s)||typeof a!="string"||typeof u!="string"||d!==void 0&&typeof d!="string"||c!==void 0&&(typeof c!="number"||!$e(c))||l!==void 0&&typeof l!="string"||E!==void 0&&typeof E!="string"||h!==void 0&&typeof h!="string"?null:{slug:n,category:o,status:s,title:a,message:u,suggestion:d,exitCode:c,detail:l,cause:f,instance:E,context:P,stack:h}}catch{return null}}var gr=i({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),fr=i({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),pr=i({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),yr=i({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),mr=i({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),Er=i({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),Rr=i({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),hr=i({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),_r=i({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),xr=i({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),Tr=i({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Ge={"config-not-found":gr,"config-invalid":fr,"config-parse-error":pr,"config-validation-error":yr,"config-type-error":mr,"import-map-invalid":Er,"cors-config-invalid":Rr,"config-validation-failed":hr,"webhook-config-invalid":_r,"schedule-config-invalid":xr,"trigger-config-invalid":Tr};var Sr=i({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Cr=i({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),Ar=i({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),br=i({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),Or=i({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),Ir=i({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),Nr=i({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),Dr=i({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),je={"build-failed":Sr,"bundle-error":Cr,"typescript-error":Ar,"mdx-compile-error":br,"asset-optimization-error":Or,"ssg-generation-error":Ir,"sourcemap-error":Nr,"compilation-error":Dr};var wr=i({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Mr=i({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Lr=i({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Pr=i({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Hr=i({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),Ur=i({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),vr=i({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),kr=i({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),$r=i({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Vr=i({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ze={"hydration-mismatch":wr,"render-error":Mr,"component-error":Lr,"layout-not-found":Pr,"page-not-found":Hr,"api-error":Ur,"middleware-error":vr,"trigger-target-not-found":kr,"trigger-execution-failed":$r,"trigger-not-supported":Vr};var Fr=i({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Br=i({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Gr=i({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),jr=i({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),zr=i({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Yr=i({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),Ye={"route-conflict":Fr,"invalid-route-file":Br,"route-handler-invalid":Gr,"dynamic-route-error":jr,"route-params-error":zr,"api-route-error":Yr};var Kr=i({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Wr=i({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Xr=i({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),qr=i({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),Jr=i({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Zr=i({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),Ke={"module-not-found":Kr,"import-resolution-error":Wr,"circular-dependency":Xr,"invalid-import":qr,"dependency-missing":Jr,"version-mismatch":Zr};var Qr=i({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),en=i({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),tn=i({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),rn=i({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),nn=i({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),on=i({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),sn=i({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),an=i({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),cn=i({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),un=i({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),ln=i({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),dn=i({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),gn=i({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),fn=i({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),pn=i({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),yn=i({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),We={"port-in-use":Qr,"server-start-error":en,"cache-error":tn,"file-watch-error":rn,"request-error":nn,"service-overloaded":on,"project-execution-unavailable":sn,"semaphore-timeout":an,"circuit-breaker-open":cn,"cache-path-mismatch":un,"network-error":ln,"api-client-error":dn,"token-storage-error":gn,"cache-invariant-violation":fn,"release-not-found":pn,"fallback-exhausted":yn};var mn=i({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),En=i({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),Rn=i({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),hn=i({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),_n=i({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),xn=i({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),Tn=i({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),Xe={"client-boundary-violation":mn,"server-only-in-client":En,"client-only-in-server":Rn,"invalid-use-client":hn,"invalid-use-server":_n,"rsc-payload-error":xn,"ssr-output-limit-exceeded":Tn};var Sn=i({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),Cn=i({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),An=i({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),bn=i({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),On=i({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),qe={"hmr-error":Sn,"dev-server-error":Cn,"fast-refresh-error":An,"error-overlay-error":bn,"source-map-error":On};var In=i({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),Nn=i({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Dn=i({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),wn=i({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),Mn=i({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Ln=i({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),Pn=i({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Hn=i({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),Un=i({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),vn=i({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),kn=i({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),$n=i({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),Je={"deployment-error":In,"platform-error":Nn,"env-var-missing":Dn,"production-build-required":wn,"environment-not-found":Mn,"release-missing-version":Ln,"release-build-timeout":Pn,"deployment-verification-timeout":Hn,"push-receipt-missing":Un,"source-digest-mismatch":vn,"preview-hostname-too-long":kn,"branch-not-found":$n};var Vn=i({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Fn=i({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Bn=i({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Gn=i({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),jn=i({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),zn=i({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Yn=i({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Ze={"agent-error":Vn,"agent-not-found":Fn,"agent-timeout":Bn,"agent-intent-error":Gn,"orchestration-error":jn,"cost-limit-exceeded":zn,"tool-id-conflict":Yn};var Kn=i({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Wn=i({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Xn=i({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),qn=i({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Jn=i({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Zn=i({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Qn=i({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),eo=i({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),to=i({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),de=i({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),ro=i({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),no=i({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Qe={"unknown-error":Kn,"authentication-required":Wn,"permission-denied":Xn,"file-not-found":qn,"resource-not-found":Jn,"invalid-argument":Zn,"timeout-error":Qn,"initialization-error":eo,"not-supported":to,"security-violation":de,"input-validation-failed":ro,"project-source-empty":no};var bs=Oe(Ge,je,ze,Ye,Ke,We,Xe,qe,Je,Ze,Qe);var oo=[{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 io(){return oo.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function so(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function M(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:o=!0}=t;for(let{pattern:s,name:a}of io())if(!(r&&a==="inline script")&&(s.lastIndex=0,!!s.test(e)&&(o&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!so())))throw de.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function L(e,t){let r=t==="root"?N:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let o=e.createElement("div");return o.id=r,e.body.appendChild(o),o}function ao(e,t){if(t.type!=="slot")return;let r=L(e,t.id);r.innerHTML=M(String(t.html??""))}function et(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let o of r){let s=o.trim();if(!s)continue;let a;try{a=JSON.parse(s)}catch(d){g.debug("[client-dom] malformed NDJSON line",{line:s,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let u=a;if(u.type==="slot"){ao(e,u);try{lo(e,u.id||"root")}catch(d){g.debug("[client-dom] hydration optional failed",d)}}}return n}function co(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function tt(e,t=document,r){let n="body"in e?e:null,o=n?.body??e;if(!o)return;n&&F(t,n.headers.get($));let s=o.getReader(),a=new TextDecoder,u="",d=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:l,value:f}=r?await Promise.race([c,co(r)]):await c;if(l){d=!0;break}u+=a.decode(f,{stream:!0}),u=et(t,u)}u&&et(t,`${u}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||g.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){d||g.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){g.debug("[client-dom] reader.releaseLock failed",c)}if(typeof o.cancel=="function")try{await o.cancel()}catch(c){g.debug("[client-dom] stream.cancel failed",c)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(c){g.debug("[client-dom] response.body.cancel failed",c)}}}function uo(e,t){let r=L(e,t),n=[],o=s=>{let a=s;a.dataset?.clientRef&&n.push(a);for(let u of s.children)o(u)};return o(r),n}function lo(e,t){let r=uo(e,t);for(let n of r){let o=n.dataset?.clientRef;o&&(n.dataset.hydrated="true",g.debug("[client-dom] marked for hydration",o))}}var go=new Set(["server","client","html","fragment"]);function rt(e){if(!e)return[];try{let t=JSON.parse(e);return po(t)?t.nodes:[]}catch{return[]}}async function fe(e,t,r){return await Promise.all(e.map(n=>fo(n,t,r)))}async function fo(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await fe(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let o=await r(e.component);return o?t.createElement(o,e.props??{},...n):null}function po(e){return!ge(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>nt(t,0))}function nt(e,t){return t>100||!ge(e)||!go.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&&!ge(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>nt(r,t+1))}function ge(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function yo(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function X(e,t,r=document){try{let n=be(r);if(!n)return e;let s=(await import(n)).wrapForHydration;return typeof s!="function"?e:s(e,{params:yo(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return g.debug("router provider wrap failed",n),e}}var mo="Unknown dependency snapshot",Eo="export default null; // Unknown dependency snapshot",pe="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function Ro(){return globalThis}async function ho(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===mo||t===Eo}catch{return!1}}async function A(e,t=()=>globalThis.location.reload()){if(!await ho(e))return!1;let r=Ro();if(r[pe])return!0;r[pe]=!0;try{t()}catch{return delete r[pe],!1}return!0}async function q(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await A(o,r)}catch{return!1}}var _o=100;function xo(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=_o){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function ot(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(g.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function To(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return g.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function So(e){return rt(e.dataset?.rscChildren)}function Co(e){return"/_veryfront/rsc/manifest"}function Ao(e){return D(e)}async function bo(e=document){try{let t=S(e),r=await fetch(Co(t),{headers:Ao(t)});return r.ok?await r.json():(await A(r),null)}catch{return null}}async function it(e,t,r,n={}){let o=Oo(e,t,r,n.releaseAssetModules),s=t.moduleUrl??t.rel;if(!s)return null;let a=`${s}#${e.hash??""}`;try{let u=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(u)return u}catch(u){g.debug("hydrate: cache get failed",u)}if(!o)return null;try{let u=await(n.importModule??(d=>import(d)))(o);try{xo(a,u)}catch(d){g.debug("hydrate: cache set failed",d)}return u}catch(u){return g.debug("hydrate: failed to import module",{moduleUrl:o,error:u}),await(n.recoverSnapshotFailure??q)(o),null}}function Oo(e,t,r,n){if(t.moduleUrl)return G(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(s=>s.rel===t.rel)?.path;return j({strategy:r,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function Io(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let o=n.parentElement;for(;o;){if(r.has(o))return!1;o=o.parentElement}return!0})}async function st(e=document){let t=null;try{t=await bo(e)}catch(c){g.debug("hydrate: fetch manifest failed",c)}if(!t){g.debug("hydrate: no manifest");return}let r=Io(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){g.debug("hydrate: hmr hash read failed",c)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){g.debug("hydrate: set hash failed",c)}return}let n=S(e),o=B(n),s=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){g.debug("hydrate: test mode flags failed",c)}let a=z(e,n?.reactVersion),[{default:u},{createRoot:d}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let c of r){let l=c.dataset?.clientRef??"";if(!l||c.dataset?.hydrated==="true")continue;let f=ot(l);if(!f)continue;let E=await it(t,f,o,{releaseAssetModules:s});if(!E)continue;let P=E[f.exportName]??E.default;if(typeof P=="function")try{let h=d(c),Z=To(c),b=So(c),at=await fe(b,{Fragment:u.Fragment,createElement(H,Q,...U){return u.createElement(H,Q,...U)}},async H=>{let Q=t.modules.find(ut=>ut.id===H),U=t.components?.[H],Ee=Q?.clientRef??(U?`${U}#default`:void 0);if(!Ee)return null;let ee=ot(Ee);if(!ee)return null;let te=await it(t,ee,o,{releaseAssetModules:s});if(!te)return null;let Re=te[ee.exportName]??te.default;return typeof Re=="function"?Re:null}),ct=await X(u.createElement(P,Z,...at),n,e);h.render(ct),c.dataset.hydrated="true"}catch(h){g.warn("hydrate: render failed",h)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){g.debug("hydrate: set hash failed (post)",c)}}var ye="data-vf-react-head-owner";var No=2*1024*1024,oa=No*2;var ia=64*1024,sa=1024*1024,aa=1024*1024;var ca=new TextEncoder;async function Do(){let e=S(document),t=z(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var wo=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function me(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||wo.has(e.tagName.toUpperCase())}function Mo(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!me(r))??t}function Lo(e,t){return e===t}function Po(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(o=>!me(o));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let o of e)!me(o)&&o.parentNode===t&&r.appendChild(o);return r}function Ho(e,t){for(let r of e){let n=[...r.hasAttribute(ye)?[r]:[],...r.querySelectorAll(`[${ye}]`)];for(let o of n)t.contains(o)||o.remove()}}function Uo(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function vo(e,t){return t?.pagePath?!1:!!e.getElementById(N)}function ko(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function $o(e){return e==="rsc-module"}function Vo(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function Fo(e,t,r){return j({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function Bo(e,t){try{let r=await fetch(I+"stream"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await tt(r,document,n.signal),"success"}catch(r){return g.debug("tryStream failed",r),"failure"}}async function J(){try{await st(document)}catch(e){g.debug("hydration failed",e)}}async function Go(e,t,r){try{let{React:n,ReactDOM:o}=await Do(),s=Fo(e,t,r);if(!s)return!1;g.debug("Loading component from:",s);let a;try{a=await import(s)}catch(E){throw await q(s),E}let u=a.default;if(typeof u!="function")return g.debug("Page component is not a function"),!1;let d=Array.from(document.body.children),c=Mo(d,document.body),l=Lo(c,document.body)?Po(d,document.body):c;Ho(d,l);let f=await X(n.createElement(u,{}),r);return $o(t)?o.createRoot(l).render(f):o.hydrateRoot(l,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),g.debug("Page component hydrated successfully"),!0}catch(n){return g.error("Page hydration failed",n),!1}}async function jo(e,t){try{let r=await fetch(I+"payload"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";let n=await r.json();if(F(document,n?.dependencyPinningCacheKey),n?.slots){for(let[o,s]of Object.entries(n.slots))L(document,o).innerHTML=M(String(s||""));return"success"}return L(document,N).innerHTML=M(String(n?.html||"")),"success"}catch(r){return g.debug("payload fetch failed",r),"failure"}}async function zo(){try{let e=S(document),t=Vo(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(ko()){await J();return}let r=e?.pagePath,n=B(e);if(r){if(Uo(globalThis.window,e,document)){g.debug("Page renderer owns hydration");return}g.debug("Found page component in hydration data:",r),await Go(r,n,e)&&g.debug("Client component hydrated successfully");return}if(!vo(document,e))return;let o=await Bo(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await J();return}let s=await jo(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await J();return}await J()}catch(e){g.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{zo()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{zo as boot,Fo as buildPageHydrationModuleUrl,Vo as buildRSCTransportQuery,Ho as retireAbandonedHeadOwnerMarkers,Mo as selectHydrationRoot,vo as shouldAttemptRSCTransport,ko as shouldHydrateOnly,$o as shouldRenderPageComponent,Uo as shouldUsePageRendererHydration,Lo as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var xe=Object.defineProperty;var _e=(t,r,e)=>r in t?xe(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var E=(t,r,e)=>_e(t,typeof r!="symbol"?r+"":r,e);var he=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Se(t,...r){let e=Object.create(null),o=t.charAt(0).toUpperCase()+t.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${o} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${o} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${o} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(e,i))throw new Error(`Duplicate ${t} slug "${i}"`);e[i]=a}return Object.freeze(e)}function P(...t){for(let r of t)for(let e of Object.values(r)){if(typeof e.slug!="string"||e.slug.length<3||e.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(e.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${e.slug}"`);if(typeof e.category!="string"||!he.has(e.category))throw new TypeError(`Registered error has unknown category "${e.category}"`);if(!Number.isInteger(e.status)||e.status<400||e.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${e.status}`);if(typeof e.title!="string"||e.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(e.suggestion!==void 0&&(typeof e.suggestion!="string"||e.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Se("error registry",...t)}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"},an={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",m=Reflect.apply;var $=RegExp.prototype.exec,y=RegExp.prototype[Symbol.replace],un=String.prototype.charCodeAt,k=String.prototype.slice,Ie=String.prototype.toLowerCase,Oe=/[^a-z0-9]/g;function D(t){let r=m(Ie,t,[]);return m(y,Oe,[r,""])}function O(t,r,e){return e===void 0?m(k,t,[r]):m(k,t,[r,e])}var Te=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Ce=512,Ne=128,S=new Map;function F(t){let r=t.length<=Ne;if(r){let s=S.get(t);if(s!==void 0)return s}let e=D(t),o=Te.some(s=>e.includes(s));if(r){if(S.size>=Ce){let s=S.keys().next().value;s!==void 0&&S.delete(s)}S.set(t,o)}return o}var Ae=["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"],De=new Set(Ae.map(D)),be=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Le=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Ue=3;function we(t){return t===" "||t==="\t"||t===","||t===";"||t==="&"||t==="?"||t==="#"}function ve(t){if(!t)return!1;let r=t.charCodeAt(0);return r>=65&&r<=90||r>=97&&r<=122}function H(t){return ve(t)||t==="_"||t==="$"}function Me(t){if(!t)return!1;let r=t.charCodeAt(0);return H(t)||r>=48&&r<=57||t==="."||t==="-"}function j(t,r){let e=r,o=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!H(t[e]))return!1;for(e++;Me(t[e]);)e++;if(o){if(t[e]!==o)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function z(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||we(t)}function Y(t,r){let e=r;for(;e=t.length||j(t,e)}function Pe(t,r){let e=r,o=!0;if(t.startsWith(p,r)){let g=r+p.length;if(V(t,g))return{end:g,replacement:p};e=g,o=!1}let s=o&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",i=!1,a=()=>s?`${s}${p}${i?s:""}`:p,c=[],d="",u=-1;for(let g=e;g0&&(f==="}"||f==="]")){if(c.at(-1)!==f)return{end:t.length,replacement:a()};if(c.pop(),g++,c.length===0&&V(t,g))return{end:g,replacement:a()};continue}if(c.length>0||!z(f)){g++;continue}let h=g;if(g=Y(t,g),g>=t.length||j(t,g))return{end:h,replacement:a()}}return{end:t.length,replacement:a()}}function G(t,r,e,o){let s=0,i="";for(let a=m($,r,[t]);a;a=m($,r,[t])){let c=a[e];if(!F(c))continue;let d=r.lastIndex,u=o===void 0?void 0:a[o],g=d+p.length;if((u==="?"||u==="&"||u===";")&&t.startsWith(p,d)&&t[g]==="#")continue;let f=Pe(t,d);i+=O(t,s,a.index),i+=a[0],i+=f.replacement,s=f.end,r.lastIndex=f.end}return s===0?t:i+O(t,s)}function $e(t,r,e){let o=e.search(/[ \\t]/);if(o<0)return!1;let s=`${r}:${O(e,0,o)}`,i=t==="//"?`https://${s}`:`${t}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function ke(t){let r=t;for(let e=0;e{let i=s.indexOf(":");if(i===-1)return`${o}${p}@`;let a=O(s,0,i);return`${o}${a}:${p}@`}]);return r=m(y,Le,[r,(e,o,s,i)=>$e(o,s,i)?e:`${o}${s}:${p}@`]),r=m(y,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[r,(e,o,s,i)=>{let a=ke(s);return De.has(D(a))||F(a)?`${o}${s}=${p}`:e}]),r=m(y,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=m(y,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[r,(e,o)=>`${o}${p}`]),r=m(y,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=G(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=G(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Ve=2048;var pn=64*1024,Ge=256,Fe="https://veryfront.com/docs/errors/",B="...[truncated]",L="unknown-error";function W(t,r){if(t.length<=r)return t;let e=Math.max(0,r-B.length);return`${He(t,e)}${B}`}function He(t,r){let e=t.slice(0,r),o=e.charCodeAt(e.length-1);return o>=55296&&o<=56319&&(e=e.slice(0,-1)),e}function je(t){let r="";for(let e=0;e=55296&&o<=56319){let s=t.charCodeAt(e+1);s>=56320&&s<=57343?(r+=t.slice(e,e+2),e++):r+="\\uFFFD";continue}r+=o>=56320&&o<=57343?"\\uFFFD":t.charAt(e)}return r}function x(t){return typeof t!="string"?p:W(b(t),Ve)}function ze(t){let r=typeof t=="string"?b(t):L,e=W(r||L,Ge),o=je(e);return o==="."||o===".."?L:o}function T(t){let r=encodeURIComponent(ze(t));return`${Fe}${r}`}var Ye=Object.freeze,Be=Object.getOwnPropertyDescriptors,K=Number.isFinite,X=new WeakSet,We=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function n(t){let r={...t},e={...r,create(o){let s=o?.message,i=o?.detail,a=o?.cause,c=o?.instance,d=o?.context,u=o?.status??r.status;return new U(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:d})}};return Ye(e)}var U=class extends Error{constructor(e,o){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");X.add(this),this.name="VeryfrontError",this.slug=o.slug,this.category=o.category,this.status=o.status,this.title=o.title,this.suggestion=o.suggestion,this.exitCode=o.exitCode,this.detail=o.detail,this.cause=o.cause,this.instance=o.instance,this.context=o.context}toRFC9457(){let e=q(this);return e?{type:T(e.slug),title:x(e.title),status:e.status,detail:e.detail===void 0?void 0:x(e.detail),instance:e.instance===void 0?void 0:x(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:x(e.suggestion),cause:typeof e.cause=="string"?x(e.cause):void 0}:{type:T("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=q(this);return T(e?.slug??"unknown-error")}};function J(t){return typeof t=="object"&&t!==null&&X.has(t)}function q(t){return J(t)?Ke(t):null}function Ke(t){try{if(!J(t))return null;let r=Be(t),e=ye=>{let A=r[ye];return A&&"value"in A?A.value:void 0},o=e("slug"),s=e("category"),i=e("status"),a=e("title"),c=e("message"),d=e("suggestion"),u=e("exitCode"),g=e("detail"),f=e("cause"),h=e("instance"),Re=e("context"),N=e("stack");return typeof o!="string"||!We.has(s)||typeof i!="number"||!K(i)||typeof a!="string"||typeof c!="string"||d!==void 0&&typeof d!="string"||u!==void 0&&(typeof u!="number"||!K(u))||g!==void 0&&typeof g!="string"||h!==void 0&&typeof h!="string"||N!==void 0&&typeof N!="string"?null:{slug:o,category:s,status:i,title:a,message:c,suggestion:d,exitCode:u,detail:g,cause:f,instance:h,context:Re,stack:N}}catch{return null}}var qe=n({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Xe=n({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Je=n({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Ze=n({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Qe=n({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),et=n({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),tt=n({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),rt=n({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),nt=n({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),ot=n({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),st=n({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Z={"config-not-found":qe,"config-invalid":Xe,"config-parse-error":Je,"config-validation-error":Ze,"config-type-error":Qe,"import-map-invalid":et,"cors-config-invalid":tt,"config-validation-failed":rt,"webhook-config-invalid":nt,"schedule-config-invalid":ot,"trigger-config-invalid":st};var it=n({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),at=n({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),ct=n({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),ut=n({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),lt=n({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),gt=n({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),dt=n({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ft=n({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Q={"build-failed":it,"bundle-error":at,"typescript-error":ct,"mdx-compile-error":ut,"asset-optimization-error":lt,"ssg-generation-error":gt,"sourcemap-error":dt,"compilation-error":ft};var pt=n({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Et=n({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),mt=n({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Rt=n({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yt=n({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),xt=n({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),_t=n({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),ht=n({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),St=n({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),It=n({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ee={"hydration-mismatch":pt,"render-error":Et,"component-error":mt,"layout-not-found":Rt,"page-not-found":yt,"api-error":xt,"middleware-error":_t,"trigger-target-not-found":ht,"trigger-execution-failed":St,"trigger-not-supported":It};var Ot=n({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Tt=n({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Ct=n({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),Nt=n({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),At=n({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Dt=n({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),te={"route-conflict":Ot,"invalid-route-file":Tt,"route-handler-invalid":Ct,"dynamic-route-error":Nt,"route-params-error":At,"api-route-error":Dt};var bt=n({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Lt=n({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Ut=n({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),wt=n({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),vt=n({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Mt=n({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),re={"module-not-found":bt,"import-resolution-error":Lt,"circular-dependency":Ut,"invalid-import":wt,"dependency-missing":vt,"version-mismatch":Mt};var Pt=n({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),$t=n({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),kt=n({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Vt=n({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Gt=n({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Ft=n({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Ht=n({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),jt=n({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),zt=n({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Yt=n({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bt=n({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Wt=n({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Kt=n({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),qt=n({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Xt=n({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Jt=n({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),ne={"port-in-use":Pt,"server-start-error":$t,"cache-error":kt,"file-watch-error":Vt,"request-error":Gt,"service-overloaded":Ft,"project-execution-unavailable":Ht,"semaphore-timeout":jt,"circuit-breaker-open":zt,"cache-path-mismatch":Yt,"network-error":Bt,"api-client-error":Wt,"token-storage-error":Kt,"cache-invariant-violation":qt,"release-not-found":Xt,"fallback-exhausted":Jt};var Zt=n({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),Qt=n({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),er=n({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),tr=n({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),rr=n({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),nr=n({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),or=n({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),oe={"client-boundary-violation":Zt,"server-only-in-client":Qt,"client-only-in-server":er,"invalid-use-client":tr,"invalid-use-server":rr,"rsc-payload-error":nr,"ssr-output-limit-exceeded":or};var sr=n({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),ir=n({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),ar=n({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),cr=n({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),ur=n({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),se={"hmr-error":sr,"dev-server-error":ir,"fast-refresh-error":ar,"error-overlay-error":cr,"source-map-error":ur};var lr=n({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),gr=n({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),dr=n({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),fr=n({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),pr=n({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Er=n({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),mr=n({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Rr=n({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),yr=n({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),xr=n({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),_r=n({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),hr=n({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),ie={"deployment-error":lr,"platform-error":gr,"env-var-missing":dr,"production-build-required":fr,"environment-not-found":pr,"release-missing-version":Er,"release-build-timeout":mr,"deployment-verification-timeout":Rr,"push-receipt-missing":yr,"source-digest-mismatch":xr,"preview-hostname-too-long":_r,"branch-not-found":hr};var Sr=n({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Ir=n({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Or=n({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Tr=n({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Cr=n({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Nr=n({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Ar=n({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),ae={"agent-error":Sr,"agent-not-found":Ir,"agent-timeout":Or,"agent-intent-error":Tr,"orchestration-error":Cr,"cost-limit-exceeded":Nr,"tool-id-conflict":Ar};var Dr=n({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),br=n({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Lr=n({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Ur=n({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),wr=n({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),vr=n({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Mr=n({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Pr=n({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),$r=n({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),w=n({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),kr=n({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Vr=n({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ce={"unknown-error":Dr,"authentication-required":br,"permission-denied":Lr,"file-not-found":Ur,"resource-not-found":wr,"invalid-argument":vr,"timeout-error":Mr,"initialization-error":Pr,"not-supported":$r,"security-violation":w,"input-validation-failed":kr,"project-source-empty":Vr};var ro=P(Z,Q,ee,te,re,ne,oe,se,ie,ae,ce);var Gr=[{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 Fr(){return Gr.map(({source:t,flags:r,name:e})=>({pattern:new RegExp(t,r),name:e}))}function Hr(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function ue(t,r={}){let{allowInlineScripts:e=!1,strict:o=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of Fr())if(!(e&&a==="inline script")&&(i.lastIndex=0,!!i.test(t)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),o||!Hr())))throw w.create({detail:`Potentially unsafe HTML: ${a} detected`});return t}var _=class{constructor(r,e){E(this,"prefix",r);E(this,"level",e)}log(r,e,o,...s){this.level>r||e?.(o,...s)}debug(r,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...e)}info(r,...e){this.log(1,console.log,`[${this.prefix}] ${r}`,...e)}warn(r,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...e)}error(r,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...e)}};function jr(){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 C=jr(),R=new _("RSC",C),yo=new _("PREFETCH",C),xo=new _("HYDRATE",C),_o=new _("VERYFRONT",C);var Io=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var zr=5e3,Yr=1e4,Co=16*1024*1024,Br=5e3;var Wr=100;var Kr=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),No=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Ao=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:zr,api:3e4,ssr:Yr,hmr:3e4,sandbox:Br}),cache:Object.freeze({jit:Object.freeze({maxSize:Wr,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Kr})});var l="/_veryfront",v={RSC:`${l}/rsc/`,FS:`${l}/fs/`,MODULES:`${l}/modules/`,PAGES:`${l}/pages/`,DATA:`${l}/data/`,LIB:`${l}/lib/`,CHUNKS:`${l}/chunks/`,CLIENT:`${l}/client/`},ge={HMR_RUNTIME:`${l}/hmr-runtime.js`,HMR:`${l}/hmr.js`,ERROR_OVERLAY:`${l}/error-overlay.js`,DEV_LOADER:`${l}/dev-loader.js`,CLIENT_LOG:`${l}/log`,CLIENT_JS:`${l}/client.js`,ROUTER_JS:`${l}/router.js`,PREFETCH_JS:`${l}/prefetch.js`,MANIFEST_JSON:`${l}/manifest.json`,APP_JS:`${l}/app.js`,RSC_CLIENT:`${l}/rsc/client.js`,RSC_MANIFEST:`${l}/rsc/manifest`,RSC_STREAM:`${l}/rsc/stream`,RSC_PAYLOAD:`${l}/rsc/payload`,RSC_RENDER:`${l}/rsc/render`,RSC_PAGE:`${l}/rsc/page`,RSC_MODULE:`${l}/rsc/module`,RSC_DOM:`${l}/rsc/dom.js`,LIB_CHAT_REACT:`${l}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${l}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${l}/lib/chat/primitives.js`};var qr={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},bo=qr.CACHE;var Lo={HMR_RUNTIME:ge.HMR_RUNTIME,ERROR_OVERLAY:ge.ERROR_OVERLAY};var Xr=v.RSC,Jr=v.FS;var de="rsc-root",M="x-veryfront-dependency-pins";var Bo=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Qr="veryfront-hydration-data";function fe(t){try{let r=[...t.querySelectorAll(`[id="${Qr}"]`)];if(r.length!==1)return null;let e=t.body;if(!e)return null;let o=r[0];return e.firstElementChild!==o&&o.parentElement!==e||o.tagName?.toLowerCase()!=="script"||o.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:o}catch{return null}}function pe(t,r){if(!r?.startsWith("on:"))return!1;try{let e=fe(t);if(!e)return!1;let o=JSON.parse(e.textContent||"{}");return o.dependencyPinningCacheKey=r,e.textContent=JSON.stringify(o),!0}catch(e){return R.debug("hydration dependency snapshot seed failed",e),!1}}function me(t,r){let e=r==="root"?de:`rsc-slot-${r}`,o=t.getElementById(e);if(o)return o;let s=t.createElement("div");return s.id=e,t.body.appendChild(s),s}function en(t,r){if(r.type!=="slot")return;let e=me(t,r.id);e.innerHTML=ue(String(r.html??""))}function Ee(t,r){let e=r.split(`\n`),o=e.pop()??"";for(let s of e){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){R.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){en(t,c);try{nn(t,c.id||"root")}catch(d){R.debug("[client-dom] hydration optional failed",d)}}}return o}function tn(t){return new Promise((r,e)=>{let o=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){o();return}t.addEventListener("abort",o,{once:!0})})}async function ds(t,r=document,e){let o="body"in t?t:null,s=o?.body??t;if(!s)return;o&&pe(r,o.headers.get(M));let i=s.getReader(),a=new TextDecoder,c="",d=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:f}=e?await Promise.race([u,tn(e)]):await u;if(g){d=!0;break}c+=a.decode(f,{stream:!0}),c=Ee(r,c)}c&&Ee(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){d||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof o?.body?.cancel=="function")try{await o.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function rn(t,r){let e=me(t,r),o=[],s=i=>{let a=i;a.dataset?.clientRef&&o.push(a);for(let c of i.children)s(c)};return s(e),o}function nn(t,r){let e=rn(t,r);for(let o of e){let s=o.dataset?.clientRef;s&&(o.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{ds as consumeNdjsonStream,me as getContainer};\n'; + 'var xe=Object.defineProperty;var _e=(t,r,e)=>r in t?xe(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var E=(t,r,e)=>_e(t,typeof r!="symbol"?r+"":r,e);var he=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Se(t,...r){let e=Object.create(null),o=t.charAt(0).toUpperCase()+t.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${o} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${o} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${o} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(e,i))throw new Error(`Duplicate ${t} slug "${i}"`);e[i]=a}return Object.freeze(e)}function P(...t){for(let r of t)for(let e of Object.values(r)){if(typeof e.slug!="string"||e.slug.length<3||e.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(e.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${e.slug}"`);if(typeof e.category!="string"||!he.has(e.category))throw new TypeError(`Registered error has unknown category "${e.category}"`);if(!Number.isInteger(e.status)||e.status<400||e.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${e.status}`);if(typeof e.title!="string"||e.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(e.suggestion!==void 0&&(typeof e.suggestion!="string"||e.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Se("error registry",...t)}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"},an={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",m=Reflect.apply;var $=RegExp.prototype.exec,y=RegExp.prototype[Symbol.replace],un=String.prototype.charCodeAt,k=String.prototype.slice,Ie=String.prototype.toLowerCase,Oe=/[^a-z0-9]/g;function D(t){let r=m(Ie,t,[]);return m(y,Oe,[r,""])}function O(t,r,e){return e===void 0?m(k,t,[r]):m(k,t,[r,e])}var Te=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Ce=512,Ae=128,S=new Map;function F(t){let r=t.length<=Ae;if(r){let s=S.get(t);if(s!==void 0)return s}let e=D(t),o=Te.some(s=>e.includes(s));if(r){if(S.size>=Ce){let s=S.keys().next().value;s!==void 0&&S.delete(s)}S.set(t,o)}return o}var Ne=["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"],De=new Set(Ne.map(D)),be=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Le=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Ue=3;function we(t){return t===" "||t==="\t"||t===","||t===";"||t==="&"||t==="?"||t==="#"}function ve(t){if(!t)return!1;let r=t.charCodeAt(0);return r>=65&&r<=90||r>=97&&r<=122}function H(t){return ve(t)||t==="_"||t==="$"}function Me(t){if(!t)return!1;let r=t.charCodeAt(0);return H(t)||r>=48&&r<=57||t==="."||t==="-"}function j(t,r){let e=r,o=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!H(t[e]))return!1;for(e++;Me(t[e]);)e++;if(o){if(t[e]!==o)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function z(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||we(t)}function Y(t,r){let e=r;for(;e=t.length||j(t,e)}function Pe(t,r){let e=r,o=!0;if(t.startsWith(p,r)){let g=r+p.length;if(V(t,g))return{end:g,replacement:p};e=g,o=!1}let s=o&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",i=!1,a=()=>s?`${s}${p}${i?s:""}`:p,c=[],d="",u=-1;for(let g=e;g0&&(f==="}"||f==="]")){if(c.at(-1)!==f)return{end:t.length,replacement:a()};if(c.pop(),g++,c.length===0&&V(t,g))return{end:g,replacement:a()};continue}if(c.length>0||!z(f)){g++;continue}let h=g;if(g=Y(t,g),g>=t.length||j(t,g))return{end:h,replacement:a()}}return{end:t.length,replacement:a()}}function G(t,r,e,o){let s=0,i="";for(let a=m($,r,[t]);a;a=m($,r,[t])){let c=a[e];if(!F(c))continue;let d=r.lastIndex,u=o===void 0?void 0:a[o],g=d+p.length;if((u==="?"||u==="&"||u===";")&&t.startsWith(p,d)&&t[g]==="#")continue;let f=Pe(t,d);i+=O(t,s,a.index),i+=a[0],i+=f.replacement,s=f.end,r.lastIndex=f.end}return s===0?t:i+O(t,s)}function $e(t,r,e){let o=e.search(/[ \\t]/);if(o<0)return!1;let s=`${r}:${O(e,0,o)}`,i=t==="//"?`https://${s}`:`${t}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function ke(t){let r=t;for(let e=0;e{let i=s.indexOf(":");if(i===-1)return`${o}${p}@`;let a=O(s,0,i);return`${o}${a}:${p}@`}]);return r=m(y,Le,[r,(e,o,s,i)=>$e(o,s,i)?e:`${o}${s}:${p}@`]),r=m(y,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[r,(e,o,s,i)=>{let a=ke(s);return De.has(D(a))||F(a)?`${o}${s}=${p}`:e}]),r=m(y,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=m(y,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[r,(e,o)=>`${o}${p}`]),r=m(y,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=G(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=G(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Ve=2048;var pn=64*1024,Ge=256,Fe="https://veryfront.com/docs/errors/",B="...[truncated]",L="unknown-error";function W(t,r){if(t.length<=r)return t;let e=Math.max(0,r-B.length);return`${He(t,e)}${B}`}function He(t,r){let e=t.slice(0,r),o=e.charCodeAt(e.length-1);return o>=55296&&o<=56319&&(e=e.slice(0,-1)),e}function je(t){let r="";for(let e=0;e=55296&&o<=56319){let s=t.charCodeAt(e+1);s>=56320&&s<=57343?(r+=t.slice(e,e+2),e++):r+="\\uFFFD";continue}r+=o>=56320&&o<=57343?"\\uFFFD":t.charAt(e)}return r}function x(t){return typeof t!="string"?p:W(b(t),Ve)}function ze(t){let r=typeof t=="string"?b(t):L,e=W(r||L,Ge),o=je(e);return o==="."||o===".."?L:o}function T(t){let r=encodeURIComponent(ze(t));return`${Fe}${r}`}var Ye=Object.freeze,Be=Object.getOwnPropertyDescriptors,K=Number.isFinite,X=new WeakSet,We=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function n(t){let r={...t},e={...r,create(o){let s=o?.message,i=o?.detail,a=o?.cause,c=o?.instance,d=o?.context,u=o?.status??r.status;return new U(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:d})}};return Ye(e)}var U=class extends Error{constructor(e,o){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");X.add(this),this.name="VeryfrontError",this.slug=o.slug,this.category=o.category,this.status=o.status,this.title=o.title,this.suggestion=o.suggestion,this.exitCode=o.exitCode,this.detail=o.detail,this.cause=o.cause,this.instance=o.instance,this.context=o.context}toRFC9457(){let e=q(this);return e?{type:T(e.slug),title:x(e.title),status:e.status,detail:e.detail===void 0?void 0:x(e.detail),instance:e.instance===void 0?void 0:x(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:x(e.suggestion),cause:typeof e.cause=="string"?x(e.cause):void 0}:{type:T("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=q(this);return T(e?.slug??"unknown-error")}};function J(t){return typeof t=="object"&&t!==null&&X.has(t)}function q(t){return J(t)?Ke(t):null}function Ke(t){try{if(!J(t))return null;let r=Be(t),e=ye=>{let N=r[ye];return N&&"value"in N?N.value:void 0},o=e("slug"),s=e("category"),i=e("status"),a=e("title"),c=e("message"),d=e("suggestion"),u=e("exitCode"),g=e("detail"),f=e("cause"),h=e("instance"),Re=e("context"),A=e("stack");return typeof o!="string"||!We.has(s)||typeof i!="number"||!K(i)||typeof a!="string"||typeof c!="string"||d!==void 0&&typeof d!="string"||u!==void 0&&(typeof u!="number"||!K(u))||g!==void 0&&typeof g!="string"||h!==void 0&&typeof h!="string"||A!==void 0&&typeof A!="string"?null:{slug:o,category:s,status:i,title:a,message:c,suggestion:d,exitCode:u,detail:g,cause:f,instance:h,context:Re,stack:A}}catch{return null}}var qe=n({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Xe=n({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Je=n({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Ze=n({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Qe=n({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),et=n({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),tt=n({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),rt=n({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),nt=n({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),ot=n({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),st=n({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Z={"config-not-found":qe,"config-invalid":Xe,"config-parse-error":Je,"config-validation-error":Ze,"config-type-error":Qe,"import-map-invalid":et,"cors-config-invalid":tt,"config-validation-failed":rt,"webhook-config-invalid":nt,"schedule-config-invalid":ot,"trigger-config-invalid":st};var it=n({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),at=n({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),ct=n({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),ut=n({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),lt=n({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),gt=n({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),dt=n({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ft=n({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Q={"build-failed":it,"bundle-error":at,"typescript-error":ct,"mdx-compile-error":ut,"asset-optimization-error":lt,"ssg-generation-error":gt,"sourcemap-error":dt,"compilation-error":ft};var pt=n({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Et=n({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),mt=n({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Rt=n({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yt=n({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),xt=n({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),_t=n({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),ht=n({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),St=n({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),It=n({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ee={"hydration-mismatch":pt,"render-error":Et,"component-error":mt,"layout-not-found":Rt,"page-not-found":yt,"api-error":xt,"middleware-error":_t,"trigger-target-not-found":ht,"trigger-execution-failed":St,"trigger-not-supported":It};var Ot=n({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Tt=n({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Ct=n({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),At=n({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Nt=n({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Dt=n({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),te={"route-conflict":Ot,"invalid-route-file":Tt,"route-handler-invalid":Ct,"dynamic-route-error":At,"route-params-error":Nt,"api-route-error":Dt};var bt=n({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Lt=n({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Ut=n({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),wt=n({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),vt=n({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Mt=n({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),re={"module-not-found":bt,"import-resolution-error":Lt,"circular-dependency":Ut,"invalid-import":wt,"dependency-missing":vt,"version-mismatch":Mt};var Pt=n({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),$t=n({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),kt=n({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Vt=n({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Gt=n({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Ft=n({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Ht=n({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),jt=n({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),zt=n({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Yt=n({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bt=n({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Wt=n({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Kt=n({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),qt=n({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Xt=n({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Jt=n({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),ne={"port-in-use":Pt,"server-start-error":$t,"cache-error":kt,"file-watch-error":Vt,"request-error":Gt,"service-overloaded":Ft,"project-execution-unavailable":Ht,"semaphore-timeout":jt,"circuit-breaker-open":zt,"cache-path-mismatch":Yt,"network-error":Bt,"api-client-error":Wt,"token-storage-error":Kt,"cache-invariant-violation":qt,"release-not-found":Xt,"fallback-exhausted":Jt};var Zt=n({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),Qt=n({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),er=n({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),tr=n({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),rr=n({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),nr=n({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),or=n({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),oe={"client-boundary-violation":Zt,"server-only-in-client":Qt,"client-only-in-server":er,"invalid-use-client":tr,"invalid-use-server":rr,"rsc-payload-error":nr,"ssr-output-limit-exceeded":or};var sr=n({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),ir=n({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),ar=n({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),cr=n({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),ur=n({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),se={"hmr-error":sr,"dev-server-error":ir,"fast-refresh-error":ar,"error-overlay-error":cr,"source-map-error":ur};var lr=n({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),gr=n({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),dr=n({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),fr=n({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),pr=n({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Er=n({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),mr=n({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Rr=n({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),yr=n({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),xr=n({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),_r=n({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),hr=n({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),ie={"deployment-error":lr,"platform-error":gr,"env-var-missing":dr,"production-build-required":fr,"environment-not-found":pr,"release-missing-version":Er,"release-build-timeout":mr,"deployment-verification-timeout":Rr,"push-receipt-missing":yr,"source-digest-mismatch":xr,"preview-hostname-too-long":_r,"branch-not-found":hr};var Sr=n({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Ir=n({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Or=n({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Tr=n({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Cr=n({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Ar=n({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Nr=n({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),ae={"agent-error":Sr,"agent-not-found":Ir,"agent-timeout":Or,"agent-intent-error":Tr,"orchestration-error":Cr,"cost-limit-exceeded":Ar,"tool-id-conflict":Nr};var Dr=n({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),br=n({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Lr=n({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Ur=n({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),wr=n({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),vr=n({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Mr=n({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Pr=n({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),$r=n({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),w=n({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),kr=n({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Vr=n({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ce={"unknown-error":Dr,"authentication-required":br,"permission-denied":Lr,"file-not-found":Ur,"resource-not-found":wr,"invalid-argument":vr,"timeout-error":Mr,"initialization-error":Pr,"not-supported":$r,"security-violation":w,"input-validation-failed":kr,"project-source-empty":Vr};var ro=P(Z,Q,ee,te,re,ne,oe,se,ie,ae,ce);var Gr=[{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 Fr(){return Gr.map(({source:t,flags:r,name:e})=>({pattern:new RegExp(t,r),name:e}))}function Hr(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function ue(t,r={}){let{allowInlineScripts:e=!1,strict:o=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of Fr())if(!(e&&a==="inline script")&&(i.lastIndex=0,!!i.test(t)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),o||!Hr())))throw w.create({detail:`Potentially unsafe HTML: ${a} detected`});return t}var _=class{constructor(r,e){E(this,"prefix",r);E(this,"level",e)}log(r,e,o,...s){this.level>r||e?.(o,...s)}debug(r,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...e)}info(r,...e){this.log(1,console.log,`[${this.prefix}] ${r}`,...e)}warn(r,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...e)}error(r,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...e)}};function jr(){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 C=jr(),R=new _("RSC",C),yo=new _("PREFETCH",C),xo=new _("HYDRATE",C),_o=new _("VERYFRONT",C);var Io=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var zr=5e3,Yr=1e4,Co=16*1024*1024,Br=5e3;var Wr=100;var Kr=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),Ao=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),No=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:zr,api:3e4,ssr:Yr,hmr:3e4,sandbox:Br}),cache:Object.freeze({jit:Object.freeze({maxSize:Wr,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Kr})});var l="/_veryfront",v={RSC:`${l}/rsc/`,FS:`${l}/fs/`,MODULES:`${l}/modules/`,PAGES:`${l}/pages/`,DATA:`${l}/data/`,LIB:`${l}/lib/`,CHUNKS:`${l}/chunks/`,CLIENT:`${l}/client/`},ge={HMR_RUNTIME:`${l}/hmr-runtime.js`,HMR:`${l}/hmr.js`,ERROR_OVERLAY:`${l}/error-overlay.js`,DEV_LOADER:`${l}/dev-loader.js`,CLIENT_LOG:`${l}/log`,CLIENT_JS:`${l}/client.js`,ROUTER_JS:`${l}/router.js`,PREFETCH_JS:`${l}/prefetch.js`,MANIFEST_JSON:`${l}/manifest.json`,APP_JS:`${l}/app.js`,RSC_CLIENT:`${l}/rsc/client.js`,RSC_MANIFEST:`${l}/rsc/manifest`,RSC_STREAM:`${l}/rsc/stream`,RSC_PAYLOAD:`${l}/rsc/payload`,RSC_RENDER:`${l}/rsc/render`,RSC_PAGE:`${l}/rsc/page`,RSC_MODULE:`${l}/rsc/module`,RSC_DOM:`${l}/rsc/dom.js`,LIB_CHAT_REACT:`${l}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${l}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${l}/lib/chat/primitives.js`};var qr={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},bo=qr.CACHE;var Lo={HMR_RUNTIME:ge.HMR_RUNTIME,ERROR_OVERLAY:ge.ERROR_OVERLAY};var Xr=v.RSC,Jr=v.FS;var de="rsc-root",M="x-veryfront-dependency-pins";var Po=Array.prototype.join,$o=Array.prototype.push;var Ko=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Qr="veryfront-hydration-data";function fe(t){try{let r=[...t.querySelectorAll(`[id="${Qr}"]`)];if(r.length!==1)return null;let e=t.body;if(!e)return null;let o=r[0];return e.firstElementChild!==o&&o.parentElement!==e||o.tagName?.toLowerCase()!=="script"||o.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:o}catch{return null}}function pe(t,r){if(!r?.startsWith("on:"))return!1;try{let e=fe(t);if(!e)return!1;let o=JSON.parse(e.textContent||"{}");return o.dependencyPinningCacheKey=r,e.textContent=JSON.stringify(o),!0}catch(e){return R.debug("hydration dependency snapshot seed failed",e),!1}}function me(t,r){let e=r==="root"?de:`rsc-slot-${r}`,o=t.getElementById(e);if(o)return o;let s=t.createElement("div");return s.id=e,t.body.appendChild(s),s}function en(t,r){if(r.type!=="slot")return;let e=me(t,r.id);e.innerHTML=ue(String(r.html??""))}function Ee(t,r){let e=r.split(`\n`),o=e.pop()??"";for(let s of e){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){R.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){en(t,c);try{nn(t,c.id||"root")}catch(d){R.debug("[client-dom] hydration optional failed",d)}}}return o}function tn(t){return new Promise((r,e)=>{let o=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){o();return}t.addEventListener("abort",o,{once:!0})})}async function ps(t,r=document,e){let o="body"in t?t:null,s=o?.body??t;if(!s)return;o&&pe(r,o.headers.get(M));let i=s.getReader(),a=new TextDecoder,c="",d=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:f}=e?await Promise.race([u,tn(e)]):await u;if(g){d=!0;break}c+=a.decode(f,{stream:!0}),c=Ee(r,c)}c&&Ee(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){d||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof o?.body?.cancel=="function")try{await o.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function rn(t,r){let e=me(t,r),o=[],s=i=>{let a=i;a.dataset?.clientRef&&o.push(a);for(let c of i.children)s(c)};return s(e),o}function nn(t,r){let e=rn(t,r);for(let o of e){let s=o.dataset?.clientRef;s&&(o.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{ps as consumeNdjsonStream,me as getContainer};\n'; From 0247bd47c001b435d8aa70cf8712666193aca613 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 12:46:05 +0200 Subject: [PATCH 07/34] fix(modules): complete import-map isolation hardening --- src/cache/config-hash.ts | 148 +++------ .../loader-primordial-poisoning.worker.ts | 85 +++++ src/modules/import-map/loader.test.ts | 29 ++ src/modules/import-map/loader.ts | 296 +++++++++++------- src/modules/import-map/merger.ts | 48 ++- .../preloader-primordial-poisoning.worker.ts | 12 +- src/modules/import-map/preloader.test.ts | 114 ++++++- src/modules/import-map/preloader.ts | 123 +++++--- .../layouts/utils/component-loader.ts | 5 +- src/rendering/orchestrator/layout.ts | 4 + src/transforms/esm/http-cache-helpers.test.ts | 19 ++ src/transforms/esm/http-cache-helpers.ts | 29 +- 12 files changed, 618 insertions(+), 294 deletions(-) create mode 100644 src/modules/import-map/loader-primordial-poisoning.worker.ts diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 024e31d9a6..138ca9abfc 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -14,15 +14,8 @@ import { } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts"; -const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; -const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; -const ReflectApply = Reflect.apply; - -function hasOwn(object: object, key: PropertyKey): boolean { - return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; -} +const ObjectCreate = Object.create; /** * Configuration that affects transform output. @@ -48,104 +41,34 @@ interface TransformConfig { dependencyPinningCacheKey?: string; } -function readOwnConfigField( - config: TransformConfig, - key: keyof TransformConfig, -): unknown { - if (config === null || typeof config !== "object") { - throw new IntrinsicTypeError("Transform config must be an object"); - } - const descriptor = ObjectGetOwnPropertyDescriptor(config, key); - if (!descriptor) return undefined; - if (!hasOwn(descriptor, "value")) { - throw new IntrinsicTypeError(`Transform config ${key} must be an own data property`); - } - return descriptor.value; -} - -function readOptionalConfigString( - config: TransformConfig, - key: keyof TransformConfig, -): string | undefined { - const value = readOwnConfigField(config, key); - if (value === undefined) return undefined; - if (typeof value !== "string") { - throw new IntrinsicTypeError(`Transform config ${key} must be a string`); - } - return value; -} - -function readOptionalConfigBoolean( - config: TransformConfig, - key: "studioEmbed" | "dev", -): boolean | undefined { - const value = readOwnConfigField(config, key); - if (value === undefined) return undefined; - if (typeof value !== "boolean") { - throw new IntrinsicTypeError(`Transform config ${key} must be a boolean`); - } - return value; -} - -function encodeNullableConfigString(value: string | null): string { - return JSONStringify(value) as string; -} - -function buildConfigIdentity(config: TransformConfig): string { - const dependencyPinningCacheKey = readOptionalConfigString( - config, - "dependencyPinningCacheKey", - ); - const moduleServerOrigin = readOptionalConfigString(config, "moduleServerOrigin"); - const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( - dependencyPinningCacheKey, - moduleServerOrigin, - ); - let identity = `v${VERSION}`; - identity += `|react=${ - encodeNullableConfigString( - readOptionalConfigString(config, "reactVersion") ?? DEFAULT_REACT_VERSION, - ) - }`; - identity += `|jsx=${ - encodeNullableConfigString( - readOptionalConfigString(config, "jsxImportSource") ?? "react", - ) - }`; - identity += `|modules=${ - encodeNullableConfigString( - readOptionalConfigString(config, "moduleServerUrl") ?? null, - ) - }`; - identity += `|vendor=${ - encodeNullableConfigString( - readOptionalConfigString(config, "vendorBundleHash") ?? null, - ) - }`; - identity += `|api=${ - encodeNullableConfigString( - readOptionalConfigString(config, "apiBaseUrl") ?? null, - ) - }`; - identity += `|studio=${readOptionalConfigBoolean(config, "studioEmbed") ?? false ? "1;" : "0;"}`; - identity += `|dev=${readOptionalConfigBoolean(config, "dev") ?? false ? "1;" : "0;"}`; - identity += `|pins=${ - encodeNullableConfigString( - dependencyPinningCacheVariant ?? null, - ) - }`; - identity += `|csstype=${encodeNullableConfigString(CSSTYPE_VERSION)}`; - identity += `|tailwind=${encodeNullableConfigString(TAILWIND_VERSION)}`; - return identity; -} - /** * Compute a hash of transform-affecting configuration. * * Changes to these values should invalidate cached transforms. */ export function computeConfigHash(config: TransformConfig): Promise { - return computeHash(buildConfigIdentity(config)); + const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( + config.dependencyPinningCacheKey, + config.moduleServerOrigin, + ); + // Null-prototype storage preserves the existing JSON cache-key format while + // preventing project code from injecting an inherited toJSON hook. + const normalized = ObjectCreate(null) as Record; + normalized.transformVersion = VERSION; + normalized.reactVersion = config.reactVersion ?? DEFAULT_REACT_VERSION; + normalized.jsxImportSource = config.jsxImportSource ?? "react"; + normalized.moduleServerUrl = config.moduleServerUrl ?? null; + normalized.vendorBundleHash = config.vendorBundleHash ?? null; + normalized.apiBaseUrl = config.apiBaseUrl ?? null; + normalized.studioEmbed = config.studioEmbed ?? false; + normalized.dev = config.dev ?? false; + if (dependencyPinningCacheVariant) { + normalized.dependencyPinningCacheVariant = dependencyPinningCacheVariant; + } + normalized.csstype = CSSTYPE_VERSION; + normalized.tailwind = TAILWIND_VERSION; + + return computeHash(JSONStringify(normalized)); } /** @@ -154,5 +77,28 @@ export function computeConfigHash(config: TransformConfig): Promise { * Use this when you need a config hash but can't afford async overhead. */ export function computeConfigHashSync(config: TransformConfig): string { - return buildConfigIdentity(config); + const parts = [ + `v${VERSION}`, + config.reactVersion ?? DEFAULT_REACT_VERSION, + config.jsxImportSource ?? "react", + encodeConfigPart("modules", config.moduleServerUrl), + encodeConfigPart("vendor", config.vendorBundleHash), + encodeConfigPart("api", config.apiBaseUrl), + config.studioEmbed ? "studio" : "", + config.dev ? "dev" : "", + ].filter(Boolean); + const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( + config.dependencyPinningCacheKey, + config.moduleServerOrigin, + ); + if (dependencyPinningCacheVariant) { + parts.push(`pins:${dependencyPinningCacheVariant}`); + } + + return parts.join(":"); +} + +function encodeConfigPart(label: string, value: string | undefined): string { + if (!value) return ""; + return `${label}:${value.length}:${value}`; } diff --git a/src/modules/import-map/loader-primordial-poisoning.worker.ts b/src/modules/import-map/loader-primordial-poisoning.worker.ts new file mode 100644 index 0000000000..bee816176a --- /dev/null +++ b/src/modules/import-map/loader-primordial-poisoning.worker.ts @@ -0,0 +1,85 @@ +import type { VeryfrontConfig } from "#veryfront/config"; +import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import { loadImportMap } from "./loader.ts"; + +const denoJson = JSON.stringify({ + imports: { + "deno-only": "https://example.com/deno.ts", + package: "https://example.com/deno-package.ts", + }, +}); +const adapter = { + fs: { + getAdapterType: () => "VeryfrontFSAdapter", + getUnderlyingAdapter: () => ({}), + isVeryfrontAdapter: () => true, + isMultiProjectMode: () => false, + readFile: () => denoJson, + }, + env: { get: () => undefined }, +} as unknown as RuntimeAdapter; +const config = { + resolve: { + importMap: { + imports: { package: "npm:package@1.0.0" }, + }, + }, +} as VeryfrontConfig; + +async function runRegression() { + const original = { + arrayFilter: Array.prototype.filter, + arrayMap: Array.prototype.map, + jsonParse: JSON.parse, + objectAssign: Object.assign, + objectEntries: Object.entries, + objectFromEntries: Object.fromEntries, + stringIndexOf: String.prototype.indexOf, + stringSlice: String.prototype.slice, + stringSplit: String.prototype.split, + stringStartsWith: String.prototype.startsWith, + }; + const poisoned = () => { + throw new Error("poisoned primordial"); + }; + let loaded: Awaited> | undefined; + try { + Reflect.set(Array.prototype, "filter", poisoned); + Reflect.set(Array.prototype, "map", poisoned); + Reflect.set(JSON, "parse", poisoned); + Reflect.set(Object, "assign", poisoned); + Reflect.set(Object, "entries", poisoned); + Reflect.set(Object, "fromEntries", poisoned); + Reflect.set(String.prototype, "indexOf", poisoned); + Reflect.set(String.prototype, "slice", poisoned); + Reflect.set(String.prototype, "split", poisoned); + Reflect.set(String.prototype, "startsWith", poisoned); + loaded = await loadImportMap("/project", adapter, config); + } finally { + Reflect.set(Array.prototype, "filter", original.arrayFilter); + Reflect.set(Array.prototype, "map", original.arrayMap); + Reflect.set(JSON, "parse", original.jsonParse); + Reflect.set(Object, "assign", original.objectAssign); + Reflect.set(Object, "entries", original.objectEntries); + Reflect.set(Object, "fromEntries", original.objectFromEntries); + Reflect.set(String.prototype, "indexOf", original.stringIndexOf); + Reflect.set(String.prototype, "slice", original.stringSlice); + Reflect.set(String.prototype, "split", original.stringSplit); + Reflect.set(String.prototype, "startsWith", original.stringStartsWith); + } + + return { + denoOnly: loaded?.imports?.["deno-only"], + package: loaded?.imports?.package, + react: loaded?.imports?.react, + }; +} + +try { + postMessage({ ok: true, result: await runRegression() }); +} catch (error) { + postMessage({ + ok: false, + error: error instanceof Error ? (error.stack ?? error.message) : String(error), + }); +} diff --git a/src/modules/import-map/loader.test.ts b/src/modules/import-map/loader.test.ts index 0b1fbdb619..15dae9679d 100644 --- a/src/modules/import-map/loader.test.ts +++ b/src/modules/import-map/loader.test.ts @@ -184,5 +184,34 @@ describe("modules/import-map/loader", () => { assert(!("relative" in appScope), "relative path in scope should be filtered"); assert("absolute" in appScope, "absolute path in scope should be kept"); }); + + it("keeps dependency resolution deterministic after primordial poisoning", async () => { + const worker = new Worker( + new URL("./loader-primordial-poisoning.worker.ts", import.meta.url), + { type: "module" }, + ); + try { + const result = await new Promise<{ + denoOnly: string | undefined; + package: string | undefined; + react: string | undefined; + }>((resolve, reject) => { + worker.onmessage = (event) => { + const message = event.data as + | { ok: true; result: Parameters[0] } + | { ok: false; error: string }; + if (message.ok) resolve(message.result); + else reject(new Error(message.error)); + }; + worker.onerror = (event) => reject(event.error ?? new Error(event.message)); + }); + + assertEquals(result.denoOnly, "https://example.com/deno.ts"); + assertEquals(result.package, "https://esm.sh/package@1.0.0?target=es2022"); + assert(result.react?.includes("esm.sh")); + } finally { + worker.terminate(); + } + }); }); }); diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index e42465207a..39c12d2346 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -1,148 +1,226 @@ -import { rendererLogger as logger } from "#veryfront/utils"; -import { dirname, join } from "#veryfront/compat/path/index.ts"; +import { getConfig, type VeryfrontConfig } from "#veryfront/config"; +import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { isVirtualFilesystem } from "#veryfront/platform/adapters/fs/wrapper.ts"; -import { getConfig, type VeryfrontConfig } from "#veryfront/config"; -import type { ImportMapConfig } from "./types.ts"; +import { snapshotImportMap } from "#veryfront/transforms/pipeline/cache-identity.ts"; +import { getReactImportMap } from "#veryfront/transforms/esm/react-cdn.ts"; +import { rendererLogger as logger } from "#veryfront/utils"; +import { dirname, join } from "#veryfront/compat/path/index.ts"; import { getDefaultImportMap } from "./default-import-map.ts"; import { mergeImportMaps } from "./merger.ts"; -import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; -import { getReactImportMap } from "#veryfront/transforms/esm/react-cdn.ts"; +import type { ImportMapConfig } from "./types.ts"; -function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConfig { - const normalizeValue = (value: string): string => { - if (!value.startsWith("npm:")) return value; +// A hosted project can execute in this realm before a later request loads its +// import map. Capture the primitives and framework-owned maps used to select +// executable modules so replacing shared globals cannot redirect resolution. +const JSONParse = JSON.parse; +const ArrayIsArray = Array.isArray; +const IntrinsicTypeError = TypeError; +const ObjectCreate = Object.create; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototype = Object.prototype; +const ObjectGetPrototypeOf = Object.getPrototypeOf; +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; +const StringPrototypeIndexOf = String.prototype.indexOf; +const StringPrototypeSlice = String.prototype.slice; +const StringPrototypeStartsWith = String.prototype.startsWith; - // Convert npm: specifiers to esm.sh URLs (should not happen with new code) - const spec = value.slice(4); - const [base, query] = spec.split("?"); - const url = `https://esm.sh/${base}`; +const DEFAULT_IMPORT_MAP = snapshotImportMap(getDefaultImportMap()); +const REACT_IMPORTS = snapshotImportMap({ imports: getReactImportMap() }).imports!; - return query ? `${url}?${query}` : `${url}?target=es2022`; - }; +function stringStartsWith(value: string, prefix: string): boolean { + return ReflectApply(StringPrototypeStartsWith, value, [prefix]) as boolean; +} + +function stringSlice(value: string, start: number, end?: number): string { + return ReflectApply( + StringPrototypeSlice, + value, + end === undefined ? [start] : [start, end], + ) as string; +} + +function readOwnDataProperty( + value: object, + key: PropertyKey, + label: string, +): unknown { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (!descriptor) return undefined; + if (!("value" in descriptor)) { + throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + } + return descriptor.value; +} + +function assertPlainObject(value: unknown, label: string): asserts value is object { + if (value === null || typeof value !== "object" || ArrayIsArray(value)) { + throw new IntrinsicTypeError(`${label} must be a plain object`); + } + const prototype = ObjectGetPrototypeOf(value); + if (prototype !== ObjectPrototype && prototype !== null) { + throw new IntrinsicTypeError(`${label} must be a plain object`); + } +} + +function readEmbeddedImportMap( + container: unknown, + label: string, +): ImportMapConfig | null { + assertPlainObject(container, label); + const imports = readOwnDataProperty(container, "imports", label); + const scopes = readOwnDataProperty(container, "scopes", label); + if (imports === undefined && scopes === undefined) return null; + return snapshotImportMap({ + imports: imports ?? ObjectCreate(null), + scopes: scopes ?? ObjectCreate(null), + }); +} + +function copyFilteredRecord( + record: Readonly>, + normalizeNpm: boolean, +): Record { + const result = ObjectCreate(null) as Record; + const keys = ReflectOwnKeys(record); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (typeof key !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(record, key); + if (!descriptor?.enumerable || !("value" in descriptor)) continue; + const value = descriptor.value as string; + if (stringStartsWith(value, "./") || stringStartsWith(value, "../")) continue; + result[key] = normalizeNpm ? normalizeImportValue(value) : value; + } + return result; +} - let imports = importMap.imports - ? Object.fromEntries(Object.entries(importMap.imports).map(([k, v]) => [k, normalizeValue(v)])) - : undefined; - - const scopes = importMap.scopes - ? Object.fromEntries( - Object.entries(importMap.scopes).map(([scope, mappings]) => [ - scope, - Object.fromEntries(Object.entries(mappings).map(([k, v]) => [k, normalizeValue(v)])), - ]), - ) - : undefined; - - // Override React mappings AFTER all other processing to ensure single instance. - // Remove any "react/" prefix match since we have explicit mappings. - if (imports) { - const veryfrontSsrMap = Object.fromEntries( - Object.entries(getDefaultImportMap().imports ?? {}).filter(([key]) => - key.startsWith("veryfront/") - ), +function filterRelativePaths(importMap: ImportMapConfig): ImportMapConfig { + const exact = snapshotImportMap(importMap); + const imports = copyFilteredRecord(exact.imports ?? ObjectCreate(null), false); + const scopes = ObjectCreate(null) as Record>; + const exactScopes = exact.scopes ?? ObjectCreate(null); + const scopeKeys = ReflectOwnKeys(exactScopes); + for (let index = 0; index < scopeKeys.length; index++) { + const scope = scopeKeys[index]; + if (typeof scope !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(exactScopes, scope); + if (!descriptor?.enumerable || !("value" in descriptor)) continue; + scopes[scope] = copyFilteredRecord( + descriptor.value as Readonly>, + false, + ); + } + return snapshotImportMap({ imports, scopes }); +} + +function normalizeImportValue(value: string): string { + if (!stringStartsWith(value, "npm:")) return value; + const specifier = stringSlice(value, 4); + const queryIndex = ReflectApply(StringPrototypeIndexOf, specifier, ["?"]) as number; + const base = queryIndex < 0 ? specifier : stringSlice(specifier, 0, queryIndex); + const query = queryIndex < 0 ? "" : stringSlice(specifier, queryIndex + 1); + const url = `https://esm.sh/${base}`; + return query ? `${url}?${query}` : `${url}?target=es2022`; +} + +function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConfig { + const exact = snapshotImportMap(importMap); + const imports = copyFilteredRecord(exact.imports ?? ObjectCreate(null), true); + const scopes = ObjectCreate(null) as Record>; + const exactScopes = exact.scopes ?? ObjectCreate(null); + const scopeKeys = ReflectOwnKeys(exactScopes); + for (let index = 0; index < scopeKeys.length; index++) { + const scope = scopeKeys[index]; + if (typeof scope !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(exactScopes, scope); + if (!descriptor?.enumerable || !("value" in descriptor)) continue; + scopes[scope] = copyFilteredRecord( + descriptor.value as Readonly>, + true, ); - const reactMap = getReactImportMap(); - delete imports["react/"]; - imports = { ...imports, ...veryfrontSsrMap, ...reactMap }; } - return { imports, scopes }; + // Framework and React mappings are authoritative, guaranteeing one React + // instance and preventing a project npm override from redirecting core code. + delete imports["react/"]; + const defaultImports = DEFAULT_IMPORT_MAP.imports ?? ObjectCreate(null); + const defaultKeys = ReflectOwnKeys(defaultImports); + for (let index = 0; index < defaultKeys.length; index++) { + const key = defaultKeys[index]; + if (typeof key !== "string" || !stringStartsWith(key, "veryfront/")) continue; + const descriptor = ObjectGetOwnPropertyDescriptor(defaultImports, key); + if (descriptor?.enumerable && "value" in descriptor) { + imports[key] = descriptor.value as string; + } + } + const reactKeys = ReflectOwnKeys(REACT_IMPORTS); + for (let index = 0; index < reactKeys.length; index++) { + const key = reactKeys[index]; + if (typeof key !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(REACT_IMPORTS, key); + if (descriptor?.enumerable && "value" in descriptor) { + imports[key] = descriptor.value as string; + } + } + return snapshotImportMap({ imports, scopes }); } async function getRuntimeAdapter(adapter?: RuntimeAdapter): Promise { if (adapter) return adapter; - const { runtime } = await import("#veryfront/platform/adapters/detect.ts"); return runtime.get(); } -/** - * Filter out relative paths from import map entries. - * - * Relative paths (./foo, ../bar) in deno.json are for Deno's native module resolution. - * They can't work in the browser/SSR context where we serve modules via /_vf_modules/. - * The default import map has correct absolute paths like /_vf_modules/_veryfront/... - */ -function filterRelativePaths(imports: Record): Record { - return Object.fromEntries( - Object.entries(imports).filter(([, value]) => - !value.startsWith("./") && !value.startsWith("../") - ), - ); -} - async function loadDenoJsonImportMap( startPath: string, adapter: RuntimeAdapter, ): Promise { - // For virtual filesystems (API-backed), only check project root - // Virtual filesystems use relative paths, not absolute local paths + const readMap = async (path: string): Promise => { + const content = await adapter.fs.readFile(path); + const parsed = ReflectApply(JSONParse, JSON, [content]) as unknown; + const map = readEmbeddedImportMap(parsed, "deno.json"); + return map ? filterRelativePaths(map) : null; + }; + if (isVirtualFilesystem(adapter.fs)) { try { - const content = await adapter.fs.readFile("deno.json"); - const config = JSON.parse(content); - - if (config.imports || config.scopes) { - logger.debug("Loaded import map from deno.json (virtual filesystem)"); - const imports = config.imports ? filterRelativePaths(config.imports) : {}; - const scopes = config.scopes - ? Object.fromEntries( - Object.entries(config.scopes as Record>).map( - ([scope, mappings]) => [scope, filterRelativePaths(mappings)], - ), - ) - : {}; - return { imports, scopes }; - } + const map = await readMap("deno.json"); + if (map) logger.debug("Loaded import map from deno.json (virtual filesystem)"); + return map; } catch (_) { - /* expected: deno.json not found in virtual filesystem */ + return null; } - return null; } - // For local filesystems, walk up directory tree let currentPath = startPath; - while (currentPath !== "/" && currentPath !== "") { const denoJsonPath = join(currentPath, "deno.json"); - try { - const content = await adapter.fs.readFile(denoJsonPath); - const config = JSON.parse(content); - - if (config.imports || config.scopes) { + const map = await readMap(denoJsonPath); + if (map) { logger.debug(`Loaded import map from ${denoJsonPath}`); - const imports = config.imports ? filterRelativePaths(config.imports) : {}; - const scopes = config.scopes - ? Object.fromEntries( - Object.entries(config.scopes as Record>).map( - ([scope, mappings]) => [scope, filterRelativePaths(mappings)], - ), - ) - : {}; - return { imports, scopes }; + return map; } } catch (_) { - /* expected: deno.json not found in this directory, continue searching */ + // A missing or invalid deno.json does not override framework defaults. } - const parent = dirname(currentPath); if (parent === currentPath) break; currentPath = parent; } - return null; } function getConfigImportMap(config: VeryfrontConfig): ImportMapConfig | null { - const importMap = config.resolve?.importMap; - if (!importMap || typeof importMap !== "object") return null; - - return { - imports: importMap.imports ?? {}, - scopes: importMap.scopes ?? {}, - }; + assertPlainObject(config, "Veryfront config"); + const resolve = readOwnDataProperty(config, "resolve", "Veryfront config"); + if (resolve === undefined) return null; + assertPlainObject(resolve, "Veryfront config resolve"); + const importMap = readOwnDataProperty(resolve, "importMap", "Veryfront config resolve"); + if (importMap === undefined || importMap === null) return null; + return snapshotImportMap(importMap); } export function loadImportMap( @@ -154,34 +232,24 @@ export function loadImportMap( "modules.importMap.load", async () => { const runtimeAdapter = await getRuntimeAdapter(adapter); - - // First, load import map from deno.json (if exists) const denoJsonMap = await loadDenoJsonImportMap(startPath, runtimeAdapter); - - // Then, try to get config's import map. A config already validated for - // the authenticated request takes precedence over re-reading it from the - // project source. let configMap: ImportMapConfig | null = null; if (config) { configMap = getConfigImportMap(config); } else { try { - const cfg = await getConfig(startPath, runtimeAdapter); - if (cfg) configMap = getConfigImportMap(cfg); + const loadedConfig = await getConfig(startPath, runtimeAdapter); + if (loadedConfig) configMap = getConfigImportMap(loadedConfig); } catch (_) { - /* expected: config not found or invalid, continue without it */ + // A missing or invalid optional config does not override safe defaults. } } - // Merge: defaults < deno.json < config - // If both deno.json and config have import maps, config takes precedence for overlapping keys - // but deno.json's unique keys (especially scopes) are preserved const merged = mergeImportMaps( - getDefaultImportMap(), + DEFAULT_IMPORT_MAP, denoJsonMap ?? { imports: {}, scopes: {} }, configMap ?? { imports: {}, scopes: {} }, ); - return normalizeImportMapForRuntime(merged); }, { "importMap.startPath": startPath }, diff --git a/src/modules/import-map/merger.ts b/src/modules/import-map/merger.ts index 6c1bfaa60e..2d64a50d5b 100644 --- a/src/modules/import-map/merger.ts +++ b/src/modules/import-map/merger.ts @@ -1,19 +1,47 @@ +import { snapshotImportMap } from "#veryfront/transforms/pipeline/cache-identity.ts"; import type { ImportMapConfig } from "./types.ts"; -export function mergeImportMaps(...maps: ImportMapConfig[]): ImportMapConfig { - const imports: Record = {}; - const scopes: Record> = {}; +// Import maps can be merged after project code has executed in this realm. +// Capture the small set of primitives used here and only read validated, +// descriptor-snapshotted records. +const ObjectCreate = Object.create; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ReflectOwnKeys = Reflect.ownKeys; + +function copyStringRecord( + target: Record, + source: Readonly>, +): void { + const keys = ReflectOwnKeys(source); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (typeof key !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(source, key); + if (descriptor?.enumerable && "value" in descriptor) { + target[key] = descriptor.value as string; + } + } +} - for (const map of maps) { - if (map.imports) Object.assign(imports, map.imports); +export function mergeImportMaps(...maps: ImportMapConfig[]): ImportMapConfig { + const imports = ObjectCreate(null) as Record; + const scopes = ObjectCreate(null) as Record>; - if (!map.scopes) continue; + for (let index = 0; index < maps.length; index++) { + const map = snapshotImportMap(maps[index]); + copyStringRecord(imports, map.imports ?? ObjectCreate(null)); - for (const [scope, scopeImports] of Object.entries(map.scopes)) { - scopes[scope] ??= {}; - Object.assign(scopes[scope], scopeImports); + const mapScopes = map.scopes ?? ObjectCreate(null); + const scopeKeys = ReflectOwnKeys(mapScopes); + for (let scopeIndex = 0; scopeIndex < scopeKeys.length; scopeIndex++) { + const scope = scopeKeys[scopeIndex]; + if (typeof scope !== "string") continue; + const descriptor = ObjectGetOwnPropertyDescriptor(mapScopes, scope); + if (!descriptor?.enumerable || !("value" in descriptor)) continue; + const target = scopes[scope] ??= ObjectCreate(null) as Record; + copyStringRecord(target, descriptor.value as Readonly>); } } - return { imports, scopes }; + return snapshotImportMap({ imports, scopes }); } diff --git a/src/modules/import-map/preloader-primordial-poisoning.worker.ts b/src/modules/import-map/preloader-primordial-poisoning.worker.ts index 93b73b4e8c..243fbc580c 100644 --- a/src/modules/import-map/preloader-primordial-poisoning.worker.ts +++ b/src/modules/import-map/preloader-primordial-poisoning.worker.ts @@ -96,8 +96,16 @@ async function runPoisoningRegression() { }, }), }); - const contextA = { contentSourceId: "source", config: configA }; - const contextB = { contentSourceId: "source", config: configB }; + const contextA = { + contentSourceId: "source", + config: configA, + projectDir: "/project", + }; + const contextB = { + contentSourceId: "source", + config: configB, + projectDir: "/project", + }; first = await preloader.preload("/project", adapter, "project", contextA); firstAgain = await preloader.preload( diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 65d0de6965..30a7a5d74f 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -234,6 +234,62 @@ describe("modules/import-map/preloader", () => { assertEquals(release === branch, false); }); + it("isolates project roots that share one project ID and content source", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + ttlMs: 1_000, + loadImportMap: async (projectDir) => ({ + imports: { projectDir, load: String(++loads) }, + }), + }); + const context = { contentSourceId: "release-1" }; + + const first = await preloader.preload( + "/releases/first", + adapter, + "project-1", + context, + ); + const second = await preloader.preload( + "/releases/second", + adapter, + "project-1", + context, + ); + + assertEquals(first.imports?.projectDir, "/releases/first"); + assertEquals(second.imports?.projectDir, "/releases/second"); + assertEquals(loads, 2); + }); + + it("rejects accessor-backed request context without invoking it", async () => { + const adapter = createMinimalAdapter(); + let getterCalls = 0; + const context = Object.defineProperty({}, "contentSourceId", { + enumerable: true, + get() { + getterCalls++; + return "poisoned"; + }, + }); + + await assertRejects( + () => + preloadImportMap( + "/accessor-context", + adapter, + "accessor-context", + context, + ), + TypeError, + "cannot be an accessor", + ); + assertEquals(getterCalls, 0); + }); + it("snapshots and deep-freezes loader output before publishing it", async () => { const adapter = createMinimalAdapter(); const loadedMap = { @@ -282,7 +338,9 @@ describe("modules/import-map/preloader", () => { "https://example.com/scoped-v1.ts", ); assertEquals( - await preloader.getCached("immutable-loader-output"), + await preloader.getCached("immutable-loader-output", { + projectDir: "/immutable-loader-output", + }), published, ); assertEquals( @@ -387,9 +445,9 @@ describe("modules/import-map/preloader", () => { }); const projectDir = "/bounded-variants"; const projectId = "project-1"; - const sourceA = { contentSourceId: "source-a" }; - const sourceB = { contentSourceId: "source-b" }; - const sourceC = { contentSourceId: "source-c" }; + const sourceA = { contentSourceId: "source-a", projectDir }; + const sourceB = { contentSourceId: "source-b", projectDir }; + const sourceC = { contentSourceId: "source-c", projectDir }; await preloader.preload(projectDir, adapter, projectId, sourceA); await preloader.preload(projectDir, adapter, projectId, sourceB); @@ -411,12 +469,21 @@ describe("modules/import-map/preloader", () => { await preloader.preload("/project-a", adapter, "project-a"); await preloader.preload("/project-b", adapter, "project-b"); - await preloader.getCached("project-a"); + await preloader.getCached("project-a", { projectDir: "/project-a" }); await preloader.preload("/project-c", adapter, "project-c"); - assertEquals(await preloader.getCached("project-a") !== undefined, true); - assertEquals(await preloader.getCached("project-b"), undefined); - assertEquals(await preloader.getCached("project-c") !== undefined, true); + assertEquals( + await preloader.getCached("project-a", { projectDir: "/project-a" }) !== undefined, + true, + ); + assertEquals( + await preloader.getCached("project-b", { projectDir: "/project-b" }), + undefined, + ); + assertEquals( + await preloader.getCached("project-c", { projectDir: "/project-c" }) !== undefined, + true, + ); }); it("expires settled entries against an injected clock and reloads them", async () => { @@ -428,7 +495,7 @@ describe("modules/import-map/preloader", () => { ttlMs: 100, now: () => now, }); - const context = { contentSourceId: "source-a" }; + const context = { contentSourceId: "source-a", projectDir: "/ttl-project" }; const first = await preloader.preload("/ttl-project", adapter, "ttl-project", context); now = 1_099; @@ -455,7 +522,10 @@ describe("modules/import-map/preloader", () => { ttlMs: 100, now: () => now, }); - const context = { contentSourceId: "source-a" }; + const context = { + contentSourceId: "source-a", + projectDir: "/direct-expiry", + }; const expired = await preloader.preload( "/direct-expiry", @@ -497,7 +567,10 @@ describe("modules/import-map/preloader", () => { return load.promise; }, }); - const context = { contentSourceId: "source-a" }; + const context = { + contentSourceId: "source-a", + projectDir: "/concurrent-direct-expiry", + }; const initialPromise = preloader.preload( "/concurrent-direct-expiry", @@ -569,7 +642,10 @@ describe("modules/import-map/preloader", () => { imports: { loaded: String(++loads) }, }), }); - const context = { contentSourceId: "source-a" }; + const context = { + contentSourceId: "source-a", + projectDir: "/throwing-clock", + }; const first = await preloader.preload( "/throwing-clock", @@ -606,8 +682,14 @@ describe("modules/import-map/preloader", () => { await preloader.preload("/project-b", adapter, "project-b"); preloader.clear("project-a"); - assertEquals(await preloader.getCached("project-a"), undefined); - assertEquals(await preloader.getCached("project-b") !== undefined, true); + assertEquals( + await preloader.getCached("project-a", { projectDir: "/project-a" }), + undefined, + ); + assertEquals( + await preloader.getCached("project-b", { projectDir: "/project-b" }) !== undefined, + true, + ); }); it("does not publish pre-clear work after identity hashing resumes", async () => { @@ -621,7 +703,7 @@ describe("modules/import-map/preloader", () => { imports: { loaded: String(++loads) }, }), }); - const context = { contentSourceId: "source-a" }; + const context = { contentSourceId: "source-a", projectDir: "/project-a" }; const preClear = preloader.preload( "/project-a", @@ -675,7 +757,7 @@ describe("modules/import-map/preloader", () => { imports: { loaded: String(++loads) }, }), }); - const context = { contentSourceId: "source-a" }; + const context = { contentSourceId: "source-a", projectDir: "/project-a" }; const preClear = preloader.preload( "/project-a", diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index b4cac40bc2..4502540d2b 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -10,6 +10,8 @@ export interface PreloadImportMapContext { contentSourceId?: string; /** Config already validated for the authenticated request. */ config?: VeryfrontConfig; + /** Project root used by cache-inspection callers when the cache key is a project ID. */ + projectDir?: string; } const IMPORT_MAP_CACHE_IDENTITY_NAMESPACE = "veryfront:preloaded-import-map:v2"; @@ -26,6 +28,7 @@ const IntrinsicMap = Map; const IntrinsicPromise = Promise; const IntrinsicRangeError = RangeError; const IntrinsicSet = Set; +const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; const MapPrototypeClear = Map.prototype.clear; const MapPrototypeDelete = Map.prototype.delete; @@ -40,6 +43,7 @@ const NumberIsFinite = Number.isFinite; const NumberIsSafeInteger = Number.isSafeInteger; const ObjectEntries = Object.entries; const ObjectFreeze = Object.freeze; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; const ReflectApply = Reflect.apply; @@ -154,33 +158,70 @@ function compareEntries( * and need not remain unchanged while SHA-256 is being computed. */ function snapshotPreloadContext( + projectDir: string, context?: PreloadImportMapContext, -): PreloadImportMapContext | undefined { - if (!context) return undefined; - const contentSourceId = context.contentSourceId; - const config = context.config; - if (!config) return ObjectFreeze({ contentSourceId }); - - const resolve = config.resolve; - const importMap = snapshotImportMap(resolve?.importMap ?? {}); +): PreloadImportMapContext { + if (typeof projectDir !== "string") { + throw new IntrinsicTypeError("Import-map projectDir must be a string"); + } + if (!context) return ObjectFreeze({ projectDir }); + if (context === null || typeof context !== "object") { + throw new IntrinsicTypeError("Import-map context must be an object"); + } + const contentSourceDescriptor = ObjectGetOwnPropertyDescriptor( + context, + "contentSourceId", + ); + if (contentSourceDescriptor && !("value" in contentSourceDescriptor)) { + throw new IntrinsicTypeError("Import-map contentSourceId cannot be an accessor"); + } + const contentSourceId = contentSourceDescriptor?.value; + if (contentSourceId !== undefined && typeof contentSourceId !== "string") { + throw new IntrinsicTypeError("Import-map contentSourceId must be a string"); + } + const configDescriptor = ObjectGetOwnPropertyDescriptor(context, "config"); + if (configDescriptor && !("value" in configDescriptor)) { + throw new IntrinsicTypeError("Import-map config cannot be an accessor"); + } + const config = configDescriptor?.value as VeryfrontConfig | undefined; + if (config === undefined) return ObjectFreeze({ contentSourceId, projectDir }); + if (config === null || typeof config !== "object") { + throw new IntrinsicTypeError("Import-map config must be an object"); + } + const resolveDescriptor = ObjectGetOwnPropertyDescriptor(config, "resolve"); + if (resolveDescriptor && !("value" in resolveDescriptor)) { + throw new IntrinsicTypeError("Import-map config resolve cannot be an accessor"); + } + const resolve = resolveDescriptor?.value; + if (resolve !== undefined && (resolve === null || typeof resolve !== "object")) { + throw new IntrinsicTypeError("Import-map config resolve must be an object"); + } + const importMapDescriptor = resolve + ? ObjectGetOwnPropertyDescriptor(resolve, "importMap") + : undefined; + if (importMapDescriptor && !("value" in importMapDescriptor)) { + throw new IntrinsicTypeError("Import-map config resolve.importMap cannot be an accessor"); + } + const importMap = snapshotImportMap(importMapDescriptor?.value ?? {}); + // The loader only consumes resolve.importMap. Keeping the request snapshot + // minimal avoids invoking unrelated config getters or retaining mutable + // tenant-controlled configuration behind a cache entry. const exactConfig = ObjectFreeze({ - ...config, resolve: ObjectFreeze({ - ...resolve, importMap, }), }) as VeryfrontConfig; - return ObjectFreeze({ contentSourceId, config: exactConfig }); + return ObjectFreeze({ contentSourceId, config: exactConfig, projectDir }); } function buildVariantCanonicalIdentity( - context?: PreloadImportMapContext, + context: PreloadImportMapContext, ): string { - const importMap = context?.config?.resolve?.importMap; + const importMap = context.config?.resolve?.importMap; let canonical = `${IMPORT_MAP_CACHE_IDENTITY_NAMESPACE}\0source:${ - JSONStringify(context?.contentSourceId ?? null) - }\0`; - if (!context?.config) return `${canonical}ambient`; + JSONStringify(context.contentSourceId ?? null) + }\0projectDir:${JSONStringify(context.projectDir)}\0`; + if (!context.config) return `${canonical}ambient`; canonical += "validated"; const imports = arraySort( @@ -506,7 +547,7 @@ export class ImportMapPreloader { projectId?: string, context?: PreloadImportMapContext, ): Promise { - const exactContext = snapshotPreloadContext(context); + const exactContext = snapshotPreloadContext(projectDir, context); const cacheKey = projectId ?? projectDir; const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); const admissionNow = this.readNow(); @@ -569,7 +610,7 @@ export class ImportMapPreloader { return this.startTrackedLoad( projectDir, adapter, - exactContext?.config, + exactContext.config, ); } @@ -595,7 +636,7 @@ export class ImportMapPreloader { return this.startTrackedLoad( projectDir, adapter, - exactContext?.config, + exactContext.config, ); } @@ -617,7 +658,7 @@ export class ImportMapPreloader { const promise = this.startTrackedLoad( projectDir, adapter, - exactContext?.config, + exactContext.config, ); const entry: CachedImportMap = { promise, expiresAt: null }; mapSet(projectCache, variantKey, entry); @@ -666,38 +707,32 @@ export class ImportMapPreloader { cacheKey: string, context?: PreloadImportMapContext, ): Promise { - const exactContext = snapshotPreloadContext(context); + const projectDirDescriptor = context && typeof context === "object" + ? ObjectGetOwnPropertyDescriptor(context, "projectDir") + : undefined; + if (projectDirDescriptor && !("value" in projectDirDescriptor)) { + throw new IntrinsicTypeError("Import-map projectDir cannot be an accessor"); + } + const contextProjectDir = projectDirDescriptor?.value; + if (contextProjectDir !== undefined && typeof contextProjectDir !== "string") { + throw new IntrinsicTypeError("Import-map projectDir must be a string"); + } + const exactContext = snapshotPreloadContext( + contextProjectDir ?? cacheKey, + context, + ); const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); const projectState = mapGet(this.projects, cacheKey); if (!projectState) return undefined; const globalGeneration = this.globalGeneration; const projectGeneration = projectState.generation; - let identityBuild: Promise | undefined; let variantKey: string; try { - identityBuild = this.getOrCreateIdentityBuild( - projectState, - canonicalIdentity, - ); - variantKey = await identityBuild; + variantKey = await computeHash(canonicalIdentity); } catch (error) { - if (identityBuild) { - this.releaseIdentityBuild( - projectState, - canonicalIdentity, - identityBuild, - ); - } this.removeEmptyProject(cacheKey, projectState); throw error; } - const releaseIdentity = (): void => { - this.releaseIdentityBuild( - projectState, - canonicalIdentity, - identityBuild, - ); - }; if ( !this.isCurrentGeneration( cacheKey, @@ -706,7 +741,6 @@ export class ImportMapPreloader { projectGeneration, ) ) { - releaseIdentity(); return undefined; } let entry: CachedImportMap | undefined; @@ -717,18 +751,13 @@ export class ImportMapPreloader { this.readNow(), ); } catch (error) { - releaseIdentity(); this.removeEmptyProject(cacheKey, projectState); throw error; } if (!entry) { - releaseIdentity(); this.removeEmptyProject(cacheKey, projectState); return undefined; } - if (entry.expiresAt !== null) { - releaseIdentity(); - } try { return await entry.promise; diff --git a/src/rendering/layouts/utils/component-loader.ts b/src/rendering/layouts/utils/component-loader.ts index 4f78b98fe8..4fa3bb4fed 100644 --- a/src/rendering/layouts/utils/component-loader.ts +++ b/src/rendering/layouts/utils/component-loader.ts @@ -355,7 +355,10 @@ export function loadMDXLayout( hasPreloadedImportMap: !!preloadedImportMap, }); - const map = preloadedImportMap ?? (await preloadImportMap(projectDir, adapter, projectId)); + const map = preloadedImportMap ?? + (await preloadImportMap(projectDir, adapter, projectId, { + contentSourceId, + })); if (preloadedImportMap) { loadMdxLayoutLog.debug("Using preloaded import map", { projectSlug }); } diff --git a/src/rendering/orchestrator/layout.ts b/src/rendering/orchestrator/layout.ts index 947bac03a3..1633e3e036 100644 --- a/src/rendering/orchestrator/layout.ts +++ b/src/rendering/orchestrator/layout.ts @@ -178,6 +178,10 @@ export class LayoutOrchestrator { this.config.projectDir, this.config.adapter, this.config.projectId, + { + contentSourceId: this.config.contentSourceId, + config: this.config.config, + }, ); this._preloadedImportMap = importMap; return { type: "importMap" as const, success: true }; diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 6216bdb683..b8de1e129c 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -48,6 +48,25 @@ describe("transforms/esm/http-cache-helpers", () => { ); }); + it("does not consult inherited toJSON hooks while fingerprinting", async () => { + const original = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + let first: string; + let second: string; + try { + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value: () => [], + }); + first = await fingerprintImportMap({ imports: { package: "version-a" } }); + second = await fingerprintImportMap({ imports: { package: "version-b" } }); + } finally { + if (original) Object.defineProperty(Array.prototype, "toJSON", original); + else delete (Array.prototype as unknown as { toJSON?: unknown }).toJSON; + } + + assertNotEquals(first, second); + }); + it("frames URL and React version components without delimiter collisions", async () => { const importMap = { imports: {}, scopes: {} }; diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 3ebc46dd29..b40f046fe2 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -106,9 +106,32 @@ export function fingerprintImportMap(importMap: ImportMapConfig): Promise 0) canonical += ","; + canonical += `[${JSONStringify(key)},${JSONStringify(value)}]`; + } + canonical += `],"scopes":[`; + for (let scopeIndex = 0; scopeIndex < scopes.length; scopeIndex++) { + const [scope, mappings] = scopes[scopeIndex] as [string, Array<[string, string]>]; + if (scopeIndex > 0) canonical += ","; + canonical += `[${JSONStringify(scope)},[`; + for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) { + const [key, value] = mappings[mappingIndex]!; + if (mappingIndex > 0) canonical += ","; + canonical += `[${JSONStringify(key)},${JSONStringify(value)}]`; + } + canonical += "]]"; + } + canonical += "]}"; + return computeHash(canonical); } function attachHttpCacheRequestIdentityContext( From 4dec2eb76b2a43b2bcb5855a201e960f871a14b7 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 13:03:18 +0200 Subject: [PATCH 08/34] Prevent poisoned primordials from collapsing import-map identities Config hash construction now preserves its canonical preimages without mutable Array methods. Import-map discovery again uses canonical path joining, whose portable array operations are captured before project code can replace them. Invalid config contexts fail at the boundary, and the revised import-map fingerprint uses a new namespace. Constraint: Existing config identity formats must remain byte-for-byte stable for the current patch release. Constraint: Local and Windows-like project paths must resolve the same deno.json probe after normalization. Rejected: Manual slash concatenation | trailing separators produced non-canonical filesystem reads. Rejected: Keep the v2 fingerprint namespace | the canonical preimage format already changed. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Bump cache identity namespaces whenever their canonical preimage format changes. Tested: Focused 12 files, 306 steps; adjacent 26 files, 522 steps; touched deno check, fmt, lint; manifest and RSC regeneration. Not-tested: Full repository unit suite runs in the push hook. --- src/cache/config-hash.test.ts | 58 ++++++++++++++++-- src/cache/config-hash.ts | 60 +++++++++---------- src/modules/import-map/loader.test.ts | 34 +++++++++++ src/modules/import-map/loader.ts | 4 +- src/modules/import-map/preloader.test.ts | 21 +++++++ src/modules/import-map/preloader.ts | 5 +- src/platform/compat/path/basic-operations.ts | 11 +++- src/platform/compat/path/portable.ts | 33 +++++++--- src/transforms/esm/http-cache-helpers.test.ts | 13 ++++ src/transforms/esm/http-cache-helpers.ts | 2 +- 10 files changed, 194 insertions(+), 47 deletions(-) diff --git a/src/cache/config-hash.test.ts b/src/cache/config-hash.test.ts index e06ffcc6e9..2242122fb9 100644 --- a/src/cache/config-hash.test.ts +++ b/src/cache/config-hash.test.ts @@ -11,7 +11,7 @@ describe("cache/config-hash", () => { it("matches the golden hash for the default transform config", async () => { assertEquals( await computeConfigHash({}), - "4b96519f8a12a74bbef6f1a0f92f1825e5e267c6202240b2d32825dab6f6ac6c", + "0dea4e3e5437669245bb85a5105b733745dde1e52b76cb86e0375802edcdc90f", ); }); @@ -28,10 +28,35 @@ describe("cache/config-hash", () => { dev: true, dependencyPinningCacheKey: CANONICAL_PIN_KEY, }), - "a868c7f22dc1518f90f638c07d7d03971d6ff4510b7e706eccf631c2b0269998", + "f436b138e6ba376debd7e561469431cbed6811f7df987953737289f3a150f3b7", ); }); + it("keeps distinct hashes stable when array push and join are poisoned", async () => { + const firstConfig = { reactVersion: "18.3.1", dev: false }; + const secondConfig = { reactVersion: "19.2.4", dev: true }; + const firstBaseline = await computeConfigHash(firstConfig); + const secondBaseline = await computeConfigHash(secondConfig); + const originalPush = Array.prototype.push; + const originalJoin = Array.prototype.join; + let firstPoisoned: string | undefined; + let secondPoisoned: string | undefined; + + try { + Reflect.set(Array.prototype, "push", () => 0); + Reflect.set(Array.prototype, "join", () => "poisoned"); + firstPoisoned = await computeConfigHash(firstConfig); + secondPoisoned = await computeConfigHash(secondConfig); + } finally { + Reflect.set(Array.prototype, "push", originalPush); + Reflect.set(Array.prototype, "join", originalJoin); + } + + assertEquals(firstPoisoned, firstBaseline); + assertEquals(secondPoisoned, secondBaseline); + assertNotEquals(firstPoisoned, secondPoisoned); + }); + it("should return a 64-char hex hash", async () => { const hash = await computeConfigHash({}); assertEquals(hash.length, 64); @@ -140,7 +165,7 @@ describe("cache/config-hash", () => { describe("computeConfigHashSync", () => { it("matches the golden identity for the default transform config", () => { - assertEquals(computeConfigHashSync({}), "v0.1.1186:19.2.4:react"); + assertEquals(computeConfigHashSync({}), "v0.1.1189:19.2.4:react"); }); it("matches the golden identity for a fully scoped transform config", () => { @@ -156,10 +181,35 @@ describe("cache/config-hash", () => { dev: true, dependencyPinningCacheKey: CANONICAL_PIN_KEY, }), - "v0.1.1186:18.3.1:preact:modules:40:https://modules.example.test/_vf_modules:vendor:8:vendor-a:api:24:https://api.example.test:studio:dev:pins:on:z7bg3qnfgtcb:origin:aHR0cHM6Ly9wcmV2aWV3LmV4YW1wbGUudGVzdA", + "v0.1.1189:18.3.1:preact:modules:40:https://modules.example.test/_vf_modules:vendor:8:vendor-a:api:24:https://api.example.test:studio:dev:pins:on:z7bg3qnfgtcb:origin:aHR0cHM6Ly9wcmV2aWV3LmV4YW1wbGUudGVzdA", ); }); + it("keeps distinct sync identities stable when array push and join are poisoned", () => { + const firstConfig = { reactVersion: "18.3.1", dev: false }; + const secondConfig = { reactVersion: "19.2.4", dev: true }; + const firstBaseline = computeConfigHashSync(firstConfig); + const secondBaseline = computeConfigHashSync(secondConfig); + const originalPush = Array.prototype.push; + const originalJoin = Array.prototype.join; + let firstPoisoned: string | undefined; + let secondPoisoned: string | undefined; + + try { + Reflect.set(Array.prototype, "push", () => 0); + Reflect.set(Array.prototype, "join", () => "poisoned"); + firstPoisoned = computeConfigHashSync(firstConfig); + secondPoisoned = computeConfigHashSync(secondConfig); + } finally { + Reflect.set(Array.prototype, "push", originalPush); + Reflect.set(Array.prototype, "join", originalJoin); + } + + assertEquals(firstPoisoned, firstBaseline); + assertEquals(secondPoisoned, secondBaseline); + assertNotEquals(firstPoisoned, secondPoisoned); + }); + it("should return a string", () => { const hash = computeConfigHashSync({}); assertEquals(typeof hash, "string"); diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 99f261e670..abec3903bf 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -57,26 +57,28 @@ function buildAsyncConfigIdentity(config: TransformConfig): string { config.dependencyPinningCacheKey, config.moduleServerOrigin, ); - const fields = [ - encodeJsonStringProperty("transformVersion", VERSION), - encodeJsonStringProperty("reactVersion", config.reactVersion ?? DEFAULT_REACT_VERSION), - encodeJsonStringProperty("jsxImportSource", config.jsxImportSource ?? "react"), - encodeJsonNullableStringProperty("moduleServerUrl", config.moduleServerUrl ?? null), - encodeJsonNullableStringProperty("vendorBundleHash", config.vendorBundleHash ?? null), - encodeJsonNullableStringProperty("apiBaseUrl", config.apiBaseUrl ?? null), - encodeJsonBooleanProperty("studioEmbed", config.studioEmbed ?? false), - encodeJsonBooleanProperty("dev", config.dev ?? false), - ]; + let fields = encodeJsonStringProperty("transformVersion", VERSION); + fields += `,${ + encodeJsonStringProperty("reactVersion", config.reactVersion ?? DEFAULT_REACT_VERSION) + }`; + fields += `,${encodeJsonStringProperty("jsxImportSource", config.jsxImportSource ?? "react")}`; + fields += `,${ + encodeJsonNullableStringProperty("moduleServerUrl", config.moduleServerUrl ?? null) + }`; + fields += `,${ + encodeJsonNullableStringProperty("vendorBundleHash", config.vendorBundleHash ?? null) + }`; + fields += `,${encodeJsonNullableStringProperty("apiBaseUrl", config.apiBaseUrl ?? null)}`; + fields += `,${encodeJsonBooleanProperty("studioEmbed", config.studioEmbed ?? false)}`; + fields += `,${encodeJsonBooleanProperty("dev", config.dev ?? false)}`; if (dependencyPinningCacheVariant) { - fields.push( - encodeJsonStringProperty("dependencyPinningCacheVariant", dependencyPinningCacheVariant), - ); + fields += `,${ + encodeJsonStringProperty("dependencyPinningCacheVariant", dependencyPinningCacheVariant) + }`; } - fields.push( - encodeJsonStringProperty("csstype", CSSTYPE_VERSION), - encodeJsonStringProperty("tailwind", TAILWIND_VERSION), - ); - return `{${fields.join(",")}}`; + fields += `,${encodeJsonStringProperty("csstype", CSSTYPE_VERSION)}`; + fields += `,${encodeJsonStringProperty("tailwind", TAILWIND_VERSION)}`; + return `{${fields}}`; } function encodeConfigPart(label: string, value: string | undefined): string { @@ -85,28 +87,26 @@ function encodeConfigPart(label: string, value: string | undefined): string { } function buildSyncConfigIdentity(config: TransformConfig): string { - const parts = [ - `v${VERSION}`, - config.reactVersion ?? DEFAULT_REACT_VERSION, - config.jsxImportSource ?? "react", - ]; + let identity = `v${VERSION}:${config.reactVersion ?? DEFAULT_REACT_VERSION}:${ + config.jsxImportSource ?? "react" + }`; const moduleServerUrlPart = encodeConfigPart("modules", config.moduleServerUrl); - if (moduleServerUrlPart) parts.push(moduleServerUrlPart); + if (moduleServerUrlPart) identity += `:${moduleServerUrlPart}`; const vendorBundleHashPart = encodeConfigPart("vendor", config.vendorBundleHash); - if (vendorBundleHashPart) parts.push(vendorBundleHashPart); + if (vendorBundleHashPart) identity += `:${vendorBundleHashPart}`; const apiBaseUrlPart = encodeConfigPart("api", config.apiBaseUrl); - if (apiBaseUrlPart) parts.push(apiBaseUrlPart); - if (config.studioEmbed) parts.push("studio"); - if (config.dev) parts.push("dev"); + if (apiBaseUrlPart) identity += `:${apiBaseUrlPart}`; + if (config.studioEmbed) identity += ":studio"; + if (config.dev) identity += ":dev"; const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( config.dependencyPinningCacheKey, config.moduleServerOrigin, ); if (dependencyPinningCacheVariant) { - parts.push(`pins:${dependencyPinningCacheVariant}`); + identity += `:pins:${dependencyPinningCacheVariant}`; } - return parts.join(":"); + return identity; } /** diff --git a/src/modules/import-map/loader.test.ts b/src/modules/import-map/loader.test.ts index 932c17bf66..0ea58af24d 100644 --- a/src/modules/import-map/loader.test.ts +++ b/src/modules/import-map/loader.test.ts @@ -79,6 +79,20 @@ describe("modules/import-map/loader", () => { assert("react" in imports, "should include default react"); }); + it("probes a canonical deno.json path when the project path has a trailing slash", async () => { + const adapter = createMockAdapter(); + adapter.fs.files.set( + "/trailing-slash-project/deno.json", + JSON.stringify({ + imports: { "project-package": "https://esm.sh/project-package@1" }, + }), + ); + + const { imports } = await loadImportMap("/trailing-slash-project/", adapter); + + assertEquals(imports?.["project-package"], "https://esm.sh/project-package@1"); + }); + it("should use esm.sh URLs for React", async () => { const adapter = createMockAdapter(); const { imports } = await loadImportMap("/any-project", adapter); @@ -205,8 +219,12 @@ describe("modules/import-map/loader", () => { jsonParse: JSON.parse, objectEntries: Object.entries, objectFromEntries: Object.fromEntries, + arrayEvery: Array.prototype.every, arrayFilter: Array.prototype.filter, + arrayJoin: Array.prototype.join, arrayMap: Array.prototype.map, + arrayPush: Array.prototype.push, + arraySome: Array.prototype.some, }; try { @@ -219,12 +237,24 @@ describe("modules/import-map/loader", () => { Object.fromEntries = (() => { throw new Error("poisoned Object.fromEntries"); }) as typeof Object.fromEntries; + Array.prototype.every = function () { + throw new Error("poisoned Array.prototype.every"); + }; Array.prototype.filter = function () { throw new Error("poisoned Array.prototype.filter"); }; + Array.prototype.join = function () { + throw new Error("poisoned Array.prototype.join"); + }; Array.prototype.map = function () { throw new Error("poisoned Array.prototype.map"); }; + Array.prototype.push = function () { + throw new Error("poisoned Array.prototype.push"); + }; + Array.prototype.some = function () { + throw new Error("poisoned Array.prototype.some"); + }; const { imports, scopes } = await loadImportMap("/poisoned-deno-json", adapter); @@ -235,8 +265,12 @@ describe("modules/import-map/loader", () => { JSON.parse = original.jsonParse; Object.entries = original.objectEntries; Object.fromEntries = original.objectFromEntries; + Array.prototype.every = original.arrayEvery; Array.prototype.filter = original.arrayFilter; + Array.prototype.join = original.arrayJoin; Array.prototype.map = original.arrayMap; + Array.prototype.push = original.arrayPush; + Array.prototype.some = original.arraySome; } }); }); diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index 756494d1d1..f984c80a2e 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -1,5 +1,5 @@ import { rendererLogger as logger } from "#veryfront/utils"; -import { dirname } from "#veryfront/compat/path/index.ts"; +import { dirname, join } from "#veryfront/compat/path/index.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { isVirtualFilesystem } from "#veryfront/platform/adapters/fs/wrapper.ts"; import { getConfig, type VeryfrontConfig } from "#veryfront/config"; @@ -134,7 +134,7 @@ async function loadDenoJsonImportMap( let currentPath = startPath; while (currentPath !== "/" && currentPath !== "") { - const denoJsonPath = currentPath === "/" ? "/deno.json" : `${currentPath}/deno.json`; + const denoJsonPath = join(currentPath, "deno.json"); try { const content = await adapter.fs.readFile(denoJsonPath); diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index e6da8873c4..b282222cd8 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -234,6 +234,27 @@ describe("modules/import-map/preloader", () => { assertEquals(release === branch, false); }); + it("rejects non-object config context values with a targeted error", async () => { + const adapter = createMinimalAdapter(); + const preloader = new ImportMapPreloader({ + loadImportMap: async () => ({ imports: {} }), + }); + + for (const config of [null, false, 0, "invalid"]) { + await assertRejects( + () => + preloader.preload( + "/invalid-config-context", + adapter, + `invalid-config-${String(config)}`, + { config } as never, + ), + TypeError, + "Preload import-map config must be a non-null object", + ); + } + }); + it("snapshots and deep-freezes loader output before publishing it", async () => { const adapter = createMinimalAdapter(); const loadedMap = { diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 3685053d52..64b9f44923 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -204,7 +204,10 @@ function snapshotPreloadContext( "config", "Preload import-map context", ) as VeryfrontConfig | undefined; - if (!config) return ObjectFreeze({ projectDir, contentSourceId }); + if (config === undefined) return ObjectFreeze({ projectDir, contentSourceId }); + if (config === null || typeof config !== "object") { + throw new IntrinsicTypeError("Preload import-map config must be a non-null object"); + } const resolve = readOptionalOwnDataProperty(config, "resolve", "Veryfront config"); const importMap = resolve && typeof resolve === "object" diff --git a/src/platform/compat/path/basic-operations.ts b/src/platform/compat/path/basic-operations.ts index fa7cc23f3b..c6fa957bf1 100644 --- a/src/platform/compat/path/basic-operations.ts +++ b/src/platform/compat/path/basic-operations.ts @@ -10,13 +10,20 @@ import { } from "./portable.ts"; import { getNativePathImplementation } from "./runtime.ts"; +const ArrayPrototypeEvery = Array.prototype.every; +const ArrayPrototypeSome = Array.prototype.some; +const ReflectApply = Reflect.apply; + function usesWindowsFlavor(paths: readonly string[]): boolean { - return runtimeUsesWindowsPaths() || paths.some(hasWindowsLikePath); + return runtimeUsesWindowsPaths() || + ReflectApply(ArrayPrototypeSome, paths, [hasWindowsLikePath]) as boolean; } /** Join and normalize path segments using their detected path flavor. */ export function join(...paths: string[]): string { - if (paths.every((path) => path.length === 0)) return "/"; + if ( + ReflectApply(ArrayPrototypeEvery, paths, [(path: string) => path.length === 0]) as boolean + ) return "/"; const windows = usesWindowsFlavor(paths); const pathApi = getNativePathImplementation(windows); const joined = pathApi diff --git a/src/platform/compat/path/portable.ts b/src/platform/compat/path/portable.ts index 2a1bcb4001..4d63fec2e1 100644 --- a/src/platform/compat/path/portable.ts +++ b/src/platform/compat/path/portable.ts @@ -1,5 +1,20 @@ import type { PathObject } from "./types.ts"; +const ArrayPrototypeAt = Array.prototype.at; +const ArrayPrototypeFilter = Array.prototype.filter; +const ArrayPrototypeJoin = Array.prototype.join; +const ArrayPrototypePop = Array.prototype.pop; +const ArrayPrototypePush = Array.prototype.push; +const ReflectApply = Reflect.apply; + +function arrayJoin(values: readonly string[], separator: string): string { + return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; +} + +function arrayPush(values: string[], value: string): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + interface RootInfo { absolute: boolean; device: string; @@ -78,15 +93,15 @@ function normalizeTail(rest: string, absolute: boolean): string[] { if (segment === "" || segment === ".") continue; if (segment !== "..") { - normalized.push(segment); + arrayPush(normalized, segment); continue; } - const previous = normalized.at(-1); + const previous = ReflectApply(ArrayPrototypeAt, normalized, [-1]) as string | undefined; if (previous !== undefined && previous !== "..") { - normalized.pop(); + ReflectApply(ArrayPrototypePop, normalized, []); } else if (!absolute) { - normalized.push(".."); + arrayPush(normalized, ".."); } } @@ -155,7 +170,7 @@ export function portableNormalize(path: string, windows: boolean): string { if (path === "") return "."; const root = analyzeRoot(path, windows); - const tail = normalizeTail(root.rest, root.absolute).join("/"); + const tail = arrayJoin(normalizeTail(root.rest, root.absolute), "/"); let result = appendRoot(root, tail); const hadTrailingSeparator = /[\\/]$/.test(path); @@ -172,9 +187,13 @@ export function portableNormalize(path: string, windows: boolean): string { } export function portableJoin(paths: readonly string[], windows: boolean): string { - const nonempty = paths.filter((path) => path.length > 0); + const nonempty = ReflectApply( + ArrayPrototypeFilter, + paths, + [(path: string) => path.length > 0], + ) as string[]; if (nonempty.length === 0) return "/"; - return portableNormalize(nonempty.join("/"), windows); + return portableNormalize(arrayJoin(nonempty, "/"), windows); } export function portableDirname(path: string, windows: boolean): string { diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 6c36010b46..085e8bc935 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -17,6 +17,7 @@ import { prepareHttpCacheRequestOptions, resolveBareSpecifier, } from "./http-cache-helpers.ts"; +import { computeHash } from "#veryfront/utils/hash-utils.ts"; describe("transforms/esm/http-cache-helpers", () => { describe("cache identity", () => { @@ -112,6 +113,18 @@ describe("transforms/esm/http-cache-helpers", () => { assertEquals(hookCalls, 0); }); + it("does not reuse the v2 namespace after changing fingerprint canonicalization", async () => { + const importMap = { + imports: { pkg: "https://modules.example.com/pkg.js" }, + scopes: {}, + }; + const legacyV2Fingerprint = await computeHash( + 'veryfront:http-import-map:v2\0import:"pkg":"https://modules.example.com/pkg.js"', + ); + + assertNotEquals(await fingerprintImportMap(importMap), legacyV2Fingerprint); + }); + it("canonicalizes and fingerprints one import map once per prepared request graph", async () => { let importEnumerations = 0; const imports = new Proxy({ pkg: "https://modules.example.com/pkg.js" }, { diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 54b5f809fc..cf9077ef80 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -76,7 +76,7 @@ function compareImportMapKeys(left: [string, string], right: [string, string]): return left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0; } -const HTTP_IMPORT_MAP_FINGERPRINT_NAMESPACE = "veryfront:http-import-map:v2"; +const HTTP_IMPORT_MAP_FINGERPRINT_NAMESPACE = "veryfront:http-import-map:v3"; const HTTP_CACHE_IDENTITY_NAMESPACE = "veryfront:http-module:v2"; const HTTP_CACHE_FILE_HASH_NAMESPACE = "veryfront:http-module-file:v2"; From 5211fc743ccd5c5578ade2236869cddcc9d70082 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 13:04:12 +0200 Subject: [PATCH 09/34] fix(modules): close import-map review gaps --- src/cache/config-hash.test.ts | 28 +++++++++ src/cache/config-hash.ts | 37 ++++++++---- src/modules/README.md | 10 +++- .../loader-primordial-poisoning.worker.ts | 3 + .../preloader-primordial-poisoning.worker.ts | 6 ++ src/modules/import-map/preloader.test.ts | 37 ++++++++---- src/modules/import-map/preloader.ts | 23 +++++--- src/transforms/esm/http-cache-helpers.test.ts | 14 +++++ .../pipeline/cache-identity.test.ts | 58 +++++++++++++++++++ src/transforms/pipeline/cache-identity.ts | 45 +++++++++----- src/utils/hash-utils.test.ts | 41 +++++++++++++ src/utils/hash-utils.ts | 14 ++++- 12 files changed, 267 insertions(+), 49 deletions(-) diff --git a/src/cache/config-hash.test.ts b/src/cache/config-hash.test.ts index 22d9653b69..6f912976b6 100644 --- a/src/cache/config-hash.test.ts +++ b/src/cache/config-hash.test.ts @@ -200,6 +200,34 @@ describe("cache/config-hash", () => { ); }); + it("preserves the established identity after array primordial poisoning", () => { + const originalFilter = Array.prototype.filter; + const originalJoin = Array.prototype.join; + const originalPush = Array.prototype.push; + let identity: string | undefined; + try { + Reflect.set(Array.prototype, "filter", () => []); + Reflect.set(Array.prototype, "join", () => "poisoned"); + Reflect.set(Array.prototype, "push", () => 0); + identity = computeConfigHashSync({ + moduleServerUrl: "https://modules.example.test/_vf_modules", + vendorBundleHash: "vendor-a", + apiBaseUrl: "https://api.example.test", + studioEmbed: true, + dev: true, + }); + } finally { + Reflect.set(Array.prototype, "filter", originalFilter); + Reflect.set(Array.prototype, "join", originalJoin); + Reflect.set(Array.prototype, "push", originalPush); + } + + assertEquals( + identity, + `v${VERSION}:${DEFAULT_REACT_VERSION}:react:modules:40:https://modules.example.test/_vf_modules:vendor:8:vendor-a:api:24:https://api.example.test:studio:dev`, + ); + }); + it("should return a string", () => { const hash = computeConfigHashSync({}); assertEquals(typeof hash, "string"); diff --git a/src/cache/config-hash.ts b/src/cache/config-hash.ts index 138ca9abfc..cb23956fef 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -16,6 +16,17 @@ import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts const JSONStringify = JSON.stringify; const ObjectCreate = Object.create; +const ArrayPrototypeJoin = Array.prototype.join; +const ArrayPrototypePush = Array.prototype.push; +const ReflectApply = Reflect.apply; + +function arrayPush(values: string[], value: string): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + +function arrayJoin(values: string[], separator: string): string { + return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; +} /** * Configuration that affects transform output. @@ -77,25 +88,27 @@ export function computeConfigHash(config: TransformConfig): Promise { * Use this when you need a config hash but can't afford async overhead. */ export function computeConfigHashSync(config: TransformConfig): string { - const parts = [ - `v${VERSION}`, - config.reactVersion ?? DEFAULT_REACT_VERSION, - config.jsxImportSource ?? "react", - encodeConfigPart("modules", config.moduleServerUrl), - encodeConfigPart("vendor", config.vendorBundleHash), - encodeConfigPart("api", config.apiBaseUrl), - config.studioEmbed ? "studio" : "", - config.dev ? "dev" : "", - ].filter(Boolean); + const parts: string[] = []; + arrayPush(parts, `v${VERSION}`); + arrayPush(parts, config.reactVersion ?? DEFAULT_REACT_VERSION); + arrayPush(parts, config.jsxImportSource ?? "react"); + const moduleServerUrlPart = encodeConfigPart("modules", config.moduleServerUrl); + if (moduleServerUrlPart) arrayPush(parts, moduleServerUrlPart); + const vendorBundleHashPart = encodeConfigPart("vendor", config.vendorBundleHash); + if (vendorBundleHashPart) arrayPush(parts, vendorBundleHashPart); + const apiBaseUrlPart = encodeConfigPart("api", config.apiBaseUrl); + if (apiBaseUrlPart) arrayPush(parts, apiBaseUrlPart); + if (config.studioEmbed) arrayPush(parts, "studio"); + if (config.dev) arrayPush(parts, "dev"); const dependencyPinningCacheVariant = buildDependencyPinningCacheVariant( config.dependencyPinningCacheKey, config.moduleServerOrigin, ); if (dependencyPinningCacheVariant) { - parts.push(`pins:${dependencyPinningCacheVariant}`); + arrayPush(parts, `pins:${dependencyPinningCacheVariant}`); } - return parts.join(":"); + return arrayJoin(parts, ":"); } function encodeConfigPart(label: string, value: string | undefined): string { diff --git a/src/modules/README.md b/src/modules/README.md index bd92f3c815..c52ba8a2b0 100644 --- a/src/modules/README.md +++ b/src/modules/README.md @@ -142,12 +142,16 @@ module before importing it. ```typescript import { loadImportMap, mergeImportMaps, resolveImport } from "#veryfront/modules"; +import type { VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; export async function resolveReact( adapter: RuntimeAdapter, + requestConfig?: VeryfrontConfig, ) { - const projectMap = await loadImportMap("/workspace/site", adapter); + const projectMap = requestConfig + ? await loadImportMap("/workspace/site", adapter, requestConfig) + : await loadImportMap("/workspace/site", adapter); const overrides = { imports: { "@app/": "/_vf_modules/app/", @@ -162,7 +166,9 @@ export async function resolveReact( `mergeImportMaps` accepts maps as separate arguments. Later maps win for exact keys, while scoped maps are merged per scope. `loadImportMap` applies framework defaults, project `deno.json`, and Veryfront configuration in that order and -then enforces the framework React mappings. +then enforces the framework React mappings. Its optional third argument accepts +an already validated request configuration; without it, the loader discovers +the project configuration from the project path. ## Operational contracts diff --git a/src/modules/import-map/loader-primordial-poisoning.worker.ts b/src/modules/import-map/loader-primordial-poisoning.worker.ts index bee816176a..903b3f3a78 100644 --- a/src/modules/import-map/loader-primordial-poisoning.worker.ts +++ b/src/modules/import-map/loader-primordial-poisoning.worker.ts @@ -30,6 +30,7 @@ async function runRegression() { const original = { arrayFilter: Array.prototype.filter, arrayMap: Array.prototype.map, + arrayPush: Array.prototype.push, jsonParse: JSON.parse, objectAssign: Object.assign, objectEntries: Object.entries, @@ -46,6 +47,7 @@ async function runRegression() { try { Reflect.set(Array.prototype, "filter", poisoned); Reflect.set(Array.prototype, "map", poisoned); + Reflect.set(Array.prototype, "push", poisoned); Reflect.set(JSON, "parse", poisoned); Reflect.set(Object, "assign", poisoned); Reflect.set(Object, "entries", poisoned); @@ -58,6 +60,7 @@ async function runRegression() { } finally { Reflect.set(Array.prototype, "filter", original.arrayFilter); Reflect.set(Array.prototype, "map", original.arrayMap); + Reflect.set(Array.prototype, "push", original.arrayPush); Reflect.set(JSON, "parse", original.jsonParse); Reflect.set(Object, "assign", original.objectAssign); Reflect.set(Object, "entries", original.objectEntries); diff --git a/src/modules/import-map/preloader-primordial-poisoning.worker.ts b/src/modules/import-map/preloader-primordial-poisoning.worker.ts index 243fbc580c..85ed7c8eae 100644 --- a/src/modules/import-map/preloader-primordial-poisoning.worker.ts +++ b/src/modules/import-map/preloader-primordial-poisoning.worker.ts @@ -24,6 +24,7 @@ const configB = { async function runPoisoningRegression() { const original = { arrayMap: Array.prototype.map, + arrayPush: Array.prototype.push, arraySort: Array.prototype.sort, dateNow: Date.now, jsonStringify: JSON.stringify, @@ -43,6 +44,7 @@ async function runPoisoningRegression() { set: Set, setAdd: Set.prototype.add, setDelete: Set.prototype.delete, + setForEach: Set.prototype.forEach, setSize: Object.getOwnPropertyDescriptor(Set.prototype, "size")!, }; const poisoned = () => { @@ -58,6 +60,7 @@ async function runPoisoningRegression() { try { Reflect.set(Array.prototype, "map", poisoned); + Reflect.set(Array.prototype, "push", poisoned); Reflect.set(Array.prototype, "sort", poisoned); Reflect.set(Date, "now", poisoned); Reflect.set(JSON, "stringify", poisoned); @@ -80,6 +83,7 @@ async function runPoisoningRegression() { Reflect.set(globalThis, "Set", class PoisonedSet {}); Reflect.set(original.set.prototype, "add", poisoned); Reflect.set(original.set.prototype, "delete", poisoned); + Reflect.set(original.set.prototype, "forEach", poisoned); Object.defineProperty(original.set.prototype, "size", { configurable: true, get: poisoned, @@ -118,6 +122,7 @@ async function runPoisoningRegression() { evicted = await preloader.getCached("project", contextA); } finally { Reflect.set(Array.prototype, "map", original.arrayMap); + Reflect.set(Array.prototype, "push", original.arrayPush); Reflect.set(Array.prototype, "sort", original.arraySort); Reflect.set(Date, "now", original.dateNow); Reflect.set(JSON, "stringify", original.jsonStringify); @@ -134,6 +139,7 @@ async function runPoisoningRegression() { Reflect.set(original.promise, "resolve", original.promiseResolve); Reflect.set(original.set.prototype, "add", original.setAdd); Reflect.set(original.set.prototype, "delete", original.setDelete); + Reflect.set(original.set.prototype, "forEach", original.setForEach); Object.defineProperty(original.set.prototype, "size", original.setSize); Reflect.set(globalThis, "Map", original.map); Reflect.set(globalThis, "Promise", original.promise); diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index c51b8bbfe3..50bb0dd6e4 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1,5 +1,10 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals, assertRejects, assertThrows } from "#veryfront/testing/assert.ts"; +import { + assertEquals, + assertRejects, + assertStrictEquals, + assertThrows, +} from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { clearImportMapCache, @@ -7,7 +12,7 @@ import { ImportMapPreloader, preloadImportMap, } from "./preloader.ts"; -import { validateVeryfrontConfig } from "#veryfront/config"; +import { validateVeryfrontConfig, type VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/constants/limits.ts"; import type { ImportMapConfig } from "./types.ts"; @@ -74,7 +79,7 @@ describe("modules/import-map/preloader", () => { const map1 = await preloadImportMap("/test-cache-same", adapter); const map2 = await preloadImportMap("/test-cache-same", adapter); - assertEquals(map1, map2); + assertStrictEquals(map1, map2); }); it("should cache different projects independently", async () => { @@ -132,7 +137,7 @@ describe("modules/import-map/preloader", () => { }, ); - assertEquals(first, firstAgain); + assertStrictEquals(first, firstAgain); assertEquals(first.imports?.package, "https://example.com/package-v1.ts"); assertEquals(changed.imports?.package, "https://example.com/package-v2.ts"); assertEquals(first === changed, false); @@ -156,13 +161,13 @@ describe("modules/import-map/preloader", () => { }; }, }); - const config = validateVeryfrontConfig({ + const config = { resolve: { importMap: { imports: { package: "https://example.com/package-a.ts" }, }, }, - }); + } as VeryfrontConfig; const context = { contentSourceId: "release", config }; const firstPromise = preloader.preload( @@ -203,7 +208,7 @@ describe("modules/import-map/preloader", () => { ); assertEquals(first.imports?.package, "https://example.com/package-a.ts"); - assertEquals(cachedOriginal, first); + assertStrictEquals(cachedOriginal, first); assertEquals(changed.imports?.package, "https://example.com/package-b.ts"); assertEquals(loads, 2); }); @@ -598,7 +603,7 @@ describe("modules/import-map/preloader", () => { ); assertEquals(replacement === expired, false); - assertEquals(cachedReplacement, replacement); + assertStrictEquals(cachedReplacement, replacement); assertEquals(getLoads(), 2); }); @@ -662,7 +667,7 @@ describe("modules/import-map/preloader", () => { assertEquals(replacement === initial, false); assertEquals(duplicate, replacement); - assertEquals(cached, replacement); + assertStrictEquals(cached, replacement); assertEquals( await preloader.preload( "/concurrent-direct-expiry", @@ -781,7 +786,7 @@ describe("modules/import-map/preloader", () => { ); const reloaded = await postClear; assertEquals(reloaded.imports?.loaded, "2"); - assertEquals(await preloader.getCached("project-a", context), reloaded); + assertStrictEquals(await preloader.getCached("project-a", context), reloaded); }); it("does not publish pre-clear work into a new global generation", async () => { @@ -833,7 +838,7 @@ describe("modules/import-map/preloader", () => { loads[0]!.resolve({ imports: { source: "a" } }); const firstResult = await first; assertEquals(firstResult.imports?.source, "a"); - assertEquals(await sameKey, firstResult); + assertStrictEquals(await sameKey, firstResult); await waitForLoadCount(loads, 2); loads[1]!.resolve({ imports: { source: "b" } }); @@ -948,14 +953,22 @@ describe("modules/import-map/preloader", () => { evicted: boolean; loads: number; }>((resolve, reject) => { + const timeoutId = setTimeout( + () => reject(new Error("primordial poisoning worker timed out")), + 30_000, + ); worker.onmessage = (event) => { + clearTimeout(timeoutId); const message = event.data as | { ok: true; result: Parameters[0] } | { ok: false; error: string }; if (message.ok) resolve(message.result); else reject(new Error(message.error)); }; - worker.onerror = (event) => reject(event.error ?? new Error(event.message)); + worker.onerror = (event) => { + clearTimeout(timeoutId); + reject(event.error ?? new Error(event.message)); + }; }); assertEquals(result.firstLoaded, "1"); diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index b1d7464bb0..49caab140b 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -28,6 +28,7 @@ const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeSort = Array.prototype.sort; const DateNow = Date.now; const IntrinsicMap = Map; +const IntrinsicPerformance = performance; const IntrinsicPromise = Promise; const IntrinsicRangeError = RangeError; const IntrinsicSet = Set; @@ -50,6 +51,7 @@ const ObjectFreeze = Object.freeze; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; +const PerformanceNow = IntrinsicPerformance.now; const ReflectApply = Reflect.apply; const SetPrototypeAdd = Set.prototype.add; const SetPrototypeDelete = Set.prototype.delete; @@ -72,6 +74,10 @@ function arrayPush(values: T[], value: T): void { ReflectApply(ArrayPrototypePush, values, [value]); } +function monotonicNow(): number { + return ReflectApply(PerformanceNow, IntrinsicPerformance, []) as number; +} + function mapClear(map: Map): void { ReflectApply(MapPrototypeClear, map, []); } @@ -172,9 +178,9 @@ export interface ImportMapPreloaderOptions { loadImportMap?: typeof loadImportMap; } -function compareEntries( - left: readonly [string, string], - right: readonly [string, string], +function compareEntries( + left: readonly [string, T], + right: readonly [string, U], ): number { return left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0; } @@ -264,7 +270,7 @@ function buildVariantCanonicalIdentity( const scopes = arraySort( ObjectEntries(importMap?.scopes ?? {}), - (left, right) => left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0, + compareEntries, ); for (let scopeIndex = 0; scopeIndex < scopes.length; scopeIndex++) { const scopeEntry = scopes[scopeIndex]!; @@ -439,7 +445,7 @@ export class ImportMapPreloader { weakSetHas(this.capacityErrors, error); } - private waitForActiveWork(): Promise { + private waitForActiveWork(timeoutMs: number): Promise { const activeWork: Array> = []; setForEach(this.activeLoads, (promise) => arrayPush(activeWork, promise)); setForEach(this.activeIdentityBuilds, (promise) => arrayPush(activeWork, promise)); @@ -455,7 +461,7 @@ export class ImportMapPreloader { const timeout = new IntrinsicPromise((_, reject) => { timeoutId = SetTimeout(() => { reject(new IntrinsicRangeError("Import-map preloader capacity wait timed out")); - }, this.loadTimeoutMs); + }, timeoutMs); }); return promiseThen( racePromises([settled, timeout]), @@ -654,12 +660,15 @@ export class ImportMapPreloader { projectId?: string, context?: PreloadImportMapContext, ): Promise { + const capacityDeadline = monotonicNow() + this.loadTimeoutMs; for (;;) { try { return await this.preloadOnce(projectDir, adapter, projectId, context); } catch (error) { if (!this.isCapacityError(error)) throw error; - await this.waitForActiveWork(); + const remainingMs = capacityDeadline - monotonicNow(); + if (remainingMs <= 0) throw error; + await this.waitForActiveWork(remainingMs); } } } diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index eb1e774f06..73a39c8a1d 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -67,6 +67,20 @@ describe("transforms/esm/http-cache-helpers", () => { assertNotEquals(first, second); }); + it("preserves the established v2 canonical fingerprint bytes", async () => { + assertEquals( + await fingerprintImportMap({ + imports: { package: "https://example.com/package.ts" }, + scopes: { + "https://example.com/": { + scoped: "https://example.com/scoped.ts", + }, + }, + }), + "c0cef34844a37f56972214c773cc169cec17fa1fdd05f80add96f1821ff4650a", + ); + }); + it("frames URL and React version components without delimiter collisions", async () => { const importMap = { imports: {}, scopes: {} }; diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index 9050a56f0e..cc743c6265 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -62,6 +62,30 @@ describe("transform pipeline cache identity", () => { assertEquals(Object.isFrozen(snapshot.imports), true); }); + it("rejects import maps that exceed the entry budget", () => { + const imports = Object.create(null) as Record; + for (let index = 0; index <= 20_000; index++) { + imports[`package-${index}`] = `/package-${index}.ts`; + } + + assertThrows( + () => snapshotImportMap({ imports }), + TypeError, + "too many entries", + ); + }); + + it("rejects import-map strings that exceed the per-field byte budget", () => { + assertThrows( + () => + snapshotImportMap({ + imports: { package: "x".repeat(64 * 1024 + 1) }, + }), + TypeError, + "too large", + ); + }); + it("fingerprints import maps independent of insertion order", async () => { const first = snapshotImportMap({ imports: { a: "/a.ts", b: "/b.ts" } }); const reordered = snapshotImportMap({ imports: { b: "/b.ts", a: "/a.ts" } }); @@ -295,6 +319,14 @@ describe("transform pipeline cache identity", () => { stringTrim: String.prototype.trim, textEncoderEncode: TextEncoder.prototype.encode, typeError: TypeError, + uint8ArrayByteLength: Object.getOwnPropertyDescriptor( + Uint8Array.prototype, + "byteLength", + ), + uint8ArrayLength: Object.getOwnPropertyDescriptor( + Uint8Array.prototype, + "length", + ), }; const rawImportMap = { imports: { package: "https://example.com/package.ts" }, @@ -358,6 +390,14 @@ describe("transform pipeline cache identity", () => { "TypeError", class PoisonedTypeError extends Error {}, ); + Object.defineProperty(Uint8Array.prototype, "byteLength", { + configurable: true, + get: () => 0, + }); + Object.defineProperty(Uint8Array.prototype, "length", { + configurable: true, + get: () => 0, + }); snapshot = snapshotImportMap(rawImportMap); [fingerprint, pipelineIdentity] = await Promise.all([ @@ -400,6 +440,24 @@ describe("transform pipeline cache identity", () => { Reflect.set(String.prototype, "trim", original.stringTrim); Reflect.set(TextEncoder.prototype, "encode", original.textEncoderEncode); Reflect.set(globalThis, "TypeError", original.typeError); + if (original.uint8ArrayByteLength) { + Object.defineProperty( + Uint8Array.prototype, + "byteLength", + original.uint8ArrayByteLength, + ); + } else { + Reflect.deleteProperty(Uint8Array.prototype, "byteLength"); + } + if (original.uint8ArrayLength) { + Object.defineProperty( + Uint8Array.prototype, + "length", + original.uint8ArrayLength, + ); + } else { + Reflect.deleteProperty(Uint8Array.prototype, "length"); + } } assertEquals(snapshot?.imports?.package, "https://example.com/package.ts"); diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index dc7ac7185e..3aa1f4a46c 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -16,6 +16,7 @@ const MAX_CUSTOM_PLUGINS = 1_000; const ArrayIsArray = Array.isArray; const ArrayPrototypePush = Array.prototype.push; const IntrinsicTextEncoder = TextEncoder; +const IntrinsicUint8Array = Uint8Array; const IntrinsicRangeError = RangeError; const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; @@ -35,6 +36,20 @@ const StringPrototypeTrim = String.prototype.trim; const TextEncoderPrototypeEncode = TextEncoder.prototype.encode; const controlCharacterPattern = /\p{Cc}/u; const encoder = new IntrinsicTextEncoder(); +const TypedArrayPrototype = ObjectGetPrototypeOf(IntrinsicUint8Array.prototype); +const TypedArrayByteLengthGetter = ObjectGetOwnPropertyDescriptor( + TypedArrayPrototype, + "byteLength", +)!.get!; + +function encodedByteLength(value: string): number { + const bytes = ReflectApply( + TextEncoderPrototypeEncode, + encoder, + [value], + ) as Uint8Array; + return ReflectApply(TypedArrayByteLengthGetter, bytes, []) as number; +} function hasOwn(object: object, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; @@ -60,9 +75,7 @@ function countIdentityString( label: string, maxBytes = MAX_IDENTITY_STRING_BYTES, ): string { - const bytes = ( - ReflectApply(TextEncoderPrototypeEncode, encoder, [value]) as Uint8Array - ).byteLength; + const bytes = encodedByteLength(value); if (bytes > maxBytes) throw new IntrinsicTypeError(`${label} is too large`); budget.bytes += bytes; if (budget.bytes > MAX_IMPORT_MAP_IDENTITY_BYTES) { @@ -202,18 +215,25 @@ export type CustomPluginCacheIdentity = export function getCustomPluginCacheIdentity( plugins: readonly TransformPlugin[] | undefined, ): CustomPluginCacheIdentity { - if (!plugins || plugins.length === 0) { + if (plugins === undefined) { + return { cacheable: true, identity: ObjectFreeze([]) }; + } + if (!ArrayIsArray(plugins)) { + throw new IntrinsicTypeError("Transform pipeline plugins must be an array"); + } + const pluginCount = readArrayLength(plugins, "Transform pipeline plugins"); + if (pluginCount === 0) { return { cacheable: true, identity: ObjectFreeze([]) }; } - if (plugins.length > MAX_CUSTOM_PLUGINS) { + if (pluginCount > MAX_CUSTOM_PLUGINS) { throw new IntrinsicRangeError( `Transform pipeline cannot contain more than ${MAX_CUSTOM_PLUGINS} plugins`, ); } const identity: Array = []; - for (let index = 0; index < plugins.length; index++) { - const plugin = plugins[index]; + for (let index = 0; index < pluginCount; index++) { + const plugin = readArrayElement(plugins, index, "Transform pipeline plugins"); if (plugin === null || typeof plugin !== "object") { throw new IntrinsicTypeError(`Transform plugin at index ${index} must be an object`); } @@ -242,9 +262,7 @@ export function getCustomPluginCacheIdentity( } if ( typeof cacheIdentity !== "string" || cacheIdentity.length === 0 || - (ReflectApply(TextEncoderPrototypeEncode, encoder, [cacheIdentity]) as Uint8Array) - .byteLength > - MAX_PLUGIN_IDENTITY_BYTES + encodedByteLength(cacheIdentity) > MAX_PLUGIN_IDENTITY_BYTES ) { throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid cacheIdentity`); } @@ -263,9 +281,7 @@ function boundedOption(value: unknown, label: string): string | null { throw new IntrinsicTypeError(`${label} must be a string`); } if ( - (ReflectApply(TextEncoderPrototypeEncode, encoder, [value]) as Uint8Array) - .byteLength > - MAX_IDENTITY_STRING_BYTES + encodedByteLength(value) > MAX_IDENTITY_STRING_BYTES ) { throw new IntrinsicTypeError(`${label} is too large for transform cache identity`); } @@ -346,8 +362,7 @@ function encodeCustomPluginIdentities( } if ( typeof cacheIdentity !== "string" || cacheIdentity.length === 0 || - (ReflectApply(TextEncoderPrototypeEncode, encoder, [cacheIdentity]) as Uint8Array) - .byteLength > MAX_PLUGIN_IDENTITY_BYTES + encodedByteLength(cacheIdentity) > MAX_PLUGIN_IDENTITY_BYTES ) { throw new IntrinsicTypeError( `Custom plugin identity ${index} has an invalid cache identity`, diff --git a/src/utils/hash-utils.test.ts b/src/utils/hash-utils.test.ts index b832d5bc38..8ef9d0154f 100644 --- a/src/utils/hash-utils.test.ts +++ b/src/utils/hash-utils.test.ts @@ -33,6 +33,47 @@ describe("hash-utils", () => { const hash = await computeHash("こんにちは世界"); assertEquals(hash.length, 64); }); + + it("uses captured typed-array accessors after prototype poisoning", async () => { + const lengthDescriptor = Object.getOwnPropertyDescriptor( + Uint8Array.prototype, + "length", + ); + const byteLengthDescriptor = Object.getOwnPropertyDescriptor( + Uint8Array.prototype, + "byteLength", + ); + let hash: string | undefined; + try { + Object.defineProperty(Uint8Array.prototype, "length", { + configurable: true, + get: () => 0, + }); + Object.defineProperty(Uint8Array.prototype, "byteLength", { + configurable: true, + get: () => 0, + }); + hash = await computeHash("typed-array-accessor-regression"); + } finally { + if (lengthDescriptor) { + Object.defineProperty(Uint8Array.prototype, "length", lengthDescriptor); + } else { + Reflect.deleteProperty(Uint8Array.prototype, "length"); + } + if (byteLengthDescriptor) { + Object.defineProperty( + Uint8Array.prototype, + "byteLength", + byteLengthDescriptor, + ); + } else { + Reflect.deleteProperty(Uint8Array.prototype, "byteLength"); + } + } + + assertEquals(hash?.length, 64); + assertEquals(hash, await computeHash("typed-array-accessor-regression")); + }); }); describe("computeCodeHash", () => { diff --git a/src/utils/hash-utils.ts b/src/utils/hash-utils.ts index f2e0785702..bd576f59e4 100644 --- a/src/utils/hash-utils.ts +++ b/src/utils/hash-utils.ts @@ -10,17 +10,29 @@ const SHORT_HASH_LENGTH = 8; const IntrinsicTextEncoder = TextEncoder; const IntrinsicUint8Array = Uint8Array; const NumberPrototypeToString = Number.prototype.toString; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectGetPrototypeOf = Object.getPrototypeOf; const ReflectApply = Reflect.apply; const StringPrototypePadStart = String.prototype.padStart; const SubtleCryptoDigest = crypto.subtle.digest; const TextEncoderPrototypeEncode = TextEncoder.prototype.encode; const cryptoSubtle = crypto.subtle; const hashTextEncoder = new IntrinsicTextEncoder(); +const TypedArrayPrototype = ObjectGetPrototypeOf(IntrinsicUint8Array.prototype); +const TypedArrayLengthGetter = ObjectGetOwnPropertyDescriptor( + TypedArrayPrototype, + "length", +)!.get!; + +function typedArrayLength(value: Uint8Array): number { + return ReflectApply(TypedArrayLengthGetter, value, []) as number; +} function toHex(buffer: ArrayBuffer): string { const bytes = new IntrinsicUint8Array(buffer); let result = ""; - for (let index = 0; index < bytes.length; index++) { + const length = typedArrayLength(bytes); + for (let index = 0; index < length; index++) { const hex = ReflectApply(NumberPrototypeToString, bytes[index], [16]) as string; result += ReflectApply(StringPrototypePadStart, hex, [2, "0"]) as string; } From 0056d6a34e30f2b6e2b6fa33605fd1dea0bda602 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 13:06:03 +0200 Subject: [PATCH 10/34] chore(modules): remove redundant context guard --- src/modules/import-map/preloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 49caab140b..265f445779 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -198,7 +198,7 @@ function snapshotPreloadContext( throw new IntrinsicTypeError("Import-map projectDir must be a string"); } if (!context) return ObjectFreeze({ projectDir }); - if (context === null || typeof context !== "object") { + if (typeof context !== "object") { throw new IntrinsicTypeError("Import-map context must be an object"); } const contentSourceDescriptor = ObjectGetOwnPropertyDescriptor( From 3777ddff28e33b80677f0002b65e9466da08ffa1 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 13:21:23 +0200 Subject: [PATCH 11/34] fix(modules): capture descriptor primitive before cache getters --- src/modules/import-map/preloader.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 265f445779..a2e6c8cd03 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -35,12 +35,13 @@ const IntrinsicSet = Set; const IntrinsicWeakSet = WeakSet; const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const MapPrototypeClear = Map.prototype.clear; const MapPrototypeDelete = Map.prototype.delete; const MapPrototypeForEach = Map.prototype.forEach; const MapPrototypeGet = Map.prototype.get; const MapPrototypeSet = Map.prototype.set; -const MapPrototypeSize = Object.getOwnPropertyDescriptor(Map.prototype, "size")! +const MapPrototypeSize = ObjectGetOwnPropertyDescriptor(Map.prototype, "size")! .get!; const MathMin = Math.min; const NUMBER_MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; @@ -48,7 +49,6 @@ const NumberIsFinite = Number.isFinite; const NumberIsSafeInteger = Number.isSafeInteger; const ObjectEntries = Object.entries; const ObjectFreeze = Object.freeze; -const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; const PerformanceNow = IntrinsicPerformance.now; @@ -56,7 +56,7 @@ const ReflectApply = Reflect.apply; const SetPrototypeAdd = Set.prototype.add; const SetPrototypeDelete = Set.prototype.delete; const SetPrototypeForEach = Set.prototype.forEach; -const SetPrototypeSize = Object.getOwnPropertyDescriptor(Set.prototype, "size")! +const SetPrototypeSize = ObjectGetOwnPropertyDescriptor(Set.prototype, "size")! .get!; const WeakSetPrototypeAdd = WeakSet.prototype.add; const WeakSetPrototypeHas = WeakSet.prototype.has; From 37313744467938403f47525c9b4bc3d425d112e7 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 13:31:28 +0200 Subject: [PATCH 12/34] Keep the review gate meaningful under parallel scheduler load Replace single-tick concurrent-render assumptions with bounded condition waits and give the RAG lock probe a realistic deadline while clearing its timer. The production behavior remains unchanged; these tests now distinguish a real deadlock or ownership error from scheduler contention. Constraint: The mandatory pre-push suite runs thousands of tests in parallel and repeatedly delayed these probes beyond their prior 0 ms and 50 ms assumptions. Rejected: Bypass the pre-push hook | would publish without the repository-required verification. Rejected: Remove the concurrency assertions | would weaken regression coverage. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep concurrency tests event-driven or bounded by realistic deadlines; do not depend on one scheduler tick. Tested: Three affected test files (3 suites, 80 steps); repeated upload and registry suites under parallel process load; format and lint. Not-tested: Full pre-push suite after this test-only commit; it will rerun on push. --- src/embedding/rag-store.test.ts | 18 +++++++++++++----- .../chat/chat/hooks/use-upload.test.tsx | 6 +++++- .../chat/hooks/use-uploads-registry.test.tsx | 6 +++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/embedding/rag-store.test.ts b/src/embedding/rag-store.test.ts index 0e90bdc72e..82bc7a8131 100644 --- a/src/embedding/rag-store.test.ts +++ b/src/embedding/rag-store.test.ts @@ -748,11 +748,19 @@ describe("ragStore", () => { const searchPromise = store.search("needle"); await queryEmbeddingStarted; - const documents = await Promise.race([ - store.listDocuments(), - new Promise<"blocked">((resolve) => setTimeout(() => resolve("blocked"), 50)), - ]); - releaseQueryEmbedding(); + let blockedTimer: ReturnType | undefined; + let documents: Awaited> | "blocked"; + try { + documents = await Promise.race([ + store.listDocuments(), + new Promise<"blocked">((resolve) => { + blockedTimer = setTimeout(() => resolve("blocked"), 5_000); + }), + ]); + } finally { + if (blockedTimer !== undefined) clearTimeout(blockedTimer); + releaseQueryEmbedding(); + } await searchPromise; assert(Array.isArray(documents)); diff --git a/src/react/components/chat/chat/hooks/use-upload.test.tsx b/src/react/components/chat/chat/hooks/use-upload.test.tsx index 4a5c8c87ba..9aeba06c80 100644 --- a/src/react/components/chat/chat/hooks/use-upload.test.tsx +++ b/src/react/components/chat/chat/hooks/use-upload.test.tsx @@ -9,6 +9,7 @@ import { assertThrows, } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { waitFor } from "#veryfront/testing/deno-compat.ts"; import { createUploadId, parseChatUploadResponse, @@ -650,7 +651,10 @@ describe("useUpload", () => { , ); }); - await new Promise((resolve) => setTimeout(resolve, 0)); + await waitFor(() => attemptedSuspendedRender, { + interval: 1, + message: "Concurrent upload render did not start", + }); assertEquals(attemptedSuspendedRender, true); assertEquals(request.aborted, false); diff --git a/src/react/components/chat/chat/hooks/use-uploads-registry.test.tsx b/src/react/components/chat/chat/hooks/use-uploads-registry.test.tsx index 18d1e18af4..db55bbb9d5 100644 --- a/src/react/components/chat/chat/hooks/use-uploads-registry.test.tsx +++ b/src/react/components/chat/chat/hooks/use-uploads-registry.test.tsx @@ -5,6 +5,7 @@ import { JSDOM } from "npm:jsdom@28.0.0"; import { unmountReactRoot } from "#veryfront/react/react-root.test-helpers.ts"; import { assert, assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { waitFor } from "#veryfront/testing/deno-compat.ts"; import { useAttachments, useUploadsRegistry, @@ -1055,7 +1056,10 @@ describe("react/components/chat/hooks/useUploadsRegistry", () => { , ); }); - await new Promise((resolve) => setTimeout(resolve, 0)); + await waitFor(() => attemptedSuspendedRender, { + interval: 1, + message: "Concurrent endpoint render did not start", + }); assertEquals(attemptedSuspendedRender, true); assertEquals(pending[0]?.signal?.aborted, false); assertEquals(pending.length, 1, "an uncommitted scope must not start a refresh"); From 9a21a7321eada218dd540743269b4adbdd0de7e3 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 14:12:45 +0200 Subject: [PATCH 13/34] fix(modules): close import-map review gaps --- src/embedding/rag-store.test.ts | 11 ++- src/modules/import-map/loader.test.ts | 83 ++++++++++++++++++- src/modules/import-map/loader.ts | 58 ++++++++++--- .../request/agent-stream.handler.test.ts | 3 +- .../import-rewriter/url-builder.test.ts | 5 ++ src/transforms/import-rewriter/url-builder.ts | 64 ++++++++++---- 6 files changed, 189 insertions(+), 35 deletions(-) diff --git a/src/embedding/rag-store.test.ts b/src/embedding/rag-store.test.ts index 8876b0ac85..754a19c77a 100644 --- a/src/embedding/rag-store.test.ts +++ b/src/embedding/rag-store.test.ts @@ -839,11 +839,12 @@ describe("ragStore", () => { const searchPromise = store.search("needle"); await queryEmbeddingStarted; + const listDocumentsPromise = store.listDocuments(); let blockedTimer: ReturnType | undefined; - let documents: Awaited> | "blocked"; + let observedDocuments: Awaited> | "blocked"; try { - documents = await Promise.race([ - store.listDocuments(), + observedDocuments = await Promise.race([ + listDocumentsPromise, new Promise<"blocked">((resolve) => { blockedTimer = setTimeout(() => resolve("blocked"), 5_000); }), @@ -852,9 +853,11 @@ describe("ragStore", () => { if (blockedTimer !== undefined) clearTimeout(blockedTimer); releaseQueryEmbedding(); } + const documents = await listDocumentsPromise; await searchPromise; - assert(Array.isArray(documents)); + assert(Array.isArray(observedDocuments)); + assertEquals(observedDocuments, documents); assertEquals(documents.length, 1); assertEquals(documents[0]?.title, "Doc"); }); diff --git a/src/modules/import-map/loader.test.ts b/src/modules/import-map/loader.test.ts index 11da0df371..ffb195ae93 100644 --- a/src/modules/import-map/loader.test.ts +++ b/src/modules/import-map/loader.test.ts @@ -1,6 +1,8 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assert, assertEquals, assertExists } from "#veryfront/testing/assert.ts"; +import { assert, assertEquals, assertExists, assertRejects } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; +import { VeryfrontError } from "#veryfront/errors"; +import type { VeryfrontConfig } from "#veryfront/config"; import { createMockAdapter } from "#veryfront/platform/adapters/mock.ts"; import { loadImportMap } from "./loader.ts"; @@ -21,6 +23,85 @@ describe("modules/import-map/loader", () => { assertExists(imports); assert("react" in imports, "should include react mapping"); assert("react-dom" in imports, "should include react-dom mapping"); + assert("react/" in imports, "should include authoritative react prefix mapping"); + assert("react-dom/" in imports, "should include authoritative react-dom prefix mapping"); + }); + + it("keeps React package prefixes authoritative over project mappings", async () => { + const adapter = createMockAdapter(); + const config = { + resolve: { + importMap: { + imports: { + "react/": "https://project.example/react/", + "react/compiler-runtime": "https://project.example/react-compiler.js", + "react-dom/": "https://project.example/react-dom/", + "react-dom/static": "https://project.example/react-dom-static.js", + }, + scopes: { + "/app/": { + react: "https://project.example/scoped-react.js", + "react-dom/static": "https://project.example/scoped-react-dom.js", + "veryfront/router": "https://project.example/scoped-router.js", + package: "https://project.example/package.js", + }, + }, + }, + }, + } as VeryfrontConfig; + + const { imports, scopes } = await loadImportMap("/any-project", adapter, config); + + assertExists(imports); + assertExists(scopes); + assertEquals(imports["react/"]?.startsWith("https://esm.sh/react@"), true); + assertEquals(imports["react-dom/"]?.startsWith("https://esm.sh/react-dom@"), true); + assertEquals(imports["react/"]?.endsWith("/"), true); + assertEquals(imports["react-dom/"]?.endsWith("/"), true); + assertEquals(imports["react/compiler-runtime"], undefined); + assertEquals(imports["react-dom/static"], undefined); + assertEquals(scopes["/app/"]?.react, undefined); + assertEquals(scopes["/app/"]?.["react-dom/static"], undefined); + assertEquals(scopes["/app/"]?.["veryfront/router"], undefined); + assertEquals(scopes["/app/"]?.package, "https://project.example/package.js"); + }); + + it("throws the registered import-map error for malformed explicit config", async () => { + const adapter = createMockAdapter(); + const config = { + resolve: { + importMap: { + imports: { package: 42 }, + }, + }, + } as unknown as VeryfrontConfig; + + const error = await assertRejects(() => loadImportMap("/any-project", adapter, config)); + + assert(error instanceof VeryfrontError); + assertEquals(error.slug, "import-map-invalid"); + assertEquals(error.detail, "Veryfront config resolve importMap is invalid"); + assertEquals(error.detail?.includes("42"), false); + }); + + it("rejects config accessors without invoking project code", async () => { + const adapter = createMockAdapter(); + let accessorCalls = 0; + const config = {} as VeryfrontConfig; + Object.defineProperty(config, "resolve", { + enumerable: true, + get() { + accessorCalls++; + return {}; + }, + }); + + const error = await assertRejects(() => loadImportMap("/any-project", adapter, config)); + + assert(error instanceof VeryfrontError); + assertEquals(error.slug, "import-map-invalid"); + assertEquals(error.detail, "Veryfront config cannot contain accessor properties"); + assertEquals(accessorCalls, 0); }); it("should include veryfront framework mappings", async () => { diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index 39c12d2346..2a8892f792 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -1,4 +1,5 @@ import { getConfig, type VeryfrontConfig } from "#veryfront/config"; +import { IMPORT_MAP_INVALID, isVeryfrontError } from "#veryfront/errors"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { isVirtualFilesystem } from "#veryfront/platform/adapters/fs/wrapper.ts"; @@ -15,7 +16,6 @@ import type { ImportMapConfig } from "./types.ts"; // executable modules so replacing shared globals cannot redirect resolution. const JSONParse = JSON.parse; const ArrayIsArray = Array.isArray; -const IntrinsicTypeError = TypeError; const ObjectCreate = Object.create; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectPrototype = Object.prototype; @@ -41,6 +41,23 @@ function stringSlice(value: string, start: number, end?: number): string { ) as string; } +function isFrameworkOwnedSpecifier(specifier: string): boolean { + return specifier === "react" || specifier === "react-dom" || + stringStartsWith(specifier, "react/") || + stringStartsWith(specifier, "react-dom/") || + stringStartsWith(specifier, "veryfront/"); +} + +function removeFrameworkOwnedMappings(record: Record): void { + const keys = ReflectOwnKeys(record); + for (let index = 0; index < keys.length; index++) { + const key = keys[index]; + if (typeof key === "string" && isFrameworkOwnedSpecifier(key)) { + delete record[key]; + } + } +} + function readOwnDataProperty( value: object, key: PropertyKey, @@ -49,18 +66,20 @@ function readOwnDataProperty( const descriptor = ObjectGetOwnPropertyDescriptor(value, key); if (!descriptor) return undefined; if (!("value" in descriptor)) { - throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + throw IMPORT_MAP_INVALID.create({ + detail: `${label} cannot contain accessor properties`, + }); } return descriptor.value; } function assertPlainObject(value: unknown, label: string): asserts value is object { if (value === null || typeof value !== "object" || ArrayIsArray(value)) { - throw new IntrinsicTypeError(`${label} must be a plain object`); + throw IMPORT_MAP_INVALID.create({ detail: `${label} must be a plain object` }); } const prototype = ObjectGetPrototypeOf(value); if (prototype !== ObjectPrototype && prototype !== null) { - throw new IntrinsicTypeError(`${label} must be a plain object`); + throw IMPORT_MAP_INVALID.create({ detail: `${label} must be a plain object` }); } } @@ -140,11 +159,13 @@ function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConf descriptor.value as Readonly>, true, ); + removeFrameworkOwnedMappings(scopes[scope]); } // Framework and React mappings are authoritative, guaranteeing one React - // instance and preventing a project npm override from redirecting core code. - delete imports["react/"]; + // instance and preventing exact, prefix, or scoped project overrides from + // redirecting core code. + removeFrameworkOwnedMappings(imports); const defaultImports = DEFAULT_IMPORT_MAP.imports ?? ObjectCreate(null); const defaultKeys = ReflectOwnKeys(defaultImports); for (let index = 0; index < defaultKeys.length; index++) { @@ -214,13 +235,24 @@ async function loadDenoJsonImportMap( } function getConfigImportMap(config: VeryfrontConfig): ImportMapConfig | null { - assertPlainObject(config, "Veryfront config"); - const resolve = readOwnDataProperty(config, "resolve", "Veryfront config"); - if (resolve === undefined) return null; - assertPlainObject(resolve, "Veryfront config resolve"); - const importMap = readOwnDataProperty(resolve, "importMap", "Veryfront config resolve"); - if (importMap === undefined || importMap === null) return null; - return snapshotImportMap(importMap); + try { + assertPlainObject(config, "Veryfront config"); + const resolve = readOwnDataProperty(config, "resolve", "Veryfront config"); + if (resolve === undefined) return null; + assertPlainObject(resolve, "Veryfront config resolve"); + const importMap = readOwnDataProperty( + resolve, + "importMap", + "Veryfront config resolve", + ); + if (importMap === undefined || importMap === null) return null; + return snapshotImportMap(importMap); + } catch (error) { + if (isVeryfrontError(error)) throw error; + throw IMPORT_MAP_INVALID.create({ + detail: "Veryfront config resolve importMap is invalid", + }); + } } export function loadImportMap( diff --git a/src/server/handlers/request/agent-stream.handler.test.ts b/src/server/handlers/request/agent-stream.handler.test.ts index 1d00351779..622be07239 100644 --- a/src/server/handlers/request/agent-stream.handler.test.ts +++ b/src/server/handlers/request/agent-stream.handler.test.ts @@ -1937,8 +1937,9 @@ describe("server/handlers/request/agent-stream.handler", () => { Deno.env.delete("VERYFRONT_API_BASE_URL"); globalThis.fetch = ((url, init) => { fetchUrls.push(String(url)); + const headers = init && "headers" in init ? init.headers : undefined; assertEquals( - new Headers(init?.headers).get("authorization"), + new Headers(headers).get("authorization"), "Bearer request-scoped-user-token", ); assertEquals( diff --git a/src/transforms/import-rewriter/url-builder.test.ts b/src/transforms/import-rewriter/url-builder.test.ts index 179da3f110..e0a649816a 100644 --- a/src/transforms/import-rewriter/url-builder.test.ts +++ b/src/transforms/import-rewriter/url-builder.test.ts @@ -250,11 +250,16 @@ describe("transforms/import-rewriter/url-builder", () => { "react/jsx-runtime", "react/jsx-dev-runtime", "react/", + "react-dom/", ] as const; for (const key of keys) { assertEquals(typeof map[key], "string"); } + + assertEquals(map["react/"]?.endsWith("/"), true); + assertEquals(map["react-dom/"]?.endsWith("/"), true); + assertEquals(map["react-dom/"]?.includes("&external=react"), true); }); }); diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index 29feba1531..fe1eb1a977 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -40,20 +40,7 @@ function arrayPush(values: string[], value: string): void { ReflectApply(ArrayPrototypePush, values, [value]); } -/** - * Build esm.sh URL with proper configuration. - * - * @param pkg - Package name (e.g., "react", "lodash") - * @param version - Package version (optional) - * @param subpath - Subpath (e.g., "/jsx-runtime") - * @param options - URL options - */ -export function buildEsmShUrl( - pkg: string, - version?: string, - subpath?: string, - options?: EsmShOptions, -): string { +function buildEsmShParams(options?: EsmShOptions): string[] { const params: string[] = []; if (options?.external?.length) { @@ -72,6 +59,25 @@ export function buildEsmShUrl( arrayPush(params, `deps=${arrayJoin(deps, ",")}`); } + return params; +} + +/** + * Build esm.sh URL with proper configuration. + * + * @param pkg - Package name (e.g., "react", "lodash") + * @param version - Package version (optional) + * @param subpath - Subpath (e.g., "/jsx-runtime") + * @param options - URL options + */ +export function buildEsmShUrl( + pkg: string, + version?: string, + subpath?: string, + options?: EsmShOptions, +): string { + const params = buildEsmShParams(options); + const versionStr = version ? `@${version}` : ""; const pathStr = subpath ?? ""; const queryStr = params.length ? `?${arrayJoin(params, "&")}` : ""; @@ -79,6 +85,20 @@ export function buildEsmShUrl( return `https://esm.sh/${pkg}${versionStr}${pathStr}${queryStr}`; } +/** + * Build an esm.sh package-prefix URL. esm.sh's `&option/` form keeps the + * trailing slash required by the import-map prefix-matching algorithm. + */ +function buildEsmShPrefixUrl( + pkg: string, + version: string, + options?: EsmShOptions, +): string { + const params = buildEsmShParams(options); + const optionStr = params.length ? `&${arrayJoin(params, "&")}` : ""; + return `https://esm.sh/${pkg}@${version}${optionStr}/`; +} + /** * Build React esm.sh URL. * Uses deps=csstype for type consistency. @@ -95,6 +115,16 @@ export function buildReactUrl( }); } +function buildReactPrefixUrl( + pkg: "react" | "react-dom", + version: string, +): string { + return buildEsmShPrefixUrl(pkg, version, { + external: ["react"], + deps: { csstype: CSSTYPE_VERSION }, + }); +} + /** * Get complete React import map for a specific version. */ @@ -106,8 +136,10 @@ export function getReactImportMap(version: string): Record { "react-dom/server": buildReactUrl("react-dom", version, "/server", true), "react/jsx-runtime": buildReactUrl("react", version, "/jsx-runtime", true), "react/jsx-dev-runtime": buildReactUrl("react", version, "/jsx-dev-runtime", true), - // Prefix match for any react/* subpath imports - "react/": buildReactUrl("react", version, "/", true), + // Prefix matches cover future package exports without allowing a project + // import map to redirect React or ReactDOM subpaths. + "react/": buildReactPrefixUrl("react", version), + "react-dom/": buildReactPrefixUrl("react-dom", version), }; } From 7276df18569eeeae234cc28ae99512c43423046a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 14:21:21 +0200 Subject: [PATCH 14/34] refactor(platform): centralize trusted array primordials --- src/build/production-build/templates.ts | 2 +- src/cache/config-hash.ts | 15 ++-- src/modules/import-map/preloader.ts | 17 ++--- src/platform/compat/path/portable.ts | 32 +++------ src/platform/compat/primordials/array.test.ts | 69 +++++++++++++++++++ src/platform/compat/primordials/array.ts | 55 +++++++++++++++ .../rsc/endpoints/rsc-bundles.generated.ts | 4 +- src/transforms/esm/http-cache-helpers.ts | 37 ++++------ src/transforms/import-rewriter/url-builder.ts | 16 ++--- 9 files changed, 164 insertions(+), 83 deletions(-) create mode 100644 src/platform/compat/primordials/array.test.ts create mode 100644 src/platform/compat/primordials/array.ts diff --git a/src/build/production-build/templates.ts b/src/build/production-build/templates.ts index da0f1d390a..5222ed6a22 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/transforms/import-rewriter/url-builder.ts\nvar ArrayPrototypeJoin = Array.prototype.join;\nvar ArrayPrototypePush = Array.prototype.push;\n\n// src/utils/constants/http.ts\nvar KB_IN_BYTES = 1024;\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 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/cache/config-hash.ts b/src/cache/config-hash.ts index cb23956fef..7dfeb9102e 100644 --- a/src/cache/config-hash.ts +++ b/src/cache/config-hash.ts @@ -7,6 +7,10 @@ import { computeHash } from "#veryfront/utils"; import { VERSION } from "#veryfront/utils/version.ts"; +import { + primordialArrayJoin as arrayJoin, + primordialArrayPush as arrayPush, +} from "#veryfront/platform/compat/primordials/array.ts"; import { CSSTYPE_VERSION, DEFAULT_REACT_VERSION, @@ -16,17 +20,6 @@ import { buildDependencyPinningCacheVariant } from "./keys/dependency-pinning.ts const JSONStringify = JSON.stringify; const ObjectCreate = Object.create; -const ArrayPrototypeJoin = Array.prototype.join; -const ArrayPrototypePush = Array.prototype.push; -const ReflectApply = Reflect.apply; - -function arrayPush(values: string[], value: string): void { - ReflectApply(ArrayPrototypePush, values, [value]); -} - -function arrayJoin(values: string[], separator: string): string { - return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; -} /** * Configuration that affects transform output. diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index a2e6c8cd03..7a83e9e804 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -1,5 +1,9 @@ import type { VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import { + primordialArrayPush as arrayPush, + primordialArraySort as arraySort, +} from "#veryfront/platform/compat/primordials/array.ts"; import { snapshotImportMap } from "#veryfront/transforms/pipeline/cache-identity.ts"; import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/constants/limits.ts"; import { computeHash } from "#veryfront/utils/hash-utils.ts"; @@ -24,8 +28,6 @@ const DEFAULT_IMPORT_MAP_LOAD_TIMEOUT_MS = 30_000; // Project code can execute in the same realm before a later request reaches // this cache. Capture every primitive used for identity, admission, and // settlement so replacing shared built-ins cannot redirect dependency graphs. -const ArrayPrototypePush = Array.prototype.push; -const ArrayPrototypeSort = Array.prototype.sort; const DateNow = Date.now; const IntrinsicMap = Map; const IntrinsicPerformance = performance; @@ -63,17 +65,6 @@ const WeakSetPrototypeHas = WeakSet.prototype.has; const SetTimeout = setTimeout; const ClearTimeout = clearTimeout; -function arraySort( - values: T[], - compare: (left: T, right: T) => number, -): T[] { - return ReflectApply(ArrayPrototypeSort, values, [compare]) as T[]; -} - -function arrayPush(values: T[], value: T): void { - ReflectApply(ArrayPrototypePush, values, [value]); -} - function monotonicNow(): number { return ReflectApply(PerformanceNow, IntrinsicPerformance, []) as number; } diff --git a/src/platform/compat/path/portable.ts b/src/platform/compat/path/portable.ts index 4d63fec2e1..08179639c8 100644 --- a/src/platform/compat/path/portable.ts +++ b/src/platform/compat/path/portable.ts @@ -1,19 +1,11 @@ import type { PathObject } from "./types.ts"; - -const ArrayPrototypeAt = Array.prototype.at; -const ArrayPrototypeFilter = Array.prototype.filter; -const ArrayPrototypeJoin = Array.prototype.join; -const ArrayPrototypePop = Array.prototype.pop; -const ArrayPrototypePush = Array.prototype.push; -const ReflectApply = Reflect.apply; - -function arrayJoin(values: readonly string[], separator: string): string { - return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; -} - -function arrayPush(values: string[], value: string): void { - ReflectApply(ArrayPrototypePush, values, [value]); -} +import { + primordialArrayAt as arrayAt, + primordialArrayFilter as arrayFilter, + primordialArrayJoin as arrayJoin, + primordialArrayPop as arrayPop, + primordialArrayPush as arrayPush, +} from "../primordials/array.ts"; interface RootInfo { absolute: boolean; @@ -97,9 +89,9 @@ function normalizeTail(rest: string, absolute: boolean): string[] { continue; } - const previous = ReflectApply(ArrayPrototypeAt, normalized, [-1]) as string | undefined; + const previous = arrayAt(normalized, -1); if (previous !== undefined && previous !== "..") { - ReflectApply(ArrayPrototypePop, normalized, []); + arrayPop(normalized); } else if (!absolute) { arrayPush(normalized, ".."); } @@ -187,11 +179,7 @@ export function portableNormalize(path: string, windows: boolean): string { } export function portableJoin(paths: readonly string[], windows: boolean): string { - const nonempty = ReflectApply( - ArrayPrototypeFilter, - paths, - [(path: string) => path.length > 0], - ) as string[]; + const nonempty = arrayFilter(paths, (path) => path.length > 0); if (nonempty.length === 0) return "/"; return portableNormalize(arrayJoin(nonempty, "/"), windows); } diff --git a/src/platform/compat/primordials/array.test.ts b/src/platform/compat/primordials/array.test.ts new file mode 100644 index 0000000000..2d43664b35 --- /dev/null +++ b/src/platform/compat/primordials/array.test.ts @@ -0,0 +1,69 @@ +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { + primordialArrayAt, + primordialArrayFilter, + primordialArrayJoin, + primordialArrayMap, + primordialArrayPop, + primordialArrayPush, + primordialArraySort, +} from "./array.ts"; + +describe("platform/compat/primordials/array", () => { + it("uses module-load-time captures after array prototypes are replaced", () => { + const originals = { + at: Array.prototype.at, + filter: Array.prototype.filter, + join: Array.prototype.join, + map: Array.prototype.map, + pop: Array.prototype.pop, + push: Array.prototype.push, + sort: Array.prototype.sort, + }; + const poisoned = () => { + throw new Error("poisoned array primordial"); + }; + + let first: number | undefined; + let filtered: number[] | undefined; + let joined: string | undefined; + let mapped: number[] | undefined; + let popped: number | undefined; + let sorted: number[] | undefined; + const values = [3, 1, 2]; + + try { + Array.prototype.at = poisoned; + Array.prototype.filter = poisoned; + Array.prototype.join = poisoned; + Array.prototype.map = poisoned; + Array.prototype.pop = poisoned; + Array.prototype.push = poisoned; + Array.prototype.sort = poisoned; + + first = primordialArrayAt(values, 0); + filtered = primordialArrayFilter(values, (value) => value > 1); + joined = primordialArrayJoin(values, ":"); + mapped = primordialArrayMap(values, (value) => value * 2); + primordialArrayPush(values, 4); + popped = primordialArrayPop(values); + sorted = primordialArraySort(values, (left, right) => left - right); + } finally { + Array.prototype.at = originals.at; + Array.prototype.filter = originals.filter; + Array.prototype.join = originals.join; + Array.prototype.map = originals.map; + Array.prototype.pop = originals.pop; + Array.prototype.push = originals.push; + Array.prototype.sort = originals.sort; + } + + assertEquals(first, 3); + assertEquals(filtered, [3, 2]); + assertEquals(joined, "3:1:2"); + assertEquals(mapped, [6, 2, 4]); + assertEquals(popped, 4); + assertEquals(sorted, [1, 2, 3]); + }); +}); diff --git a/src/platform/compat/primordials/array.ts b/src/platform/compat/primordials/array.ts new file mode 100644 index 0000000000..4ce6116853 --- /dev/null +++ b/src/platform/compat/primordials/array.ts @@ -0,0 +1,55 @@ +// Capture shared array intrinsics once, before project code can replace mutable +// prototype methods in a long-lived runtime. Keep this module dependency-free so +// low-level compatibility code and higher framework layers can use the same +// trusted operations without introducing an import cycle. +const ArrayPrototypeAt = Array.prototype.at; +const ArrayPrototypeFilter = Array.prototype.filter; +const ArrayPrototypeJoin = Array.prototype.join; +const ArrayPrototypeMap = Array.prototype.map; +const ArrayPrototypePop = Array.prototype.pop; +const ArrayPrototypePush = Array.prototype.push; +const ArrayPrototypeSort = Array.prototype.sort; +const ReflectApply = Reflect.apply; + +export function primordialArrayAt( + values: readonly T[], + index: number, +): T | undefined { + return ReflectApply(ArrayPrototypeAt, values, [index]) as T | undefined; +} + +export function primordialArrayFilter( + values: readonly T[], + predicate: (value: T, index: number, array: readonly T[]) => unknown, +): T[] { + return ReflectApply(ArrayPrototypeFilter, values, [predicate]) as T[]; +} + +export function primordialArrayJoin( + values: readonly unknown[], + separator: string, +): string { + return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; +} + +export function primordialArrayMap( + values: readonly T[], + callback: (value: T, index: number, array: readonly T[]) => U, +): U[] { + return ReflectApply(ArrayPrototypeMap, values, [callback]) as U[]; +} + +export function primordialArrayPop(values: T[]): T | undefined { + return ReflectApply(ArrayPrototypePop, values, []) as T | undefined; +} + +export function primordialArrayPush(values: T[], value: T): void { + ReflectApply(ArrayPrototypePush, values, [value]); +} + +export function primordialArraySort( + values: T[], + compare: (left: T, right: T) => number, +): T[] { + return ReflectApply(ArrayPrototypeSort, values, [compare]) as T[]; +} diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index ab4639c82c..65bb090cbb 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 lt=Object.defineProperty;var dt=(e,t,r)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var m=(e,t,r)=>dt(e,typeof t!="symbol"?t+"":t,r);var gt="3.2.3",ft=Array.prototype.join,pt=Array.prototype.push,yt=Object.entries,he=Reflect.apply;function re(e,t){return he(ft,e,[t])}function U(e,t){he(pt,e,[t])}function mt(e,t,r,n){let s=[];if(n?.external?.length&&U(s,`external=${re(n.external,",")}`),U(s,`target=${n?.target??"es2022"}`),n?.deps){let d=[],c=yt(n.deps);for(let l=0;lt||r?.(n,...s)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function Nt(){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 V=Nt(),g=new T("RSC",V),_s=new T("PREFETCH",V),xs=new T("HYDRATE",V),Ts=new T("VERYFRONT",V);var Dt="veryfront-hydration-data";function ie(e){try{let t=[...e.querySelectorAll(`[id="${Dt}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function S(e=document){try{let t=ie(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return g.debug("hydration data parse failed",t),null}}function F(e,t){if(!t?.startsWith("on:"))return!1;try{let r=ie(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return g.debug("hydration dependency snapshot seed failed",r),!1}}function B(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function wt(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function G(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),s=r===-1?e:e.slice(0,r),i=s.indexOf("?"),a=i===-1?s:s.slice(0,i),u=new URLSearchParams(i===-1?"":s.slice(i+1));u.set("pins",t);let d=u.toString();return`${a}${d?`?${d}`:""}${n}`}function Mt(e,t){return wt(`${Ae}${ne(e)}.js`,t)}function Lt(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return G(`${I}module?rel=${encodeURIComponent(e)}${n}`,r)}function D(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[$]:t}:{}}function Pt(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ht=/\\.(tsx|ts|jsx|mdx|js)$/;function vt(e){let t=Pt(e),r=[e,t];return Ht.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function Ut(e,t){if(!e)return null;for(let r of vt(t)){let n=e[r];if(n)return n}return null}function j(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?G(Mt(r,e.version),e.dependencyPinningCacheKey):null}let t=Ut(e.releaseAssetModules,e.rel);return t||Lt(e.rel,e.version,e.dependencyPinningCacheKey)}function z(e=document,t=O){let r=oe(e);return{react:k("react",r)?"react":xe(t),reactDomClient:k("react-dom/client",r)?"react-dom/client":Te(t)}}function be(e=document){let t=oe(e);return k("veryfront/router",t)?"veryfront/router":null}var kt=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function $t(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let s of t)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${i}" must define a slug`);let u=a.slug;if(typeof u!="string")throw new Error(`${n} entry "${i}" must define a string slug`);if(u!==i)throw new Error(`${n} key "${i}" does not match entry slug "${u}"`);if(Object.hasOwn(r,i))throw new Error(`Duplicate ${e} slug "${i}"`);r[i]=a}return Object.freeze(r)}function Oe(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!kt.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return $t("error registry",...e)}var Y={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"},Ps={debug:Y.gray,info:Y.green,warn:Y.yellow,error:Y.red};var y="[REDACTED]",R=Reflect.apply;var Ie=RegExp.prototype.exec,x=RegExp.prototype[Symbol.replace],vs=String.prototype.charCodeAt,Ne=String.prototype.slice,Vt=String.prototype.toLowerCase,Ft=/[^a-z0-9]/g;function ae(e){let t=R(Vt,e,[]);return R(x,Ft,[t,""])}function K(e,t,r){return r===void 0?R(Ne,e,[t]):R(Ne,e,[t,r])}var Bt=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Gt=512,jt=128,w=new Map;function Me(e){let t=e.length<=jt;if(t){let s=w.get(e);if(s!==void 0)return s}let r=ae(e),n=Bt.some(s=>r.includes(s));if(t){if(w.size>=Gt){let s=w.keys().next().value;s!==void 0&&w.delete(s)}w.set(e,n)}return n}var zt=["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"],Yt=new Set(zt.map(ae)),Kt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Wt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Xt=3;function qt(e){return e===" "||e==="\t"||e===","||e===";"||e==="&"||e==="?"||e==="#"}function Jt(e){if(!e)return!1;let t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}function Le(e){return Jt(e)||e==="_"||e==="$"}function Zt(e){if(!e)return!1;let t=e.charCodeAt(0);return Le(e)||t>=48&&t<=57||e==="."||e==="-"}function Pe(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!Le(e[r]))return!1;for(r++;Zt(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function He(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||qt(e)}function ve(e,t){let r=t;for(;r=e.length||Pe(e,r)}function Qt(e,t){let r=t,n=!0;if(e.startsWith(y,t)){let l=t+y.length;if(De(e,l))return{end:l,replacement:y};r=l,n=!1}let s=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",i=!1,a=()=>s?`${s}${y}${i?s:""}`:y,u=[],d="",c=-1;for(let l=r;l0&&(f==="}"||f==="]")){if(u.at(-1)!==f)return{end:e.length,replacement:a()};if(u.pop(),l++,u.length===0&&De(e,l))return{end:l,replacement:a()};continue}if(u.length>0||!He(f)){l++;continue}let E=l;if(l=ve(e,l),l>=e.length||Pe(e,l))return{end:E,replacement:a()}}return{end:e.length,replacement:a()}}function we(e,t,r,n){let s=0,i="";for(let a=R(Ie,t,[e]);a;a=R(Ie,t,[e])){let u=a[r];if(!Me(u))continue;let d=t.lastIndex,c=n===void 0?void 0:a[n],l=d+y.length;if((c==="?"||c==="&"||c===";")&&e.startsWith(y,d)&&e[l]==="#")continue;let f=Qt(e,d);i+=K(e,s,a.index),i+=a[0],i+=f.replacement,s=f.end,t.lastIndex=f.end}return s===0?e:i+K(e,s)}function er(e,t,r){let n=r.search(/[ \\t]/);if(n<0)return!1;let s=`${t}:${K(r,0,n)}`,i=e==="//"?`https://${s}`:`${e}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function tr(e){let t=e;for(let r=0;r{let i=s.indexOf(":");if(i===-1)return`${n}${y}@`;let a=K(s,0,i);return`${n}${a}:${y}@`}]);return t=R(x,Wt,[t,(r,n,s,i)=>er(n,s,i)?r:`${n}${s}:${y}@`]),t=R(x,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[t,(r,n,s,i)=>{let a=tr(s);return Yt.has(ae(a))||Me(a)?`${n}${s}=${y}`:r}]),t=R(x,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[t,(r,n,s)=>`${n}${s}${y}`]),t=R(x,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[t,(r,n)=>`${n}${y}`]),t=R(x,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[t,(r,n,s)=>`${n}${s}${y}`]),t=we(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=we(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var rr=2048;var Fs=64*1024,nr=256,or="https://veryfront.com/docs/errors/",Ue="...[truncated]",ue="unknown-error";function ke(e,t){if(e.length<=t)return e;let r=Math.max(0,t-Ue.length);return`${sr(e,r)}${Ue}`}function sr(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function ir(e){let t="";for(let r=0;r=55296&&n<=56319){let s=e.charCodeAt(r+1);s>=56320&&s<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function C(e){return typeof e!="string"?y:ke(ce(e),rr)}function ar(e){let t=typeof e=="string"?ce(e):ue,r=ke(t||ue,nr),n=ir(r);return n==="."||n===".."?ue:n}function W(e){let t=encodeURIComponent(ar(e));return`${or}${t}`}var cr=Object.freeze,ur=Object.getOwnPropertyDescriptors,$e=Number.isFinite,Fe=new WeakSet,lr=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function o(e){let t={...e},r={...t,create(n){let s=n?.message,i=n?.detail,a=n?.cause,u=n?.instance,d=n?.context,c=n?.status??t.status;return new le(s||i||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:a,instance:u,context:d})}};return cr(r)}var le=class extends Error{constructor(r,n){super(r);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");Fe.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=Ve(this);return r?{type:W(r.slug),title:C(r.title),status:r.status,detail:r.detail===void 0?void 0:C(r.detail),instance:r.instance===void 0?void 0:C(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:C(r.suggestion),cause:typeof r.cause=="string"?C(r.cause):void 0}:{type:W("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=Ve(this);return W(r?.slug??"unknown-error")}};function Be(e){return typeof e=="object"&&e!==null&&Fe.has(e)}function Ve(e){return Be(e)?dr(e):null}function dr(e){try{if(!Be(e))return null;let t=ur(e),r=Z=>{let b=t[Z];return b&&"value"in b?b.value:void 0},n=r("slug"),s=r("category"),i=r("status"),a=r("title"),u=r("message"),d=r("suggestion"),c=r("exitCode"),l=r("detail"),f=r("cause"),E=r("instance"),P=r("context"),h=r("stack");return typeof n!="string"||!lr.has(s)||typeof i!="number"||!$e(i)||typeof a!="string"||typeof u!="string"||d!==void 0&&typeof d!="string"||c!==void 0&&(typeof c!="number"||!$e(c))||l!==void 0&&typeof l!="string"||E!==void 0&&typeof E!="string"||h!==void 0&&typeof h!="string"?null:{slug:n,category:s,status:i,title:a,message:u,suggestion:d,exitCode:c,detail:l,cause:f,instance:E,context:P,stack:h}}catch{return null}}var gr=o({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),fr=o({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),pr=o({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),yr=o({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),mr=o({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),Er=o({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),Rr=o({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),hr=o({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),_r=o({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),xr=o({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),Tr=o({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Ge={"config-not-found":gr,"config-invalid":fr,"config-parse-error":pr,"config-validation-error":yr,"config-type-error":mr,"import-map-invalid":Er,"cors-config-invalid":Rr,"config-validation-failed":hr,"webhook-config-invalid":_r,"schedule-config-invalid":xr,"trigger-config-invalid":Tr};var Sr=o({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Cr=o({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),Ar=o({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),br=o({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),Or=o({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),Ir=o({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),Nr=o({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),Dr=o({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),je={"build-failed":Sr,"bundle-error":Cr,"typescript-error":Ar,"mdx-compile-error":br,"asset-optimization-error":Or,"ssg-generation-error":Ir,"sourcemap-error":Nr,"compilation-error":Dr};var wr=o({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Mr=o({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Lr=o({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Pr=o({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Hr=o({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),vr=o({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),Ur=o({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),kr=o({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),$r=o({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Vr=o({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ze={"hydration-mismatch":wr,"render-error":Mr,"component-error":Lr,"layout-not-found":Pr,"page-not-found":Hr,"api-error":vr,"middleware-error":Ur,"trigger-target-not-found":kr,"trigger-execution-failed":$r,"trigger-not-supported":Vr};var Fr=o({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Br=o({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Gr=o({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),jr=o({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),zr=o({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Yr=o({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),Ye={"route-conflict":Fr,"invalid-route-file":Br,"route-handler-invalid":Gr,"dynamic-route-error":jr,"route-params-error":zr,"api-route-error":Yr};var Kr=o({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Wr=o({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Xr=o({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),qr=o({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),Jr=o({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Zr=o({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),Ke={"module-not-found":Kr,"import-resolution-error":Wr,"circular-dependency":Xr,"invalid-import":qr,"dependency-missing":Jr,"version-mismatch":Zr};var Qr=o({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),en=o({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),tn=o({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),rn=o({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),nn=o({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),on=o({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),sn=o({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),an=o({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),cn=o({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),un=o({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),ln=o({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),dn=o({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),gn=o({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),fn=o({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),pn=o({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),yn=o({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),mn=o({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),En=o({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),We={"port-in-use":Qr,"server-start-error":en,"cache-error":tn,"file-watch-error":rn,"request-error":nn,"service-overloaded":on,"project-execution-unavailable":sn,"semaphore-timeout":an,"circuit-breaker-open":cn,"cache-path-mismatch":un,"network-error":ln,"api-client-error":dn,"token-storage-error":gn,"cache-invariant-violation":fn,"release-not-found":pn,"fallback-exhausted":yn,"rag-store-corrupt":mn,"rag-store-unavailable":En};var Rn=o({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),hn=o({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),_n=o({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),xn=o({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),Tn=o({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),Sn=o({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),Cn=o({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),Xe={"client-boundary-violation":Rn,"server-only-in-client":hn,"client-only-in-server":_n,"invalid-use-client":xn,"invalid-use-server":Tn,"rsc-payload-error":Sn,"ssr-output-limit-exceeded":Cn};var An=o({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),bn=o({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),On=o({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),In=o({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),Nn=o({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),qe={"hmr-error":An,"dev-server-error":bn,"fast-refresh-error":On,"error-overlay-error":In,"source-map-error":Nn};var Dn=o({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),wn=o({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Mn=o({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Ln=o({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),Pn=o({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Hn=o({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),vn=o({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Un=o({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),kn=o({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),$n=o({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Vn=o({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Fn=o({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),Je={"deployment-error":Dn,"platform-error":wn,"env-var-missing":Mn,"production-build-required":Ln,"environment-not-found":Pn,"release-missing-version":Hn,"release-build-timeout":vn,"deployment-verification-timeout":Un,"push-receipt-missing":kn,"source-digest-mismatch":$n,"preview-hostname-too-long":Vn,"branch-not-found":Fn};var Bn=o({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Gn=o({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),jn=o({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),zn=o({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Yn=o({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Kn=o({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Wn=o({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Xn=o({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),Ze={"agent-error":Bn,"agent-not-found":Gn,"agent-timeout":jn,"agent-intent-error":zn,"orchestration-error":Yn,"cost-limit-exceeded":Kn,"tool-id-conflict":Wn,"durable-run-event-persistence-failed":Xn};var qn=o({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Jn=o({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Zn=o({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Qn=o({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),eo=o({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),to=o({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),ro=o({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),no=o({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),oo=o({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),de=o({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),so=o({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),io=o({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Qe={"unknown-error":qn,"authentication-required":Jn,"permission-denied":Zn,"file-not-found":Qn,"resource-not-found":eo,"invalid-argument":to,"timeout-error":ro,"initialization-error":no,"not-supported":oo,"security-violation":de,"input-validation-failed":so,"project-source-empty":io};var Ni=Oe(Ge,je,ze,Ye,Ke,We,Xe,qe,Je,Ze,Qe);var ao=[{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 co(){return ao.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function uo(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function M(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:s=!0}=t;for(let{pattern:i,name:a}of co())if(!(r&&a==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!uo())))throw de.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function L(e,t){let r=t==="root"?N:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let s=e.createElement("div");return s.id=r,e.body.appendChild(s),s}function lo(e,t){if(t.type!=="slot")return;let r=L(e,t.id);r.innerHTML=M(String(t.html??""))}function et(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let s of r){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){g.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let u=a;if(u.type==="slot"){lo(e,u);try{po(e,u.id||"root")}catch(d){g.debug("[client-dom] hydration optional failed",d)}}}return n}function go(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function tt(e,t=document,r){let n="body"in e?e:null,s=n?.body??e;if(!s)return;n&&F(t,n.headers.get($));let i=s.getReader(),a=new TextDecoder,u="",d=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let c=i.read(),{done:l,value:f}=r?await Promise.race([c,go(r)]):await c;if(l){d=!0;break}u+=a.decode(f,{stream:!0}),u=et(t,u)}u&&et(t,`${u}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||g.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await i.cancel()}catch(c){d||g.debug("[client-dom] reader.cancel failed",c)}try{i.releaseLock()}catch(c){g.debug("[client-dom] reader.releaseLock failed",c)}if(typeof s.cancel=="function")try{await s.cancel()}catch(c){g.debug("[client-dom] stream.cancel failed",c)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(c){g.debug("[client-dom] response.body.cancel failed",c)}}}function fo(e,t){let r=L(e,t),n=[],s=i=>{let a=i;a.dataset?.clientRef&&n.push(a);for(let u of i.children)s(u)};return s(r),n}function po(e,t){let r=fo(e,t);for(let n of r){let s=n.dataset?.clientRef;s&&(n.dataset.hydrated="true",g.debug("[client-dom] marked for hydration",s))}}var yo=new Set(["server","client","html","fragment"]);function rt(e){if(!e)return[];try{let t=JSON.parse(e);return Eo(t)?t.nodes:[]}catch{return[]}}async function fe(e,t,r){return await Promise.all(e.map(n=>mo(n,t,r)))}async function mo(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await fe(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let s=await r(e.component);return s?t.createElement(s,e.props??{},...n):null}function Eo(e){return!ge(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>nt(t,0))}function nt(e,t){return t>100||!ge(e)||!yo.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&&!ge(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>nt(r,t+1))}function ge(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ro(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function X(e,t,r=document){try{let n=be(r);if(!n)return e;let i=(await import(n)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Ro(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return g.debug("router provider wrap failed",n),e}}var ho="Unknown dependency snapshot",_o="export default null; // Unknown dependency snapshot",pe="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function xo(){return globalThis}async function To(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===ho||t===_o}catch{return!1}}async function A(e,t=()=>globalThis.location.reload()){if(!await To(e))return!1;let r=xo();if(r[pe])return!0;r[pe]=!0;try{t()}catch{return delete r[pe],!1}return!0}async function q(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let s=await t(e,{cache:"no-store"});return await A(s,r)}catch{return!1}}var So=100;function Co(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=So){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function ot(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(g.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function Ao(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return g.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function bo(e){return rt(e.dataset?.rscChildren)}function Oo(e){return"/_veryfront/rsc/manifest"}function Io(e){return D(e)}async function No(e=document){try{let t=S(e),r=await fetch(Oo(t),{headers:Io(t)});return r.ok?await r.json():(await A(r),null)}catch{return null}}async function st(e,t,r,n={}){let s=Do(e,t,r,n.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let a=`${i}#${e.hash??""}`;try{let u=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(u)return u}catch(u){g.debug("hydrate: cache get failed",u)}if(!s)return null;try{let u=await(n.importModule??(d=>import(d)))(s);try{Co(a,u)}catch(d){g.debug("hydrate: cache set failed",d)}return u}catch(u){return g.debug("hydrate: failed to import module",{moduleUrl:s,error:u}),await(n.recoverSnapshotFailure??q)(s),null}}function Do(e,t,r,n){if(t.moduleUrl)return G(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let s=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return j({strategy:r,rel:t.rel,absPath:s,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function wo(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let s=n.parentElement;for(;s;){if(r.has(s))return!1;s=s.parentElement}return!0})}async function it(e=document){let t=null;try{t=await No(e)}catch(c){g.debug("hydrate: fetch manifest failed",c)}if(!t){g.debug("hydrate: no manifest");return}let r=wo(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){g.debug("hydrate: hmr hash read failed",c)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){g.debug("hydrate: set hash failed",c)}return}let n=S(e),s=B(n),i=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){g.debug("hydrate: test mode flags failed",c)}let a=z(e,n?.reactVersion),[{default:u},{createRoot:d}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let c of r){let l=c.dataset?.clientRef??"";if(!l||c.dataset?.hydrated==="true")continue;let f=ot(l);if(!f)continue;let E=await st(t,f,s,{releaseAssetModules:i});if(!E)continue;let P=E[f.exportName]??E.default;if(typeof P=="function")try{let h=d(c),Z=Ao(c),b=bo(c),at=await fe(b,{Fragment:u.Fragment,createElement(H,Q,...v){return u.createElement(H,Q,...v)}},async H=>{let Q=t.modules.find(ut=>ut.id===H),v=t.components?.[H],Ee=Q?.clientRef??(v?`${v}#default`:void 0);if(!Ee)return null;let ee=ot(Ee);if(!ee)return null;let te=await st(t,ee,s,{releaseAssetModules:i});if(!te)return null;let Re=te[ee.exportName]??te.default;return typeof Re=="function"?Re:null}),ct=await X(u.createElement(P,Z,...at),n,e);h.render(ct),c.dataset.hydrated="true"}catch(h){g.warn("hydrate: render failed",h)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){g.debug("hydrate: set hash failed (post)",c)}}var ye="data-vf-react-head-owner";var Mo=2*1024*1024,aa=Mo*2;var ca=64*1024,ua=1024*1024,la=1024*1024;var da=new TextEncoder;async function Lo(){let e=S(document),t=z(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var Po=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function me(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||Po.has(e.tagName.toUpperCase())}function Ho(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!me(r))??t}function vo(e,t){return e===t}function Uo(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(s=>!me(s));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let s of e)!me(s)&&s.parentNode===t&&r.appendChild(s);return r}function ko(e,t){for(let r of e){let n=[...r.hasAttribute(ye)?[r]:[],...r.querySelectorAll(`[${ye}]`)];for(let s of n)t.contains(s)||s.remove()}}function $o(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function Vo(e,t){return t?.pagePath?!1:!!e.getElementById(N)}function Fo(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function Bo(e){return e==="rsc-module"}function Go(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function jo(e,t,r){return j({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function zo(e,t){try{let r=await fetch(I+"stream"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await tt(r,document,n.signal),"success"}catch(r){return g.debug("tryStream failed",r),"failure"}}async function J(){try{await it(document)}catch(e){g.debug("hydration failed",e)}}async function Yo(e,t,r){try{let{React:n,ReactDOM:s}=await Lo(),i=jo(e,t,r);if(!i)return!1;g.debug("Loading component from:",i);let a;try{a=await import(i)}catch(E){throw await q(i),E}let u=a.default;if(typeof u!="function")return g.debug("Page component is not a function"),!1;let d=Array.from(document.body.children),c=Ho(d,document.body),l=vo(c,document.body)?Uo(d,document.body):c;ko(d,l);let f=await X(n.createElement(u,{}),r);return Bo(t)?s.createRoot(l).render(f):s.hydrateRoot(l,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),g.debug("Page component hydrated successfully"),!0}catch(n){return g.error("Page hydration failed",n),!1}}async function Ko(e,t){try{let r=await fetch(I+"payload"+e,{headers:D(t)});if(!r.ok)return await A(r)?"snapshot-conflict":"failure";let n=await r.json();if(F(document,n?.dependencyPinningCacheKey),n?.slots){for(let[s,i]of Object.entries(n.slots))L(document,s).innerHTML=M(String(i||""));return"success"}return L(document,N).innerHTML=M(String(n?.html||"")),"success"}catch(r){return g.debug("payload fetch failed",r),"failure"}}async function Wo(){try{let e=S(document),t=Go(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(Fo()){await J();return}let r=e?.pagePath,n=B(e);if(r){if($o(globalThis.window,e,document)){g.debug("Page renderer owns hydration");return}g.debug("Found page component in hydration data:",r),await Yo(r,n,e)&&g.debug("Client component hydrated successfully");return}if(!Vo(document,e))return;let s=await zo(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await J();return}let i=await Ko(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await J();return}await J()}catch(e){g.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Wo()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Wo as boot,jo as buildPageHydrationModuleUrl,Go as buildRSCTransportQuery,ko as retireAbandonedHeadOwnerMarkers,Ho as selectHydrationRoot,Vo as shouldAttemptRSCTransport,Fo as shouldHydrateOnly,Bo as shouldRenderPageComponent,$o as shouldUsePageRendererHydration,vo as shouldWrapPageHydrationRoot};\n'; + 'var lt=Object.defineProperty;var dt=(e,t,r)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var m=(e,t,r)=>dt(e,typeof t!="symbol"?t+"":t,r);var Jo=Array.prototype.at,Zo=Array.prototype.filter,gt=Array.prototype.join,Qo=Array.prototype.map,ei=Array.prototype.pop,ft=Array.prototype.push,ti=Array.prototype.sort,he=Reflect.apply;function k(e,t){return he(gt,e,[t])}function O(e,t){he(ft,e,[t])}var pt="3.2.3",yt=Object.entries;function mt(e){let t=[];if(e?.external?.length&&O(t,`external=${k(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let r=[],n=yt(e.deps);for(let i=0;it||r?.(n,...i)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function Dt(){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 F=Dt(),l=new T("RSC",F),Ii=new T("PREFETCH",F),Ni=new T("HYDRATE",F),Di=new T("VERYFRONT",F);var wt="veryfront-hydration-data";function se(e){try{let t=[...e.querySelectorAll(`[id="${wt}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function S(e=document){try{let t=se(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return l.debug("hydration data parse failed",t),null}}function B(e,t){if(!t?.startsWith("on:"))return!1;try{let r=se(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return l.debug("hydration dependency snapshot seed failed",r),!1}}function G(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Mt(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function j(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),i=r===-1?e:e.slice(0,r),s=i.indexOf("?"),a=s===-1?i:i.slice(0,s),u=new URLSearchParams(s===-1?"":i.slice(s+1));u.set("pins",t);let d=u.toString();return`${a}${d?`?${d}`:""}${n}`}function Lt(e,t){return Mt(`${Ce}${ne(e)}.js`,t)}function Pt(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return j(`${N}module?rel=${encodeURIComponent(e)}${n}`,r)}function w(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[V]:t}:{}}function Ht(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ut=/\\.(tsx|ts|jsx|mdx|js)$/;function vt(e){let t=Ht(e),r=[e,t];return Ut.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function kt(e,t){if(!e)return null;for(let r of vt(t)){let n=e[r];if(n)return n}return null}function z(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?j(Lt(r,e.version),e.dependencyPinningCacheKey):null}let t=kt(e.releaseAssetModules,e.rel);return t||Pt(e.rel,e.version,e.dependencyPinningCacheKey)}function Y(e=document,t=I){let r=oe(e);return{react:$("react",r)?"react":xe(t),reactDomClient:$("react-dom/client",r)?"react-dom/client":Te(t)}}function be(e=document){let t=oe(e);return $("veryfront/router",t)?"veryfront/router":null}var $t=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Vt(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let i of t)for(let[s,a]of Object.entries(i)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${s}" must define a slug`);let u=a.slug;if(typeof u!="string")throw new Error(`${n} entry "${s}" must define a string slug`);if(u!==s)throw new Error(`${n} key "${s}" does not match entry slug "${u}"`);if(Object.hasOwn(r,s))throw new Error(`Duplicate ${e} slug "${s}"`);r[s]=a}return Object.freeze(r)}function Oe(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!$t.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Vt("error registry",...e)}var K={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"},Bi={debug:K.gray,info:K.green,warn:K.yellow,error:K.red};var y="[REDACTED]",E=Reflect.apply;var Ie=RegExp.prototype.exec,x=RegExp.prototype[Symbol.replace],ji=String.prototype.charCodeAt,Ne=String.prototype.slice,Ft=String.prototype.toLowerCase,Bt=/[^a-z0-9]/g;function ae(e){let t=E(Ft,e,[]);return E(x,Bt,[t,""])}function W(e,t,r){return r===void 0?E(Ne,e,[t]):E(Ne,e,[t,r])}var Gt=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],jt=512,zt=128,M=new Map;function Me(e){let t=e.length<=zt;if(t){let i=M.get(e);if(i!==void 0)return i}let r=ae(e),n=Gt.some(i=>r.includes(i));if(t){if(M.size>=jt){let i=M.keys().next().value;i!==void 0&&M.delete(i)}M.set(e,n)}return n}var Yt=["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"],Kt=new Set(Yt.map(ae)),Wt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Xt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,qt=3;function Jt(e){return e===" "||e==="\t"||e===","||e===";"||e==="&"||e==="?"||e==="#"}function Zt(e){if(!e)return!1;let t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}function Le(e){return Zt(e)||e==="_"||e==="$"}function Qt(e){if(!e)return!1;let t=e.charCodeAt(0);return Le(e)||t>=48&&t<=57||e==="."||e==="-"}function Pe(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!Le(e[r]))return!1;for(r++;Qt(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function He(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||Jt(e)}function Ue(e,t){let r=t;for(;r=e.length||Pe(e,r)}function er(e,t){let r=t,n=!0;if(e.startsWith(y,t)){let g=t+y.length;if(De(e,g))return{end:g,replacement:y};r=g,n=!1}let i=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",s=!1,a=()=>i?`${i}${y}${s?i:""}`:y,u=[],d="",c=-1;for(let g=r;g0&&(f==="}"||f==="]")){if(u.at(-1)!==f)return{end:e.length,replacement:a()};if(u.pop(),g++,u.length===0&&De(e,g))return{end:g,replacement:a()};continue}if(u.length>0||!He(f)){g++;continue}let R=g;if(g=Ue(e,g),g>=e.length||Pe(e,g))return{end:R,replacement:a()}}return{end:e.length,replacement:a()}}function we(e,t,r,n){let i=0,s="";for(let a=E(Ie,t,[e]);a;a=E(Ie,t,[e])){let u=a[r];if(!Me(u))continue;let d=t.lastIndex,c=n===void 0?void 0:a[n],g=d+y.length;if((c==="?"||c==="&"||c===";")&&e.startsWith(y,d)&&e[g]==="#")continue;let f=er(e,d);s+=W(e,i,a.index),s+=a[0],s+=f.replacement,i=f.end,t.lastIndex=f.end}return i===0?e:s+W(e,i)}function tr(e,t,r){let n=r.search(/[ \\t]/);if(n<0)return!1;let i=`${t}:${W(r,0,n)}`,s=e==="//"?`https://${i}`:`${e}${i}`;try{let a=new URL(s);return a.username.length===0&&a.password.length===0}catch{return!1}}function rr(e){let t=e;for(let r=0;r{let s=i.indexOf(":");if(s===-1)return`${n}${y}@`;let a=W(i,0,s);return`${n}${a}:${y}@`}]);return t=E(x,Xt,[t,(r,n,i,s)=>tr(n,i,s)?r:`${n}${i}:${y}@`]),t=E(x,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[t,(r,n,i,s)=>{let a=rr(i);return Kt.has(ae(a))||Me(a)?`${n}${i}=${y}`:r}]),t=E(x,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[t,(r,n,i)=>`${n}${i}${y}`]),t=E(x,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[t,(r,n)=>`${n}${y}`]),t=E(x,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[t,(r,n,i)=>`${n}${i}${y}`]),t=we(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=we(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var nr=2048;var Xi=64*1024,or=256,ir="https://veryfront.com/docs/errors/",ve="...[truncated]",ue="unknown-error";function ke(e,t){if(e.length<=t)return e;let r=Math.max(0,t-ve.length);return`${sr(e,r)}${ve}`}function sr(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function ar(e){let t="";for(let r=0;r=55296&&n<=56319){let i=e.charCodeAt(r+1);i>=56320&&i<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function A(e){return typeof e!="string"?y:ke(ce(e),nr)}function cr(e){let t=typeof e=="string"?ce(e):ue,r=ke(t||ue,or),n=ar(r);return n==="."||n===".."?ue:n}function X(e){let t=encodeURIComponent(cr(e));return`${ir}${t}`}var ur=Object.freeze,lr=Object.getOwnPropertyDescriptors,$e=Number.isFinite,Fe=new WeakSet,dr=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function o(e){let t={...e},r={...t,create(n){let i=n?.message,s=n?.detail,a=n?.cause,u=n?.instance,d=n?.context,c=n?.status??t.status;return new le(i||s||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:s,cause:a,instance:u,context:d})}};return ur(r)}var le=class extends Error{constructor(r,n){super(r);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");Fe.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=Ve(this);return r?{type:X(r.slug),title:A(r.title),status:r.status,detail:r.detail===void 0?void 0:A(r.detail),instance:r.instance===void 0?void 0:A(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:A(r.suggestion),cause:typeof r.cause=="string"?A(r.cause):void 0}:{type:X("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=Ve(this);return X(r?.slug??"unknown-error")}};function Be(e){return typeof e=="object"&&e!==null&&Fe.has(e)}function Ve(e){return Be(e)?gr(e):null}function gr(e){try{if(!Be(e))return null;let t=lr(e),r=Q=>{let b=t[Q];return b&&"value"in b?b.value:void 0},n=r("slug"),i=r("category"),s=r("status"),a=r("title"),u=r("message"),d=r("suggestion"),c=r("exitCode"),g=r("detail"),f=r("cause"),R=r("instance"),H=r("context"),h=r("stack");return typeof n!="string"||!dr.has(i)||typeof s!="number"||!$e(s)||typeof a!="string"||typeof u!="string"||d!==void 0&&typeof d!="string"||c!==void 0&&(typeof c!="number"||!$e(c))||g!==void 0&&typeof g!="string"||R!==void 0&&typeof R!="string"||h!==void 0&&typeof h!="string"?null:{slug:n,category:i,status:s,title:a,message:u,suggestion:d,exitCode:c,detail:g,cause:f,instance:R,context:H,stack:h}}catch{return null}}var fr=o({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),pr=o({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),yr=o({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),mr=o({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Er=o({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),Rr=o({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),hr=o({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),_r=o({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),xr=o({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),Tr=o({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),Sr=o({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Ge={"config-not-found":fr,"config-invalid":pr,"config-parse-error":yr,"config-validation-error":mr,"config-type-error":Er,"import-map-invalid":Rr,"cors-config-invalid":hr,"config-validation-failed":_r,"webhook-config-invalid":xr,"schedule-config-invalid":Tr,"trigger-config-invalid":Sr};var Ar=o({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Cr=o({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),br=o({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),Or=o({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),Ir=o({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),Nr=o({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),Dr=o({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),wr=o({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),je={"build-failed":Ar,"bundle-error":Cr,"typescript-error":br,"mdx-compile-error":Or,"asset-optimization-error":Ir,"ssg-generation-error":Nr,"sourcemap-error":Dr,"compilation-error":wr};var Mr=o({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Lr=o({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Pr=o({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Hr=o({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Ur=o({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),vr=o({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),kr=o({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),$r=o({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),Vr=o({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Fr=o({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ze={"hydration-mismatch":Mr,"render-error":Lr,"component-error":Pr,"layout-not-found":Hr,"page-not-found":Ur,"api-error":vr,"middleware-error":kr,"trigger-target-not-found":$r,"trigger-execution-failed":Vr,"trigger-not-supported":Fr};var Br=o({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Gr=o({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),jr=o({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),zr=o({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Yr=o({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Kr=o({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),Ye={"route-conflict":Br,"invalid-route-file":Gr,"route-handler-invalid":jr,"dynamic-route-error":zr,"route-params-error":Yr,"api-route-error":Kr};var Wr=o({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Xr=o({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),qr=o({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),Jr=o({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),Zr=o({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Qr=o({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),Ke={"module-not-found":Wr,"import-resolution-error":Xr,"circular-dependency":qr,"invalid-import":Jr,"dependency-missing":Zr,"version-mismatch":Qr};var en=o({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),tn=o({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),rn=o({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),nn=o({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),on=o({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),sn=o({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),an=o({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),cn=o({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),un=o({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),ln=o({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),dn=o({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),gn=o({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),fn=o({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),pn=o({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),yn=o({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),mn=o({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),En=o({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Rn=o({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),We={"port-in-use":en,"server-start-error":tn,"cache-error":rn,"file-watch-error":nn,"request-error":on,"service-overloaded":sn,"project-execution-unavailable":an,"semaphore-timeout":cn,"circuit-breaker-open":un,"cache-path-mismatch":ln,"network-error":dn,"api-client-error":gn,"token-storage-error":fn,"cache-invariant-violation":pn,"release-not-found":yn,"fallback-exhausted":mn,"rag-store-corrupt":En,"rag-store-unavailable":Rn};var hn=o({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),_n=o({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),xn=o({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),Tn=o({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),Sn=o({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),An=o({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),Cn=o({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),Xe={"client-boundary-violation":hn,"server-only-in-client":_n,"client-only-in-server":xn,"invalid-use-client":Tn,"invalid-use-server":Sn,"rsc-payload-error":An,"ssr-output-limit-exceeded":Cn};var bn=o({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),On=o({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),In=o({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),Nn=o({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),Dn=o({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),qe={"hmr-error":bn,"dev-server-error":On,"fast-refresh-error":In,"error-overlay-error":Nn,"source-map-error":Dn};var wn=o({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),Mn=o({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Ln=o({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Pn=o({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),Hn=o({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Un=o({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),vn=o({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),kn=o({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),$n=o({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),Vn=o({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Fn=o({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Bn=o({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),Je={"deployment-error":wn,"platform-error":Mn,"env-var-missing":Ln,"production-build-required":Pn,"environment-not-found":Hn,"release-missing-version":Un,"release-build-timeout":vn,"deployment-verification-timeout":kn,"push-receipt-missing":$n,"source-digest-mismatch":Vn,"preview-hostname-too-long":Fn,"branch-not-found":Bn};var Gn=o({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),jn=o({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),zn=o({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Yn=o({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Kn=o({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Wn=o({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Xn=o({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),qn=o({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),Ze={"agent-error":Gn,"agent-not-found":jn,"agent-timeout":zn,"agent-intent-error":Yn,"orchestration-error":Kn,"cost-limit-exceeded":Wn,"tool-id-conflict":Xn,"durable-run-event-persistence-failed":qn};var Jn=o({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Zn=o({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Qn=o({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),eo=o({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),to=o({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ro=o({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),no=o({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),oo=o({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),io=o({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),de=o({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),so=o({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),ao=o({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Qe={"unknown-error":Jn,"authentication-required":Zn,"permission-denied":Qn,"file-not-found":eo,"resource-not-found":to,"invalid-argument":ro,"timeout-error":no,"initialization-error":oo,"not-supported":io,"security-violation":de,"input-validation-failed":so,"project-source-empty":ao};var vs=Oe(Ge,je,ze,Ye,Ke,We,Xe,qe,Je,Ze,Qe);var co=[{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 uo(){return co.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function lo(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function L(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:i=!0}=t;for(let{pattern:s,name:a}of uo())if(!(r&&a==="inline script")&&(s.lastIndex=0,!!s.test(e)&&(i&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!lo())))throw de.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function P(e,t){let r=t==="root"?D:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let i=e.createElement("div");return i.id=r,e.body.appendChild(i),i}function go(e,t){if(t.type!=="slot")return;let r=P(e,t.id);r.innerHTML=L(String(t.html??""))}function et(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let i of r){let s=i.trim();if(!s)continue;let a;try{a=JSON.parse(s)}catch(d){l.debug("[client-dom] malformed NDJSON line",{line:s,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let u=a;if(u.type==="slot"){go(e,u);try{yo(e,u.id||"root")}catch(d){l.debug("[client-dom] hydration optional failed",d)}}}return n}function fo(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function tt(e,t=document,r){let n="body"in e?e:null,i=n?.body??e;if(!i)return;n&&B(t,n.headers.get(V));let s=i.getReader(),a=new TextDecoder,u="",d=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:g,value:f}=r?await Promise.race([c,fo(r)]):await c;if(g){d=!0;break}u+=a.decode(f,{stream:!0}),u=et(t,u)}u&&et(t,`${u}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||l.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){d||l.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){l.debug("[client-dom] reader.releaseLock failed",c)}if(typeof i.cancel=="function")try{await i.cancel()}catch(c){l.debug("[client-dom] stream.cancel failed",c)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(c){l.debug("[client-dom] response.body.cancel failed",c)}}}function po(e,t){let r=P(e,t),n=[],i=s=>{let a=s;a.dataset?.clientRef&&n.push(a);for(let u of s.children)i(u)};return i(r),n}function yo(e,t){let r=po(e,t);for(let n of r){let i=n.dataset?.clientRef;i&&(n.dataset.hydrated="true",l.debug("[client-dom] marked for hydration",i))}}var mo=new Set(["server","client","html","fragment"]);function rt(e){if(!e)return[];try{let t=JSON.parse(e);return Ro(t)?t.nodes:[]}catch{return[]}}async function fe(e,t,r){return await Promise.all(e.map(n=>Eo(n,t,r)))}async function Eo(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await fe(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let i=await r(e.component);return i?t.createElement(i,e.props??{},...n):null}function Ro(e){return!ge(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>nt(t,0))}function nt(e,t){return t>100||!ge(e)||!mo.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&&!ge(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>nt(r,t+1))}function ge(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function ho(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function q(e,t,r=document){try{let n=be(r);if(!n)return e;let s=(await import(n)).wrapForHydration;return typeof s!="function"?e:s(e,{params:ho(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return l.debug("router provider wrap failed",n),e}}var _o="Unknown dependency snapshot",xo="export default null; // Unknown dependency snapshot",pe="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function To(){return globalThis}async function So(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===_o||t===xo}catch{return!1}}async function C(e,t=()=>globalThis.location.reload()){if(!await So(e))return!1;let r=To();if(r[pe])return!0;r[pe]=!0;try{t()}catch{return delete r[pe],!1}return!0}async function J(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let i=await t(e,{cache:"no-store"});return await C(i,r)}catch{return!1}}var Ao=100;function Co(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Ao){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function ot(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(l.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function bo(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return l.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function Oo(e){return rt(e.dataset?.rscChildren)}function Io(e){return"/_veryfront/rsc/manifest"}function No(e){return w(e)}async function Do(e=document){try{let t=S(e),r=await fetch(Io(t),{headers:No(t)});return r.ok?await r.json():(await C(r),null)}catch{return null}}async function it(e,t,r,n={}){let i=wo(e,t,r,n.releaseAssetModules),s=t.moduleUrl??t.rel;if(!s)return null;let a=`${s}#${e.hash??""}`;try{let u=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(u)return u}catch(u){l.debug("hydrate: cache get failed",u)}if(!i)return null;try{let u=await(n.importModule??(d=>import(d)))(i);try{Co(a,u)}catch(d){l.debug("hydrate: cache set failed",d)}return u}catch(u){return l.debug("hydrate: failed to import module",{moduleUrl:i,error:u}),await(n.recoverSnapshotFailure??J)(i),null}}function wo(e,t,r,n){if(t.moduleUrl)return j(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let i=e.graphIds?.client.find(s=>s.rel===t.rel)?.path;return z({strategy:r,rel:t.rel,absPath:i,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function Mo(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let i=n.parentElement;for(;i;){if(r.has(i))return!1;i=i.parentElement}return!0})}async function st(e=document){let t=null;try{t=await Do(e)}catch(c){l.debug("hydrate: fetch manifest failed",c)}if(!t){l.debug("hydrate: no manifest");return}let r=Mo(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){l.debug("hydrate: hmr hash read failed",c)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed",c)}return}let n=S(e),i=G(n),s=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){l.debug("hydrate: test mode flags failed",c)}let a=Y(e,n?.reactVersion),[{default:u},{createRoot:d}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let c of r){let g=c.dataset?.clientRef??"";if(!g||c.dataset?.hydrated==="true")continue;let f=ot(g);if(!f)continue;let R=await it(t,f,i,{releaseAssetModules:s});if(!R)continue;let H=R[f.exportName]??R.default;if(typeof H=="function")try{let h=d(c),Q=bo(c),b=Oo(c),at=await fe(b,{Fragment:u.Fragment,createElement(U,ee,...v){return u.createElement(U,ee,...v)}},async U=>{let ee=t.modules.find(ut=>ut.id===U),v=t.components?.[U],Ee=ee?.clientRef??(v?`${v}#default`:void 0);if(!Ee)return null;let te=ot(Ee);if(!te)return null;let re=await it(t,te,i,{releaseAssetModules:s});if(!re)return null;let Re=re[te.exportName]??re.default;return typeof Re=="function"?Re:null}),ct=await q(u.createElement(H,Q,...at),n,e);h.render(ct),c.dataset.hydrated="true"}catch(h){l.warn("hydrate: render failed",h)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed (post)",c)}}var ye="data-vf-react-head-owner";var Lo=2*1024*1024,ya=Lo*2;var ma=64*1024,Ea=1024*1024,Ra=1024*1024;var ha=new TextEncoder;async function Po(){let e=S(document),t=Y(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var Ho=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function me(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||Ho.has(e.tagName.toUpperCase())}function Uo(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!me(r))??t}function vo(e,t){return e===t}function ko(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(i=>!me(i));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let i of e)!me(i)&&i.parentNode===t&&r.appendChild(i);return r}function $o(e,t){for(let r of e){let n=[...r.hasAttribute(ye)?[r]:[],...r.querySelectorAll(`[${ye}]`)];for(let i of n)t.contains(i)||i.remove()}}function Vo(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function Fo(e,t){return t?.pagePath?!1:!!e.getElementById(D)}function Bo(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function Go(e){return e==="rsc-module"}function jo(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function zo(e,t,r){return z({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function Yo(e,t){try{let r=await fetch(N+"stream"+e,{headers:w(t)});if(!r.ok)return await C(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await tt(r,document,n.signal),"success"}catch(r){return l.debug("tryStream failed",r),"failure"}}async function Z(){try{await st(document)}catch(e){l.debug("hydration failed",e)}}async function Ko(e,t,r){try{let{React:n,ReactDOM:i}=await Po(),s=zo(e,t,r);if(!s)return!1;l.debug("Loading component from:",s);let a;try{a=await import(s)}catch(R){throw await J(s),R}let u=a.default;if(typeof u!="function")return l.debug("Page component is not a function"),!1;let d=Array.from(document.body.children),c=Uo(d,document.body),g=vo(c,document.body)?ko(d,document.body):c;$o(d,g);let f=await q(n.createElement(u,{}),r);return Go(t)?i.createRoot(g).render(f):i.hydrateRoot(g,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),l.debug("Page component hydrated successfully"),!0}catch(n){return l.error("Page hydration failed",n),!1}}async function Wo(e,t){try{let r=await fetch(N+"payload"+e,{headers:w(t)});if(!r.ok)return await C(r)?"snapshot-conflict":"failure";let n=await r.json();if(B(document,n?.dependencyPinningCacheKey),n?.slots){for(let[i,s]of Object.entries(n.slots))P(document,i).innerHTML=L(String(s||""));return"success"}return P(document,D).innerHTML=L(String(n?.html||"")),"success"}catch(r){return l.debug("payload fetch failed",r),"failure"}}async function Xo(){try{let e=S(document),t=jo(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(Bo()){await Z();return}let r=e?.pagePath,n=G(e);if(r){if(Vo(globalThis.window,e,document)){l.debug("Page renderer owns hydration");return}l.debug("Found page component in hydration data:",r),await Ko(r,n,e)&&l.debug("Client component hydrated successfully");return}if(!Fo(document,e))return;let i=await Yo(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await Z();return}let s=await Wo(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await Z();return}await Z()}catch(e){l.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Xo()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Xo as boot,zo as buildPageHydrationModuleUrl,jo as buildRSCTransportQuery,$o as retireAbandonedHeadOwnerMarkers,Uo as selectHydrationRoot,Fo as shouldAttemptRSCTransport,Bo as shouldHydrateOnly,Go as shouldRenderPageComponent,Vo as shouldUsePageRendererHydration,vo as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var xe=Object.defineProperty;var _e=(t,r,e)=>r in t?xe(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var E=(t,r,e)=>_e(t,typeof r!="symbol"?r+"":r,e);var he=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Se(t,...r){let e=Object.create(null),o=t.charAt(0).toUpperCase()+t.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${o} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${o} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${o} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(e,i))throw new Error(`Duplicate ${t} slug "${i}"`);e[i]=a}return Object.freeze(e)}function P(...t){for(let r of t)for(let e of Object.values(r)){if(typeof e.slug!="string"||e.slug.length<3||e.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(e.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${e.slug}"`);if(typeof e.category!="string"||!he.has(e.category))throw new TypeError(`Registered error has unknown category "${e.category}"`);if(!Number.isInteger(e.status)||e.status<400||e.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${e.status}`);if(typeof e.title!="string"||e.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(e.suggestion!==void 0&&(typeof e.suggestion!="string"||e.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Se("error registry",...t)}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"},ln={debug:I.gray,info:I.green,warn:I.yellow,error:I.red};var p="[REDACTED]",m=Reflect.apply;var $=RegExp.prototype.exec,y=RegExp.prototype[Symbol.replace],dn=String.prototype.charCodeAt,k=String.prototype.slice,Ie=String.prototype.toLowerCase,Oe=/[^a-z0-9]/g;function D(t){let r=m(Ie,t,[]);return m(y,Oe,[r,""])}function O(t,r,e){return e===void 0?m(k,t,[r]):m(k,t,[r,e])}var Te=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Ce=512,Ae=128,S=new Map;function F(t){let r=t.length<=Ae;if(r){let s=S.get(t);if(s!==void 0)return s}let e=D(t),o=Te.some(s=>e.includes(s));if(r){if(S.size>=Ce){let s=S.keys().next().value;s!==void 0&&S.delete(s)}S.set(t,o)}return o}var Ne=["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"],De=new Set(Ne.map(D)),be=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Le=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Ue=3;function ve(t){return t===" "||t==="\t"||t===","||t===";"||t==="&"||t==="?"||t==="#"}function we(t){if(!t)return!1;let r=t.charCodeAt(0);return r>=65&&r<=90||r>=97&&r<=122}function H(t){return we(t)||t==="_"||t==="$"}function Me(t){if(!t)return!1;let r=t.charCodeAt(0);return H(t)||r>=48&&r<=57||t==="."||t==="-"}function j(t,r){let e=r,o=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!H(t[e]))return!1;for(e++;Me(t[e]);)e++;if(o){if(t[e]!==o)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function z(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||ve(t)}function Y(t,r){let e=r;for(;e=t.length||j(t,e)}function Pe(t,r){let e=r,o=!0;if(t.startsWith(p,r)){let g=r+p.length;if(V(t,g))return{end:g,replacement:p};e=g,o=!1}let s=o&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",i=!1,a=()=>s?`${s}${p}${i?s:""}`:p,c=[],d="",u=-1;for(let g=e;g0&&(f==="}"||f==="]")){if(c.at(-1)!==f)return{end:t.length,replacement:a()};if(c.pop(),g++,c.length===0&&V(t,g))return{end:g,replacement:a()};continue}if(c.length>0||!z(f)){g++;continue}let h=g;if(g=Y(t,g),g>=t.length||j(t,g))return{end:h,replacement:a()}}return{end:t.length,replacement:a()}}function G(t,r,e,o){let s=0,i="";for(let a=m($,r,[t]);a;a=m($,r,[t])){let c=a[e];if(!F(c))continue;let d=r.lastIndex,u=o===void 0?void 0:a[o],g=d+p.length;if((u==="?"||u==="&"||u===";")&&t.startsWith(p,d)&&t[g]==="#")continue;let f=Pe(t,d);i+=O(t,s,a.index),i+=a[0],i+=f.replacement,s=f.end,r.lastIndex=f.end}return s===0?t:i+O(t,s)}function $e(t,r,e){let o=e.search(/[ \\t]/);if(o<0)return!1;let s=`${r}:${O(e,0,o)}`,i=t==="//"?`https://${s}`:`${t}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function ke(t){let r=t;for(let e=0;e{let i=s.indexOf(":");if(i===-1)return`${o}${p}@`;let a=O(s,0,i);return`${o}${a}:${p}@`}]);return r=m(y,Le,[r,(e,o,s,i)=>$e(o,s,i)?e:`${o}${s}:${p}@`]),r=m(y,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[r,(e,o,s,i)=>{let a=ke(s);return De.has(D(a))||F(a)?`${o}${s}=${p}`:e}]),r=m(y,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=m(y,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[r,(e,o)=>`${o}${p}`]),r=m(y,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=G(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=G(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Ve=2048;var Rn=64*1024,Ge=256,Fe="https://veryfront.com/docs/errors/",B="...[truncated]",L="unknown-error";function W(t,r){if(t.length<=r)return t;let e=Math.max(0,r-B.length);return`${He(t,e)}${B}`}function He(t,r){let e=t.slice(0,r),o=e.charCodeAt(e.length-1);return o>=55296&&o<=56319&&(e=e.slice(0,-1)),e}function je(t){let r="";for(let e=0;e=55296&&o<=56319){let s=t.charCodeAt(e+1);s>=56320&&s<=57343?(r+=t.slice(e,e+2),e++):r+="\\uFFFD";continue}r+=o>=56320&&o<=57343?"\\uFFFD":t.charAt(e)}return r}function x(t){return typeof t!="string"?p:W(b(t),Ve)}function ze(t){let r=typeof t=="string"?b(t):L,e=W(r||L,Ge),o=je(e);return o==="."||o===".."?L:o}function T(t){let r=encodeURIComponent(ze(t));return`${Fe}${r}`}var Ye=Object.freeze,Be=Object.getOwnPropertyDescriptors,K=Number.isFinite,X=new WeakSet,We=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function n(t){let r={...t},e={...r,create(o){let s=o?.message,i=o?.detail,a=o?.cause,c=o?.instance,d=o?.context,u=o?.status??r.status;return new U(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:d})}};return Ye(e)}var U=class extends Error{constructor(e,o){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");X.add(this),this.name="VeryfrontError",this.slug=o.slug,this.category=o.category,this.status=o.status,this.title=o.title,this.suggestion=o.suggestion,this.exitCode=o.exitCode,this.detail=o.detail,this.cause=o.cause,this.instance=o.instance,this.context=o.context}toRFC9457(){let e=q(this);return e?{type:T(e.slug),title:x(e.title),status:e.status,detail:e.detail===void 0?void 0:x(e.detail),instance:e.instance===void 0?void 0:x(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:x(e.suggestion),cause:typeof e.cause=="string"?x(e.cause):void 0}:{type:T("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=q(this);return T(e?.slug??"unknown-error")}};function J(t){return typeof t=="object"&&t!==null&&X.has(t)}function q(t){return J(t)?Ke(t):null}function Ke(t){try{if(!J(t))return null;let r=Be(t),e=ye=>{let N=r[ye];return N&&"value"in N?N.value:void 0},o=e("slug"),s=e("category"),i=e("status"),a=e("title"),c=e("message"),d=e("suggestion"),u=e("exitCode"),g=e("detail"),f=e("cause"),h=e("instance"),Re=e("context"),A=e("stack");return typeof o!="string"||!We.has(s)||typeof i!="number"||!K(i)||typeof a!="string"||typeof c!="string"||d!==void 0&&typeof d!="string"||u!==void 0&&(typeof u!="number"||!K(u))||g!==void 0&&typeof g!="string"||h!==void 0&&typeof h!="string"||A!==void 0&&typeof A!="string"?null:{slug:o,category:s,status:i,title:a,message:c,suggestion:d,exitCode:u,detail:g,cause:f,instance:h,context:Re,stack:A}}catch{return null}}var qe=n({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Xe=n({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Je=n({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Ze=n({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Qe=n({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),et=n({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),tt=n({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),rt=n({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),nt=n({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),ot=n({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),st=n({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Z={"config-not-found":qe,"config-invalid":Xe,"config-parse-error":Je,"config-validation-error":Ze,"config-type-error":Qe,"import-map-invalid":et,"cors-config-invalid":tt,"config-validation-failed":rt,"webhook-config-invalid":nt,"schedule-config-invalid":ot,"trigger-config-invalid":st};var it=n({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),at=n({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),ct=n({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),ut=n({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),lt=n({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),gt=n({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),dt=n({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ft=n({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Q={"build-failed":it,"bundle-error":at,"typescript-error":ct,"mdx-compile-error":ut,"asset-optimization-error":lt,"ssg-generation-error":gt,"sourcemap-error":dt,"compilation-error":ft};var pt=n({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Et=n({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),mt=n({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Rt=n({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yt=n({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),xt=n({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),_t=n({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),ht=n({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),St=n({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),It=n({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ee={"hydration-mismatch":pt,"render-error":Et,"component-error":mt,"layout-not-found":Rt,"page-not-found":yt,"api-error":xt,"middleware-error":_t,"trigger-target-not-found":ht,"trigger-execution-failed":St,"trigger-not-supported":It};var Ot=n({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Tt=n({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Ct=n({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),At=n({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Nt=n({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Dt=n({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),te={"route-conflict":Ot,"invalid-route-file":Tt,"route-handler-invalid":Ct,"dynamic-route-error":At,"route-params-error":Nt,"api-route-error":Dt};var bt=n({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Lt=n({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Ut=n({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),vt=n({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),wt=n({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Mt=n({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),re={"module-not-found":bt,"import-resolution-error":Lt,"circular-dependency":Ut,"invalid-import":vt,"dependency-missing":wt,"version-mismatch":Mt};var Pt=n({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),$t=n({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),kt=n({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Vt=n({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Gt=n({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Ft=n({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Ht=n({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),jt=n({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),zt=n({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Yt=n({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bt=n({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Wt=n({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Kt=n({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),qt=n({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Xt=n({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Jt=n({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),Zt=n({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Qt=n({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),ne={"port-in-use":Pt,"server-start-error":$t,"cache-error":kt,"file-watch-error":Vt,"request-error":Gt,"service-overloaded":Ft,"project-execution-unavailable":Ht,"semaphore-timeout":jt,"circuit-breaker-open":zt,"cache-path-mismatch":Yt,"network-error":Bt,"api-client-error":Wt,"token-storage-error":Kt,"cache-invariant-violation":qt,"release-not-found":Xt,"fallback-exhausted":Jt,"rag-store-corrupt":Zt,"rag-store-unavailable":Qt};var er=n({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),tr=n({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),rr=n({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),nr=n({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),or=n({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),sr=n({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),ir=n({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),oe={"client-boundary-violation":er,"server-only-in-client":tr,"client-only-in-server":rr,"invalid-use-client":nr,"invalid-use-server":or,"rsc-payload-error":sr,"ssr-output-limit-exceeded":ir};var ar=n({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),cr=n({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),ur=n({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),lr=n({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),gr=n({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),se={"hmr-error":ar,"dev-server-error":cr,"fast-refresh-error":ur,"error-overlay-error":lr,"source-map-error":gr};var dr=n({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),fr=n({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),pr=n({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Er=n({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),mr=n({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Rr=n({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),yr=n({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),xr=n({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),_r=n({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),hr=n({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Sr=n({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Ir=n({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),ie={"deployment-error":dr,"platform-error":fr,"env-var-missing":pr,"production-build-required":Er,"environment-not-found":mr,"release-missing-version":Rr,"release-build-timeout":yr,"deployment-verification-timeout":xr,"push-receipt-missing":_r,"source-digest-mismatch":hr,"preview-hostname-too-long":Sr,"branch-not-found":Ir};var Or=n({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Tr=n({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Cr=n({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Ar=n({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Nr=n({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Dr=n({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),br=n({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Lr=n({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),ae={"agent-error":Or,"agent-not-found":Tr,"agent-timeout":Cr,"agent-intent-error":Ar,"orchestration-error":Nr,"cost-limit-exceeded":Dr,"tool-id-conflict":br,"durable-run-event-persistence-failed":Lr};var Ur=n({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vr=n({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),wr=n({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Mr=n({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Pr=n({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),$r=n({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),kr=n({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Vr=n({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Gr=n({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),v=n({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Fr=n({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Hr=n({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ce={"unknown-error":Ur,"authentication-required":vr,"permission-denied":wr,"file-not-found":Mr,"resource-not-found":Pr,"invalid-argument":$r,"timeout-error":kr,"initialization-error":Vr,"not-supported":Gr,"security-violation":v,"input-validation-failed":Fr,"project-source-empty":Hr};var so=P(Z,Q,ee,te,re,ne,oe,se,ie,ae,ce);var jr=[{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 zr(){return jr.map(({source:t,flags:r,name:e})=>({pattern:new RegExp(t,r),name:e}))}function Yr(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function ue(t,r={}){let{allowInlineScripts:e=!1,strict:o=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of zr())if(!(e&&a==="inline script")&&(i.lastIndex=0,!!i.test(t)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),o||!Yr())))throw v.create({detail:`Potentially unsafe HTML: ${a} detected`});return t}var _=class{constructor(r,e){E(this,"prefix",r);E(this,"level",e)}log(r,e,o,...s){this.level>r||e?.(o,...s)}debug(r,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...e)}info(r,...e){this.log(1,console.log,`[${this.prefix}] ${r}`,...e)}warn(r,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...e)}error(r,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...e)}};function Br(){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 C=Br(),R=new _("RSC",C),ho=new _("PREFETCH",C),So=new _("HYDRATE",C),Io=new _("VERYFRONT",C);var Co=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Wr=5e3,Kr=1e4,Do=16*1024*1024,qr=5e3;var Xr=100;var Jr=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),bo=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Lo=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Wr,api:3e4,ssr:Kr,hmr:3e4,sandbox:qr}),cache:Object.freeze({jit:Object.freeze({maxSize:Xr,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Jr})});var l="/_veryfront",w={RSC:`${l}/rsc/`,FS:`${l}/fs/`,MODULES:`${l}/modules/`,PAGES:`${l}/pages/`,DATA:`${l}/data/`,LIB:`${l}/lib/`,CHUNKS:`${l}/chunks/`,CLIENT:`${l}/client/`},ge={HMR_RUNTIME:`${l}/hmr-runtime.js`,HMR:`${l}/hmr.js`,ERROR_OVERLAY:`${l}/error-overlay.js`,DEV_LOADER:`${l}/dev-loader.js`,CLIENT_LOG:`${l}/log`,CLIENT_JS:`${l}/client.js`,ROUTER_JS:`${l}/router.js`,PREFETCH_JS:`${l}/prefetch.js`,MANIFEST_JSON:`${l}/manifest.json`,APP_JS:`${l}/app.js`,RSC_CLIENT:`${l}/rsc/client.js`,RSC_MANIFEST:`${l}/rsc/manifest`,RSC_STREAM:`${l}/rsc/stream`,RSC_PAYLOAD:`${l}/rsc/payload`,RSC_RENDER:`${l}/rsc/render`,RSC_PAGE:`${l}/rsc/page`,RSC_MODULE:`${l}/rsc/module`,RSC_DOM:`${l}/rsc/dom.js`,LIB_CHAT_REACT:`${l}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${l}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${l}/lib/chat/primitives.js`};var Zr={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},vo=Zr.CACHE;var wo={HMR_RUNTIME:ge.HMR_RUNTIME,ERROR_OVERLAY:ge.ERROR_OVERLAY};var Qr=w.RSC,en=w.FS;var de="rsc-root",M="x-veryfront-dependency-pins";var Vo=Array.prototype.join,Go=Array.prototype.push;var Jo=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var rn="veryfront-hydration-data";function fe(t){try{let r=[...t.querySelectorAll(`[id="${rn}"]`)];if(r.length!==1)return null;let e=t.body;if(!e)return null;let o=r[0];return e.firstElementChild!==o&&o.parentElement!==e||o.tagName?.toLowerCase()!=="script"||o.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:o}catch{return null}}function pe(t,r){if(!r?.startsWith("on:"))return!1;try{let e=fe(t);if(!e)return!1;let o=JSON.parse(e.textContent||"{}");return o.dependencyPinningCacheKey=r,e.textContent=JSON.stringify(o),!0}catch(e){return R.debug("hydration dependency snapshot seed failed",e),!1}}function me(t,r){let e=r==="root"?de:`rsc-slot-${r}`,o=t.getElementById(e);if(o)return o;let s=t.createElement("div");return s.id=e,t.body.appendChild(s),s}function nn(t,r){if(r.type!=="slot")return;let e=me(t,r.id);e.innerHTML=ue(String(r.html??""))}function Ee(t,r){let e=r.split(`\n`),o=e.pop()??"";for(let s of e){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){R.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){nn(t,c);try{an(t,c.id||"root")}catch(d){R.debug("[client-dom] hydration optional failed",d)}}}return o}function on(t){return new Promise((r,e)=>{let o=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){o();return}t.addEventListener("abort",o,{once:!0})})}async function Rs(t,r=document,e){let o="body"in t?t:null,s=o?.body??t;if(!s)return;o&&pe(r,o.headers.get(M));let i=s.getReader(),a=new TextDecoder,c="",d=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:f}=e?await Promise.race([u,on(e)]):await u;if(g){d=!0;break}c+=a.decode(f,{stream:!0}),c=Ee(r,c)}c&&Ee(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){d||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof o?.body?.cancel=="function")try{await o.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function sn(t,r){let e=me(t,r),o=[],s=i=>{let a=i;a.dataset?.clientRef&&o.push(a);for(let c of i.children)s(c)};return s(e),o}function an(t,r){let e=sn(t,r);for(let o of e){let s=o.dataset?.clientRef;s&&(o.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{Rs as consumeNdjsonStream,me as getContainer};\n'; + 'var xe=Object.defineProperty;var he=(t,r,e)=>r in t?xe(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var E=(t,r,e)=>he(t,typeof r!="symbol"?r+"":r,e);var _e=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Se(t,...r){let e=Object.create(null),o=t.charAt(0).toUpperCase()+t.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${o} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${o} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${o} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(e,i))throw new Error(`Duplicate ${t} slug "${i}"`);e[i]=a}return Object.freeze(e)}function M(...t){for(let r of t)for(let e of Object.values(r)){if(typeof e.slug!="string"||e.slug.length<3||e.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(e.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${e.slug}"`);if(typeof e.category!="string"||!_e.has(e.category))throw new TypeError(`Registered error has unknown category "${e.category}"`);if(!Number.isInteger(e.status)||e.status<400||e.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${e.status}`);if(typeof e.title!="string"||e.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(e.suggestion!==void 0&&(typeof e.suggestion!="string"||e.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Se("error registry",...t)}var T={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"},ln={debug:T.gray,info:T.green,warn:T.yellow,error:T.red};var p="[REDACTED]",m=Reflect.apply;var $=RegExp.prototype.exec,y=RegExp.prototype[Symbol.replace],dn=String.prototype.charCodeAt,k=String.prototype.slice,Te=String.prototype.toLowerCase,Ie=/[^a-z0-9]/g;function b(t){let r=m(Te,t,[]);return m(y,Ie,[r,""])}function I(t,r,e){return e===void 0?m(k,t,[r]):m(k,t,[r,e])}var Oe=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Ae=512,Ce=128,S=new Map;function F(t){let r=t.length<=Ce;if(r){let s=S.get(t);if(s!==void 0)return s}let e=b(t),o=Oe.some(s=>e.includes(s));if(r){if(S.size>=Ae){let s=S.keys().next().value;s!==void 0&&S.delete(s)}S.set(t,o)}return o}var Ne=["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"],be=new Set(Ne.map(b)),De=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Le=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Ue=3;function ve(t){return t===" "||t==="\t"||t===","||t===";"||t==="&"||t==="?"||t==="#"}function we(t){if(!t)return!1;let r=t.charCodeAt(0);return r>=65&&r<=90||r>=97&&r<=122}function H(t){return we(t)||t==="_"||t==="$"}function Pe(t){if(!t)return!1;let r=t.charCodeAt(0);return H(t)||r>=48&&r<=57||t==="."||t==="-"}function j(t,r){let e=r,o=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!H(t[e]))return!1;for(e++;Pe(t[e]);)e++;if(o){if(t[e]!==o)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function z(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||ve(t)}function Y(t,r){let e=r;for(;e=t.length||j(t,e)}function Me(t,r){let e=r,o=!0;if(t.startsWith(p,r)){let g=r+p.length;if(V(t,g))return{end:g,replacement:p};e=g,o=!1}let s=o&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",i=!1,a=()=>s?`${s}${p}${i?s:""}`:p,c=[],d="",u=-1;for(let g=e;g0&&(f==="}"||f==="]")){if(c.at(-1)!==f)return{end:t.length,replacement:a()};if(c.pop(),g++,c.length===0&&V(t,g))return{end:g,replacement:a()};continue}if(c.length>0||!z(f)){g++;continue}let _=g;if(g=Y(t,g),g>=t.length||j(t,g))return{end:_,replacement:a()}}return{end:t.length,replacement:a()}}function G(t,r,e,o){let s=0,i="";for(let a=m($,r,[t]);a;a=m($,r,[t])){let c=a[e];if(!F(c))continue;let d=r.lastIndex,u=o===void 0?void 0:a[o],g=d+p.length;if((u==="?"||u==="&"||u===";")&&t.startsWith(p,d)&&t[g]==="#")continue;let f=Me(t,d);i+=I(t,s,a.index),i+=a[0],i+=f.replacement,s=f.end,r.lastIndex=f.end}return s===0?t:i+I(t,s)}function $e(t,r,e){let o=e.search(/[ \\t]/);if(o<0)return!1;let s=`${r}:${I(e,0,o)}`,i=t==="//"?`https://${s}`:`${t}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function ke(t){let r=t;for(let e=0;e{let i=s.indexOf(":");if(i===-1)return`${o}${p}@`;let a=I(s,0,i);return`${o}${a}:${p}@`}]);return r=m(y,Le,[r,(e,o,s,i)=>$e(o,s,i)?e:`${o}${s}:${p}@`]),r=m(y,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[r,(e,o,s,i)=>{let a=ke(s);return be.has(b(a))||F(a)?`${o}${s}=${p}`:e}]),r=m(y,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=m(y,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[r,(e,o)=>`${o}${p}`]),r=m(y,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=G(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=G(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Ve=2048;var Rn=64*1024,Ge=256,Fe="https://veryfront.com/docs/errors/",B="...[truncated]",L="unknown-error";function W(t,r){if(t.length<=r)return t;let e=Math.max(0,r-B.length);return`${He(t,e)}${B}`}function He(t,r){let e=t.slice(0,r),o=e.charCodeAt(e.length-1);return o>=55296&&o<=56319&&(e=e.slice(0,-1)),e}function je(t){let r="";for(let e=0;e=55296&&o<=56319){let s=t.charCodeAt(e+1);s>=56320&&s<=57343?(r+=t.slice(e,e+2),e++):r+="\\uFFFD";continue}r+=o>=56320&&o<=57343?"\\uFFFD":t.charAt(e)}return r}function x(t){return typeof t!="string"?p:W(D(t),Ve)}function ze(t){let r=typeof t=="string"?D(t):L,e=W(r||L,Ge),o=je(e);return o==="."||o===".."?L:o}function O(t){let r=encodeURIComponent(ze(t));return`${Fe}${r}`}var Ye=Object.freeze,Be=Object.getOwnPropertyDescriptors,K=Number.isFinite,X=new WeakSet,We=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function n(t){let r={...t},e={...r,create(o){let s=o?.message,i=o?.detail,a=o?.cause,c=o?.instance,d=o?.context,u=o?.status??r.status;return new U(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:d})}};return Ye(e)}var U=class extends Error{constructor(e,o){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");X.add(this),this.name="VeryfrontError",this.slug=o.slug,this.category=o.category,this.status=o.status,this.title=o.title,this.suggestion=o.suggestion,this.exitCode=o.exitCode,this.detail=o.detail,this.cause=o.cause,this.instance=o.instance,this.context=o.context}toRFC9457(){let e=q(this);return e?{type:O(e.slug),title:x(e.title),status:e.status,detail:e.detail===void 0?void 0:x(e.detail),instance:e.instance===void 0?void 0:x(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:x(e.suggestion),cause:typeof e.cause=="string"?x(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=q(this);return O(e?.slug??"unknown-error")}};function J(t){return typeof t=="object"&&t!==null&&X.has(t)}function q(t){return J(t)?Ke(t):null}function Ke(t){try{if(!J(t))return null;let r=Be(t),e=ye=>{let N=r[ye];return N&&"value"in N?N.value:void 0},o=e("slug"),s=e("category"),i=e("status"),a=e("title"),c=e("message"),d=e("suggestion"),u=e("exitCode"),g=e("detail"),f=e("cause"),_=e("instance"),Re=e("context"),C=e("stack");return typeof o!="string"||!We.has(s)||typeof i!="number"||!K(i)||typeof a!="string"||typeof c!="string"||d!==void 0&&typeof d!="string"||u!==void 0&&(typeof u!="number"||!K(u))||g!==void 0&&typeof g!="string"||_!==void 0&&typeof _!="string"||C!==void 0&&typeof C!="string"?null:{slug:o,category:s,status:i,title:a,message:c,suggestion:d,exitCode:u,detail:g,cause:f,instance:_,context:Re,stack:C}}catch{return null}}var qe=n({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Xe=n({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Je=n({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Ze=n({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Qe=n({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),et=n({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),tt=n({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),rt=n({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),nt=n({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),ot=n({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),st=n({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Z={"config-not-found":qe,"config-invalid":Xe,"config-parse-error":Je,"config-validation-error":Ze,"config-type-error":Qe,"import-map-invalid":et,"cors-config-invalid":tt,"config-validation-failed":rt,"webhook-config-invalid":nt,"schedule-config-invalid":ot,"trigger-config-invalid":st};var it=n({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),at=n({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),ct=n({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),ut=n({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),lt=n({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),gt=n({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),dt=n({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ft=n({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Q={"build-failed":it,"bundle-error":at,"typescript-error":ct,"mdx-compile-error":ut,"asset-optimization-error":lt,"ssg-generation-error":gt,"sourcemap-error":dt,"compilation-error":ft};var pt=n({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Et=n({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),mt=n({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Rt=n({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yt=n({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),xt=n({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),ht=n({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),_t=n({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),St=n({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Tt=n({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ee={"hydration-mismatch":pt,"render-error":Et,"component-error":mt,"layout-not-found":Rt,"page-not-found":yt,"api-error":xt,"middleware-error":ht,"trigger-target-not-found":_t,"trigger-execution-failed":St,"trigger-not-supported":Tt};var It=n({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Ot=n({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),At=n({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),Ct=n({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Nt=n({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),bt=n({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),te={"route-conflict":It,"invalid-route-file":Ot,"route-handler-invalid":At,"dynamic-route-error":Ct,"route-params-error":Nt,"api-route-error":bt};var Dt=n({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Lt=n({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Ut=n({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),vt=n({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),wt=n({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Pt=n({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),re={"module-not-found":Dt,"import-resolution-error":Lt,"circular-dependency":Ut,"invalid-import":vt,"dependency-missing":wt,"version-mismatch":Pt};var Mt=n({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),$t=n({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),kt=n({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Vt=n({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Gt=n({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Ft=n({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Ht=n({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),jt=n({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),zt=n({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Yt=n({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bt=n({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Wt=n({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Kt=n({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),qt=n({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Xt=n({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Jt=n({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),Zt=n({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Qt=n({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),ne={"port-in-use":Mt,"server-start-error":$t,"cache-error":kt,"file-watch-error":Vt,"request-error":Gt,"service-overloaded":Ft,"project-execution-unavailable":Ht,"semaphore-timeout":jt,"circuit-breaker-open":zt,"cache-path-mismatch":Yt,"network-error":Bt,"api-client-error":Wt,"token-storage-error":Kt,"cache-invariant-violation":qt,"release-not-found":Xt,"fallback-exhausted":Jt,"rag-store-corrupt":Zt,"rag-store-unavailable":Qt};var er=n({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),tr=n({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),rr=n({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),nr=n({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),or=n({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),sr=n({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),ir=n({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),oe={"client-boundary-violation":er,"server-only-in-client":tr,"client-only-in-server":rr,"invalid-use-client":nr,"invalid-use-server":or,"rsc-payload-error":sr,"ssr-output-limit-exceeded":ir};var ar=n({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),cr=n({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),ur=n({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),lr=n({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),gr=n({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),se={"hmr-error":ar,"dev-server-error":cr,"fast-refresh-error":ur,"error-overlay-error":lr,"source-map-error":gr};var dr=n({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),fr=n({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),pr=n({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Er=n({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),mr=n({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Rr=n({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),yr=n({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),xr=n({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),hr=n({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),_r=n({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Sr=n({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Tr=n({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),ie={"deployment-error":dr,"platform-error":fr,"env-var-missing":pr,"production-build-required":Er,"environment-not-found":mr,"release-missing-version":Rr,"release-build-timeout":yr,"deployment-verification-timeout":xr,"push-receipt-missing":hr,"source-digest-mismatch":_r,"preview-hostname-too-long":Sr,"branch-not-found":Tr};var Ir=n({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Or=n({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Ar=n({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Cr=n({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Nr=n({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),br=n({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Dr=n({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Lr=n({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),ae={"agent-error":Ir,"agent-not-found":Or,"agent-timeout":Ar,"agent-intent-error":Cr,"orchestration-error":Nr,"cost-limit-exceeded":br,"tool-id-conflict":Dr,"durable-run-event-persistence-failed":Lr};var Ur=n({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vr=n({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),wr=n({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Pr=n({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Mr=n({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),$r=n({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),kr=n({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Vr=n({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Gr=n({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),v=n({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Fr=n({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Hr=n({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ce={"unknown-error":Ur,"authentication-required":vr,"permission-denied":wr,"file-not-found":Pr,"resource-not-found":Mr,"invalid-argument":$r,"timeout-error":kr,"initialization-error":Vr,"not-supported":Gr,"security-violation":v,"input-validation-failed":Fr,"project-source-empty":Hr};var so=M(Z,Q,ee,te,re,ne,oe,se,ie,ae,ce);var jr=[{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 zr(){return jr.map(({source:t,flags:r,name:e})=>({pattern:new RegExp(t,r),name:e}))}function Yr(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function ue(t,r={}){let{allowInlineScripts:e=!1,strict:o=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of zr())if(!(e&&a==="inline script")&&(i.lastIndex=0,!!i.test(t)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),o||!Yr())))throw v.create({detail:`Potentially unsafe HTML: ${a} detected`});return t}var h=class{constructor(r,e){E(this,"prefix",r);E(this,"level",e)}log(r,e,o,...s){this.level>r||e?.(o,...s)}debug(r,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...e)}info(r,...e){this.log(1,console.log,`[${this.prefix}] ${r}`,...e)}warn(r,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...e)}error(r,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...e)}};function Br(){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 A=Br(),R=new h("RSC",A),_o=new h("PREFETCH",A),So=new h("HYDRATE",A),To=new h("VERYFRONT",A);var Ao=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Wr=5e3,Kr=1e4,bo=16*1024*1024,qr=5e3;var Xr=100;var Jr=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),Do=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Lo=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Wr,api:3e4,ssr:Kr,hmr:3e4,sandbox:qr}),cache:Object.freeze({jit:Object.freeze({maxSize:Xr,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Jr})});var l="/_veryfront",w={RSC:`${l}/rsc/`,FS:`${l}/fs/`,MODULES:`${l}/modules/`,PAGES:`${l}/pages/`,DATA:`${l}/data/`,LIB:`${l}/lib/`,CHUNKS:`${l}/chunks/`,CLIENT:`${l}/client/`},ge={HMR_RUNTIME:`${l}/hmr-runtime.js`,HMR:`${l}/hmr.js`,ERROR_OVERLAY:`${l}/error-overlay.js`,DEV_LOADER:`${l}/dev-loader.js`,CLIENT_LOG:`${l}/log`,CLIENT_JS:`${l}/client.js`,ROUTER_JS:`${l}/router.js`,PREFETCH_JS:`${l}/prefetch.js`,MANIFEST_JSON:`${l}/manifest.json`,APP_JS:`${l}/app.js`,RSC_CLIENT:`${l}/rsc/client.js`,RSC_MANIFEST:`${l}/rsc/manifest`,RSC_STREAM:`${l}/rsc/stream`,RSC_PAYLOAD:`${l}/rsc/payload`,RSC_RENDER:`${l}/rsc/render`,RSC_PAGE:`${l}/rsc/page`,RSC_MODULE:`${l}/rsc/module`,RSC_DOM:`${l}/rsc/dom.js`,LIB_CHAT_REACT:`${l}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${l}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${l}/lib/chat/primitives.js`};var Zr={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},vo=Zr.CACHE;var wo={HMR_RUNTIME:ge.HMR_RUNTIME,ERROR_OVERLAY:ge.ERROR_OVERLAY};var Qr=w.RSC,en=w.FS;var de="rsc-root",P="x-veryfront-dependency-pins";var Vo=Array.prototype.at,Go=Array.prototype.filter,Fo=Array.prototype.join,Ho=Array.prototype.map,jo=Array.prototype.pop,zo=Array.prototype.push,Yo=Array.prototype.sort;var is=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var rn="veryfront-hydration-data";function fe(t){try{let r=[...t.querySelectorAll(`[id="${rn}"]`)];if(r.length!==1)return null;let e=t.body;if(!e)return null;let o=r[0];return e.firstElementChild!==o&&o.parentElement!==e||o.tagName?.toLowerCase()!=="script"||o.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:o}catch{return null}}function pe(t,r){if(!r?.startsWith("on:"))return!1;try{let e=fe(t);if(!e)return!1;let o=JSON.parse(e.textContent||"{}");return o.dependencyPinningCacheKey=r,e.textContent=JSON.stringify(o),!0}catch(e){return R.debug("hydration dependency snapshot seed failed",e),!1}}function me(t,r){let e=r==="root"?de:`rsc-slot-${r}`,o=t.getElementById(e);if(o)return o;let s=t.createElement("div");return s.id=e,t.body.appendChild(s),s}function nn(t,r){if(r.type!=="slot")return;let e=me(t,r.id);e.innerHTML=ue(String(r.html??""))}function Ee(t,r){let e=r.split(`\n`),o=e.pop()??"";for(let s of e){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){R.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){nn(t,c);try{an(t,c.id||"root")}catch(d){R.debug("[client-dom] hydration optional failed",d)}}}return o}function on(t){return new Promise((r,e)=>{let o=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){o();return}t.addEventListener("abort",o,{once:!0})})}async function As(t,r=document,e){let o="body"in t?t:null,s=o?.body??t;if(!s)return;o&&pe(r,o.headers.get(P));let i=s.getReader(),a=new TextDecoder,c="",d=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:f}=e?await Promise.race([u,on(e)]):await u;if(g){d=!0;break}c+=a.decode(f,{stream:!0}),c=Ee(r,c)}c&&Ee(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){d||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof o?.body?.cancel=="function")try{await o.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function sn(t,r){let e=me(t,r),o=[],s=i=>{let a=i;a.dataset?.clientRef&&o.push(a);for(let c of i.children)s(c)};return s(e),o}function an(t,r){let e=sn(t,r);for(let o of e){let s=o.dataset?.clientRef;s&&(o.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{As as consumeNdjsonStream,me as getContainer};\n'; diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index b40f046fe2..ca49099073 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -6,6 +6,10 @@ import { isAbsolute, join } from "#veryfront/compat/path/index.ts"; import { cwd } from "#veryfront/platform/compat/process.ts"; +import { + primordialArrayMap as arrayMap, + primordialArraySort as arraySort, +} from "#veryfront/platform/compat/primordials/array.ts"; import { rendererLogger } from "#veryfront/utils"; import { resolveImport } from "#veryfront/modules/import-map/resolver.ts"; import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; @@ -15,11 +19,8 @@ import { DEFAULT_REACT_VERSION, getReactImportMap } from "./react-cdn.ts"; import { computeHash } from "#veryfront/utils/hash-utils.ts"; const logger = rendererLogger.component("http-cache"); -const ArrayPrototypeMap = Array.prototype.map; -const ArrayPrototypeSort = Array.prototype.sort; const JSONStringify = JSON.stringify; const ObjectEntries = Object.entries; -const ReflectApply = Reflect.apply; /** * Cache interface for dependency injection (matches LRU essential methods). @@ -82,28 +83,18 @@ const HTTP_CACHE_FILE_HASH_NAMESPACE = "veryfront:http-module-file:v2"; /** Build an order-independent fingerprint covering imports and scoped imports. */ export function fingerprintImportMap(importMap: ImportMapConfig): Promise { - const imports = ReflectApply( - ArrayPrototypeSort, - ObjectEntries(importMap.imports ?? {}), - [compareImportMapKeys], - ) as Array<[string, string]>; - const sortedScopes = ReflectApply( - ArrayPrototypeSort, + const imports = arraySort(ObjectEntries(importMap.imports ?? {}), compareImportMapKeys); + const sortedScopes = arraySort( ObjectEntries(importMap.scopes ?? {}), - [([left]: [string, Record], [right]: [string, Record]) => - left < right ? -1 : left > right ? 1 : 0], - ) as Array<[string, Record]>; - const scopes = ReflectApply( - ArrayPrototypeMap, + ([left], [right]) => left < right ? -1 : left > right ? 1 : 0, + ); + const scopes = arrayMap( sortedScopes, - [([scope, scopedImports]: [string, Record]) => [ - scope, - ReflectApply( - ArrayPrototypeSort, - ObjectEntries(scopedImports), - [compareImportMapKeys], - ), - ]], + ([scope, scopedImports]) => + [ + scope, + arraySort(ObjectEntries(scopedImports), compareImportMapKeys), + ] as const, ); // Serialize only string primitives. JSON.stringify on arrays/objects still diff --git a/src/transforms/import-rewriter/url-builder.ts b/src/transforms/import-rewriter/url-builder.ts index fe1eb1a977..5b79589397 100644 --- a/src/transforms/import-rewriter/url-builder.ts +++ b/src/transforms/import-rewriter/url-builder.ts @@ -5,6 +5,11 @@ * Ensures consistent URLs across SSR and browser for hydration parity. */ +import { + primordialArrayJoin as arrayJoin, + primordialArrayPush as arrayPush, +} from "#veryfront/platform/compat/primordials/array.ts"; + /** * Default React version - used when not specified. * @@ -27,18 +32,7 @@ type EsmShOptions = { deps?: Record; }; -const ArrayPrototypeJoin = Array.prototype.join; -const ArrayPrototypePush = Array.prototype.push; const ObjectEntries = Object.entries; -const ReflectApply = Reflect.apply; - -function arrayJoin(values: string[], separator: string): string { - return ReflectApply(ArrayPrototypeJoin, values, [separator]) as string; -} - -function arrayPush(values: string[], value: string): void { - ReflectApply(ArrayPrototypePush, values, [value]); -} function buildEsmShParams(options?: EsmShOptions): string[] { const params: string[] = []; From d752f166448abae3997c67db0368c8982e1b2bc1 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 14:38:00 +0200 Subject: [PATCH 15/34] Align import-map integration coverage with scoped framework hardening The import-map loader now intentionally strips framework-owned specifiers from project scopes so scoped deno.json entries cannot redirect React or Veryfront runtime imports. The integration fixture still asserted the old permissive behavior, which made the hosted integration check fail after the hardening landed. Constraint: Framework-owned React and Veryfront import targets remain authoritative across global and scoped project import maps. Rejected: Preserve scoped React overrides for compatibility | that would re-open the redirect surface this PR closes. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 test --no-check --allow-all tests/integration/module-loading/import-map-loader.test.ts Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/loader.test.ts src/modules/import-map/preloader.test.ts src/modules/import-map/default-import-map.test.ts --- tests/integration/module-loading/import-map-loader.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/module-loading/import-map-loader.test.ts b/tests/integration/module-loading/import-map-loader.test.ts index 7ddbfbc027..3ea79e7a35 100644 --- a/tests/integration/module-loading/import-map-loader.test.ts +++ b/tests/integration/module-loading/import-map-loader.test.ts @@ -43,7 +43,7 @@ describe("import-map-loader", () => { }); }); - it("should load deno.json with both imports and scopes", async () => { + it("should load deno.json imports and strip scoped framework overrides", async () => { await withImportMapTestContext("import-map-load-scopes", async (context, adapter) => { const denoConfig = { imports: { @@ -73,7 +73,7 @@ describe("import-map-loader", () => { assertExists(importMap.scopes); assertEquals(typeof importMap.scopes, "object"); - assertEquals(importMap.scopes?.["/vendor/"]?.["react"], "https://esm.sh/react@17.0.2"); + assertEquals(importMap.scopes?.["/vendor/"]?.["react"], undefined); }); }); From 4f7e508fb761a2a4fea5a5e29db3f28323b44631 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 14:54:33 +0200 Subject: [PATCH 16/34] Clarify import-map follow-up review paths The latest PR feedback identified dead capacity branches in getCached, an unbounded post-release test await, and README wording that still implied optional config branching. Keep the production change minimal while making the test failure mode bounded and the docs match the validated-config contract. Constraint: Address PR review comments without changing established import-map cache formats or widening the existing hardening scope. Rejected: Change mergeImportMaps strict validation in this PR | the stricter snapshot behavior is already intentional hardening and needs separate public-contract documentation if adjusted. Confidence: high Scope-risk: narrow Tested: deno test --no-check --allow-all src/modules/import-map/preloader.test.ts Tested: deno test --no-check --allow-all src/embedding/rag-store.test.ts Tested: deno fmt --check src/modules/import-map/preloader.ts src/embedding/rag-store.test.ts src/modules/README.md Tested: deno lint src/modules/import-map/preloader.ts src/embedding/rag-store.test.ts Tested: deno check src/modules/import-map/preloader.ts src/embedding/rag-store.test.ts Tested: git diff --check --- src/embedding/rag-store.test.ts | 13 ++++++++++++- src/modules/README.md | 6 ++---- src/modules/import-map/preloader.ts | 6 ++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/embedding/rag-store.test.ts b/src/embedding/rag-store.test.ts index 754a19c77a..bf7873fc22 100644 --- a/src/embedding/rag-store.test.ts +++ b/src/embedding/rag-store.test.ts @@ -853,7 +853,18 @@ describe("ragStore", () => { if (blockedTimer !== undefined) clearTimeout(blockedTimer); releaseQueryEmbedding(); } - const documents = await listDocumentsPromise; + let settleTimer: ReturnType | undefined; + const documents = await Promise.race([ + listDocumentsPromise, + new Promise((_, reject) => { + settleTimer = setTimeout( + () => reject(new Error("listDocuments did not settle after query embedding release")), + 5_000, + ); + }), + ]).finally(() => { + if (settleTimer !== undefined) clearTimeout(settleTimer); + }); await searchPromise; assert(Array.isArray(observedDocuments)); diff --git a/src/modules/README.md b/src/modules/README.md index 6e23b76d6b..14506c243f 100644 --- a/src/modules/README.md +++ b/src/modules/README.md @@ -147,11 +147,9 @@ import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; export async function resolveReact( adapter: RuntimeAdapter, - requestConfig?: VeryfrontConfig, + validatedConfig?: VeryfrontConfig, ) { - const projectMap = requestConfig - ? await loadImportMap("/workspace/site", adapter, requestConfig) - : await loadImportMap("/workspace/site", adapter); + const projectMap = await loadImportMap("/workspace/site", adapter, validatedConfig); const overrides = { imports: { "@app/": "/_vf_modules/app/", diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 7a83e9e804..990a258310 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -854,8 +854,7 @@ export class ImportMapPreloader { variantKey = await computeHash(canonicalIdentity); } catch (error) { this.removeEmptyProject(cacheKey, projectState); - if (!this.isCapacityError(error)) throw error; - return undefined; + throw error; } if ( !this.isCurrentGeneration( @@ -876,8 +875,7 @@ export class ImportMapPreloader { ); } catch (error) { this.removeEmptyProject(cacheKey, projectState); - if (!this.isCapacityError(error)) throw error; - return undefined; + throw error; } if (!entry) { this.removeEmptyProject(cacheKey, projectState); From a258e7fc101b0218b3dc94100c5367c2b67834e6 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 15:23:35 +0200 Subject: [PATCH 17/34] fix(transforms): bind pipeline cache identities --- src/modules/import-map/merger.test.ts | 11 ++ src/modules/import-map/merger.ts | 16 +- src/modules/import-map/preloader.test.ts | 29 ++++ src/modules/import-map/preloader.ts | 33 ++-- src/transforms/pipeline/cache-identity.ts | 55 +++++-- src/transforms/pipeline/index.test.ts | 150 +++++++++++++++++- src/transforms/pipeline/index.ts | 111 ++++++++----- .../stages/ssr-vf-modules/constants.ts | 9 +- .../pipeline/stages/ssr-vf-modules/index.ts | 8 + .../stages/ssr-vf-modules/transform.test.ts | 16 ++ .../stages/ssr-vf-modules/transform.ts | 13 +- 11 files changed, 383 insertions(+), 68 deletions(-) diff --git a/src/modules/import-map/merger.test.ts b/src/modules/import-map/merger.test.ts index efeda7c38f..a4b1531b69 100644 --- a/src/modules/import-map/merger.test.ts +++ b/src/modules/import-map/merger.test.ts @@ -52,5 +52,16 @@ describe("modules/import-map/merger", () => { const result = mergeImportMaps({ imports: { a: "b" } }); assertEquals(result.imports?.a, "b"); }); + + it("should preserve compatibility with enumerable metadata fields", () => { + const map = { + imports: { a: "b" }, + metadata: { source: "project" }, + } as { imports: Record; metadata: { source: string } }; + + const result = mergeImportMaps(map); + + assertEquals(result.imports?.a, "b"); + }); }); }); diff --git a/src/modules/import-map/merger.ts b/src/modules/import-map/merger.ts index 2d64a50d5b..84eff72085 100644 --- a/src/modules/import-map/merger.ts +++ b/src/modules/import-map/merger.ts @@ -7,6 +7,20 @@ import type { ImportMapConfig } from "./types.ts"; const ObjectCreate = Object.create; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ReflectOwnKeys = Reflect.ownKeys; +const IntrinsicTypeError = TypeError; + +function snapshotMergeInput(map: ImportMapConfig): ImportMapConfig { + const input = ObjectCreate(null) as ImportMapConfig; + for (const key of ["imports", "scopes"] as const) { + const descriptor = ObjectGetOwnPropertyDescriptor(map, key); + if (!descriptor) continue; + if (!("value" in descriptor)) { + throw new IntrinsicTypeError(`Import map ${key} cannot contain accessor properties`); + } + input[key] = descriptor.value; + } + return snapshotImportMap(input); +} function copyStringRecord( target: Record, @@ -28,7 +42,7 @@ export function mergeImportMaps(...maps: ImportMapConfig[]): ImportMapConfig { const scopes = ObjectCreate(null) as Record>; for (let index = 0; index < maps.length; index++) { - const map = snapshotImportMap(maps[index]); + const map = snapshotMergeInput(maps[index]!); copyStringRecord(imports, map.imports ?? ObjectCreate(null)); const mapScopes = map.scopes ?? ObjectCreate(null); diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index ad1167ab4a..4d70125fd1 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -478,6 +478,35 @@ describe("modules/import-map/preloader", () => { assertEquals(typeof cached, "object"); assertEquals(cached !== undefined, true); }); + + it("preserves project-id lookup compatibility only for one unambiguous variant", async () => { + const preloader = new ImportMapPreloader({ + loadImportMap: () => + Promise.resolve({ + imports: { package: "https://example.com/package.ts" }, + }), + }); + const adapter = createMinimalAdapter(); + + await preloader.preload("/release/project", adapter, "project-id", { + contentSourceId: "release-1", + }); + + const cached = await preloader.getCached("project-id"); + assertEquals(cached?.imports?.package, "https://example.com/package.ts"); + + await preloader.preload("/branch/project", adapter, "project-id", { + contentSourceId: "branch-1", + }); + assertEquals(await preloader.getCached("project-id"), undefined); + assertEquals( + (await preloader.getCached("project-id", { + projectDir: "/release/project", + contentSourceId: "release-1", + }))?.imports?.package, + "https://example.com/package.ts", + ); + }); }); describe("bounded cache lifecycle", () => { diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 990a258310..2a50671e09 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -840,21 +840,34 @@ export class ImportMapPreloader { if (contextProjectDir !== undefined && typeof contextProjectDir !== "string") { throw new IntrinsicTypeError("Import-map projectDir must be a string"); } - const exactContext = snapshotPreloadContext( - contextProjectDir ?? cacheKey, - context, - ); - const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); const projectState = mapGet(this.projects, cacheKey); if (!projectState) return undefined; const globalGeneration = this.globalGeneration; const projectGeneration = projectState.generation; let variantKey: string; - try { - variantKey = await computeHash(canonicalIdentity); - } catch (error) { - this.removeEmptyProject(cacheKey, projectState); - throw error; + + // Before variants existed, callers could retrieve a projectId-keyed entry + // without also retaining its project directory. Preserve that contract + // only while the lookup is unambiguous. + if (context === undefined && mapSize(projectState.variants) === 1) { + let onlyVariantKey: string | undefined; + mapForEach(projectState.variants, (_entry, key) => { + onlyVariantKey = key; + }); + if (onlyVariantKey === undefined) return undefined; + variantKey = onlyVariantKey; + } else { + const exactContext = snapshotPreloadContext( + contextProjectDir ?? cacheKey, + context, + ); + const canonicalIdentity = buildVariantCanonicalIdentity(exactContext); + try { + variantKey = await computeHash(canonicalIdentity); + } catch (error) { + this.removeEmptyProject(cacheKey, projectState); + throw error; + } } if ( !this.isCurrentGeneration( diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 3aa1f4a46c..83a243a8e2 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -208,22 +208,34 @@ export function fingerprintPipelineImportMap(importMap: ImportMapConfig): Promis } export type CustomPluginCacheIdentity = - | { cacheable: true; identity: ReadonlyArray } - | { cacheable: false; reason: string }; + | { + cacheable: true; + identity: ReadonlyArray; + plugins: ReadonlyArray; + } + | { cacheable: false; reason: string; plugins: ReadonlyArray }; /** Require explicit versioned identities for caller-supplied executable code. */ export function getCustomPluginCacheIdentity( plugins: readonly TransformPlugin[] | undefined, ): CustomPluginCacheIdentity { if (plugins === undefined) { - return { cacheable: true, identity: ObjectFreeze([]) }; + return { + cacheable: true, + identity: ObjectFreeze([]), + plugins: ObjectFreeze([]), + }; } if (!ArrayIsArray(plugins)) { throw new IntrinsicTypeError("Transform pipeline plugins must be an array"); } const pluginCount = readArrayLength(plugins, "Transform pipeline plugins"); if (pluginCount === 0) { - return { cacheable: true, identity: ObjectFreeze([]) }; + return { + cacheable: true, + identity: ObjectFreeze([]), + plugins: ObjectFreeze([]), + }; } if (pluginCount > MAX_CUSTOM_PLUGINS) { throw new IntrinsicRangeError( @@ -232,6 +244,8 @@ export function getCustomPluginCacheIdentity( } const identity: Array = []; + const pluginSnapshot: TransformPlugin[] = []; + let uncacheableReason: string | undefined; for (let index = 0; index < pluginCount; index++) { const plugin = readArrayElement(plugins, index, "Transform pipeline plugins"); if (plugin === null || typeof plugin !== "object") { @@ -244,6 +258,8 @@ export function getCustomPluginCacheIdentity( "cacheIdentity", `Transform plugin ${index}`, ); + const condition = readOwnDataProperty(plugin, "condition", `Transform plugin ${index}`); + const transform = readOwnDataProperty(plugin, "transform", `Transform plugin ${index}`); if ( typeof name !== "string" || name.length === 0 || name.length > 256 || (ReflectApply(StringPrototypeTrim, name, []) as string) !== name || @@ -254,11 +270,24 @@ export function getCustomPluginCacheIdentity( if (typeof stage !== "number" || !NumberIsFinite(stage) || MathAbs(stage) > 1_000_000) { throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid stage`); } + if (condition !== undefined && typeof condition !== "function") { + throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid condition`); + } + if (typeof transform !== "function") { + throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid transform`); + } + + const exactPlugin = ObjectCreate(null) as TransformPlugin; + exactPlugin.name = name; + exactPlugin.stage = stage; + if (cacheIdentity !== undefined) exactPlugin.cacheIdentity = cacheIdentity as string; + if (condition !== undefined) exactPlugin.condition = condition as TransformPlugin["condition"]; + exactPlugin.transform = transform as TransformPlugin["transform"]; + ReflectApply(ArrayPrototypePush, pluginSnapshot, [ObjectFreeze(exactPlugin)]); + if (cacheIdentity === undefined) { - return { - cacheable: false, - reason: `custom transform plugin ${name} has no cacheIdentity`, - }; + uncacheableReason ??= `custom transform plugin ${name} has no cacheIdentity`; + continue; } if ( typeof cacheIdentity !== "string" || cacheIdentity.length === 0 || @@ -272,7 +301,15 @@ export function getCustomPluginCacheIdentity( [ObjectFreeze([index, name, stage, cacheIdentity] as const)], ); } - return { cacheable: true, identity: ObjectFreeze(identity) }; + const exactPlugins = ObjectFreeze(pluginSnapshot); + if (uncacheableReason !== undefined) { + return { cacheable: false, reason: uncacheableReason, plugins: exactPlugins }; + } + return { + cacheable: true, + identity: ObjectFreeze(identity), + plugins: exactPlugins, + }; } function boundedOption(value: unknown, label: string): string | null { diff --git a/src/transforms/pipeline/index.test.ts b/src/transforms/pipeline/index.test.ts index ee212af0e4..e825155491 100644 --- a/src/transforms/pipeline/index.test.ts +++ b/src/transforms/pipeline/index.test.ts @@ -11,7 +11,7 @@ import { } from "#veryfront/testing/deno-compat.ts"; import { join } from "#veryfront/compat/path"; import * as esbuild from "veryfront/extensions/bundler"; -import { runPipeline, transformToESM } from "./index.ts"; +import { runPipeline, TransformStage, transformToESM } from "./index.ts"; import { getHostEnv, setEnv } from "#veryfront/platform/compat/process.ts"; import { DEPENDENCY_PINNING_ENV_FLAG } from "../../release-assets/constants.ts"; import { @@ -136,6 +136,154 @@ export default function App() { return dep; }`; } }); + it("invalidates cached transforms when the project import map changes", async () => { + const projectDir = await makeTempDir({ prefix: "vf-pipeline-import-map-" }); + const mainFile = join(projectDir, "main.ts"); + const denoJsonPath = join(projectDir, "deno.json"); + const source = `import value from "project-alias"; export default value;`; + const options = { + projectId: "import-map-cache-project", + dev: false, + ssr: true, + }; + + try { + destroyTransformCache(); + await writeTextFile(mainFile, source); + await writeTextFile( + denoJsonPath, + JSON.stringify({ imports: { "project-alias": "/project-v1.js" } }), + ); + const first = await runPipeline(source, mainFile, projectDir, options); + + await writeTextFile( + denoJsonPath, + JSON.stringify({ imports: { "project-alias": "/project-v2.js" } }), + ); + const second = await runPipeline(source, mainFile, projectDir, options); + + assertEquals(first.code.includes("/project-v1.js"), true); + assertEquals(second.cached, false); + assertEquals(second.code.includes("/project-v2.js"), true); + assertEquals(second.code.includes("/project-v1.js"), false); + } finally { + destroyTransformCache(); + await remove(projectDir, { recursive: true }); + } + }); + + it("isolates identified custom plugin output and disables caching without an identity", async () => { + const projectDir = await makeTempDir({ prefix: "vf-pipeline-custom-plugin-" }); + const mainFile = join(projectDir, "main.ts"); + const source = "export const value = 1;"; + const options = { + projectId: "custom-plugin-cache-project", + dev: false, + ssr: false, + }; + + try { + destroyTransformCache(); + const first = await runPipeline(source, mainFile, projectDir, options, { + plugins: [{ + name: "custom-output", + stage: TransformStage.FINALIZE, + cacheIdentity: "custom-output@1", + transform: (ctx) => `${ctx.code}\n/* custom-v1 */`, + }], + }); + const changed = await runPipeline(source, mainFile, projectDir, options, { + plugins: [{ + name: "custom-output", + stage: TransformStage.FINALIZE, + cacheIdentity: "custom-output@2", + transform: (ctx) => `${ctx.code}\n/* custom-v2 */`, + }], + }); + + assertEquals(first.code.includes("custom-v1"), true); + assertEquals(changed.cached, false); + assertEquals(changed.code.includes("custom-v2"), true); + assertEquals(changed.code.includes("custom-v1"), false); + + destroyTransformCache(); + let calls = 0; + const unidentified = { + plugins: [{ + name: "unidentified-output", + stage: TransformStage.FINALIZE, + transform: (ctx: { code: string }) => { + calls++; + return `${ctx.code}\n/* unidentified-${calls} */`; + }, + }], + }; + const uncachedFirst = await runPipeline( + source, + mainFile, + projectDir, + options, + unidentified, + ); + const uncachedSecond = await runPipeline( + source, + mainFile, + projectDir, + options, + unidentified, + ); + + assertEquals(uncachedFirst.cached, false); + assertEquals(uncachedSecond.cached, false); + assertEquals(calls, 2); + assertEquals(uncachedSecond.code.includes("unidentified-2"), true); + } finally { + destroyTransformCache(); + await remove(projectDir, { recursive: true }); + } + }); + + it("binds custom plugin execution to its cache-identity snapshot", async () => { + const projectDir = await makeTempDir({ prefix: "vf-pipeline-plugin-snapshot-" }); + const mainFile = join(projectDir, "main.ts"); + const dependencyFile = join(projectDir, "dependency.ts"); + const source = `import "./dependency.ts"; export const value = 1;`; + const plugin = { + name: "mutable-output", + stage: TransformStage.FINALIZE, + cacheIdentity: "mutable-output@1", + transform: (ctx: { code: string }) => `${ctx.code}\n/* snapshot-v1 */`, + }; + + try { + destroyTransformCache(); + await writeTextFile(mainFile, source); + await writeTextFile(dependencyFile, "export const dependency = 1;"); + + const result = await runPipeline( + source, + mainFile, + projectDir, + { + projectId: "plugin-snapshot-cache-project", + dev: false, + ssr: false, + readFile: async (path) => { + plugin.transform = (ctx) => `${ctx.code}\n/* mutated-v2 */`; + return await readTextFile(path); + }, + }, + { plugins: [plugin] }, + ); + + assertEquals(result.code.includes("snapshot-v1"), true); + assertEquals(result.code.includes("mutated-v2"), false); + } finally { + destroyTransformCache(); + await remove(projectDir, { recursive: true }); + } + }); + it("replays cached unresolved dependencies through TTL and current-snapshot gates", async () => { const projectDir = await makeTempDir({ prefix: "vf-pipeline-retry-replay-" }); const mainFile = join(projectDir, "main.ts"); diff --git a/src/transforms/pipeline/index.ts b/src/transforms/pipeline/index.ts index f17d840607..e64a1125c1 100644 --- a/src/transforms/pipeline/index.ts +++ b/src/transforms/pipeline/index.ts @@ -12,7 +12,6 @@ import { import { rendererLogger } from "#veryfront/utils"; import { createTransformContext, formatTimingLog, recordStageTiming } from "./context.ts"; import { withSpan } from "#veryfront/observability/tracing/otlp-setup.ts"; -import { computeConfigHash } from "#veryfront/cache/config-hash.ts"; import { computeDepsHash } from "#veryfront/cache/dependency-graph.ts"; import type { PipelineConfig, @@ -43,6 +42,12 @@ import { validateDependencyResolutionObservations, } from "../import-rewriter/dependency-resolution.ts"; import { getDependencyResolutionObservations } from "./stages/resolve-imports.ts"; +import { loadImportMap } from "#veryfront/modules/import-map/index.ts"; +import { + computePipelineConfigIdentity, + fingerprintPipelineImportMap, + getCustomPluginCacheIdentity, +} from "./cache-identity.ts"; const SSR_PIPELINE: TransformPlugin[] = [ parsePlugin, @@ -188,6 +193,9 @@ export function runPipeline( "transform.pipeline", async () => { const transformStart = performance.now(); + // Snapshot executable custom-plugin fields before the first await. The + // same immutable view must supply both cache identity and execution. + const pluginCacheIdentity = getCustomPluginCacheIdentity(config?.plugins); const dependencySnapshot = await resolveDependencyPinningSnapshot( options.dependencyPinningSource ?? projectDir, @@ -208,35 +216,50 @@ export function runPipeline( ctx.debug = config?.debug ?? false; ctx.onProgress?.({ phase: "pipeline:context", filePath }); - const configHash = await computeConfigHash({ - reactVersion: ctx.reactVersion, - jsxImportSource: ctx.jsxImportSource, - moduleServerUrl: ctx.moduleServerUrl, - moduleServerOrigin: ctx.moduleServerOrigin, - vendorBundleHash: ctx.vendorBundleHash, - apiBaseUrl: ctx.apiBaseUrl, - studioEmbed: ctx.studioEmbed, - dev: ctx.dev, - dependencyPinningCacheKey, - }); - - const depsHash = await computeDepsHashSafe( - filePath, - projectDir, - effectiveOptions.readFile, - effectiveOptions.dependencyHashCache, - ); + let importMapFingerprint: string | undefined; + if (effectiveOptions.ssr) { + const importMap = await loadImportMap(projectDir); + importMapFingerprint = await fingerprintPipelineImportMap(importMap); + ctx.metadata.set("importMap", importMap); + ctx.metadata.set("importMapFingerprint", importMapFingerprint); + } - const cacheKey = generateCacheKey( - filePath, - ctx.contentHash, - effectiveOptions.ssr ?? false, - effectiveOptions.studioEmbed ?? false, - { depsHash, configHash, projectId: effectiveOptions.projectId }, - ); + let cacheKey: string | undefined; + if (pluginCacheIdentity.cacheable) { + const [configHash, depsHash] = await Promise.all([ + computePipelineConfigIdentity({ + reactVersion: ctx.reactVersion, + jsxImportSource: ctx.jsxImportSource, + moduleServerUrl: ctx.moduleServerUrl, + moduleServerOrigin: ctx.moduleServerOrigin, + vendorBundleHash: ctx.vendorBundleHash, + apiBaseUrl: ctx.apiBaseUrl, + studioEmbed: ctx.studioEmbed ?? false, + dev: ctx.dev, + ssr: effectiveOptions.ssr ?? false, + projectDir, + importMapFingerprint, + dependencyPinningCacheKey, + customPlugins: pluginCacheIdentity.identity, + }), + computeDepsHashSafe( + filePath, + projectDir, + effectiveOptions.readFile, + effectiveOptions.dependencyHashCache, + ), + ]); + cacheKey = generateCacheKey( + filePath, + ctx.contentHash, + effectiveOptions.ssr ?? false, + effectiveOptions.studioEmbed ?? false, + { depsHash, configHash, projectId: effectiveOptions.projectId }, + ); + } - const cached = await getCachedTransformAsync(cacheKey); - if (cached) { + const cached = cacheKey ? await getCachedTransformAsync(cacheKey) : undefined; + if (cached && cacheKey) { const dependencyResolutionObservations = validateCachedDependencyResolutionObservations( cached, ctx, @@ -302,8 +325,8 @@ export function runPipeline( } const basePipeline = effectiveOptions.ssr ? SSR_PIPELINE : BROWSER_PIPELINE; - const pipeline = config?.plugins - ? [...basePipeline, ...config.plugins].sort((a, b) => a.stage - b.stage) + const pipeline = pluginCacheIdentity.plugins.length > 0 + ? [...basePipeline, ...pluginCacheIdentity.plugins].sort((a, b) => a.stage - b.stage) : basePipeline; for (const plugin of pipeline) { @@ -333,19 +356,21 @@ export function runPipeline( // Store the bundleManifestId from ssrHttpCachePlugin for future cache validation const bundleManifestId = ctx.metadata.get("bundleManifestId") as string | undefined; const dependencyResolutionObservations = getDependencyResolutionObservations(ctx); - setCachedTransformAsync( - cacheKey, - ctx.code, - ctx.contentHash, - undefined, - bundleManifestId, - dependencyResolutionObservations, - ) - .catch( - (error) => { - logger.debug("Failed to cache transform", { error }); - }, - ); + if (cacheKey) { + setCachedTransformAsync( + cacheKey, + ctx.code, + ctx.contentHash, + undefined, + bundleManifestId, + dependencyResolutionObservations, + ) + .catch( + (error) => { + logger.debug("Failed to cache transform", { error }); + }, + ); + } const totalMs = performance.now() - transformStart; diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts b/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts index 5aec0d4a52..f6ff26fb8e 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts @@ -5,6 +5,7 @@ import { join } from "#veryfront/compat/path/index.ts"; import { createFileSystem } from "#veryfront/platform/compat/fs.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; import { getFrameworkRootFromMeta } from "#veryfront/platform/compat/vfs-paths.ts"; import { Singleflight } from "#veryfront/utils/singleflight.ts"; import { LRUCache } from "#veryfront/utils/lru-wrapper.ts"; @@ -73,11 +74,15 @@ export function buildFrameworkTransformCacheKey( reactVersion: string, projectDir: string, sourceContent: string, + importMapFingerprint?: string, ): string { const contentFingerprint = `${sourceContent.length}:${hashCodeHex(sourceContent)}:${ fnv1aHash(sourceContent) }`; - return JSON.stringify([projectDir, reactVersion, identifier, contentFingerprint]); + const identity = importMapFingerprint === undefined + ? [projectDir, reactVersion, identifier, contentFingerprint] + : [projectDir, reactVersion, importMapFingerprint, identifier, contentFingerprint]; + return JSON.stringify(identity); } // Maximum entries for the per-process framework transform caches. @@ -114,6 +119,8 @@ export interface TransformContext { reactVersion: string; projectDir: string; fs: ReturnType; + importMap?: ImportMapConfig; + importMapFingerprint?: string; onProgress?: TransformProgressListener; /** Transform keys already visited by the current recursive traversal. */ transformAncestry?: ReadonlySet; diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/index.ts b/src/transforms/pipeline/stages/ssr-vf-modules/index.ts index 730e9ca032..e9b0cf4a57 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/index.ts @@ -1,4 +1,5 @@ import { CIRCULAR_DEPENDENCY } from "#veryfront/errors"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; /** * SSR VF Modules Stage - resolves /_vf_modules/_veryfront/ paths to framework source. @@ -146,11 +147,16 @@ export const ssrVfModulesPlugin: TransformPlugin = { }); const reactVersion = ctx.reactVersion ?? REACT_DEFAULT_VERSION; + const importMap = ctx.metadata.get("importMap") as ImportMapConfig | undefined; + const importMapFingerprint = ctx.metadata.get("importMapFingerprint") as + | string + | undefined; const transformKey = buildFrameworkTransformCacheKey( resolved.sourcePath, reactVersion, ctx.projectDir, resolved.content, + importMapFingerprint, ); const cachePath = await frameworkTransformFlight.do(transformKey, async () => { const transformed = await transformFrameworkSource( @@ -160,6 +166,8 @@ export const ssrVfModulesPlugin: TransformPlugin = { ctx.projectDir, fs, ctx.onProgress, + importMap, + importMapFingerprint, ); // Skip cycle placeholders - don't cache or use them diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts b/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts index 05e95d2f61..0a9443514c 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts @@ -129,8 +129,24 @@ describe("transformFrameworkCode depth-limit fallback", { const keyA = buildFrameworkTransformCacheKey(sourcePath, "19.2.4", projectA, source); const keyB = buildFrameworkTransformCacheKey(sourcePath, "19.2.4", projectB, source); + const importMapKeyA = buildFrameworkTransformCacheKey( + sourcePath, + "19.2.4", + projectA, + source, + "import-map-v1", + ); + const importMapKeyB = buildFrameworkTransformCacheKey( + sourcePath, + "19.2.4", + projectA, + source, + "import-map-v2", + ); + try { assertEquals(keyA === keyB, false); + assertEquals(importMapKeyA === importMapKeyB, false); await transformFrameworkCode( source, diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts b/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts index a3170b38db..20074b6523 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts @@ -217,6 +217,7 @@ async function transformAndCacheFallbackDep( ctx.reactVersion, ctx.projectDir, depContent, + ctx.importMapFingerprint, ); // Prefer the main path's fully-resolved cache entry when present — // that output is strictly higher quality than what the fallback @@ -337,7 +338,7 @@ async function rewriteFallbackRelativeImports( // from file://, and Node rejects `import ... from "https:"` // (ERR_UNSUPPORTED_ESM_URL_SCHEME); leaving the remote specifier in would // break SSR under Node whenever a deep framework file hits this fallback. - const importMap = await loadImportMap(ctx.projectDir); + const importMap = ctx.importMap ?? (await loadImportMap(ctx.projectDir)); const cacheResult = await cacheHttpImportsToLocal(rewritten, { cacheDir: getHttpBundleCacheDir(), importMap, @@ -364,6 +365,7 @@ export async function transformFrameworkCode( ctx.reactVersion, ctx.projectDir, content, + ctx.importMapFingerprint, ); const ancestry = ctx.transformAncestry ?? new Set(); @@ -428,6 +430,7 @@ async function transformFrameworkCodeUncoalesced( ctx.reactVersion, ctx.projectDir, content, + ctx.importMapFingerprint, ); const cached = frameworkFileCache.get(transformKey); if (cached) { @@ -554,6 +557,7 @@ async function transformFrameworkCodeUncoalesced( ctx.reactVersion, ctx.projectDir, depContent, + ctx.importMapFingerprint, ); const existingFileUrl = frameworkFileCache.get(dependencyTransformKey); if (existingFileUrl) { @@ -640,7 +644,7 @@ async function transformFrameworkCodeUncoalesced( transformed = await stripJsonAttributesFromModuleImports(transformed); // Cache HTTP imports to local filesystem - const importMap = await loadImportMap(ctx.projectDir); + const importMap = ctx.importMap ?? (await loadImportMap(ctx.projectDir)); const cacheResult = await cacheHttpImportsToLocal(transformed, { cacheDir: getHttpBundleCacheDir(), importMap, @@ -676,6 +680,7 @@ export async function resolveAndTransformVeryfrontImport( ctx.reactVersion, ctx.projectDir, content, + ctx.importMapFingerprint, ); const cached = veryfrontTransformCache.get(transformKey); if (cached) { @@ -755,11 +760,13 @@ export async function transformFrameworkSource( projectDir: string, fs: ReturnType, onProgress?: TransformContext["onProgress"], + importMap?: TransformContext["importMap"], + importMapFingerprint?: string, ): Promise { return transformFrameworkCode( content, sourcePath, - { reactVersion, projectDir, fs, onProgress }, + { reactVersion, projectDir, fs, onProgress, importMap, importMapFingerprint }, true, ); } From ad56bd46534e0ab73a9f3efab93e2ccd20db935c Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 15:25:15 +0200 Subject: [PATCH 18/34] Preserve PR cache identity compatibility The import-map preloader now supports per-project variants keyed by project directory. Existing callers can still ask for a cached map by project id only when the retained project bucket is unambiguous, while explicit variant lookups still require their context. The SSR VF modules stage also treats missing direct-call metadata as absent metadata, preserving the pipeline metadata path while keeping direct plugin tests and callers from silently returning unresolved module imports. Constraint: Current PR head had suppressed review feedback for old getCached(projectId) callers and a pre-push regression in direct ssr-vf-modules tests. Rejected: Guess across multiple variants | would return the wrong release or environment import map. Rejected: Require metadata for direct plugin calls | existing tests cover direct plugin usage without pipeline context. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/transforms/pipeline/stages/ssr-vf-modules.test.ts Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/preloader.test.ts Tested: fmt, lint, deno check on touched files; git diff --check --- src/modules/import-map/preloader.test.ts | 45 +++++++++++++++++++ src/modules/import-map/preloader.ts | 42 ++++++++++++++--- .../pipeline/stages/ssr-vf-modules/index.ts | 4 +- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 4d70125fd1..cf70ca71b5 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -415,6 +415,51 @@ describe("modules/import-map/preloader", () => { ); }); + it("falls back to the only retained variant for project-id cache lookups", async () => { + const adapter = createMinimalAdapter(); + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + loadImportMap: async () => ({ + imports: { loaded: "single" }, + }), + }); + + const loaded = await preloader.preload("/release-a", adapter, "project", { + projectDir: "/release-a", + contentSourceId: "source-a", + }); + + assertEquals(await preloader.getCached("project"), loaded); + assertEquals( + await preloader.getCached("project", { contentSourceId: "source-a" }), + undefined, + ); + }); + + it("does not guess a project-id cache lookup across multiple variants", async () => { + const adapter = createMinimalAdapter(); + let loads = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + loadImportMap: async () => ({ + imports: { loaded: String(++loads) }, + }), + }); + + await preloader.preload("/release-a", adapter, "project", { + projectDir: "/release-a", + contentSourceId: "source-a", + }); + await preloader.preload("/release-b", adapter, "project", { + projectDir: "/release-b", + contentSourceId: "source-b", + }); + + assertEquals(await preloader.getCached("project"), undefined); + }); + it("rejects malformed loader output before publication and permits retry", async () => { const adapter = createMinimalAdapter(); let loads = 0; diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 2a50671e09..4f18fbe10a 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -423,6 +423,38 @@ export class ImportMapPreloader { return entry; } + private getSingleVariantEntry( + cacheKey: string, + projectState: ProjectImportMapState, + now: number, + ): CachedImportMap | undefined { + const projectCache = projectState.variants; + let foundKey: string | undefined; + let foundEntry: CachedImportMap | undefined; + let foundEntries = 0; + + mapForEach(projectCache, (entry, variantKey) => { + if (entry.expiresAt !== null && entry.expiresAt <= now) { + mapDelete(projectCache, variantKey); + return; + } + foundEntries += 1; + if (foundEntries === 1) { + foundKey = variantKey; + foundEntry = entry; + } + }); + + if (foundEntries !== 1 || foundKey === undefined || foundEntry === undefined) { + this.removeEmptyProject(cacheKey, projectState); + return undefined; + } + + this.touchVariant(projectCache, foundKey, foundEntry); + this.touchProject(cacheKey, projectState); + return foundEntry; + } + private capacityError(scope: "projects" | "variants" | "loads"): RangeError { const error = new IntrinsicRangeError( `Import-map preloader ${scope} capacity is occupied by in-flight loads; retry after a load settles`, @@ -881,11 +913,11 @@ export class ImportMapPreloader { } let entry: CachedImportMap | undefined; try { - entry = this.getEntry( - cacheKey, - variantKey, - this.readNow(), - ); + const now = this.readNow(); + entry = this.getEntry(cacheKey, variantKey, now) ?? + (context === undefined + ? this.getSingleVariantEntry(cacheKey, projectState, now) + : undefined); } catch (error) { this.removeEmptyProject(cacheKey, projectState); throw error; diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/index.ts b/src/transforms/pipeline/stages/ssr-vf-modules/index.ts index e9b0cf4a57..fd8ccbcc1d 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/index.ts @@ -147,8 +147,8 @@ export const ssrVfModulesPlugin: TransformPlugin = { }); const reactVersion = ctx.reactVersion ?? REACT_DEFAULT_VERSION; - const importMap = ctx.metadata.get("importMap") as ImportMapConfig | undefined; - const importMapFingerprint = ctx.metadata.get("importMapFingerprint") as + const importMap = ctx.metadata?.get("importMap") as ImportMapConfig | undefined; + const importMapFingerprint = ctx.metadata?.get("importMapFingerprint") as | string | undefined; const transformKey = buildFrameworkTransformCacheKey( From 4a8fefaa099a604e5c0cf9f2cd8ca694ec82dc20 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 15:46:01 +0200 Subject: [PATCH 19/34] Harden cache identity execution primordials --- src/modules/import-map/loader.ts | 17 +++-- src/modules/import-map/merger.test.ts | 43 ++++++++++++ src/modules/import-map/merger.ts | 33 +++++++--- src/transforms/esm/http-cache-helpers.test.ts | 39 +++++++++++ src/transforms/esm/http-cache-helpers.ts | 10 ++- src/transforms/pipeline/index.test.ts | 66 +++++++++++++++++++ src/transforms/pipeline/index.ts | 24 +++++-- 7 files changed, 206 insertions(+), 26 deletions(-) diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index 2a8892f792..30c8945bef 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -18,6 +18,7 @@ const JSONParse = JSON.parse; const ArrayIsArray = Array.isArray; const ObjectCreate = Object.create; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ObjectPrototype = Object.prototype; const ObjectGetPrototypeOf = Object.getPrototypeOf; const ReflectApply = Reflect.apply; @@ -41,6 +42,10 @@ function stringSlice(value: string, start: number, end?: number): string { ) as string; } +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} + function isFrameworkOwnedSpecifier(specifier: string): boolean { return specifier === "react" || specifier === "react-dom" || stringStartsWith(specifier, "react/") || @@ -65,7 +70,7 @@ function readOwnDataProperty( ): unknown { const descriptor = ObjectGetOwnPropertyDescriptor(value, key); if (!descriptor) return undefined; - if (!("value" in descriptor)) { + if (!hasOwn(descriptor, "value")) { throw IMPORT_MAP_INVALID.create({ detail: `${label} cannot contain accessor properties`, }); @@ -107,7 +112,7 @@ function copyFilteredRecord( const key = keys[index]; if (typeof key !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(record, key); - if (!descriptor?.enumerable || !("value" in descriptor)) continue; + if (!descriptor?.enumerable || !hasOwn(descriptor, "value")) continue; const value = descriptor.value as string; if (stringStartsWith(value, "./") || stringStartsWith(value, "../")) continue; result[key] = normalizeNpm ? normalizeImportValue(value) : value; @@ -125,7 +130,7 @@ function filterRelativePaths(importMap: ImportMapConfig): ImportMapConfig { const scope = scopeKeys[index]; if (typeof scope !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(exactScopes, scope); - if (!descriptor?.enumerable || !("value" in descriptor)) continue; + if (!descriptor?.enumerable || !hasOwn(descriptor, "value")) continue; scopes[scope] = copyFilteredRecord( descriptor.value as Readonly>, false, @@ -154,7 +159,7 @@ function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConf const scope = scopeKeys[index]; if (typeof scope !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(exactScopes, scope); - if (!descriptor?.enumerable || !("value" in descriptor)) continue; + if (!descriptor?.enumerable || !hasOwn(descriptor, "value")) continue; scopes[scope] = copyFilteredRecord( descriptor.value as Readonly>, true, @@ -172,7 +177,7 @@ function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConf const key = defaultKeys[index]; if (typeof key !== "string" || !stringStartsWith(key, "veryfront/")) continue; const descriptor = ObjectGetOwnPropertyDescriptor(defaultImports, key); - if (descriptor?.enumerable && "value" in descriptor) { + if (descriptor?.enumerable && hasOwn(descriptor, "value")) { imports[key] = descriptor.value as string; } } @@ -181,7 +186,7 @@ function normalizeImportMapForRuntime(importMap: ImportMapConfig): ImportMapConf const key = reactKeys[index]; if (typeof key !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(REACT_IMPORTS, key); - if (descriptor?.enumerable && "value" in descriptor) { + if (descriptor?.enumerable && hasOwn(descriptor, "value")) { imports[key] = descriptor.value as string; } } diff --git a/src/modules/import-map/merger.test.ts b/src/modules/import-map/merger.test.ts index a4b1531b69..fe20a78ade 100644 --- a/src/modules/import-map/merger.test.ts +++ b/src/modules/import-map/merger.test.ts @@ -2,6 +2,7 @@ import "#veryfront/schemas/_test-setup.ts"; import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { mergeImportMaps } from "./merger.ts"; +import type { ImportMapConfig } from "./types.ts"; describe("modules/import-map/merger", () => { describe("mergeImportMaps", () => { @@ -63,5 +64,47 @@ describe("modules/import-map/merger", () => { assertEquals(result.imports?.a, "b"); }); + + it("rejects accessors after inherited descriptor poisoning without iterating keys", () => { + const originalArrayIterator = Array.prototype[Symbol.iterator]; + const originalValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + let getterCalls = 0; + let accessorError: unknown; + const accessorMap = Object.defineProperty({}, "imports", { + enumerable: true, + get() { + getterCalls++; + return { accessed: "https://example.com/accessed.ts" }; + }, + }) as ImportMapConfig; + + try { + Reflect.set(Array.prototype, Symbol.iterator, function (this: unknown[]) { + if (this.length === 2 && this[0] === "imports" && this[1] === "scopes") { + return { next: () => ({ done: true, value: undefined }) }; + } + return Reflect.apply(originalArrayIterator, this, []); + }); + Object.defineProperty(Object.prototype, "value", { + configurable: true, + value: { poisoned: "https://example.com/poisoned.ts" }, + }); + + try { + mergeImportMaps(accessorMap); + } catch (error) { + accessorError = error; + } + const merged = mergeImportMaps({ imports: { safe: "https://example.com/safe.ts" } }); + assertEquals(merged.imports?.safe, "https://example.com/safe.ts"); + } finally { + Reflect.set(Array.prototype, Symbol.iterator, originalArrayIterator); + if (originalValue) Object.defineProperty(Object.prototype, "value", originalValue); + else Reflect.deleteProperty(Object.prototype, "value"); + } + + assertEquals(accessorError instanceof TypeError, true); + assertEquals(getterCalls, 0); + }); }); }); diff --git a/src/modules/import-map/merger.ts b/src/modules/import-map/merger.ts index 84eff72085..0969af7ef1 100644 --- a/src/modules/import-map/merger.ts +++ b/src/modules/import-map/merger.ts @@ -6,19 +6,32 @@ import type { ImportMapConfig } from "./types.ts"; // descriptor-snapshotted records. const ObjectCreate = Object.create; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; const ReflectOwnKeys = Reflect.ownKeys; const IntrinsicTypeError = TypeError; +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} + +function copySnapshotField( + input: ImportMapConfig, + map: ImportMapConfig, + key: "imports" | "scopes", +): void { + const descriptor = ObjectGetOwnPropertyDescriptor(map, key); + if (!descriptor) return; + if (!hasOwn(descriptor, "value")) { + throw new IntrinsicTypeError(`Import map ${key} cannot contain accessor properties`); + } + input[key] = descriptor.value; +} + function snapshotMergeInput(map: ImportMapConfig): ImportMapConfig { const input = ObjectCreate(null) as ImportMapConfig; - for (const key of ["imports", "scopes"] as const) { - const descriptor = ObjectGetOwnPropertyDescriptor(map, key); - if (!descriptor) continue; - if (!("value" in descriptor)) { - throw new IntrinsicTypeError(`Import map ${key} cannot contain accessor properties`); - } - input[key] = descriptor.value; - } + copySnapshotField(input, map, "imports"); + copySnapshotField(input, map, "scopes"); return snapshotImportMap(input); } @@ -31,7 +44,7 @@ function copyStringRecord( const key = keys[index]; if (typeof key !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(source, key); - if (descriptor?.enumerable && "value" in descriptor) { + if (descriptor?.enumerable && hasOwn(descriptor, "value")) { target[key] = descriptor.value as string; } } @@ -51,7 +64,7 @@ export function mergeImportMaps(...maps: ImportMapConfig[]): ImportMapConfig { const scope = scopeKeys[scopeIndex]; if (typeof scope !== "string") continue; const descriptor = ObjectGetOwnPropertyDescriptor(mapScopes, scope); - if (!descriptor?.enumerable || !("value" in descriptor)) continue; + if (!descriptor?.enumerable || !hasOwn(descriptor, "value")) continue; const target = scopes[scope] ??= ObjectCreate(null) as Record; copyStringRecord(target, descriptor.value as Readonly>); } diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index ce157c1b51..101c43160b 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -95,6 +95,45 @@ describe("transforms/esm/http-cache-helpers", () => { ); }); + it("does not consult mutable JSON or array hooks for final identities", async () => { + const importMap = { imports: {}, scopes: {} }; + const baseline = await buildHttpCacheIdentity( + "https://modules.example.com/root.js", + { importMap, reactVersion: "19.0.0" }, + ); + assertEquals( + baseline, + 'veryfront:http-module:v2:["https://modules.example.com/root.js","19.0.0","318ae612f9deb78c22b7ccf3a2d45fe489d63ca499d03712e9df30c41f9c39e5"]', + ); + const originalStringify = JSON.stringify; + const arrayToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + let hookCalls = 0; + let poisoned: string; + + try { + Reflect.set(JSON, "stringify", () => "poisoned"); + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return []; + }, + writable: true, + }); + poisoned = await buildHttpCacheIdentity( + "https://modules.example.com/root.js", + { importMap, reactVersion: "19.0.0" }, + ); + } finally { + Reflect.set(JSON, "stringify", originalStringify); + if (arrayToJson) Object.defineProperty(Array.prototype, "toJSON", arrayToJson); + else Reflect.deleteProperty(Array.prototype, "toJSON"); + } + + assertEquals(poisoned, baseline); + assertEquals(hookCalls, 0); + }); + it("does not consult inherited toJSON hooks while fingerprinting import maps", async () => { const importMap = { imports: { pkg: "https://modules.example.com/pkg-v1.js" }, diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 4981703356..c941e50455 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -191,12 +191,10 @@ export async function buildHttpCacheIdentity( const effective = getEffectiveHttpCacheRequest(url, options); const normalizedUrl = normalizeHttpUrl(effective.url); const importMapFingerprint = await getRequestImportMapFingerprint(url, effective.options); - const components = [ - normalizedUrl, - effective.options.reactVersion ?? null, - importMapFingerprint, - ]; - return `${HTTP_CACHE_IDENTITY_NAMESPACE}:${JSON.stringify(components)}`; + const reactVersion = effective.options.reactVersion; + return `${HTTP_CACHE_IDENTITY_NAMESPACE}:[${JSONStringify(normalizedUrl)},${ + reactVersion === undefined ? "null" : JSONStringify(reactVersion) + },${JSONStringify(importMapFingerprint)}]`; } /** Build recoverable metadata while reusing the request graph's import-map fingerprint. */ diff --git a/src/transforms/pipeline/index.test.ts b/src/transforms/pipeline/index.test.ts index e825155491..6b2f59bd7a 100644 --- a/src/transforms/pipeline/index.test.ts +++ b/src/transforms/pipeline/index.test.ts @@ -284,6 +284,72 @@ export default function App() { return dep; }`; } }); + it("uses captured array operations for custom plugin execution", async () => { + const projectDir = await makeTempDir({ prefix: "vf-pipeline-plugin-primordials-" }); + const mainFile = join(projectDir, "main.ts"); + const source = "export const value = 1;"; + const originalArrayIterator = Array.prototype[Symbol.iterator]; + const originalArraySort = Array.prototype.sort; + const isSentinelPipeline = (values: unknown[]): boolean => { + for (let index = 0; index < values.length; index++) { + const value = values[index] as { name?: unknown } | undefined; + if (value?.name === "sentinel-early" || value?.name === "sentinel-late") return true; + } + return false; + }; + + try { + destroyTransformCache(); + await writeTextFile(mainFile, source); + Reflect.set(Array.prototype, Symbol.iterator, function (this: unknown[]) { + const values = this as unknown[]; + if (isSentinelPipeline(values)) { + return { next: () => ({ done: true, value: undefined }) }; + } + return Reflect.apply(originalArrayIterator, values, []); + }); + Reflect.set(Array.prototype, "sort", function ( + this: unknown[], + compare?: (left: unknown, right: unknown) => number, + ) { + if (isSentinelPipeline(this)) return this; + return Reflect.apply(originalArraySort, this, [compare]); + }); + + const result = await runPipeline( + source, + mainFile, + projectDir, + { projectId: "plugin-primordial-project", dev: false, ssr: false }, + { + plugins: [{ + name: "sentinel-late", + stage: TransformStage.FINALIZE + 2, + cacheIdentity: "sentinel-late@1", + transform: (ctx) => `${ctx.code}\n/* sentinel-late */`, + }, { + name: "sentinel-early", + stage: TransformStage.FINALIZE + 1, + cacheIdentity: "sentinel-early@1", + transform: (ctx) => `${ctx.code}\n/* sentinel-early */`, + }], + }, + ); + + assertEquals(result.code.includes("sentinel-early"), true); + assertEquals(result.code.includes("sentinel-late"), true); + assertEquals( + result.code.indexOf("sentinel-early") < result.code.indexOf("sentinel-late"), + true, + ); + } finally { + Reflect.set(Array.prototype, Symbol.iterator, originalArrayIterator); + Reflect.set(Array.prototype, "sort", originalArraySort); + destroyTransformCache(); + await remove(projectDir, { recursive: true }); + } + }); + it("replays cached unresolved dependencies through TTL and current-snapshot gates", async () => { const projectDir = await makeTempDir({ prefix: "vf-pipeline-retry-replay-" }); const mainFile = join(projectDir, "main.ts"); diff --git a/src/transforms/pipeline/index.ts b/src/transforms/pipeline/index.ts index e64a1125c1..30505f58ca 100644 --- a/src/transforms/pipeline/index.ts +++ b/src/transforms/pipeline/index.ts @@ -48,6 +48,10 @@ import { fingerprintPipelineImportMap, getCustomPluginCacheIdentity, } from "./cache-identity.ts"; +import { + primordialArrayPush, + primordialArraySort, +} from "#veryfront/platform/compat/primordials/array.ts"; const SSR_PIPELINE: TransformPlugin[] = [ parsePlugin, @@ -325,11 +329,23 @@ export function runPipeline( } const basePipeline = effectiveOptions.ssr ? SSR_PIPELINE : BROWSER_PIPELINE; - const pipeline = pluginCacheIdentity.plugins.length > 0 - ? [...basePipeline, ...pluginCacheIdentity.plugins].sort((a, b) => a.stage - b.stage) - : basePipeline; + let pipeline: readonly TransformPlugin[] = basePipeline; + if (pluginCacheIdentity.plugins.length > 0) { + const sortedPipeline: TransformPlugin[] = []; + for (let index = 0; index < basePipeline.length; index++) { + primordialArrayPush(sortedPipeline, basePipeline[index]); + } + for (let index = 0; index < pluginCacheIdentity.plugins.length; index++) { + primordialArrayPush(sortedPipeline, pluginCacheIdentity.plugins[index]); + } + pipeline = primordialArraySort( + sortedPipeline, + (left, right) => left.stage - right.stage, + ); + } - for (const plugin of pipeline) { + for (let index = 0; index < pipeline.length; index++) { + const plugin = pipeline[index]!; if (plugin.condition?.(ctx) === false) continue; const stageStart = performance.now(); From cf03534ba4d5ee4ddf7f98c0814a2ed76ba1bc32 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 15:59:53 +0200 Subject: [PATCH 20/34] fix(modules): close remaining cache poisoning gaps --- src/modules/import-map/preloader.test.ts | 77 +++++++++++++++++++ src/modules/import-map/preloader.ts | 15 ++-- .../stages/ssr-vf-modules/constants.ts | 22 +++++- .../stages/ssr-vf-modules/transform.test.ts | 61 +++++++++++++++ 4 files changed, 166 insertions(+), 9 deletions(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index cf70ca71b5..605d5404b7 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -3,6 +3,7 @@ import { assertEquals, assertRejects, assertStrictEquals, + assertStringIncludes, assertThrows, } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; @@ -296,6 +297,50 @@ describe("modules/import-map/preloader", () => { assertEquals(getterCalls, 0); }); + it("rejects accessor-backed config after inherited descriptor poisoning", async () => { + const adapter = createMinimalAdapter(); + const originalValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + let getterCalls = 0; + let accessorError: unknown; + const context = Object.defineProperty({}, "config", { + enumerable: true, + get() { + getterCalls++; + return undefined; + }, + }); + + try { + Object.defineProperty(Object.prototype, "value", { + configurable: true, + value: { + resolve: { + importMap: { + imports: { poisoned: "https://example.com/poisoned.ts" }, + }, + }, + }, + }); + try { + await preloadImportMap( + "/inherited-value-accessor-context", + adapter, + "inherited-value-accessor-context", + context, + ); + } catch (error) { + accessorError = error; + } + } finally { + if (originalValue) Object.defineProperty(Object.prototype, "value", originalValue); + else Reflect.deleteProperty(Object.prototype, "value"); + } + + assertEquals(accessorError instanceof TypeError, true); + assertStringIncludes((accessorError as Error).message, "cannot be an accessor"); + assertEquals(getterCalls, 0); + }); + it("rejects non-object config context values", async () => { const adapter = createMinimalAdapter(); @@ -524,6 +569,38 @@ describe("modules/import-map/preloader", () => { assertEquals(cached !== undefined, true); }); + it("rejects accessor-backed projectDir after inherited descriptor poisoning", async () => { + const originalValue = Object.getOwnPropertyDescriptor(Object.prototype, "value"); + let getterCalls = 0; + let accessorError: unknown; + const context = Object.defineProperty({}, "projectDir", { + enumerable: true, + get() { + getterCalls++; + return "/accessed-project"; + }, + }); + + try { + Object.defineProperty(Object.prototype, "value", { + configurable: true, + value: "/inherited-project", + }); + try { + await getCachedImportMap("inherited-project", context); + } catch (error) { + accessorError = error; + } + } finally { + if (originalValue) Object.defineProperty(Object.prototype, "value", originalValue); + else Reflect.deleteProperty(Object.prototype, "value"); + } + + assertEquals(accessorError instanceof TypeError, true); + assertStringIncludes((accessorError as Error).message, "cannot be an accessor"); + assertEquals(getterCalls, 0); + }); + it("preserves project-id lookup compatibility only for one unambiguous variant", async () => { const preloader = new ImportMapPreloader({ loadImportMap: () => diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 4f18fbe10a..ef660f628c 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -38,6 +38,7 @@ const IntrinsicWeakSet = WeakSet; const IntrinsicTypeError = TypeError; const JSONStringify = JSON.stringify; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const MapPrototypeClear = Map.prototype.clear; const MapPrototypeDelete = Map.prototype.delete; const MapPrototypeForEach = Map.prototype.forEach; @@ -69,6 +70,10 @@ function monotonicNow(): number { return ReflectApply(PerformanceNow, IntrinsicPerformance, []) as number; } +function hasOwn(object: object, key: PropertyKey): boolean { + return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; +} + function mapClear(map: Map): void { ReflectApply(MapPrototypeClear, map, []); } @@ -196,7 +201,7 @@ function snapshotPreloadContext( context, "contentSourceId", ); - if (contentSourceDescriptor && !("value" in contentSourceDescriptor)) { + if (contentSourceDescriptor && !hasOwn(contentSourceDescriptor, "value")) { throw new IntrinsicTypeError("Import-map contentSourceId cannot be an accessor"); } const contentSourceId = contentSourceDescriptor?.value; @@ -204,7 +209,7 @@ function snapshotPreloadContext( throw new IntrinsicTypeError("Import-map contentSourceId must be a string"); } const configDescriptor = ObjectGetOwnPropertyDescriptor(context, "config"); - if (configDescriptor && !("value" in configDescriptor)) { + if (configDescriptor && !hasOwn(configDescriptor, "value")) { throw new IntrinsicTypeError("Import-map config cannot be an accessor"); } const config = configDescriptor?.value as VeryfrontConfig | undefined; @@ -213,7 +218,7 @@ function snapshotPreloadContext( throw new IntrinsicTypeError("Import-map config must be an object"); } const resolveDescriptor = ObjectGetOwnPropertyDescriptor(config, "resolve"); - if (resolveDescriptor && !("value" in resolveDescriptor)) { + if (resolveDescriptor && !hasOwn(resolveDescriptor, "value")) { throw new IntrinsicTypeError("Import-map config resolve cannot be an accessor"); } const resolve = resolveDescriptor?.value; @@ -223,7 +228,7 @@ function snapshotPreloadContext( const importMapDescriptor = resolve ? ObjectGetOwnPropertyDescriptor(resolve, "importMap") : undefined; - if (importMapDescriptor && !("value" in importMapDescriptor)) { + if (importMapDescriptor && !hasOwn(importMapDescriptor, "value")) { throw new IntrinsicTypeError("Import-map config resolve.importMap cannot be an accessor"); } const importMap = snapshotImportMap(importMapDescriptor?.value ?? {}); @@ -865,7 +870,7 @@ export class ImportMapPreloader { const projectDirDescriptor = context && typeof context === "object" ? ObjectGetOwnPropertyDescriptor(context, "projectDir") : undefined; - if (projectDirDescriptor && !("value" in projectDirDescriptor)) { + if (projectDirDescriptor && !hasOwn(projectDirDescriptor, "value")) { throw new IntrinsicTypeError("Import-map projectDir cannot be an accessor"); } const contextProjectDir = projectDirDescriptor?.value; diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts b/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts index f6ff26fb8e..52565f4590 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts @@ -14,6 +14,15 @@ import { fnv1aHash, hashCodeHex } from "#veryfront/utils/hash-utils.ts"; export const LOG_PREFIX = "[SSR-VF-MODULES]"; +// Framework transforms can run after project code has modified shared +// prototypes. Quote each primitive directly with the captured intrinsic so an +// inherited Array.prototype.toJSON cannot collapse otherwise distinct keys. +const JSONStringify = JSON.stringify; + +function quoteCacheIdentityPart(value: string): string { + return JSONStringify(value); +} + // Extensions to try when resolving framework files export const EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"]; @@ -79,10 +88,15 @@ export function buildFrameworkTransformCacheKey( const contentFingerprint = `${sourceContent.length}:${hashCodeHex(sourceContent)}:${ fnv1aHash(sourceContent) }`; - const identity = importMapFingerprint === undefined - ? [projectDir, reactVersion, identifier, contentFingerprint] - : [projectDir, reactVersion, importMapFingerprint, identifier, contentFingerprint]; - return JSON.stringify(identity); + const projectPart = quoteCacheIdentityPart(projectDir); + const reactPart = quoteCacheIdentityPart(reactVersion); + const identifierPart = quoteCacheIdentityPart(identifier); + const contentPart = quoteCacheIdentityPart(contentFingerprint); + if (importMapFingerprint === undefined) { + return `[${projectPart},${reactPart},${identifierPart},${contentPart}]`; + } + const importMapPart = quoteCacheIdentityPart(importMapFingerprint); + return `[${projectPart},${reactPart},${importMapPart},${identifierPart},${contentPart}]`; } // Maximum entries for the per-process framework transform caches. diff --git a/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts b/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts index 0a9443514c..1114497147 100644 --- a/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts +++ b/src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts @@ -20,6 +20,7 @@ import { } from "./constants.ts"; import { buildReactUrl } from "#veryfront/transforms/import-rewriter/url-builder.ts"; import { resolveVeryfrontSourcePath } from "./path-resolver.ts"; +import { fnv1aHash, hashCodeHex } from "#veryfront/utils/hash-utils.ts"; describe("reactReExportToEsmUrl", () => { const reactPath = (name: string) => join(FRAMEWORK_ROOT, "react", name); @@ -168,6 +169,66 @@ describe("transformFrameworkCode depth-limit fallback", { } }); + it("preserves framed cache-key bytes under inherited Array.prototype.toJSON", () => { + const identifier = '/framework/quoted"module.ts'; + const reactVersion = "19.2.4"; + const projectDir = "/projects/line\nbreak"; + const source = 'export const marker = "quoted";\n'; + const importMapFingerprint = "import-map-v2"; + const contentFingerprint = `${source.length}:${hashCodeHex(source)}:${fnv1aHash(source)}`; + const expectedLegacy = JSON.stringify([ + projectDir, + reactVersion, + identifier, + contentFingerprint, + ]); + const expectedScoped = JSON.stringify([ + projectDir, + reactVersion, + importMapFingerprint, + identifier, + contentFingerprint, + ]); + const originalToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + let poisonedLegacy: string | undefined; + let poisonedScoped: string | undefined; + let distinctScoped: string | undefined; + + try { + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value: () => [], + }); + poisonedLegacy = buildFrameworkTransformCacheKey( + identifier, + reactVersion, + projectDir, + source, + ); + poisonedScoped = buildFrameworkTransformCacheKey( + identifier, + reactVersion, + projectDir, + source, + importMapFingerprint, + ); + distinctScoped = buildFrameworkTransformCacheKey( + identifier, + reactVersion, + projectDir, + source, + "import-map-v3", + ); + } finally { + if (originalToJson) Object.defineProperty(Array.prototype, "toJSON", originalToJson); + else Reflect.deleteProperty(Array.prototype, "toJSON"); + } + + assertEquals(poisonedLegacy, expectedLegacy); + assertEquals(poisonedScoped, expectedScoped); + assertEquals(poisonedScoped === distinctScoped, false); + }); + it("coalesces concurrent transforms instead of reporting a false cycle", async () => { const tmp = await Deno.makeTempDir({ prefix: "vf-vfmod-concurrent-" }); const sourcePath = `${tmp}/framework-module.ts`; From 00db338a6d2e0d22f653a938dd771ea8cc66bd28 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 16:00:36 +0200 Subject: [PATCH 21/34] Require concrete transform plugin stages Custom plugin cache identities model execution stages and are reused for persistent transform cache keys. Fractional stage values cannot represent a stable TransformStage position, so the validation now rejects them both from caller-supplied plugins and from precomputed identity tuples. Constraint: PR review feedback called out fractional stage values as outside the transform-stage domain. Rejected: Only validate direct plugin input | precomputed identity tuples feed the same cache key encoder and need the same contract. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.test.ts src/modules/import-map/loader.test.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.test.ts src/modules/import-map/preloader.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/transforms/pipeline/cache-identity.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.ts src/transforms/pipeline/index.test.ts src/modules/import-map/loader.ts src/modules/import-map/merger.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 lint src/transforms/pipeline/cache-identity.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.ts src/transforms/pipeline/index.test.ts src/modules/import-map/loader.ts src/modules/import-map/merger.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 check src/transforms/pipeline/cache-identity.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.ts src/transforms/pipeline/index.test.ts src/modules/import-map/loader.ts src/modules/import-map/merger.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: git diff --check origin/main...HEAD -- src/transforms/pipeline/cache-identity.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.ts src/transforms/pipeline/index.test.ts src/modules/import-map/loader.ts src/modules/import-map/merger.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts --- .../pipeline/cache-identity.test.ts | 24 +++++++++++++++++++ src/transforms/pipeline/cache-identity.ts | 10 ++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index cc743c6265..db19169be7 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -179,6 +179,17 @@ describe("transform pipeline cache identity", () => { assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid name"); }); + it("rejects fractional custom plugin stages", () => { + const plugin: TransformPlugin = { + name: "custom", + stage: TransformStage.FINALIZE + 0.5, + cacheIdentity: "custom@1", + transform, + }; + + assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid stage"); + }); + it("rejects oversized base identity fields before hashing", async () => { await assertRejects( () => @@ -207,6 +218,19 @@ describe("transform pipeline cache identity", () => { assertNotEquals(plugins, baseline); }); + it("rejects fractional stages in precomputed custom plugin identities", async () => { + await assertRejects( + () => + computePipelineConfigIdentity( + identityInput({ + customPlugins: [[0, "custom", TransformStage.FINALIZE + 0.5, "custom@1"]], + }), + ), + TypeError, + "invalid stage", + ); + }); + it("changes identity when moduleServerOrigin changes", async () => { const baseline = await computePipelineConfigIdentity( identityInput({ moduleServerOrigin: "https://app.example.test" }), diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 83a243a8e2..9e57ad65d9 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -267,7 +267,10 @@ export function getCustomPluginCacheIdentity( ) { throw new IntrinsicTypeError(`Transform plugin at index ${index} has an invalid name`); } - if (typeof stage !== "number" || !NumberIsFinite(stage) || MathAbs(stage) > 1_000_000) { + if ( + typeof stage !== "number" || !NumberIsFinite(stage) || + !NumberIsSafeInteger(stage) || MathAbs(stage) > 1_000_000 + ) { throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid stage`); } if (condition !== undefined && typeof condition !== "function") { @@ -394,7 +397,10 @@ function encodeCustomPluginIdentities( ) { throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid name`); } - if (typeof stage !== "number" || !NumberIsFinite(stage) || MathAbs(stage) > 1_000_000) { + if ( + typeof stage !== "number" || !NumberIsFinite(stage) || + !NumberIsSafeInteger(stage) || MathAbs(stage) > 1_000_000 + ) { throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid stage`); } if ( From cb3e27c4a6f77bda817e0ae93f5b55e7bb9cb187 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 16:10:06 +0200 Subject: [PATCH 22/34] fix(transforms): preserve fractional plugin stages --- .../pipeline/cache-identity.test.ts | 50 ++++++++++++++----- src/transforms/pipeline/cache-identity.ts | 21 +++++--- src/transforms/pipeline/index.test.ts | 4 +- src/transforms/pipeline/types.ts | 6 ++- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index db19169be7..e62c6e1a9b 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -179,15 +179,37 @@ describe("transform pipeline cache identity", () => { assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid name"); }); - it("rejects fractional custom plugin stages", () => { + it("accepts fractional custom plugin stages between enum anchors", () => { const plugin: TransformPlugin = { name: "custom", - stage: TransformStage.FINALIZE + 0.5, + stage: TransformStage.RESOLVE_ALIASES + 0.5, cacheIdentity: "custom@1", transform, }; - assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid stage"); + const result = getCustomPluginCacheIdentity([plugin]); + assertEquals(result.cacheable, true); + if (result.cacheable) { + assertEquals(result.identity, [[ + 0, + "custom", + TransformStage.RESOLVE_ALIASES + 0.5, + "custom@1", + ]]); + } + }); + + it("rejects non-finite and unreasonably large custom plugin stages", () => { + for (const stage of [NaN, Infinity, -Infinity, 1_000_001, -1_000_001]) { + const plugin = { + name: "custom", + stage, + cacheIdentity: "custom@1", + transform, + } as TransformPlugin; + + assertThrows(() => getCustomPluginCacheIdentity([plugin]), TypeError, "invalid stage"); + } }); it("rejects oversized base identity fields before hashing", async () => { @@ -218,17 +240,19 @@ describe("transform pipeline cache identity", () => { assertNotEquals(plugins, baseline); }); - it("rejects fractional stages in precomputed custom plugin identities", async () => { - await assertRejects( - () => - computePipelineConfigIdentity( - identityInput({ - customPlugins: [[0, "custom", TransformStage.FINALIZE + 0.5, "custom@1"]], - }), - ), - TypeError, - "invalid stage", + it("keeps distinct fractional stages distinct in precomputed identities", async () => { + const early = await computePipelineConfigIdentity( + identityInput({ + customPlugins: [[0, "custom", TransformStage.COMPILE + 0.5, "custom@1"]], + }), + ); + const late = await computePipelineConfigIdentity( + identityInput({ + customPlugins: [[0, "custom", TransformStage.COMPILE + 0.7, "custom@1"]], + }), ); + + assertNotEquals(early, late); }); it("changes identity when moduleServerOrigin changes", async () => { diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 9e57ad65d9..57a997013e 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -9,6 +9,7 @@ const MAX_IDENTITY_STRING_BYTES = 64 * 1024; const MAX_IMPORT_MAP_IDENTITY_BYTES = 8 * 1024 * 1024; const MAX_PLUGIN_IDENTITY_BYTES = 4 * 1024; const MAX_CUSTOM_PLUGINS = 1_000; +const MAX_TRANSFORM_STAGE_MAGNITUDE = 1_000_000; // Transform identities are derived after project code may have run in the // shared realm. Keep descriptor inspection, freezing, and bounded string @@ -55,6 +56,16 @@ function hasOwn(object: object, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; } +/** + * Transform stages are ordered numeric coordinates, not enum membership. + * Built-in and custom plugins deliberately use fractional coordinates to run + * between the public enum anchors, so every finite bounded number is valid. + */ +function isValidTransformStage(value: unknown): value is number { + return typeof value === "number" && NumberIsFinite(value) && + MathAbs(value) <= MAX_TRANSFORM_STAGE_MAGNITUDE; +} + interface ImportMapBudget { entries: number; bytes: number; @@ -267,10 +278,7 @@ export function getCustomPluginCacheIdentity( ) { throw new IntrinsicTypeError(`Transform plugin at index ${index} has an invalid name`); } - if ( - typeof stage !== "number" || !NumberIsFinite(stage) || - !NumberIsSafeInteger(stage) || MathAbs(stage) > 1_000_000 - ) { + if (!isValidTransformStage(stage)) { throw new IntrinsicTypeError(`Transform plugin ${name} has an invalid stage`); } if (condition !== undefined && typeof condition !== "function") { @@ -397,10 +405,7 @@ function encodeCustomPluginIdentities( ) { throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid name`); } - if ( - typeof stage !== "number" || !NumberIsFinite(stage) || - !NumberIsSafeInteger(stage) || MathAbs(stage) > 1_000_000 - ) { + if (!isValidTransformStage(stage)) { throw new IntrinsicTypeError(`Custom plugin identity ${index} has an invalid stage`); } if ( diff --git a/src/transforms/pipeline/index.test.ts b/src/transforms/pipeline/index.test.ts index 6b2f59bd7a..097fb72724 100644 --- a/src/transforms/pipeline/index.test.ts +++ b/src/transforms/pipeline/index.test.ts @@ -324,12 +324,12 @@ export default function App() { return dep; }`; { plugins: [{ name: "sentinel-late", - stage: TransformStage.FINALIZE + 2, + stage: TransformStage.FINALIZE + 0.75, cacheIdentity: "sentinel-late@1", transform: (ctx) => `${ctx.code}\n/* sentinel-late */`, }, { name: "sentinel-early", - stage: TransformStage.FINALIZE + 1, + stage: TransformStage.FINALIZE + 0.25, cacheIdentity: "sentinel-early@1", transform: (ctx) => `${ctx.code}\n/* sentinel-early */`, }], diff --git a/src/transforms/pipeline/types.ts b/src/transforms/pipeline/types.ts index e1dd69daa7..411ba24d12 100644 --- a/src/transforms/pipeline/types.ts +++ b/src/transforms/pipeline/types.ts @@ -141,7 +141,11 @@ export interface TransformContext { export interface TransformPlugin { /** Plugin name for logging/debugging */ name: string; - /** Stage this plugin runs at */ + /** + * Numeric ordering coordinate for this plugin. + * TransformStage values are phase anchors; finite fractional values may run + * between anchors when a plugin needs a stable intermediate position. + */ stage: TransformStage; /** * Stable, versioned identity for output-affecting custom plugin behavior. From 95fd72b5e51e1c864f89f1cd2f183c1965acccab Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Mon, 3 Aug 2026 16:17:07 +0200 Subject: [PATCH 23/34] test(rendering): lock import-map preload context wiring end to end The variant-isolation reviews asked for regression coverage proving the production call sites actually pass the preload context, and for the inherited-toJSON poisoning test to cover the pipeline import-map fingerprint itself. - Add an orchestrator test asserting LayoutOrchestrator.preloadLayoutModules registers the preloaded map under the exact projectDir/contentSourceId/ config variant and that a different content source misses. - Add the same end-to-end assertion for the loadMDXLayout call site in component-loader. Both tests fail if a call site drops the context argument (verified by mutation). - Extend the cache-identity toJSON-poisoning test so poisoned Array/Object/String prototype toJSON hooks neither move nor collapse fingerprintPipelineImportMap results. Co-Authored-By: Claude Fable 5 --- .../layouts/utils/component-loader.test.ts | 74 ++++++++++++ src/rendering/orchestrator/layout.test.ts | 106 ++++++++++++++++++ .../pipeline/cache-identity.test.ts | 23 ++++ 3 files changed, 203 insertions(+) create mode 100644 src/rendering/orchestrator/layout.test.ts diff --git a/src/rendering/layouts/utils/component-loader.test.ts b/src/rendering/layouts/utils/component-loader.test.ts index 213ccfbca8..5411e26b94 100644 --- a/src/rendering/layouts/utils/component-loader.test.ts +++ b/src/rendering/layouts/utils/component-loader.test.ts @@ -14,6 +14,11 @@ import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts"; import type { MdxBundle } from "#veryfront/types"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { hashString } from "#veryfront/cache/hash.ts"; +import { validateVeryfrontConfig } from "#veryfront/config"; +import { + clearImportMapCache, + getCachedImportMap, +} from "#veryfront/modules/import-map/preloader.ts"; function cacheKeyForDependencies( dependencies: Readonly>, @@ -519,6 +524,75 @@ describe("rendering/layouts/utils/component-loader", () => { } }); + it("preloads the MDX import map under the exact request context", async () => { + clearImportMapCache(); + const originalLoadModuleESM = mdxRenderer.loadModuleESM; + const mutableRenderer = mdxRenderer as unknown as { + loadModuleESM: typeof mdxRenderer.loadModuleESM; + }; + mutableRenderer.loadModuleESM = + (() => Promise.resolve({ default: () => null })) as typeof mdxRenderer.loadModuleESM; + + const adapter = { + fs: { + readFile: () => { + const error = new Error("not found") as Error & { code: string }; + error.code = "ENOENT"; + throw error; + }, + exists: () => false, + }, + env: { get: () => undefined }, + } as unknown as RuntimeAdapter; + const config = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { "context-package": "https://example.com/context-package.ts" }, + }, + }, + }); + + try { + await loadMDXLayout( + { compiledCode: "export default function Layout() { return null; }" } as MdxBundle, + "/context-project", + adapter, + "context-project-id", + "project-slug", + "release-1", + undefined, + "19.1.0", + SNAPSHOT_A_PIN_KEY, + SNAPSHOT_A_DEPENDENCIES, + undefined, + undefined, + config, + ); + + // The production call site must register the preloaded map under the + // exact release/config variant, not the ambient projectId-only variant. + const exactVariant = await getCachedImportMap("context-project-id", { + projectDir: "/context-project", + contentSourceId: "release-1", + config, + }); + assertEquals( + exactVariant?.imports?.["context-package"], + "https://example.com/context-package.ts", + ); + + const otherContentSource = await getCachedImportMap("context-project-id", { + projectDir: "/context-project", + contentSourceId: "release-2", + config, + }); + assertEquals(otherContentSource, undefined); + } finally { + mutableRenderer.loadModuleESM = originalLoadModuleESM; + clearImportMapCache(); + } + }); + it("uses the request snapshot in the TSX layout cache key", async () => { function CachedLayout() { return null; diff --git a/src/rendering/orchestrator/layout.test.ts b/src/rendering/orchestrator/layout.test.ts new file mode 100644 index 0000000000..f42eb824c8 --- /dev/null +++ b/src/rendering/orchestrator/layout.test.ts @@ -0,0 +1,106 @@ +import "#veryfront/schemas/_test-setup.ts"; +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; +import { LayoutOrchestrator } from "./layout.ts"; +import { createLayoutComponentCache } from "../layouts/utils/component-loader.ts"; +import type { LayoutCollector, LayoutCompiler } from "../layouts/index.ts"; +import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts"; +import { validateVeryfrontConfig } from "#veryfront/config"; +import { + clearImportMapCache, + getCachedImportMap, +} from "#veryfront/modules/import-map/preloader.ts"; +import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; +import type { LayoutItem, MdxBundle } from "#veryfront/types"; + +function createMissingFileAdapter(): RuntimeAdapter { + return { + fs: { + readFile: () => { + const error = new Error("not found") as Error & { code: string }; + error.code = "ENOENT"; + throw error; + }, + exists: () => false, + }, + env: { get: () => undefined }, + } as unknown as RuntimeAdapter; +} + +describe("rendering/orchestrator/layout", () => { + it("preloads the MDX import map under the exact request context", async () => { + clearImportMapCache(); + const originalLoadModuleESM = mdxRenderer.loadModuleESM; + const mutableRenderer = mdxRenderer as unknown as { + loadModuleESM: typeof mdxRenderer.loadModuleESM; + }; + mutableRenderer.loadModuleESM = + (() => Promise.resolve({ default: () => null })) as typeof mdxRenderer.loadModuleESM; + + const config = validateVeryfrontConfig({ + resolve: { + importMap: { + imports: { + "orchestrator-package": "https://example.com/orchestrator-package.ts", + }, + }, + }, + }); + const orchestrator = new LayoutOrchestrator({ + projectDir: "/orchestrator-project", + projectId: "orchestrator-project-id", + projectSlug: "orchestrator-slug", + contentSourceId: "release-1", + adapter: createMissingFileAdapter(), + config, + mode: "production", + layoutCollector: {} as LayoutCollector, + layoutCompiler: {} as LayoutCompiler, + layoutCache: createLayoutComponentCache(), + componentRegistry: {}, + }); + const mdxLayout: LayoutItem = { + kind: "mdx", + path: "/orchestrator-project/layout.mdx", + bundle: { + compiledCode: "export default function Layout() { return null; }", + } as MdxBundle, + }; + + try { + const summary = await orchestrator.preloadLayoutModules( + [mdxLayout], + undefined, + { react: "19.1.0" }, + ); + + assertEquals(summary.importMapSuccess, true); + assertEquals( + orchestrator.getPreloadedImportMap()?.imports?.["orchestrator-package"], + "https://example.com/orchestrator-package.ts", + ); + + // The orchestrator call site must register the preloaded map under the + // exact release/config variant, not the ambient projectId-only variant. + const exactVariant = await getCachedImportMap("orchestrator-project-id", { + projectDir: "/orchestrator-project", + contentSourceId: "release-1", + config, + }); + assertEquals( + exactVariant?.imports?.["orchestrator-package"], + "https://example.com/orchestrator-package.ts", + ); + + const otherContentSource = await getCachedImportMap("orchestrator-project-id", { + projectDir: "/orchestrator-project", + contentSourceId: "release-2", + config, + }); + assertEquals(otherContentSource, undefined); + } finally { + mutableRenderer.loadModuleESM = originalLoadModuleESM; + clearImportMapCache(); + } + }); +}); diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index e62c6e1a9b..02a337494c 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -291,6 +291,15 @@ describe("transform pipeline cache identity", () => { it("does not consult inherited toJSON hooks while hashing", async () => { const customPlugins = [[0, "custom", TransformStage.FINALIZE, "custom@1"]] as const; const baseline = await computePipelineConfigIdentity(identityInput({ customPlugins })); + const importMapSnapshot = snapshotImportMap({ + imports: { react: "https://esm.sh/react@19.1.0" }, + scopes: { "/scope/": { dep: "https://example.com/dep.ts" } }, + }); + const fingerprintBaseline = await fingerprintPipelineImportMap(importMapSnapshot); + const distinctSnapshot = snapshotImportMap({ + imports: { react: "https://esm.sh/react@18.3.1" }, + }); + const distinctFingerprintBaseline = await fingerprintPipelineImportMap(distinctSnapshot); const arrayToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); const objectToJson = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); const stringToJson = Object.getOwnPropertyDescriptor(String.prototype, "toJSON"); @@ -326,6 +335,20 @@ describe("transform pipeline cache identity", () => { await computePipelineConfigIdentity(identityInput({ customPlugins })), baseline, ); + // Poisoned toJSON hooks must neither move nor collapse import-map + // fingerprints: distinct maps stay distinct under poisoning. + assertEquals( + await fingerprintPipelineImportMap(importMapSnapshot), + fingerprintBaseline, + ); + assertEquals( + await fingerprintPipelineImportMap(distinctSnapshot), + distinctFingerprintBaseline, + ); + assertNotEquals( + await fingerprintPipelineImportMap(importMapSnapshot), + await fingerprintPipelineImportMap(distinctSnapshot), + ); } finally { if (arrayToJson) { Object.defineProperty(Array.prototype, "toJSON", arrayToJson); From 65b0ddb9d449b1b27cba16879024c05f6af54eee Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 17:08:43 +0200 Subject: [PATCH 24/34] Bound timed-out import-map loads by project Timed-out import-map loads previously stayed in global active-load accounting until the underlying adapter settled. A permanently hung adapter could therefore consume global capacity and block unrelated projects, even though the caller-facing operation had already failed. This keeps the timed-out underlying work accounted against the originating project, releases global load capacity, and uses one shared capacity-change signal instead of attaching each waiter to every in-flight promise. HTTP cache identity setup also uses module-load captured Object.defineProperty and URL constructors so request identity cannot be forged after primordial replacement. Constraint: The underlying loader promise must remain observed after caller timeout to prevent retry trains for the same project. Rejected: Drop timed-out loaders from all accounting | would allow a single project to spawn unbounded overlapping hung loads. Rejected: Keep timed-out loaders in global capacity forever | lets one tenant starve unrelated tenants. Confidence: high Scope-risk: moderate Directive: Keep caller timeout, orphan accounting, and capacity-change notification aligned; do not reintroduce per-waiter handlers on all active promises. Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/preloader.test.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/preloader.test.ts src/modules/import-map/loader.test.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 lint src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 task generate:manifests:check Tested: git diff --check Not-tested: Hosted CI and full pre-push hook will run on normal push. --- src/modules/import-map/preloader.test.ts | 27 ++- src/modules/import-map/preloader.ts | 162 ++++++++++++++---- src/transforms/esm/http-cache-helpers.test.ts | 50 ++++++ src/transforms/esm/http-cache-helpers.ts | 8 +- 4 files changed, 204 insertions(+), 43 deletions(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 605d5404b7..1b53fff5aa 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1073,11 +1073,11 @@ describe("modules/import-map/preloader", () => { await first; }); - it("does not release capacity or duplicate work when a loader times out", async () => { + it("keeps timed-out underlying work scoped to its project capacity", async () => { const adapter = createMinimalAdapter(); const loads: Array>> = []; const preloader = new ImportMapPreloader({ - maxProjects: 1, + maxProjects: 2, maxVariantsPerProject: 1, ttlMs: 1_000, loadTimeoutMs: 20, @@ -1088,24 +1088,35 @@ describe("modules/import-map/preloader", () => { }, }); + const hungA = preloader.preload("/hung-a", adapter, "hung-a"); + await waitForLoadCount(loads, 1); await assertRejects( - () => preloader.preload("/hung", adapter, "hung"), + () => hungA, RangeError, "load timed out", ); await assertRejects( - () => preloader.preload("/next", adapter, "next"), + () => preloader.preload("/hung-a", adapter, "hung-a"), RangeError, "capacity wait timed out", ); assertEquals(loads.length, 1); - loads[0]!.resolve({ imports: { source: "late" } }); - await Promise.resolve(); - const recovered = preloader.preload("/next", adapter, "next"); + const hungB = preloader.preload("/hung-b", adapter, "hung-b"); await waitForLoadCount(loads, 2); - loads[1]!.resolve({ imports: { source: "next" } }); + const recovered = preloader.preload("/next", adapter, "next"); + await waitForLoadCount(loads, 3); + loads[1]!.resolve({ imports: { source: "b" } }); + loads[2]!.resolve({ imports: { source: "next" } }); + assertEquals((await hungB).imports?.source, "b"); assertEquals((await recovered).imports?.source, "next"); + + loads[0]!.resolve({ imports: { source: "late" } }); + await Promise.resolve(); + const sameProjectRecovered = preloader.preload("/hung-a", adapter, "hung-a"); + await waitForLoadCount(loads, 4); + loads[3]!.resolve({ imports: { source: "hung-recovered" } }); + assertEquals((await sameProjectRecovered).imports?.source, "hung-recovered"); }); it("keeps variant identity and capacity deterministic after primordial poisoning", async () => { diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index ef660f628c..6025882ff3 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -1,12 +1,10 @@ import type { VeryfrontConfig } from "#veryfront/config"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; -import { - primordialArrayPush as arrayPush, - primordialArraySort as arraySort, -} from "#veryfront/platform/compat/primordials/array.ts"; +import { primordialArraySort as arraySort } from "#veryfront/platform/compat/primordials/array.ts"; import { snapshotImportMap } from "#veryfront/transforms/pipeline/cache-identity.ts"; import { MAX_TIMER_DELAY_MS } from "#veryfront/utils/constants/limits.ts"; import { computeHash } from "#veryfront/utils/hash-utils.ts"; +import { rendererLogger } from "#veryfront/utils"; import type { ImportMapConfig } from "./types.ts"; import { loadImportMap } from "./loader.ts"; @@ -24,6 +22,7 @@ const DEFAULT_MAX_IMPORT_MAP_PROJECTS = 512; const DEFAULT_MAX_IMPORT_MAP_VARIANTS_PER_PROJECT = 16; const DEFAULT_IMPORT_MAP_TTL_MS = 10 * 60 * 1_000; const DEFAULT_IMPORT_MAP_LOAD_TIMEOUT_MS = 30_000; +const logger = rendererLogger.component("import-map-preloader"); // Project code can execute in the same realm before a later request reaches // this cache. Capture every primitive used for identity, admission, and @@ -58,7 +57,6 @@ const PerformanceNow = IntrinsicPerformance.now; const ReflectApply = Reflect.apply; const SetPrototypeAdd = Set.prototype.add; const SetPrototypeDelete = Set.prototype.delete; -const SetPrototypeForEach = Set.prototype.forEach; const SetPrototypeSize = ObjectGetOwnPropertyDescriptor(Set.prototype, "size")! .get!; const WeakSetPrototypeAdd = WeakSet.prototype.add; @@ -116,6 +114,13 @@ function resolvedPromise(): Promise { return ReflectApply(PromiseResolve, IntrinsicPromise, []) as Promise; } +function raceTwo(first: Promise, second: Promise): Promise { + return new IntrinsicPromise((resolve, reject) => { + promiseThen(first, resolve, reject); + promiseThen(second, resolve, reject); + }); +} + function setAdd(set: Set, value: T): void { ReflectApply(SetPrototypeAdd, set, [value]); } @@ -124,10 +129,6 @@ function setDelete(set: Set, value: T): boolean { return ReflectApply(SetPrototypeDelete, set, [value]) as boolean; } -function setForEach(set: Set, callback: (value: T) => void): void { - ReflectApply(SetPrototypeForEach, set, [callback]); -} - function setSize(set: Set): number { return ReflectApply(SetPrototypeSize, set, []) as number; } @@ -155,10 +156,23 @@ interface ProjectImportMapState { readonly identityBuilds: Map>; } +interface CapacityChangeSignal { + promise: Promise; + resolve: () => void; +} + function createGeneration(): object { return ObjectFreeze({}); } +function createCapacityChangeSignal(): CapacityChangeSignal { + let resolve!: () => void; + const promise = new IntrinsicPromise((resolvePromise) => { + resolve = resolvePromise; + }); + return { promise, resolve }; +} + export interface ImportMapPreloaderOptions { /** Maximum tenant/project buckets retained by one preloader. */ maxProjects?: number; @@ -280,14 +294,6 @@ function buildVariantCanonicalIdentity( return canonical; } -function racePromises(promises: Array>): Promise { - return new IntrinsicPromise((resolve, reject) => { - for (let index = 0; index < promises.length; index++) { - promiseThen(promises[index]!, resolve, reject); - } - }); -} - function readPositiveSafeInteger( value: number | undefined, fallback: number, @@ -321,7 +327,12 @@ export class ImportMapPreloader { /** Underlying loader work remains accounted for even after explicit invalidation. */ private readonly activeLoads = new IntrinsicSet>(); private readonly activeIdentityBuilds = new IntrinsicSet>(); + private readonly orphanedLoadsByProject = new IntrinsicMap< + string, + Set> + >(); private readonly capacityErrors = new IntrinsicWeakSet(); + private capacityChange = createCapacityChangeSignal(); private globalGeneration = createGeneration(); private readonly maxProjects: number; private readonly maxVariantsPerProject: number; @@ -473,18 +484,28 @@ export class ImportMapPreloader { weakSetHas(this.capacityErrors, error); } + private notifyCapacityChange(): void { + const signal = this.capacityChange; + this.capacityChange = createCapacityChangeSignal(); + signal.resolve(); + } + + private projectOrphanCount(cacheKey: string): number { + const orphanedLoads = mapGet(this.orphanedLoadsByProject, cacheKey); + return orphanedLoads ? setSize(orphanedLoads) : 0; + } + + private hasProjectLoadCapacity(cacheKey: string): boolean { + return this.projectOrphanCount(cacheKey) < this.maxVariantsPerProject; + } + private waitForActiveWork(timeoutMs: number): Promise { - const activeWork: Array> = []; - setForEach(this.activeLoads, (promise) => arrayPush(activeWork, promise)); - setForEach(this.activeIdentityBuilds, (promise) => arrayPush(activeWork, promise)); + const hasActiveWork = setSize(this.activeLoads) + setSize(this.activeIdentityBuilds) + + mapSize(this.orphanedLoadsByProject) > + 0; // Work can settle between the capacity check and this snapshot. Retry the // admission loop immediately instead of surfacing a stale capacity error. - if (activeWork.length === 0) return resolvedPromise(); - const settled = promiseThen( - racePromises(activeWork), - () => resolvedPromise(), - () => resolvedPromise(), - ); + if (!hasActiveWork) return resolvedPromise(); let timeoutId: ReturnType | undefined; const timeout = new IntrinsicPromise((_, reject) => { timeoutId = SetTimeout(() => { @@ -492,7 +513,7 @@ export class ImportMapPreloader { }, timeoutMs); }); return promiseThen( - racePromises([settled, timeout]), + raceTwo(this.capacityChange.promise, timeout), () => { if (timeoutId !== undefined) ClearTimeout(timeoutId); }, @@ -513,7 +534,8 @@ export class ImportMapPreloader { }); if ( mapSize(projectCache) === 0 && - mapSize(projectState.identityBuilds) === 0 + mapSize(projectState.identityBuilds) === 0 && + this.projectOrphanCount(cacheKey) === 0 ) { mapDelete(this.projects, cacheKey); } @@ -559,15 +581,73 @@ export class ImportMapPreloader { } } + private trackOrphanedLoad( + cacheKey: string, + promise: Promise, + ): void { + let orphanedLoads = mapGet(this.orphanedLoadsByProject, cacheKey); + if (!orphanedLoads) { + if (mapSize(this.orphanedLoadsByProject) >= this.maxProjects) { + return; + } + orphanedLoads = new IntrinsicSet>(); + mapSet(this.orphanedLoadsByProject, cacheKey, orphanedLoads); + } + setAdd(orphanedLoads, promise); + void promiseThen( + promise, + () => { + this.releaseOrphanedLoad(cacheKey, promise); + }, + () => { + this.releaseOrphanedLoad(cacheKey, promise); + }, + ); + } + + private releaseOrphanedLoad( + cacheKey: string, + promise: Promise, + ): void { + const orphanedLoads = mapGet(this.orphanedLoadsByProject, cacheKey); + if (!orphanedLoads) return; + const removed = setDelete(orphanedLoads, promise); + if (setSize(orphanedLoads) === 0) { + mapDelete(this.orphanedLoadsByProject, cacheKey); + } + if (removed) this.notifyCapacityChange(); + } + + private reportTimedOutLoad(cacheKey: string): void { + void promiseThen( + computeHash(cacheKey), + (cacheKeyHash) => { + logger.warn("Import-map load timed out with underlying work still active", { + cacheKeyHash, + orphanedLoadsForProject: this.projectOrphanCount(cacheKey), + orphanedProjects: mapSize(this.orphanedLoadsByProject), + activeLoads: setSize(this.activeLoads), + }); + }, + () => { + logger.warn("Import-map load timed out with underlying work still active", { + orphanedLoadsForProject: this.projectOrphanCount(cacheKey), + orphanedProjects: mapSize(this.orphanedLoadsByProject), + activeLoads: setSize(this.activeLoads), + }); + }, + ); + } + private trackActiveLoad(promise: Promise): void { setAdd(this.activeLoads, promise); promiseThen( promise, () => { - setDelete(this.activeLoads, promise); + if (setDelete(this.activeLoads, promise)) this.notifyCapacityChange(); }, () => { - setDelete(this.activeLoads, promise); + if (setDelete(this.activeLoads, promise)) this.notifyCapacityChange(); }, ); } @@ -606,7 +686,7 @@ export class ImportMapPreloader { // request can reach and join that in-flight entry even when load capacity // is otherwise full. const releaseActive = (): void => { - setDelete(this.activeIdentityBuilds, promise); + if (setDelete(this.activeIdentityBuilds, promise)) this.notifyCapacityChange(); }; promiseThen( promise, @@ -644,10 +724,20 @@ export class ImportMapPreloader { } private startTrackedLoad( + cacheKey: string, projectDir: string, adapter: RuntimeAdapter, config: VeryfrontConfig | undefined, ): Promise { + if (!this.hasProjectLoadCapacity(cacheKey)) { + throw this.capacityError("loads"); + } + if ( + !mapGet(this.orphanedLoadsByProject, cacheKey) && + mapSize(this.orphanedLoadsByProject) >= this.maxProjects + ) { + throw this.capacityError("projects"); + } if (!this.hasActiveWorkCapacity()) { throw this.capacityError("loads"); } @@ -661,11 +751,16 @@ export class ImportMapPreloader { let timeoutId: ReturnType | undefined; const timeoutPromise = new IntrinsicPromise((_, reject) => { timeoutId = SetTimeout(() => { + if (setDelete(this.activeLoads, loaderPromise)) { + this.trackOrphanedLoad(cacheKey, loaderPromise); + this.notifyCapacityChange(); + this.reportTimedOutLoad(cacheKey); + } reject(new IntrinsicRangeError("Import-map preloader load timed out")); }, this.loadTimeoutMs); }); const boundedLoaderPromise = promiseThen( - racePromises([loaderPromise, timeoutPromise]), + raceTwo(loaderPromise, timeoutPromise), (value) => { if (timeoutId !== undefined) ClearTimeout(timeoutId); return value; @@ -768,6 +863,7 @@ export class ImportMapPreloader { ) { releaseIdentity(); return this.startTrackedLoad( + cacheKey, projectDir, adapter, exactContext.config, @@ -794,6 +890,7 @@ export class ImportMapPreloader { ) { releaseIdentity(); return this.startTrackedLoad( + cacheKey, projectDir, adapter, exactContext.config, @@ -816,6 +913,7 @@ export class ImportMapPreloader { this.makeVariantRoom(projectCache, now); const promise = this.startTrackedLoad( + cacheKey, projectDir, adapter, exactContext.config, diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 101c43160b..cd058d1065 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -134,6 +134,56 @@ describe("transforms/esm/http-cache-helpers", () => { assertEquals(hookCalls, 0); }); + it("uses captured request-context and URL primordials for identities", async () => { + const importMap = { imports: {}, scopes: {} }; + const baseline = await buildHttpCacheIdentity( + "https://esm.sh/lodash@4?z=1&a=2", + prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), + ); + const objectDefineProperty = Object.defineProperty; + const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL")!; + let definePropertyCalls = 0; + let urlCalls = 0; + let poisoned: string; + + try { + objectDefineProperty(Object, "defineProperty", { + configurable: true, + value() { + definePropertyCalls++; + throw new Error("poisoned defineProperty"); + }, + writable: true, + }); + objectDefineProperty(globalThis, "URL", { + configurable: true, + value: class PoisonedURL { + constructor() { + urlCalls++; + throw new Error("poisoned URL"); + } + }, + writable: true, + }); + + poisoned = await buildHttpCacheIdentity( + "https://esm.sh/lodash@4?z=1&a=2", + prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), + ); + } finally { + objectDefineProperty(Object, "defineProperty", { + configurable: true, + value: objectDefineProperty, + writable: true, + }); + objectDefineProperty(globalThis, "URL", urlDescriptor); + } + + assertEquals(poisoned, baseline); + assertEquals(definePropertyCalls, 0); + assertEquals(urlCalls, 0); + }); + it("does not consult inherited toJSON hooks while fingerprinting import maps", async () => { const importMap = { imports: { pkg: "https://modules.example.com/pkg-v1.js" }, diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index c941e50455..babc356acf 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -20,6 +20,8 @@ import { computeHash } from "#veryfront/utils/hash-utils.ts"; const logger = rendererLogger.component("http-cache"); const JSONStringify = JSON.stringify; +const IntrinsicURL = URL; +const ObjectDefineProperty = Object.defineProperty; const ObjectEntries = Object.entries; /** @@ -133,7 +135,7 @@ function attachHttpCacheRequestIdentityContext Date: Mon, 3 Aug 2026 17:27:32 +0200 Subject: [PATCH 25/34] Keep timed-out import-map loads inside project admission limits The preloader now treats live project buckets and orphan-only timed-out loads as one occupancy set, so maxProjects remains a hard ceiling while timed-out work is still retained. Explicit config import maps also use the embedded import-map reader so metadata fields remain compatible without invoking accessors. Constraint: PR review required maxProjects to cover orphaned and active project occupancy together. Constraint: Explicit config import maps must ignore extra metadata but still reject accessor-backed imports or scopes. Rejected: Silently dropping timed-out loads when orphan capacity is full | leaves underlying work untracked after admission. Confidence: high Scope-risk: moderate Tested: npx --yes deno@2.7.7 fmt --check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/modules/import-map/loader.ts src/modules/import-map/loader.test.ts Tested: git diff --check Tested: npx --yes deno@2.7.7 lint src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/modules/import-map/loader.ts src/modules/import-map/loader.test.ts Tested: npx --yes deno@2.7.7 check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts src/modules/import-map/loader.ts src/modules/import-map/loader.test.ts Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/loader.test.ts src/modules/import-map/preloader.test.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.test.ts --- src/modules/import-map/loader.test.ts | 23 ++++++++++++++ src/modules/import-map/loader.ts | 6 +++- src/modules/import-map/preloader.test.ts | 13 +++++--- src/modules/import-map/preloader.ts | 39 ++++++++++++++++++------ 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/modules/import-map/loader.test.ts b/src/modules/import-map/loader.test.ts index ffb195ae93..e40ffe73cf 100644 --- a/src/modules/import-map/loader.test.ts +++ b/src/modules/import-map/loader.test.ts @@ -84,6 +84,29 @@ describe("modules/import-map/loader", () => { assertEquals(error.detail?.includes("42"), false); }); + it("ignores extra explicit config import-map metadata without invoking accessors", async () => { + const adapter = createMockAdapter(); + let metadataCalls = 0; + const importMap = { + imports: { package: "https://project.example/package.js" }, + }; + Object.defineProperty(importMap, "metadata", { + enumerable: true, + get() { + metadataCalls++; + return { source: "project" }; + }, + }); + const config = { + resolve: { importMap }, + } as VeryfrontConfig; + + const { imports } = await loadImportMap("/any-project", adapter, config); + + assertEquals(imports?.package, "https://project.example/package.js"); + assertEquals(metadataCalls, 0); + }); + it("rejects config accessors without invoking project code", async () => { const adapter = createMockAdapter(); let accessorCalls = 0; diff --git a/src/modules/import-map/loader.ts b/src/modules/import-map/loader.ts index 30c8945bef..ae950f77f0 100644 --- a/src/modules/import-map/loader.ts +++ b/src/modules/import-map/loader.ts @@ -251,7 +251,11 @@ function getConfigImportMap(config: VeryfrontConfig): ImportMapConfig | null { "Veryfront config resolve", ); if (importMap === undefined || importMap === null) return null; - return snapshotImportMap(importMap); + const embedded = readEmbeddedImportMap( + importMap, + "Veryfront config resolve importMap", + ); + return embedded ?? snapshotImportMap({}); } catch (error) { if (isVeryfrontError(error)) throw error; throw IMPORT_MAP_INVALID.create({ diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 1b53fff5aa..b674822a40 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1104,14 +1104,17 @@ describe("modules/import-map/preloader", () => { const hungB = preloader.preload("/hung-b", adapter, "hung-b"); await waitForLoadCount(loads, 2); - const recovered = preloader.preload("/next", adapter, "next"); - await waitForLoadCount(loads, 3); + const nextBlockedByCapacity = preloader.preload("/next", adapter, "next"); + await new Promise((resolve) => setTimeout(resolve, 0)); + assertEquals(loads.length, 2); + loads[1]!.resolve({ imports: { source: "b" } }); - loads[2]!.resolve({ imports: { source: "next" } }); assertEquals((await hungB).imports?.source, "b"); - assertEquals((await recovered).imports?.source, "next"); - loads[0]!.resolve({ imports: { source: "late" } }); + await waitForLoadCount(loads, 3); + loads[2]!.resolve({ imports: { source: "next" } }); + assertEquals((await nextBlockedByCapacity).imports?.source, "next"); + await Promise.resolve(); const sameProjectRecovered = preloader.preload("/hung-a", adapter, "hung-a"); await waitForLoadCount(loads, 4); diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 6025882ff3..2a4488661f 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -406,6 +406,7 @@ export class ImportMapPreloader { removeEmptyProject && mapSize(projectCache) === 0 && mapSize(projectState.identityBuilds) === 0 && + this.projectOrphanCount(cacheKey) === 0 && mapGet(this.projects, cacheKey) === projectState ) { mapDelete(this.projects, cacheKey); @@ -499,6 +500,20 @@ export class ImportMapPreloader { return this.projectOrphanCount(cacheKey) < this.maxVariantsPerProject; } + private occupiedProjectCount(): number { + let orphanOnlyProjects = 0; + mapForEach(this.orphanedLoadsByProject, (_orphanedLoads, cacheKey) => { + if (!mapGet(this.projects, cacheKey)) orphanOnlyProjects += 1; + }); + return mapSize(this.projects) + orphanOnlyProjects; + } + + private hasProjectOccupancyCapacity(cacheKey: string): boolean { + return Boolean(mapGet(this.projects, cacheKey)) || + Boolean(mapGet(this.orphanedLoadsByProject, cacheKey)) || + this.occupiedProjectCount() < this.maxProjects; + } + private waitForActiveWork(timeoutMs: number): Promise { const hasActiveWork = setSize(this.activeLoads) + setSize(this.activeIdentityBuilds) + mapSize(this.orphanedLoadsByProject) > @@ -524,7 +539,7 @@ export class ImportMapPreloader { ); } - private makeProjectRoom(now: number): void { + private makeProjectRoom(now: number, cacheKey: string): void { mapForEach(this.projects, (projectState, cacheKey) => { const projectCache = projectState.variants; mapForEach(projectCache, (entry, variantKey) => { @@ -541,11 +556,12 @@ export class ImportMapPreloader { } }); - while (mapSize(this.projects) >= this.maxProjects) { + while (!this.hasProjectOccupancyCapacity(cacheKey)) { let oldestSettledProject: string | undefined; mapForEach(this.projects, (projectState, cacheKey) => { if (oldestSettledProject !== undefined) return; if (mapSize(projectState.identityBuilds) > 0) return; + if (this.projectOrphanCount(cacheKey) > 0) return; let hasInFlightEntry = false; mapForEach(projectState.variants, (entry) => { if (entry.expiresAt === null) { @@ -587,7 +603,12 @@ export class ImportMapPreloader { ): void { let orphanedLoads = mapGet(this.orphanedLoadsByProject, cacheKey); if (!orphanedLoads) { - if (mapSize(this.orphanedLoadsByProject) >= this.maxProjects) { + if (!this.hasProjectOccupancyCapacity(cacheKey)) { + logger.warn("Import-map load timed out without orphan capacity", { + orphanedProjects: mapSize(this.orphanedLoadsByProject), + occupiedProjects: this.occupiedProjectCount(), + maxProjects: this.maxProjects, + }); return; } orphanedLoads = new IntrinsicSet>(); @@ -717,6 +738,7 @@ export class ImportMapPreloader { if ( mapSize(projectState.identityBuilds) === 0 && mapSize(projectState.variants) === 0 && + this.projectOrphanCount(cacheKey) === 0 && mapGet(this.projects, cacheKey) === projectState ) { mapDelete(this.projects, cacheKey); @@ -729,15 +751,12 @@ export class ImportMapPreloader { adapter: RuntimeAdapter, config: VeryfrontConfig | undefined, ): Promise { + if (!this.hasProjectOccupancyCapacity(cacheKey)) { + throw this.capacityError("projects"); + } if (!this.hasProjectLoadCapacity(cacheKey)) { throw this.capacityError("loads"); } - if ( - !mapGet(this.orphanedLoadsByProject, cacheKey) && - mapSize(this.orphanedLoadsByProject) >= this.maxProjects - ) { - throw this.capacityError("projects"); - } if (!this.hasActiveWorkCapacity()) { throw this.capacityError("loads"); } @@ -808,7 +827,7 @@ export class ImportMapPreloader { const admissionNow = this.readNow(); let projectState = mapGet(this.projects, cacheKey); if (!projectState) { - this.makeProjectRoom(admissionNow); + this.makeProjectRoom(admissionNow, cacheKey); projectState = { variants: new IntrinsicMap(), generation: createGeneration(), From d84d5736179681a3eaf74042d90b3574a0331b35 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 17:45:31 +0200 Subject: [PATCH 26/34] Keep import-map admission within its documented ceilings Timed-out loaders must not consume global load slots forever, but they must continue to reserve their project and variant budgets until the underlying work settles. Reserve that work at admission, count live buckets and reservations as one project domain, and capture capacity notifications before retrying so settlement cannot be missed. Constraint: Timed-out adapter work cannot be cancelled reliably and must remain isolated without blocking unrelated tenants globally. Rejected: Track loads only after timeout | creates an untracked transition race and allows live buckets plus orphan buckets to exceed maxProjects. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep project reservations attached to the underlying loader promise, not the caller-facing timeout promise. Tested: 6 import-map suites, 99 steps; touched-file fmt/lint/check; verify:quick; git diff --check. Not-tested: Full pre-push unit suite before commit. --- src/modules/import-map/preloader.test.ts | 37 ++++++++ src/modules/import-map/preloader.ts | 109 ++++++++++++++++------- 2 files changed, 113 insertions(+), 33 deletions(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index b674822a40..57e84ef0a6 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1122,6 +1122,43 @@ describe("modules/import-map/preloader", () => { assertEquals((await sameProjectRecovered).imports?.source, "hung-recovered"); }); + it("reserves project capacity before an invalidated load times out", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 2, + maxVariantsPerProject: 2, + ttlMs: 1_000, + loadTimeoutMs: 1_000, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const invalidated = preloader.preload("/project-a", adapter, "project-a"); + await waitForLoadCount(loads, 1); + preloader.clear("project-a"); + + const second = preloader.preload("/project-b", adapter, "project-b"); + await waitForLoadCount(loads, 2); + const queued = preloader.preload("/project-c", adapter, "project-c"); + for (let attempt = 0; attempt < 10; attempt++) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + assertEquals(loads.length, 2); + + loads[1]!.resolve({ imports: { source: "b" } }); + assertEquals((await second).imports?.source, "b"); + await waitForLoadCount(loads, 3); + loads[2]!.resolve({ imports: { source: "c" } }); + assertEquals((await queued).imports?.source, "c"); + + loads[0]!.resolve({ imports: { source: "a" } }); + assertEquals((await invalidated).imports?.source, "a"); + }); + it("keeps variant identity and capacity deterministic after primordial poisoning", async () => { const worker = new Worker( new URL("./preloader-primordial-poisoning.worker.ts", import.meta.url), diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 2a4488661f..436837d993 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -327,6 +327,11 @@ export class ImportMapPreloader { /** Underlying loader work remains accounted for even after explicit invalidation. */ private readonly activeLoads = new IntrinsicSet>(); private readonly activeIdentityBuilds = new IntrinsicSet>(); + /** Every underlying loader stays reserved here until it actually settles. */ + private readonly reservedLoadsByProject = new IntrinsicMap< + string, + Set> + >(); private readonly orphanedLoadsByProject = new IntrinsicMap< string, Set> @@ -406,7 +411,6 @@ export class ImportMapPreloader { removeEmptyProject && mapSize(projectCache) === 0 && mapSize(projectState.identityBuilds) === 0 && - this.projectOrphanCount(cacheKey) === 0 && mapGet(this.projects, cacheKey) === projectState ) { mapDelete(this.projects, cacheKey); @@ -497,26 +501,30 @@ export class ImportMapPreloader { } private hasProjectLoadCapacity(cacheKey: string): boolean { - return this.projectOrphanCount(cacheKey) < this.maxVariantsPerProject; + const reservedLoads = mapGet(this.reservedLoadsByProject, cacheKey); + return (reservedLoads ? setSize(reservedLoads) : 0) < + this.maxVariantsPerProject; } - private occupiedProjectCount(): number { - let orphanOnlyProjects = 0; - mapForEach(this.orphanedLoadsByProject, (_orphanedLoads, cacheKey) => { - if (!mapGet(this.projects, cacheKey)) orphanOnlyProjects += 1; + private projectOccupancy(): number { + let occupied = mapSize(this.projects); + mapForEach(this.reservedLoadsByProject, (_loads, cacheKey) => { + if (!mapGet(this.projects, cacheKey)) occupied += 1; }); - return mapSize(this.projects) + orphanOnlyProjects; + return occupied; } - private hasProjectOccupancyCapacity(cacheKey: string): boolean { - return Boolean(mapGet(this.projects, cacheKey)) || - Boolean(mapGet(this.orphanedLoadsByProject, cacheKey)) || - this.occupiedProjectCount() < this.maxProjects; + private hasProjectOccupancy(cacheKey: string): boolean { + return mapGet(this.projects, cacheKey) !== undefined || + mapGet(this.reservedLoadsByProject, cacheKey) !== undefined; } - private waitForActiveWork(timeoutMs: number): Promise { + private waitForActiveWork( + capacityChange: Promise, + timeoutMs: number, + ): Promise { const hasActiveWork = setSize(this.activeLoads) + setSize(this.activeIdentityBuilds) + - mapSize(this.orphanedLoadsByProject) > + mapSize(this.reservedLoadsByProject) > 0; // Work can settle between the capacity check and this snapshot. Retry the // admission loop immediately instead of surfacing a stale capacity error. @@ -528,7 +536,7 @@ export class ImportMapPreloader { }, timeoutMs); }); return promiseThen( - raceTwo(this.capacityChange.promise, timeout), + raceTwo(capacityChange, timeout), () => { if (timeoutId !== undefined) ClearTimeout(timeoutId); }, @@ -539,7 +547,7 @@ export class ImportMapPreloader { ); } - private makeProjectRoom(now: number, cacheKey: string): void { + private makeProjectRoom(now: number, requestedCacheKey: string): void { mapForEach(this.projects, (projectState, cacheKey) => { const projectCache = projectState.variants; mapForEach(projectCache, (entry, variantKey) => { @@ -550,18 +558,20 @@ export class ImportMapPreloader { if ( mapSize(projectCache) === 0 && mapSize(projectState.identityBuilds) === 0 && - this.projectOrphanCount(cacheKey) === 0 + mapGet(this.reservedLoadsByProject, cacheKey) === undefined ) { mapDelete(this.projects, cacheKey); } }); - while (!this.hasProjectOccupancyCapacity(cacheKey)) { + if (this.hasProjectOccupancy(requestedCacheKey)) return; + + while (this.projectOccupancy() >= this.maxProjects) { let oldestSettledProject: string | undefined; mapForEach(this.projects, (projectState, cacheKey) => { if (oldestSettledProject !== undefined) return; if (mapSize(projectState.identityBuilds) > 0) return; - if (this.projectOrphanCount(cacheKey) > 0) return; + if (mapGet(this.reservedLoadsByProject, cacheKey)) return; let hasInFlightEntry = false; mapForEach(projectState.variants, (entry) => { if (entry.expiresAt === null) { @@ -603,14 +613,6 @@ export class ImportMapPreloader { ): void { let orphanedLoads = mapGet(this.orphanedLoadsByProject, cacheKey); if (!orphanedLoads) { - if (!this.hasProjectOccupancyCapacity(cacheKey)) { - logger.warn("Import-map load timed out without orphan capacity", { - orphanedProjects: mapSize(this.orphanedLoadsByProject), - occupiedProjects: this.occupiedProjectCount(), - maxProjects: this.maxProjects, - }); - return; - } orphanedLoads = new IntrinsicSet>(); mapSet(this.orphanedLoadsByProject, cacheKey, orphanedLoads); } @@ -673,6 +675,27 @@ export class ImportMapPreloader { ); } + private reserveUnderlyingLoad( + cacheKey: string, + promise: Promise, + ): void { + let reservedLoads = mapGet(this.reservedLoadsByProject, cacheKey); + if (!reservedLoads) { + reservedLoads = new IntrinsicSet>(); + mapSet(this.reservedLoadsByProject, cacheKey, reservedLoads); + } + setAdd(reservedLoads, promise); + const release = (): void => { + const current = mapGet(this.reservedLoadsByProject, cacheKey); + if (!current || !setDelete(current, promise)) return; + if (setSize(current) === 0) { + mapDelete(this.reservedLoadsByProject, cacheKey); + } + this.notifyCapacityChange(); + }; + promiseThen(promise, release, release); + } + private hasActiveWorkCapacity(): boolean { return setSize(this.activeLoads) + setSize(this.activeIdentityBuilds) < this.maxConcurrentLoads; @@ -738,7 +761,6 @@ export class ImportMapPreloader { if ( mapSize(projectState.identityBuilds) === 0 && mapSize(projectState.variants) === 0 && - this.projectOrphanCount(cacheKey) === 0 && mapGet(this.projects, cacheKey) === projectState ) { mapDelete(this.projects, cacheKey); @@ -751,12 +773,15 @@ export class ImportMapPreloader { adapter: RuntimeAdapter, config: VeryfrontConfig | undefined, ): Promise { - if (!this.hasProjectOccupancyCapacity(cacheKey)) { - throw this.capacityError("projects"); - } if (!this.hasProjectLoadCapacity(cacheKey)) { throw this.capacityError("loads"); } + if ( + !this.hasProjectOccupancy(cacheKey) && + this.projectOccupancy() >= this.maxProjects + ) { + throw this.capacityError("projects"); + } if (!this.hasActiveWorkCapacity()) { throw this.capacityError("loads"); } @@ -764,8 +789,10 @@ export class ImportMapPreloader { resolvedPromise(), () => this.loader(projectDir, adapter, config), ); - // The caller-facing timeout must not release capacity while the underlying - // adapter is still working; doing so would permit an unbounded retry train. + // Reserve the project and its per-project load budget before the loader can + // start. A caller timeout can release global admission, but the underlying + // work retains this reservation until it actually settles. + this.reserveUnderlyingLoad(cacheKey, loaderPromise); this.trackActiveLoad(loaderPromise); let timeoutId: ReturnType | undefined; const timeoutPromise = new IntrinsicPromise((_, reject) => { @@ -804,13 +831,14 @@ export class ImportMapPreloader { ): Promise { const capacityDeadline = monotonicNow() + this.loadTimeoutMs; for (;;) { + const capacityChange = this.capacityChange.promise; try { return await this.preloadOnce(projectDir, adapter, projectId, context); } catch (error) { if (!this.isCapacityError(error)) throw error; const remainingMs = capacityDeadline - monotonicNow(); if (remainingMs <= 0) throw error; - await this.waitForActiveWork(remainingMs); + await this.waitForActiveWork(capacityChange, remainingMs); } } } @@ -838,6 +866,17 @@ export class ImportMapPreloader { this.touchProject(cacheKey, projectState); } + // Joining the exact in-flight identity remains allowed at the per-project + // ceiling. A different identity cannot start, so reject it before hashing + // creates a settle-and-retry notification loop while an orphan is active. + if ( + mapGet(projectState.identityBuilds, canonicalIdentity) === undefined && + !this.hasProjectLoadCapacity(cacheKey) + ) { + this.removeEmptyProject(cacheKey, projectState); + throw this.capacityError("loads"); + } + const globalGeneration = this.globalGeneration; const projectGeneration = projectState.generation; @@ -955,20 +994,24 @@ export class ImportMapPreloader { settledAt = this.now(); } catch (_) { this.deleteEntry(cacheKey, projectState, variantKey, entry); + this.notifyCapacityChange(); return; } if (!NumberIsFinite(settledAt)) { this.deleteEntry(cacheKey, projectState, variantKey, entry); + this.notifyCapacityChange(); return; } entry.expiresAt = MathMin( NUMBER_MAX_SAFE_INTEGER, settledAt + this.ttlMs, ); + this.notifyCapacityChange(); }, () => { releaseIdentity(); this.deleteEntry(cacheKey, projectState, variantKey, entry); + this.notifyCapacityChange(); }, ); From 69d3138c800d07184c388832d5c25b0cc8fe4ee0 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 17:50:35 +0200 Subject: [PATCH 27/34] Preserve import-map metadata compatibility during preload The preloader context snapshot was still sending resolve.importMap through the strict import-map snapshotter, so harmless enumerable metadata could reject a validated config even though the loader and merger intentionally ignore it. Snapshot only the executable imports and scopes fields, while still rejecting accessors before any tenant code can run. Constraint: PR #3308 hardens import-map identity without changing public import-map metadata compatibility. Rejected: Relax snapshotImportMap globally | strict unknown-field rejection remains useful for internal canonical snapshots. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/preloader.test.ts Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/preloader.test.ts src/modules/import-map/loader.test.ts src/modules/import-map/default-import-map.test.ts src/modules/import-map/merger.test.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/pipeline/cache-identity.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts && npx --yes deno@2.7.7 lint src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts && npx --yes deno@2.7.7 check src/modules/import-map/preloader.ts src/modules/import-map/preloader.test.ts && git diff --check --- src/modules/import-map/preloader.test.ts | 55 ++++++++++++++++++++++++ src/modules/import-map/preloader.ts | 25 ++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 57e84ef0a6..162802a6d3 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -144,6 +144,61 @@ describe("modules/import-map/preloader", () => { assertEquals(first === changed, false); }); + it("ignores extra config import-map metadata without invoking accessors", async () => { + clearImportMapCache(); + const adapter = createMinimalAdapter(); + let metadataCalls = 0; + const importMap = { + imports: { package: "https://example.com/package.ts" }, + }; + Object.defineProperty(importMap, "metadata", { + enumerable: true, + get() { + metadataCalls++; + return { source: "project" }; + }, + }); + const config = { resolve: { importMap } } as VeryfrontConfig; + + const result = await preloadImportMap( + "/metadata-project", + adapter, + "metadata-project", + { config }, + ); + + assertEquals(result.imports?.package, "https://example.com/package.ts"); + assertEquals(metadataCalls, 0); + }); + + it("rejects accessor-backed config import-map fields without invoking them", async () => { + clearImportMapCache(); + const adapter = createMinimalAdapter(); + let importsCalls = 0; + const importMap = {}; + Object.defineProperty(importMap, "imports", { + enumerable: true, + get() { + importsCalls++; + return { package: "https://example.com/package.ts" }; + }, + }); + const config = { resolve: { importMap } } as VeryfrontConfig; + + await assertRejects( + () => + preloadImportMap( + "/accessor-import-map-project", + adapter, + "accessor-import-map-project", + { config }, + ), + TypeError, + "imports cannot be an accessor", + ); + assertEquals(importsCalls, 0); + }); + it("binds variant identity and loading to one pre-await config snapshot", async () => { const adapter = createMinimalAdapter(); const releaseLoader = createDeferred(); diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index 436837d993..bf1371a410 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -72,6 +72,29 @@ function hasOwn(object: object, key: PropertyKey): boolean { return ReflectApply(ObjectPrototypeHasOwnProperty, object, [key]) as boolean; } +function snapshotEmbeddedImportMap(value: unknown): ImportMapConfig { + if (value === undefined || value === null) return snapshotImportMap({}); + if (typeof value !== "object") return snapshotImportMap(value); + + const importsDescriptor = ObjectGetOwnPropertyDescriptor(value, "imports"); + if (importsDescriptor && !hasOwn(importsDescriptor, "value")) { + throw new IntrinsicTypeError( + "Import-map config resolve.importMap imports cannot be an accessor", + ); + } + const scopesDescriptor = ObjectGetOwnPropertyDescriptor(value, "scopes"); + if (scopesDescriptor && !hasOwn(scopesDescriptor, "value")) { + throw new IntrinsicTypeError( + "Import-map config resolve.importMap scopes cannot be an accessor", + ); + } + + return snapshotImportMap({ + imports: importsDescriptor?.value ?? {}, + scopes: scopesDescriptor?.value ?? {}, + }); +} + function mapClear(map: Map): void { ReflectApply(MapPrototypeClear, map, []); } @@ -245,7 +268,7 @@ function snapshotPreloadContext( if (importMapDescriptor && !hasOwn(importMapDescriptor, "value")) { throw new IntrinsicTypeError("Import-map config resolve.importMap cannot be an accessor"); } - const importMap = snapshotImportMap(importMapDescriptor?.value ?? {}); + const importMap = snapshotEmbeddedImportMap(importMapDescriptor?.value); // The loader only consumes resolve.importMap. Keeping the request snapshot // minimal avoids invoking unrelated config getters or retaining mutable // tenant-controlled configuration behind a cache entry. From 080e0503106e96d603337d789eefe87088461a14 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 17:51:15 +0200 Subject: [PATCH 28/34] test(modules): lock import-map capacity transitions --- src/modules/import-map/preloader.test.ts | 117 +++++++++++++++++++++++ src/modules/import-map/preloader.ts | 17 ++-- 2 files changed, 128 insertions(+), 6 deletions(-) diff --git a/src/modules/import-map/preloader.test.ts b/src/modules/import-map/preloader.test.ts index 162802a6d3..9e093c0b46 100644 --- a/src/modules/import-map/preloader.test.ts +++ b/src/modules/import-map/preloader.test.ts @@ -1128,6 +1128,45 @@ describe("modules/import-map/preloader", () => { await first; }); + it("counts cleared underlying work against the total project bound", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 2, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadTimeoutMs: 1_000, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const cached = preloader.preload("/cached", adapter, "cached"); + await waitForLoadCount(loads, 1); + loads[0]!.resolve({ imports: { source: "cached" } }); + await cached; + + const cleared = preloader.preload("/project-a", adapter, "project-a"); + await waitForLoadCount(loads, 2); + preloader.clear("project-a"); + + const activeB = preloader.preload("/project-b", adapter, "project-b"); + await waitForLoadCount(loads, 3); + const queuedD = preloader.preload("/project-d", adapter, "project-d"); + await Promise.resolve(); + assertEquals(loads.length, 3); + + loads[1]!.resolve({ imports: { source: "late-a" } }); + assertEquals((await cleared).imports?.source, "late-a"); + await waitForLoadCount(loads, 4); + loads[2]!.resolve({ imports: { source: "b" } }); + loads[3]!.resolve({ imports: { source: "d" } }); + assertEquals((await activeB).imports?.source, "b"); + assertEquals((await queuedD).imports?.source, "d"); + }); + it("keeps timed-out underlying work scoped to its project capacity", async () => { const adapter = createMinimalAdapter(); const loads: Array>> = []; @@ -1214,6 +1253,84 @@ describe("modules/import-map/preloader", () => { assertEquals((await invalidated).imports?.source, "a"); }); + it("counts timed-out work against the total project bound", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + const preloader = new ImportMapPreloader({ + maxProjects: 2, + maxVariantsPerProject: 1, + ttlMs: 1_000, + loadTimeoutMs: 100, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const timedOutA = preloader.preload("/project-a", adapter, "project-a"); + await waitForLoadCount(loads, 1); + await assertRejects(() => timedOutA, RangeError, "load timed out"); + + const activeB = preloader.preload("/project-b", adapter, "project-b"); + await waitForLoadCount(loads, 2); + const queuedC = preloader.preload("/project-c", adapter, "project-c"); + await Promise.resolve(); + assertEquals(loads.length, 2); + + loads[0]!.resolve({ imports: { source: "late-a" } }); + await waitForLoadCount(loads, 3); + loads[1]!.resolve({ imports: { source: "b" } }); + loads[2]!.resolve({ imports: { source: "c" } }); + assertEquals((await activeB).imports?.source, "b"); + assertEquals((await queuedC).imports?.source, "c"); + }); + + it("does not miss capacity released before a waiter observes its signal", async () => { + const adapter = createMinimalAdapter(); + const loads: Array>> = []; + let releaseDuringAdmission = false; + let admissionClockReads = 0; + let clock = 0; + const preloader = new ImportMapPreloader({ + maxProjects: 1, + maxVariantsPerProject: 2, + ttlMs: 1_000, + loadTimeoutMs: 1_000, + now: () => { + if (releaseDuringAdmission && admissionClockReads++ === 0) { + loads[0]!.resolve({ imports: { source: "a" } }); + } + return ++clock; + }, + loadImportMap: () => { + const load = createDeferred(); + loads.push(load); + return load.promise; + }, + }); + + const first = preloader.preload("/project", adapter, "project", { + contentSourceId: "source-a", + }); + const unrelated = preloader.preload("/project", adapter, "project", { + contentSourceId: "source-b", + }); + await waitForLoadCount(loads, 2); + + releaseDuringAdmission = true; + const queued = preloader.preload("/project", adapter, "project", { + contentSourceId: "source-c", + }); + + await waitForLoadCount(loads, 3); + assertEquals((await first).imports?.source, "a"); + loads[2]!.resolve({ imports: { source: "c" } }); + assertEquals((await queued).imports?.source, "c"); + loads[1]!.resolve({ imports: { source: "b" } }); + assertEquals((await unrelated).imports?.source, "b"); + }); + it("keeps variant identity and capacity deterministic after primordial poisoning", async () => { const worker = new Worker( new URL("./preloader-primordial-poisoning.worker.ts", import.meta.url), diff --git a/src/modules/import-map/preloader.ts b/src/modules/import-map/preloader.ts index bf1371a410..158fdb26fe 100644 --- a/src/modules/import-map/preloader.ts +++ b/src/modules/import-map/preloader.ts @@ -665,21 +665,26 @@ export class ImportMapPreloader { } private reportTimedOutLoad(cacheKey: string): void { + // Hashing is asynchronous. Snapshot the transition counters first so the + // diagnostic describes this timeout rather than later concurrent changes. + const orphanedLoadsForProject = this.projectOrphanCount(cacheKey); + const orphanedProjects = mapSize(this.orphanedLoadsByProject); + const activeLoads = setSize(this.activeLoads); void promiseThen( computeHash(cacheKey), (cacheKeyHash) => { logger.warn("Import-map load timed out with underlying work still active", { cacheKeyHash, - orphanedLoadsForProject: this.projectOrphanCount(cacheKey), - orphanedProjects: mapSize(this.orphanedLoadsByProject), - activeLoads: setSize(this.activeLoads), + orphanedLoadsForProject, + orphanedProjects, + activeLoads, }); }, () => { logger.warn("Import-map load timed out with underlying work still active", { - orphanedLoadsForProject: this.projectOrphanCount(cacheKey), - orphanedProjects: mapSize(this.orphanedLoadsByProject), - activeLoads: setSize(this.activeLoads), + orphanedLoadsForProject, + orphanedProjects, + activeLoads, }); }, ); From ad6f7fe900acd93d3bd45773f8c250e5ca7ef3a9 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 18:23:18 +0200 Subject: [PATCH 29/34] Preserve HTTP cache identity after URL prototype mutation Capture the URL and URLSearchParams accessors used by normalization so post-import project mutations cannot collapse distinct module URLs onto one cache identity. Extend the existing primordial regression to poison every captured URL surface. Constraint: Cache identities must remain stable after untrusted project code mutates shared prototypes Rejected: Capture URL.prototype.toString only | neighboring mutable URL accessors and search-param methods expose the same failure mode Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep URL normalization on captured primordials when adding new URL operations Tested: http-cache-helpers test, deno check, deno lint, formatting, git diff --check --- src/transforms/esm/http-cache-helpers.test.ts | 39 ++++++++ src/transforms/esm/http-cache-helpers.ts | 95 +++++++++++++++---- 2 files changed, 118 insertions(+), 16 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index cd058d1065..43cf9f2301 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -142,8 +142,13 @@ describe("transforms/esm/http-cache-helpers", () => { ); const objectDefineProperty = Object.defineProperty; const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL")!; + const urlPrototypeDescriptors = Object.getOwnPropertyDescriptors(URL.prototype); + const searchParamsPrototypeDescriptors = Object.getOwnPropertyDescriptors( + URLSearchParams.prototype, + ); let definePropertyCalls = 0; let urlCalls = 0; + let urlPrototypeCalls = 0; let poisoned: string; try { @@ -165,6 +170,37 @@ describe("transforms/esm/http-cache-helpers", () => { }, writable: true, }); + for (const name of ["hostname", "pathname", "searchParams"]) { + objectDefineProperty(URL.prototype, name, { + configurable: true, + get() { + urlPrototypeCalls++; + throw new Error(`poisoned URL.prototype.${name}`); + }, + set() { + urlPrototypeCalls++; + throw new Error(`poisoned URL.prototype.${name}`); + }, + }); + } + objectDefineProperty(URL.prototype, "toString", { + configurable: true, + value() { + urlPrototypeCalls++; + return "https://evil.invalid/collapsed"; + }, + writable: true, + }); + for (const name of ["get", "has", "set", "sort"]) { + objectDefineProperty(URLSearchParams.prototype, name, { + configurable: true, + value() { + urlPrototypeCalls++; + throw new Error(`poisoned URLSearchParams.prototype.${name}`); + }, + writable: true, + }); + } poisoned = await buildHttpCacheIdentity( "https://esm.sh/lodash@4?z=1&a=2", @@ -177,11 +213,14 @@ describe("transforms/esm/http-cache-helpers", () => { writable: true, }); objectDefineProperty(globalThis, "URL", urlDescriptor); + Object.defineProperties(URL.prototype, urlPrototypeDescriptors); + Object.defineProperties(URLSearchParams.prototype, searchParamsPrototypeDescriptors); } assertEquals(poisoned, baseline); assertEquals(definePropertyCalls, 0); assertEquals(urlCalls, 0); + assertEquals(urlPrototypeCalls, 0); }); it("does not consult inherited toJSON hooks while fingerprinting import maps", async () => { diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index babc356acf..6e3a4875d8 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -21,8 +21,68 @@ import { computeHash } from "#veryfront/utils/hash-utils.ts"; const logger = rendererLogger.component("http-cache"); const JSONStringify = JSON.stringify; const IntrinsicURL = URL; +const IntrinsicURLSearchParams = URLSearchParams; const ObjectDefineProperty = Object.defineProperty; const ObjectEntries = Object.entries; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ReflectApply = Reflect.apply; +const URLHostnameGet = ObjectGetOwnPropertyDescriptor( + IntrinsicURL.prototype, + "hostname", +)!.get!; +const URLPathnameGet = ObjectGetOwnPropertyDescriptor( + IntrinsicURL.prototype, + "pathname", +)!.get!; +const URLPathnameSet = ObjectGetOwnPropertyDescriptor( + IntrinsicURL.prototype, + "pathname", +)!.set!; +const URLSearchParamsGet = ObjectGetOwnPropertyDescriptor( + IntrinsicURL.prototype, + "searchParams", +)!.get!; +const URLToString = IntrinsicURL.prototype.toString; +const URLSearchParamsGetValue = IntrinsicURLSearchParams.prototype.get; +const URLSearchParamsHas = IntrinsicURLSearchParams.prototype.has; +const URLSearchParamsSetValue = IntrinsicURLSearchParams.prototype.set; +const URLSearchParamsSort = IntrinsicURLSearchParams.prototype.sort; + +function getURLHostname(url: URL): string { + return ReflectApply(URLHostnameGet, url, []); +} + +function getURLPathname(url: URL): string { + return ReflectApply(URLPathnameGet, url, []); +} + +function setURLPathname(url: URL, pathname: string): void { + ReflectApply(URLPathnameSet, url, [pathname]); +} + +function getURLSearchParams(url: URL): URLSearchParams { + return ReflectApply(URLSearchParamsGet, url, []); +} + +function stringifyURL(url: URL): string { + return ReflectApply(URLToString, url, []); +} + +function getURLSearchParam(searchParams: URLSearchParams, name: string): string | null { + return ReflectApply(URLSearchParamsGetValue, searchParams, [name]); +} + +function hasURLSearchParam(searchParams: URLSearchParams, name: string): boolean { + return ReflectApply(URLSearchParamsHas, searchParams, [name]); +} + +function setURLSearchParam(searchParams: URLSearchParams, name: string, value: string): void { + ReflectApply(URLSearchParamsSetValue, searchParams, [name, value]); +} + +function sortURLSearchParams(searchParams: URLSearchParams): void { + ReflectApply(URLSearchParamsSort, searchParams, []); +} /** * Cache interface for dependency injection (matches LRU essential methods). @@ -237,9 +297,9 @@ interface CanonicalReactEsmPackage { function parseCanonicalReactEsmPackage(rawUrl: string): CanonicalReactEsmPackage | null { try { const url = new IntrinsicURL(rawUrl); - if (url.hostname !== "esm.sh") return null; + if (getURLHostname(url) !== "esm.sh") return null; - const pathSegments = url.pathname.split("/").filter(Boolean); + const pathSegments = getURLPathname(url).split("/").filter(Boolean); const prefix = pathSegments[0] ?? ""; const packageIndex = prefix === "stable" || /^v\d+$/.test(prefix) ? 1 : 0; const match = /^(react|react-dom)@(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)$/.exec( @@ -279,7 +339,7 @@ export function getEffectiveHttpCacheRequest const version = options.reactVersion ?? parsed.version; if (version !== parsed.version) { parsed.pathSegments[parsed.packageIndex] = `${parsed.packageName}@${version}`; - parsed.url.pathname = `/${parsed.pathSegments.join("/")}`; + setURLPathname(parsed.url, `/${parsed.pathSegments.join("/")}`); } const effectiveOptions = { @@ -290,7 +350,7 @@ export function getEffectiveHttpCacheRequest const context = getHttpCacheRequestIdentityContext(options); if (context) attachHttpCacheRequestIdentityContext(effectiveOptions, context); - return { url: parsed.url.toString(), options: effectiveOptions }; + return { url: stringifyURL(parsed.url), options: effectiveOptions }; } /** @@ -335,25 +395,27 @@ export function isInternalBare(specifier: string): boolean { } export function normalizeEsmShUrl(url: URL): void { - if (url.hostname !== "esm.sh") return; + if (getURLHostname(url) !== "esm.sh") return; - if (url.pathname.includes("/denonext/")) { - url.pathname = url.pathname.replace("/denonext/", "/"); + const originalPathname = getURLPathname(url); + if (originalPathname.includes("/denonext/")) { + setURLPathname(url, originalPathname.replace("/denonext/", "/")); } - if (!url.searchParams.has("target")) { - url.searchParams.set("target", "es2022"); + const searchParams = getURLSearchParams(url); + if (!hasURLSearchParam(searchParams, "target")) { + setURLSearchParam(searchParams, "target", "es2022"); } - const pathname = url.pathname.replace(/^\/+/, ""); + const pathname = getURLPathname(url).replace(/^\/+/, ""); const isBaseReact = /^react@[\d.]+(?:\?|$)/.test(pathname); if (isBaseReact) return; - const existing = url.searchParams.get("external"); + const existing = getURLSearchParam(searchParams, "external"); const externals = existing ? existing.split(",") : []; if (!externals.includes("react")) { externals.push("react"); - url.searchParams.set("external", externals.join(",")); + setURLSearchParam(searchParams, "external", externals.join(",")); } } @@ -361,15 +423,16 @@ export function normalizeHttpUrl(raw: string): string { try { const url = new IntrinsicURL(raw); normalizeEsmShUrl(url); - url.searchParams.sort(); - const normalized = url.toString(); + const searchParams = getURLSearchParams(url); + sortURLSearchParams(searchParams); + const normalized = stringifyURL(url); // esm.sh misbehaves when list-valued params such as // `external=react,react-dom` are percent-encoded as `%2C`. // Preserve literal commas only for the affected param so unrelated // query values remain canonically encoded. - if (url.hostname === "esm.sh") { - const external = url.searchParams.get("external"); + if (getURLHostname(url) === "esm.sh") { + const external = getURLSearchParam(searchParams, "external"); if (!external) return normalized; const encodedExternal = encodeURIComponent(external); From a85156ac80254bc6da3e22a4a8b47826fb015726 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 18:36:59 +0200 Subject: [PATCH 30/34] Close remaining HTTP identity poisoning paths Route every mutable string, array, regexp, and URL operation used by HTTP identity normalization through captured intrinsics. Extend the regression to poison the complete operation set and prove two distinct module URLs remain distinct. Constraint: Project code can mutate shared prototypes after the cache module loads Rejected: Capture String.replace only | split, array joining, regexp dispatch, and encoding could still forge or collapse canonical URLs Confidence: high Scope-risk: narrow Reversibility: clean Directive: New HTTP identity normalization operations must use captured primordials and retain the distinct-identity poisoning regression Tested: http-cache-helpers 69 steps, deno fmt, lint, check, git diff --check --- src/transforms/esm/http-cache-helpers.test.ts | 70 ++++++++++++- src/transforms/esm/http-cache-helpers.ts | 97 ++++++++++++++++--- 2 files changed, 148 insertions(+), 19 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 43cf9f2301..e9abb56a3b 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -140,16 +140,30 @@ describe("transforms/esm/http-cache-helpers", () => { "https://esm.sh/lodash@4?z=1&a=2", prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), ); + const otherBaseline = await buildHttpCacheIdentity( + "https://esm.sh/preact@10?z=2&b=3", + prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), + ); const objectDefineProperty = Object.defineProperty; const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL")!; - const urlPrototypeDescriptors = Object.getOwnPropertyDescriptors(URL.prototype); + const encodeURIComponentDescriptor = Object.getOwnPropertyDescriptor( + globalThis, + "encodeURIComponent", + )!; + const urlPrototype = URL.prototype; + const urlPrototypeDescriptors = Object.getOwnPropertyDescriptors(urlPrototype); const searchParamsPrototypeDescriptors = Object.getOwnPropertyDescriptors( URLSearchParams.prototype, ); + const stringPrototypeDescriptors = Object.getOwnPropertyDescriptors(String.prototype); + const arrayPrototype: object = Array.prototype; + const arrayPrototypeDescriptors = Object.getOwnPropertyDescriptors(arrayPrototype); + const regExpPrototypeDescriptors = Object.getOwnPropertyDescriptors(RegExp.prototype); let definePropertyCalls = 0; let urlCalls = 0; let urlPrototypeCalls = 0; let poisoned: string; + let otherPoisoned: string; try { objectDefineProperty(Object, "defineProperty", { @@ -171,7 +185,7 @@ describe("transforms/esm/http-cache-helpers", () => { writable: true, }); for (const name of ["hostname", "pathname", "searchParams"]) { - objectDefineProperty(URL.prototype, name, { + objectDefineProperty(urlPrototype, name, { configurable: true, get() { urlPrototypeCalls++; @@ -183,7 +197,7 @@ describe("transforms/esm/http-cache-helpers", () => { }, }); } - objectDefineProperty(URL.prototype, "toString", { + objectDefineProperty(urlPrototype, "toString", { configurable: true, value() { urlPrototypeCalls++; @@ -201,11 +215,53 @@ describe("transforms/esm/http-cache-helpers", () => { writable: true, }); } + for (const name of ["includes", "replace", "split"]) { + objectDefineProperty(String.prototype, name, { + configurable: true, + value() { + urlPrototypeCalls++; + return "https://evil.invalid/collapsed"; + }, + writable: true, + }); + } + for (const name of ["filter", "includes", "join", "push"]) { + objectDefineProperty(arrayPrototype, name, { + configurable: true, + value() { + urlPrototypeCalls++; + throw new Error(`poisoned Array.prototype.${name}`); + }, + writable: true, + }); + } + for (const name of ["exec", "test"]) { + objectDefineProperty(RegExp.prototype, name, { + configurable: true, + value() { + urlPrototypeCalls++; + throw new Error(`poisoned RegExp.prototype.${name}`); + }, + writable: true, + }); + } + objectDefineProperty(globalThis, "encodeURIComponent", { + configurable: true, + value() { + urlPrototypeCalls++; + return "collapsed"; + }, + writable: true, + }); poisoned = await buildHttpCacheIdentity( "https://esm.sh/lodash@4?z=1&a=2", prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), ); + otherPoisoned = await buildHttpCacheIdentity( + "https://esm.sh/preact@10?z=2&b=3", + prepareHttpCacheRequestOptions({ cacheDir: ".cache", importMap }), + ); } finally { objectDefineProperty(Object, "defineProperty", { configurable: true, @@ -213,11 +269,17 @@ describe("transforms/esm/http-cache-helpers", () => { writable: true, }); objectDefineProperty(globalThis, "URL", urlDescriptor); - Object.defineProperties(URL.prototype, urlPrototypeDescriptors); + objectDefineProperty(globalThis, "encodeURIComponent", encodeURIComponentDescriptor); + Object.defineProperties(urlPrototype, urlPrototypeDescriptors); Object.defineProperties(URLSearchParams.prototype, searchParamsPrototypeDescriptors); + Object.defineProperties(String.prototype, stringPrototypeDescriptors); + Object.defineProperties(arrayPrototype, arrayPrototypeDescriptors); + Object.defineProperties(RegExp.prototype, regExpPrototypeDescriptors); } assertEquals(poisoned, baseline); + assertEquals(otherPoisoned, otherBaseline); + assertNotEquals(poisoned, otherPoisoned); assertEquals(definePropertyCalls, 0); assertEquals(urlCalls, 0); assertEquals(urlPrototypeCalls, 0); diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 6e3a4875d8..28bdbdade7 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -7,7 +7,10 @@ import { isAbsolute, join } from "#veryfront/compat/path/index.ts"; import { cwd } from "#veryfront/platform/compat/process.ts"; import { + primordialArrayFilter as arrayFilter, + primordialArrayJoin as arrayJoin, primordialArrayMap as arrayMap, + primordialArrayPush as arrayPush, primordialArraySort as arraySort, } from "#veryfront/platform/compat/primordials/array.ts"; import { rendererLogger } from "#veryfront/utils"; @@ -19,13 +22,20 @@ import { DEFAULT_REACT_VERSION, getReactImportMap } from "./react-cdn.ts"; import { computeHash } from "#veryfront/utils/hash-utils.ts"; const logger = rendererLogger.component("http-cache"); +const ArrayIncludes = Array.prototype.includes; +const EncodeURIComponent = encodeURIComponent; const JSONStringify = JSON.stringify; const IntrinsicURL = URL; const IntrinsicURLSearchParams = URLSearchParams; const ObjectDefineProperty = Object.defineProperty; const ObjectEntries = Object.entries; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const RegExpExec = RegExp.prototype.exec; const ReflectApply = Reflect.apply; +const StringIncludes = String.prototype.includes; +const StringReplace = String.prototype.replace; +const StringSlice = String.prototype.slice; +const StringSplit = String.prototype.split; const URLHostnameGet = ObjectGetOwnPropertyDescriptor( IntrinsicURL.prototype, "hostname", @@ -84,6 +94,58 @@ function sortURLSearchParams(searchParams: URLSearchParams): void { ReflectApply(URLSearchParamsSort, searchParams, []); } +function arrayIncludesValue(values: readonly T[], value: T): boolean { + return ReflectApply(ArrayIncludes, values, [value]); +} + +function execRegExp(pattern: RegExp, value: string): RegExpExecArray | null { + return ReflectApply(RegExpExec, pattern, [value]); +} + +function testRegExp(pattern: RegExp, value: string): boolean { + return execRegExp(pattern, value) !== null; +} + +function stringIncludes(value: string, search: string): boolean { + return ReflectApply(StringIncludes, value, [search]); +} + +function stringReplace(value: string, search: string, replacement: string): string { + return ReflectApply(StringReplace, value, [search, replacement]); +} + +function stringSlice(value: string, start: number): string { + return ReflectApply(StringSlice, value, [start]); +} + +function stringSplit(value: string, separator: string): string[] { + return ReflectApply(StringSplit, value, [separator]); +} + +function stripLeadingSlashes(value: string): string { + let index = 0; + while (value[index] === "/") index++; + return index === 0 ? value : stringSlice(value, index); +} + +function decodeEncodedCommas(value: string): string { + let decoded = ""; + let index = 0; + while (index < value.length) { + if ( + value[index] === "%" && value[index + 1] === "2" && + (value[index + 2] === "C" || value[index + 2] === "c") + ) { + decoded += ","; + index += 3; + continue; + } + decoded += value[index]; + index++; + } + return decoded; +} + /** * Cache interface for dependency injection (matches LRU essential methods). */ @@ -299,10 +361,14 @@ function parseCanonicalReactEsmPackage(rawUrl: string): CanonicalReactEsmPackage const url = new IntrinsicURL(rawUrl); if (getURLHostname(url) !== "esm.sh") return null; - const pathSegments = getURLPathname(url).split("/").filter(Boolean); + const pathSegments = arrayFilter( + stringSplit(getURLPathname(url), "/"), + (segment) => segment.length > 0, + ); const prefix = pathSegments[0] ?? ""; - const packageIndex = prefix === "stable" || /^v\d+$/.test(prefix) ? 1 : 0; - const match = /^(react|react-dom)@(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)$/.exec( + const packageIndex = prefix === "stable" || testRegExp(/^v\d+$/, prefix) ? 1 : 0; + const match = execRegExp( + /^(react|react-dom)@(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)$/, pathSegments[packageIndex] ?? "", ); if (!match?.[1] || !match[2]) return null; @@ -339,7 +405,7 @@ export function getEffectiveHttpCacheRequest const version = options.reactVersion ?? parsed.version; if (version !== parsed.version) { parsed.pathSegments[parsed.packageIndex] = `${parsed.packageName}@${version}`; - setURLPathname(parsed.url, `/${parsed.pathSegments.join("/")}`); + setURLPathname(parsed.url, `/${arrayJoin(parsed.pathSegments, "/")}`); } const effectiveOptions = { @@ -398,8 +464,8 @@ export function normalizeEsmShUrl(url: URL): void { if (getURLHostname(url) !== "esm.sh") return; const originalPathname = getURLPathname(url); - if (originalPathname.includes("/denonext/")) { - setURLPathname(url, originalPathname.replace("/denonext/", "/")); + if (stringIncludes(originalPathname, "/denonext/")) { + setURLPathname(url, stringReplace(originalPathname, "/denonext/", "/")); } const searchParams = getURLSearchParams(url); @@ -407,15 +473,15 @@ export function normalizeEsmShUrl(url: URL): void { setURLSearchParam(searchParams, "target", "es2022"); } - const pathname = getURLPathname(url).replace(/^\/+/, ""); - const isBaseReact = /^react@[\d.]+(?:\?|$)/.test(pathname); + const pathname = stripLeadingSlashes(getURLPathname(url)); + const isBaseReact = testRegExp(/^react@[\d.]+(?:\?|$)/, pathname); if (isBaseReact) return; const existing = getURLSearchParam(searchParams, "external"); - const externals = existing ? existing.split(",") : []; - if (!externals.includes("react")) { - externals.push("react"); - setURLSearchParam(searchParams, "external", externals.join(",")); + const externals = existing ? stringSplit(existing, ",") : []; + if (!arrayIncludesValue(externals, "react")) { + arrayPush(externals, "react"); + setURLSearchParam(searchParams, "external", arrayJoin(externals, ",")); } } @@ -435,10 +501,11 @@ export function normalizeHttpUrl(raw: string): string { const external = getURLSearchParam(searchParams, "external"); if (!external) return normalized; - const encodedExternal = encodeURIComponent(external); - return normalized.replace( + const encodedExternal = EncodeURIComponent(external); + return stringReplace( + normalized, `external=${encodedExternal}`, - `external=${encodedExternal.replace(/%2C/gi, ",")}`, + `external=${decodeEncodedCommas(encodedExternal)}`, ); } From 60abe115bbe74bebe87b12ba4a9a8bd56e18f3eb Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 18:56:24 +0200 Subject: [PATCH 31/34] Reject poisoned cached bundle paths Cached HTTP bundle recovery must reject file URLs from a different cache root even when project code mutates common JavaScript prototypes after module import. The scanner now uses captured intrinsics for the regex iteration, bundle marker check, and cache-dir prefix check so recovered code cannot collapse the incompatible-path result. Constraint: Existing local branch fix/import-map-preloader-hardening is checked out in another worktree at an older commit, so this commit is created in the requested exact-head detached worktree without moving that branch ref. Rejected: Switch this worktree onto the local branch | Git reports the branch is already checked out elsewhere and it is not at the requested head. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep hasIncompatibleFilePaths on captured intrinsics; do not reintroduce direct prototype method calls in the cache-trust scanner. Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 lint src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 check src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Tested: git diff --check -- src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts Not-tested: Full repository test suite --- src/transforms/esm/http-cache-helpers.test.ts | 35 +++++++++++++++++++ src/transforms/esm/http-cache-helpers.ts | 11 ++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index e9abb56a3b..22c3770015 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -618,6 +618,41 @@ describe("transforms/esm/http-cache-helpers", () => { assertEquals(hasIncompatibleFilePaths(code, "/cache"), true); }); + it("uses captured intrinsics after prototype poisoning", () => { + const code = 'import x from "file:///remote/cache/veryfront-http-bundle/http-deadbeef.mjs";'; + const stringPrototypeDescriptors = Object.getOwnPropertyDescriptors(String.prototype); + const regExpPrototypeDescriptors = Object.getOwnPropertyDescriptors(RegExp.prototype); + + try { + Object.defineProperty(RegExp.prototype, "exec", { + configurable: true, + value() { + return null; + }, + writable: true, + }); + Object.defineProperty(String.prototype, "includes", { + configurable: true, + value() { + return false; + }, + writable: true, + }); + Object.defineProperty(String.prototype, "startsWith", { + configurable: true, + value() { + return true; + }, + writable: true, + }); + + assertEquals(hasIncompatibleFilePaths(code, "/local/cache"), true); + } finally { + Object.defineProperties(String.prototype, stringPrototypeDescriptors); + Object.defineProperties(RegExp.prototype, regExpPrototypeDescriptors); + } + }); + it("ignores non-bundle file:// paths", () => { const code = 'import "file:///other/some-file.js";'; assertEquals(hasIncompatibleFilePaths(code, "/cache"), false); diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 28bdbdade7..752325a9b8 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -36,6 +36,7 @@ const StringIncludes = String.prototype.includes; const StringReplace = String.prototype.replace; const StringSlice = String.prototype.slice; const StringSplit = String.prototype.split; +const StringStartsWith = String.prototype.startsWith; const URLHostnameGet = ObjectGetOwnPropertyDescriptor( IntrinsicURL.prototype, "hostname", @@ -122,6 +123,10 @@ function stringSplit(value: string, separator: string): string[] { return ReflectApply(StringSplit, value, [separator]); } +function stringStartsWith(value: string, search: string): boolean { + return ReflectApply(StringStartsWith, value, [search]); +} + function stripLeadingSlashes(value: string): string { let index = 0; while (value[index] === "/") index++; @@ -558,11 +563,11 @@ export function hasIncompatibleFilePaths(code: string, localCacheDir: string): b const filePathPattern = /file:\/\/([^"'\s]+)/gi; let match: RegExpExecArray | null; - while ((match = filePathPattern.exec(code)) !== null) { + while ((match = execRegExp(filePathPattern, code)) !== null) { const path = match[1]!; - if (!path.includes("veryfront-http-bundle")) continue; + if (!stringIncludes(path, "veryfront-http-bundle")) continue; - if (!path.startsWith(localCacheDir)) { + if (!stringStartsWith(path, localCacheDir)) { logger.debug("Bundle has incompatible file path from different environment", { path, expectedDir: localCacheDir, From 04b5badca75c2a895b5a310617d95b4d53042cdf Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 19:04:35 +0200 Subject: [PATCH 32/34] Keep transform cache boundaries exact HTTP bundle reuse must reject sibling cache directories, and transform plugin cache identity must keep supporting public class-style plugins without invoking accessors. This change narrows file path acceptance to the normalized cache root boundary and reads only data descriptors from plugin instances and their prototype chain. Constraint: Preserve descriptor-only hardening from the audited PR head. Constraint: Current main advanced through the dev UI extraction while this PR was under review. Rejected: Reading plugin fields through normal property access | would invoke accessors from project code. Rejected: Queue previous head | GitHub reported DIRTY after main advanced. Confidence: high Scope-risk: narrow Directive: Do not widen cache path checks with raw startsWith comparisons. Tested: git diff --check origin/main...HEAD Tested: npx --yes deno@2.7.7 fmt --check scripts/lint/test-typecheck-baseline.json src/build/production-build/templates.ts src/cache/config-hash.test.ts src/cache/config-hash.ts src/embedding/rag-store.test.ts src/modules/README.md src/modules/import-map/loader-primordial-poisoning.worker.ts src/modules/import-map/loader.test.ts src/modules/import-map/loader.ts src/modules/import-map/merger.test.ts src/modules/import-map/merger.ts src/modules/import-map/preloader-primordial-poisoning.worker.ts src/modules/import-map/preloader.test.ts src/modules/import-map/preloader.ts src/platform/compat/path/basic-operations.ts src/platform/compat/path/portable.ts src/platform/compat/primordials/array.test.ts src/platform/compat/primordials/array.ts src/react/components/chat/chat/hooks/use-upload.test.tsx src/react/components/chat/chat/hooks/use-uploads-registry.test.tsx src/rendering/layouts/layout-applicator.ts src/rendering/layouts/utils/applicator.ts src/rendering/layouts/utils/component-loader.test.ts src/rendering/layouts/utils/component-loader.ts src/rendering/orchestrator/layout.test.ts src/rendering/orchestrator/layout.ts src/server/services/rsc/endpoints/rsc-bundles.generated.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/import-rewriter/url-builder.test.ts src/transforms/import-rewriter/url-builder.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/cache-identity.ts src/transforms/pipeline/index.test.ts src/transforms/pipeline/index.ts src/transforms/pipeline/stages/ssr-vf-modules/constants.ts src/transforms/pipeline/stages/ssr-vf-modules/index.ts src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts src/transforms/pipeline/stages/ssr-vf-modules/transform.ts src/transforms/pipeline/types.ts src/utils/hash-utils.test.ts src/utils/hash-utils.ts tests/integration/module-loading/import-map-loader.test.ts Tested: git diff --name-only origin/main...HEAD | rg '\.(ts|tsx|js|jsx)$' | xargs npx --yes deno@2.7.7 lint Tested: git diff --name-only origin/main...HEAD | rg '\.(ts|tsx)$' | xargs npx --yes deno@2.7.7 check Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/modules/import-map/loader.test.ts src/modules/import-map/merger.test.ts src/modules/import-map/preloader.test.ts src/platform/compat/primordials/array.test.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.test.ts src/transforms/pipeline/stages/ssr-vf-modules/transform.test.ts src/transforms/import-rewriter/url-builder.test.ts tests/integration/module-loading/import-map-loader.test.ts Not-tested: Full pre-push after rebase; src/server/handlers/request/agent-stream.handler.test.ts still fails on current main's MCP discovery expectations and has no PR delta. --- src/transforms/esm/http-cache-helpers.test.ts | 17 +++++++ src/transforms/esm/http-cache-helpers.ts | 8 ++-- .../pipeline/cache-identity.test.ts | 44 +++++++++++++++++++ src/transforms/pipeline/cache-identity.ts | 25 ++++++++--- 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index 22c3770015..a205307489 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -613,6 +613,23 @@ describe("transforms/esm/http-cache-helpers", () => { assertEquals(hasIncompatibleFilePaths(code, "/cache"), false); }); + it("requires bundle paths to stay inside the local cache directory boundary", () => { + assertEquals( + hasIncompatibleFilePaths( + 'import "file:///cache/veryfront-http-bundle/http-123.mjs";', + "/cache", + ), + false, + ); + assertEquals( + hasIncompatibleFilePaths( + 'import "file:///cache-other/veryfront-http-bundle/http-123.mjs";', + "/cache", + ), + true, + ); + }); + it("returns true when bundle paths are from different environment", () => { const code = 'import "file:///other/veryfront-http-bundle/http-123.mjs";'; assertEquals(hasIncompatibleFilePaths(code, "/cache"), true); diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 752325a9b8..6dda2af838 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -4,7 +4,7 @@ * @module transforms/esm/http-cache-helpers */ -import { isAbsolute, join } from "#veryfront/compat/path/index.ts"; +import { isAbsolute, join, normalize } from "#veryfront/compat/path/index.ts"; import { cwd } from "#veryfront/platform/compat/process.ts"; import { primordialArrayFilter as arrayFilter, @@ -561,16 +561,18 @@ export function resolveBareSpecifier( */ export function hasIncompatibleFilePaths(code: string, localCacheDir: string): boolean { const filePathPattern = /file:\/\/([^"'\s]+)/gi; + const expectedCacheRoot = normalize(localCacheDir); + const expectedCacheChildPrefix = `${expectedCacheRoot}/`; let match: RegExpExecArray | null; while ((match = execRegExp(filePathPattern, code)) !== null) { const path = match[1]!; if (!stringIncludes(path, "veryfront-http-bundle")) continue; - if (!stringStartsWith(path, localCacheDir)) { + if (path !== expectedCacheRoot && !stringStartsWith(path, expectedCacheChildPrefix)) { logger.debug("Bundle has incompatible file path from different environment", { path, - expectedDir: localCacheDir, + expectedDir: expectedCacheRoot, }); return true; } diff --git a/src/transforms/pipeline/cache-identity.test.ts b/src/transforms/pipeline/cache-identity.test.ts index 02a337494c..2a1da5ce73 100644 --- a/src/transforms/pipeline/cache-identity.test.ts +++ b/src/transforms/pipeline/cache-identity.test.ts @@ -168,6 +168,50 @@ describe("transform pipeline cache identity", () => { assertEquals(getterCalls, 0); }); + it("accepts class-style plugins with methods on the prototype", () => { + class ClassPlugin implements TransformPlugin { + name = "class-plugin"; + stage = TransformStage.FINALIZE; + cacheIdentity = "class-plugin@1"; + + transform(ctx: { code: string }): string { + return ctx.code; + } + } + + const result = getCustomPluginCacheIdentity([new ClassPlugin()]); + assertEquals(result.cacheable, true); + if (!result.cacheable) { + throw new Error("Expected a cacheable plugin identity"); + } + assertEquals(result.identity, [[ + 0, + "class-plugin", + TransformStage.FINALIZE, + "class-plugin@1", + ]]); + }); + + it("rejects accessor-backed prototype plugin fields without invoking them", () => { + let getterCalls = 0; + const plugin = Object.create({ + name: "custom", + stage: TransformStage.FINALIZE, + cacheIdentity: "custom@1", + get transform() { + getterCalls++; + return transform; + }, + }) as TransformPlugin; + + assertThrows( + () => getCustomPluginCacheIdentity([plugin]), + TypeError, + "accessor properties", + ); + assertEquals(getterCalls, 0); + }); + it("rejects control characters in plugin names used for logs and spans", () => { const plugin: TransformPlugin = { name: "custom\nforged-stage", diff --git a/src/transforms/pipeline/cache-identity.ts b/src/transforms/pipeline/cache-identity.ts index 57a997013e..6fe24e4f10 100644 --- a/src/transforms/pipeline/cache-identity.ts +++ b/src/transforms/pipeline/cache-identity.ts @@ -80,6 +80,21 @@ function readOwnDataProperty(value: object, key: PropertyKey, label: string): un return descriptor.value; } +function readPluginDataProperty(value: object, key: PropertyKey, label: string): unknown { + let current: object | null = value; + while (current !== null && current !== ObjectPrototype) { + const descriptor = ObjectGetOwnPropertyDescriptor(current, key); + if (descriptor) { + if (!hasOwn(descriptor, "value")) { + throw new IntrinsicTypeError(`${label} cannot contain accessor properties`); + } + return descriptor.value; + } + current = ObjectGetPrototypeOf(current); + } + return undefined; +} + function countIdentityString( value: string, budget: ImportMapBudget, @@ -262,15 +277,15 @@ export function getCustomPluginCacheIdentity( if (plugin === null || typeof plugin !== "object") { throw new IntrinsicTypeError(`Transform plugin at index ${index} must be an object`); } - const name = readOwnDataProperty(plugin, "name", `Transform plugin ${index}`); - const stage = readOwnDataProperty(plugin, "stage", `Transform plugin ${index}`); - const cacheIdentity = readOwnDataProperty( + const name = readPluginDataProperty(plugin, "name", `Transform plugin ${index}`); + const stage = readPluginDataProperty(plugin, "stage", `Transform plugin ${index}`); + const cacheIdentity = readPluginDataProperty( plugin, "cacheIdentity", `Transform plugin ${index}`, ); - const condition = readOwnDataProperty(plugin, "condition", `Transform plugin ${index}`); - const transform = readOwnDataProperty(plugin, "transform", `Transform plugin ${index}`); + const condition = readPluginDataProperty(plugin, "condition", `Transform plugin ${index}`); + const transform = readPluginDataProperty(plugin, "transform", `Transform plugin ${index}`); if ( typeof name !== "string" || name.length === 0 || name.length > 256 || (ReflectApply(StringPrototypeTrim, name, []) as string) !== name || From 11eca22a71f1e3b84a060cf02c91cd4e94205e4a Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 19:15:25 +0200 Subject: [PATCH 33/34] Unify SSR import-map cache identity SSR transforms were deriving the transform cache key from a direct import-map load before stages consumed the map, which bypassed the preloader's immutable request snapshot. This routes SSR cache identity through an explicit preloaded snapshot when present, otherwise through the import-map preloader when an adapter is available, and stores that same map on pipeline metadata for all SSR stages. React esm.sh normalization now treats prefixed base React URLs as self-contained React package URLs instead of externalizing react from itself. Constraint: Preserve existing direct runPipeline fallback behavior for low-level callers that do not have adapter/preload context. Rejected: Loading the import map separately in each SSR stage | keeps cache identity and executable resolution on potentially different snapshots. Rejected: Broad parser-level String.prototype poisoning regression | import extraction has a separate boundary and made the specifier resolver test exercise the wrong code. Confidence: high Scope-risk: moderate Directive: SSR transform cache identity must be derived from the same import-map snapshot stored on pipeline metadata for SSR stages. Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/transforms/esm/http-cache-helpers.test.ts src/transforms/pipeline/cache-identity.test.ts src/transforms/pipeline/index.test.ts src/transforms/esm/specifier-resolver.test.ts Tested: npx --yes deno@2.7.7 check src/transforms/pipeline/index.ts src/transforms/pipeline/types.ts src/transforms/esm/types.ts src/transforms/pipeline/index.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/transforms/pipeline/index.ts src/transforms/pipeline/types.ts src/transforms/esm/types.ts src/transforms/pipeline/index.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Tested: npx --yes deno@2.7.7 lint src/transforms/pipeline/index.ts src/transforms/pipeline/types.ts src/transforms/esm/types.ts src/transforms/pipeline/index.test.ts src/transforms/esm/http-cache-helpers.ts src/transforms/esm/http-cache-helpers.test.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts Tested: git diff --check --- src/transforms/esm/http-cache-helpers.test.ts | 47 ++++++++++++++++++ src/transforms/esm/http-cache-helpers.ts | 48 +++++++++---------- src/transforms/esm/specifier-resolver.ts | 28 ++++++++--- src/transforms/esm/types.ts | 8 ++++ src/transforms/pipeline/index.test.ts | 47 ++++++++++++++++++ src/transforms/pipeline/index.ts | 32 +++++++++++-- src/transforms/pipeline/types.ts | 9 ++++ 7 files changed, 181 insertions(+), 38 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.test.ts b/src/transforms/esm/http-cache-helpers.test.ts index a205307489..df1384f750 100644 --- a/src/transforms/esm/http-cache-helpers.test.ts +++ b/src/transforms/esm/http-cache-helpers.test.ts @@ -417,6 +417,21 @@ describe("transforms/esm/http-cache-helpers", () => { }), ); }); + + it("does not externalize prefixed base React package URLs", () => { + assertEquals( + normalizeHttpUrl("https://esm.sh/stable/react@18.3.1"), + "https://esm.sh/stable/react@18.3.1?target=es2022", + ); + assertEquals( + normalizeHttpUrl("https://esm.sh/v135/react@18.3.1"), + "https://esm.sh/v135/react@18.3.1?target=es2022", + ); + assertEquals( + normalizeHttpUrl("https://esm.sh/v135/react-dom@18.3.1/server.js"), + "https://esm.sh/v135/react-dom@18.3.1/server.js?external=react&target=es2022", + ); + }); }); describe("isHttpUrl", () => { @@ -699,5 +714,37 @@ describe("transforms/esm/http-cache-helpers", () => { const result = resolveBareSpecifier("react-dom/client", emptyImportMap); assertEquals(result.includes("react-dom"), true); }); + + it("uses captured string intrinsics after prototype poisoning", () => { + const stringPrototypeDescriptors = Object.getOwnPropertyDescriptors(String.prototype); + + try { + Object.defineProperty(String.prototype, "startsWith", { + configurable: true, + value() { + return false; + }, + writable: true, + }); + Object.defineProperty(String.prototype, "slice", { + configurable: true, + value() { + return "poisoned"; + }, + writable: true, + }); + + assertEquals(isHttpUrl("https://esm.sh/react@19"), true); + assertEquals(isExternalScheme("file:///tmp/module.js"), true); + assertEquals(isRelative("./local.js"), true); + assertEquals(isInternalBare("veryfront/runtime"), true); + assertEquals( + resolveBareSpecifier("react-dom/client", emptyImportMap, "19.1.0"), + "https://esm.sh/react-dom@19.1.0/client?external=react&target=es2022&deps=csstype@3.2.3", + ); + } finally { + Object.defineProperties(String.prototype, stringPrototypeDescriptors); + } + }); }); }); diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 6dda2af838..0d01c1776f 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -127,12 +127,6 @@ function stringStartsWith(value: string, search: string): boolean { return ReflectApply(StringStartsWith, value, [search]); } -function stripLeadingSlashes(value: string): string { - let index = 0; - while (value[index] === "/") index++; - return index === 0 ? value : stringSlice(value, index); -} - function decodeEncodedCommas(value: string): string { let decoded = ""; let index = 0; @@ -350,7 +344,7 @@ export function ensureAbsoluteDir(path: string): string { } export function isHttpUrl(specifier: string): boolean { - return specifier.startsWith("https://") || specifier.startsWith("http://"); + return stringStartsWith(specifier, "https://") || stringStartsWith(specifier, "http://"); } interface CanonicalReactEsmPackage { @@ -436,15 +430,16 @@ export function isCanonicalReactEsmUrl(rawUrl: string): boolean { } export function isExternalScheme(specifier: string): boolean { - return specifier.startsWith("node:") || - specifier.startsWith("data:") || - specifier.startsWith("file:") || - specifier.startsWith("bun:") || - specifier.startsWith("jsr:"); + return stringStartsWith(specifier, "node:") || + stringStartsWith(specifier, "data:") || + stringStartsWith(specifier, "file:") || + stringStartsWith(specifier, "bun:") || + stringStartsWith(specifier, "jsr:"); } export function isRelative(specifier: string): boolean { - return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/"); + return stringStartsWith(specifier, "./") || stringStartsWith(specifier, "../") || + stringStartsWith(specifier, "/"); } /** @@ -456,13 +451,13 @@ export function isParentHttpModule(baseUrl: string | undefined): boolean { } export function isInternalBare(specifier: string): boolean { - return specifier.startsWith("veryfront/") || - specifier.startsWith("#") || - specifier.startsWith("@std/") || - specifier.startsWith("_vf_modules/") || - specifier.startsWith("/_vf_modules/") || - specifier.startsWith("_veryfront/") || - specifier.startsWith("/_veryfront/"); + return stringStartsWith(specifier, "veryfront/") || + stringStartsWith(specifier, "#") || + stringStartsWith(specifier, "@std/") || + stringStartsWith(specifier, "_vf_modules/") || + stringStartsWith(specifier, "/_vf_modules/") || + stringStartsWith(specifier, "_veryfront/") || + stringStartsWith(specifier, "/_veryfront/"); } export function normalizeEsmShUrl(url: URL): void { @@ -478,8 +473,9 @@ export function normalizeEsmShUrl(url: URL): void { setURLSearchParam(searchParams, "target", "es2022"); } - const pathname = stripLeadingSlashes(getURLPathname(url)); - const isBaseReact = testRegExp(/^react@[\d.]+(?:\?|$)/, pathname); + const canonicalReact = parseCanonicalReactEsmPackage(stringifyURL(url)); + const isBaseReact = canonicalReact?.packageName === "react" && + canonicalReact.pathSegments.length === canonicalReact.packageIndex + 1; if (isBaseReact) return; const existing = getURLSearchParam(searchParams, "external"); @@ -530,13 +526,13 @@ export function resolveBareSpecifier( const reactMapped = reactMap[specifier]; if (reactMapped) return reactMapped; - if (specifier.startsWith("react/")) { - const subpath = specifier.slice("react/".length); + if (stringStartsWith(specifier, "react/")) { + const subpath = stringSlice(specifier, "react/".length); return `https://esm.sh/react@${reactVersion}/${subpath}?external=react&target=es2022`; } - if (specifier.startsWith("react-dom/")) { - const subpath = specifier.slice("react-dom/".length); + if (stringStartsWith(specifier, "react-dom/")) { + const subpath = stringSlice(specifier, "react-dom/".length); return `https://esm.sh/react-dom@${reactVersion}/${subpath}?external=react&target=es2022`; } diff --git a/src/transforms/esm/specifier-resolver.ts b/src/transforms/esm/specifier-resolver.ts index c69d099787..81ef3bfcd5 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -26,13 +26,25 @@ import { resolveBareSpecifier, } from "./http-cache-helpers.ts"; +const ReflectApply = Reflect.apply; +const StringSlice = String.prototype.slice; +const StringStartsWith = String.prototype.startsWith; + /** Function signature for caching an HTTP module and returning its local path. */ export type CacheHttpModuleFn = (url: string, options: CacheOptions) => Promise; +function stringSlice(value: string, start: number): string { + return ReflectApply(StringSlice, value, [start]); +} + +function stringStartsWith(value: string, search: string): boolean { + return ReflectApply(StringStartsWith, value, [search]); +} + function isLocalMappedSpecifier(specifier: string): boolean { - return specifier.startsWith("/_vf_modules/") || - specifier.startsWith("_vf_modules/") || - specifier.startsWith("file://"); + return stringStartsWith(specifier, "/_vf_modules/") || + stringStartsWith(specifier, "_vf_modules/") || + stringStartsWith(specifier, "file://"); } /** @@ -56,7 +68,9 @@ async function resolveSpecifier( // 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. - const serverOnlyCandidate = specifier.startsWith("npm:") ? specifier.slice(4) : specifier; + const serverOnlyCandidate = stringStartsWith(specifier, "npm:") + ? stringSlice(specifier, 4) + : specifier; const serverOnlyParsed = parseBarePackageSpecifier(serverOnlyCandidate); if (serverOnlyParsed && isServerOnlyPackage(serverOnlyParsed.packageName)) return null; @@ -67,8 +81,8 @@ async function resolveSpecifier( return resolveSpecifier(mapped, baseUrl, options, cacheHttpModule); } - if (specifier.startsWith("npm:")) { - const bareSpecifier = specifier.slice(4); + if (stringStartsWith(specifier, "npm:")) { + const bareSpecifier = stringSlice(specifier, 4); const cached = await cacheHttpModule(`https://esm.sh/${bareSpecifier}`, options); if (!cached) return bareSpecifier; @@ -107,7 +121,7 @@ async function resolveSpecifier( } if (isRelative(specifier)) { - if (specifier.startsWith("/_vf_modules/")) return null; + if (stringStartsWith(specifier, "/_vf_modules/")) return null; if (!baseUrl || !isHttpUrl(baseUrl)) return null; const resolved = new URL(specifier, baseUrl).toString(); diff --git a/src/transforms/esm/types.ts b/src/transforms/esm/types.ts index 3430871b36..3c884f4f3f 100644 --- a/src/transforms/esm/types.ts +++ b/src/transforms/esm/types.ts @@ -1,5 +1,7 @@ import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import type { DependencyHashCache } from "#veryfront/cache/dependency-graph.ts"; +import type { PreloadImportMapContext } from "#veryfront/modules/import-map/preloader.ts"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; import type { DependencyPinningSourceInput } from "./package-registry.ts"; import type { DependencyResolutionObservation } from "../import-rewriter/dependency-resolution.ts"; @@ -17,6 +19,12 @@ export interface TransformOptions { studioEmbed?: boolean; /** React version for transforms (from project config, defaults to DEFAULT_REACT_VERSION) */ reactVersion?: string; + /** Immutable import-map snapshot already selected for this render. */ + preloadedImportMap?: ImportMapConfig; + /** Adapter used to load and cache the project import map before SSR cache identity. */ + importMapAdapter?: RuntimeAdapter; + /** Content-source/config identity for the import-map preloader. */ + importMapPreloadContext?: PreloadImportMapContext; /** Internal per-render dependency hash cache. */ dependencyHashCache?: DependencyHashCache; /** Internal stable flag + package dependency-map key for cache isolation. */ diff --git a/src/transforms/pipeline/index.test.ts b/src/transforms/pipeline/index.test.ts index 097fb72724..032b0eb1d9 100644 --- a/src/transforms/pipeline/index.test.ts +++ b/src/transforms/pipeline/index.test.ts @@ -172,6 +172,53 @@ export default function App() { return dep; }`; } }); + it("uses one preloaded SSR import-map snapshot for cache identity and stages", async () => { + const projectDir = await makeTempDir({ prefix: "vf-pipeline-preloaded-map-" }); + const mainFile = join(projectDir, "main.ts"); + const denoJsonPath = join(projectDir, "deno.json"); + const source = `import value from "project-alias"; export default value;`; + const options = { + projectId: "preloaded-import-map-cache-project", + dev: false, + ssr: true, + preloadedImportMap: { + imports: { "project-alias": "/preloaded-v1.js" }, + scopes: {}, + }, + }; + + try { + destroyTransformCache(); + await writeTextFile(mainFile, source); + await writeTextFile( + denoJsonPath, + JSON.stringify({ imports: { "project-alias": "/disk-v2.js" } }), + ); + + const first = await runPipeline(source, mainFile, projectDir, options); + const second = await runPipeline(source, mainFile, projectDir, options); + const changed = await runPipeline(source, mainFile, projectDir, { + ...options, + preloadedImportMap: { + imports: { "project-alias": "/preloaded-v2.js" }, + scopes: {}, + }, + }); + + assertEquals(first.cached, false); + assertEquals(first.code.includes("/preloaded-v1.js"), true); + assertEquals(first.code.includes("/disk-v2.js"), false); + assertEquals(second.cached, true); + assertEquals(second.code.includes("/preloaded-v1.js"), true); + assertEquals(changed.cached, false); + assertEquals(changed.code.includes("/preloaded-v2.js"), true); + assertEquals(changed.code.includes("/preloaded-v1.js"), false); + } finally { + destroyTransformCache(); + await remove(projectDir, { recursive: true }); + } + }); + it("isolates identified custom plugin output and disables caching without an identity", async () => { const projectDir = await makeTempDir({ prefix: "vf-pipeline-custom-plugin-" }); const mainFile = join(projectDir, "main.ts"); diff --git a/src/transforms/pipeline/index.ts b/src/transforms/pipeline/index.ts index 30505f58ca..eda262e381 100644 --- a/src/transforms/pipeline/index.ts +++ b/src/transforms/pipeline/index.ts @@ -42,7 +42,9 @@ import { validateDependencyResolutionObservations, } from "../import-rewriter/dependency-resolution.ts"; import { getDependencyResolutionObservations } from "./stages/resolve-imports.ts"; -import { loadImportMap } from "#veryfront/modules/import-map/index.ts"; +import { loadImportMap, preloadImportMap } from "#veryfront/modules/import-map/index.ts"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; +import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import { computePipelineConfigIdentity, fingerprintPipelineImportMap, @@ -222,7 +224,7 @@ export function runPipeline( let importMapFingerprint: string | undefined; if (effectiveOptions.ssr) { - const importMap = await loadImportMap(projectDir); + const importMap = await resolvePipelineImportMap(projectDir, effectiveOptions); importMapFingerprint = await fingerprintPipelineImportMap(importMap); ctx.metadata.set("importMap", importMap); ctx.metadata.set("importMapFingerprint", importMapFingerprint); @@ -440,14 +442,34 @@ export async function transformToESM( ): Promise { if (filePath.endsWith(".css") || filePath.endsWith(".json")) return source; - const enrichedOptions: TransformOptions = options.readFile - ? options - : { ...options, readFile: buildReadFile(adapter, projectDir) }; + const importMapAdapter = options.importMapAdapter ?? + (adapter ? adapter as RuntimeAdapter : undefined); + const enrichedOptions: TransformOptions = { + ...options, + ...(options.readFile ? {} : { readFile: buildReadFile(adapter, projectDir) }), + ...(importMapAdapter ? { importMapAdapter } : {}), + }; const { code } = await runPipeline(source, filePath, projectDir, enrichedOptions); return code; } +async function resolvePipelineImportMap( + projectDir: string, + options: TransformOptions, +): Promise { + if (options.preloadedImportMap) return options.preloadedImportMap; + if (options.importMapAdapter) { + return await preloadImportMap( + projectDir, + options.importMapAdapter, + options.projectId, + options.importMapPreloadContext, + ); + } + return await loadImportMap(projectDir); +} + /** Extract readFile from adapter if available, for dependency hash computation. */ function extractReadFile(adapter: unknown): ((path: string) => Promise) | undefined { const a = adapter as { fs?: { readFile?: (path: string) => Promise } } | null; diff --git a/src/transforms/pipeline/types.ts b/src/transforms/pipeline/types.ts index 411ba24d12..62887b2b7f 100644 --- a/src/transforms/pipeline/types.ts +++ b/src/transforms/pipeline/types.ts @@ -6,6 +6,9 @@ */ import type { DependencyHashCache } from "#veryfront/cache/dependency-graph.ts"; +import type { PreloadImportMapContext } from "#veryfront/modules/import-map/preloader.ts"; +import type { ImportMapConfig } from "#veryfront/modules/import-map/types.ts"; +import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; import type { TransformProgressListener } from "#veryfront/transforms/progress.ts"; import type { DependencyPinningSourceInput } from "../esm/package-registry.ts"; import type { DependencyResolutionObservation } from "../import-rewriter/dependency-resolution.ts"; @@ -62,6 +65,12 @@ export interface TransformOptions { studioEmbed?: boolean; /** React version to use (detected from project package.json if not provided) */ reactVersion?: string; + /** Immutable import-map snapshot already selected for this render. */ + preloadedImportMap?: ImportMapConfig; + /** Adapter used to load and cache the project import map before SSR cache identity. */ + importMapAdapter?: RuntimeAdapter; + /** Content-source/config identity for the import-map preloader. */ + importMapPreloadContext?: PreloadImportMapContext; /** File reader for dependency hash computation. When provided, enables dependency-aware cache invalidation. */ readFile?: (path: string) => Promise; /** Internal per-render dependency hash cache. */ From 97e33736167d9e3468d3211bd8930de649dab6bf Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Mon, 3 Aug 2026 19:13:26 +0200 Subject: [PATCH 34/34] Keep HTTP specifier routing stable after prefix poisoning The import-map hardening branch already captures most cache-identity primordials, but specifier routing still depended on live string prefix and slice methods after module parsing. A hosted project mutating String.prototype.startsWith after import could misroute npm and helper classification paths. Constraint: PR #3308 must remain robust after project code mutates shared built-ins in the same realm Rejected: Limit the fix to http-cache-helpers | specifier-resolver and server-only package classification still run on the same routing path Confidence: high Scope-risk: narrow Directive: Keep specifier classification on captured intrinsics when it can run after tenant code Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/transforms/esm/specifier-resolver.test.ts src/transforms/esm/http-cache-helpers.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts src/transforms/shared/server-only-packages.ts Tested: npx --yes deno@2.7.7 lint src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts src/transforms/shared/server-only-packages.ts Tested: npx --yes deno@2.7.7 check src/transforms/esm/http-cache-helpers.ts src/transforms/esm/specifier-resolver.ts src/transforms/esm/specifier-resolver.test.ts src/transforms/shared/server-only-packages.ts Tested: git diff --check Not-tested: Full repository test suite --- src/transforms/esm/http-cache-helpers.ts | 7 +++--- src/transforms/esm/specifier-resolver.test.ts | 22 +++++++++++++++++++ src/transforms/esm/specifier-resolver.ts | 12 +++++----- src/transforms/shared/server-only-packages.ts | 22 +++++++++++++++++-- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/transforms/esm/http-cache-helpers.ts b/src/transforms/esm/http-cache-helpers.ts index 0d01c1776f..e315ecc47c 100644 --- a/src/transforms/esm/http-cache-helpers.ts +++ b/src/transforms/esm/http-cache-helpers.ts @@ -115,8 +115,8 @@ function stringReplace(value: string, search: string, replacement: string): stri return ReflectApply(StringReplace, value, [search, replacement]); } -function stringSlice(value: string, start: number): string { - return ReflectApply(StringSlice, value, [start]); +function stringSlice(value: string, start: number, end?: number): string { + return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]); } function stringSplit(value: string, separator: string): string[] { @@ -438,7 +438,8 @@ export function isExternalScheme(specifier: string): boolean { } export function isRelative(specifier: string): boolean { - return stringStartsWith(specifier, "./") || stringStartsWith(specifier, "../") || + return stringStartsWith(specifier, "./") || + stringStartsWith(specifier, "../") || stringStartsWith(specifier, "/"); } diff --git a/src/transforms/esm/specifier-resolver.test.ts b/src/transforms/esm/specifier-resolver.test.ts index 0ae9a5b057..6680e29aed 100644 --- a/src/transforms/esm/specifier-resolver.test.ts +++ b/src/transforms/esm/specifier-resolver.test.ts @@ -93,6 +93,28 @@ describe("transforms/esm/specifier-resolver", () => { assertEquals(result.replacements.get("npm:react@18"), "react@18"); }); + it("resolves npm: specifiers after String prefix poisoning", async () => { + const stringPrototypeDescriptors = Object.getOwnPropertyDescriptors(String.prototype); + + try { + Object.defineProperty(String.prototype, "startsWith", { + configurable: true, + value() { + throw new Error("poisoned String.prototype.startsWith"); + }, + writable: true, + }); + + const code = `import React from "npm:react@18";`; + const result = await buildReplacements(code, undefined, defaultOptions, async () => { + return "/tmp/cache/http-12345.mjs"; + }); + assertEquals(result.replacements.get("npm:react@18"), "file:///tmp/cache/http-12345.mjs"); + } finally { + Object.defineProperties(String.prototype, stringPrototypeDescriptors); + } + }); + 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 81ef3bfcd5..279859a035 100644 --- a/src/transforms/esm/specifier-resolver.ts +++ b/src/transforms/esm/specifier-resolver.ts @@ -30,17 +30,17 @@ const ReflectApply = Reflect.apply; const StringSlice = String.prototype.slice; const StringStartsWith = String.prototype.startsWith; -/** Function signature for caching an HTTP module and returning its local path. */ -export type CacheHttpModuleFn = (url: string, options: CacheOptions) => Promise; - -function stringSlice(value: string, start: number): string { - return ReflectApply(StringSlice, value, [start]); +function stringSlice(value: string, start: number, end?: number): string { + return ReflectApply(StringSlice, value, end === undefined ? [start] : [start, end]) as string; } function stringStartsWith(value: string, search: string): boolean { - return ReflectApply(StringStartsWith, value, [search]); + return ReflectApply(StringStartsWith, value, [search]) as boolean; } +/** Function signature for caching an HTTP module and returning its local path. */ +export type CacheHttpModuleFn = (url: string, options: CacheOptions) => Promise; + function isLocalMappedSpecifier(specifier: string): boolean { return stringStartsWith(specifier, "/_vf_modules/") || stringStartsWith(specifier, "_vf_modules/") || diff --git a/src/transforms/shared/server-only-packages.ts b/src/transforms/shared/server-only-packages.ts index ffe25d504f..e14ef57c35 100644 --- a/src/transforms/shared/server-only-packages.ts +++ b/src/transforms/shared/server-only-packages.ts @@ -32,6 +32,22 @@ const SERVER_ONLY_PACKAGES: ReadonlySet = new Set([ "oracledb", "cassandra-driver", ]); +const ReflectApply = Reflect.apply; +const SetHas = Set.prototype.has; +const StringSlice = String.prototype.slice; +const StringStartsWith = String.prototype.startsWith; + +function setHas(set: ReadonlySet, value: T): boolean { + return ReflectApply(SetHas, set, [value]) as boolean; +} + +function stringSlice(value: string, start: number): string { + return ReflectApply(StringSlice, value, [start]) as string; +} + +function stringStartsWith(value: string, search: string): boolean { + return ReflectApply(StringStartsWith, value, [search]) as boolean; +} /** * True if a bare package specifier's package name is a known server-only @@ -42,8 +58,10 @@ const SERVER_ONLY_PACKAGES: ReadonlySet = new Set([ * before matching so both `redis` and `npm:redis@5.11.0` are recognized. */ export function isServerOnlyPackage(packageName: string): boolean { - const bare = packageName.startsWith("npm:") ? packageName.slice("npm:".length) : packageName; - return SERVER_ONLY_PACKAGES.has(bare); + const bare = stringStartsWith(packageName, "npm:") + ? stringSlice(packageName, "npm:".length) + : packageName; + return setHas(SERVER_ONLY_PACKAGES, bare); } export { SERVER_ONLY_PACKAGES };