Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "veryfront",
"version": "0.1.1126",
"version": "0.1.1127",
"license": "Apache-2.0",
Comment thread
kojiwakayama marked this conversation as resolved.
"nodeModulesDir": "auto",
"minimumDependencyAge": {
Expand Down
9 changes: 5 additions & 4 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/api-reference/veryfront/index.client.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "veryfront/index.client"
description: "Client and SSR safe mirror of the `veryfront` root barrel. Use this import surface in code that can run in a browser bundle. It exposes the same browser-safe helpers as `veryfront` while omitting server bootstrap functions such as `createHandler`, `startServer`, and `toNodeHandler`."
description: "Browser- and SSR-safe helpers from the `veryfront` package. This entrypoint exposes the root package's client-safe configuration, platform, routing, data, and security helpers without server bootstrap functions. Most app code can import these helpers from `veryfront`; use this explicit entrypoint when a browser or SSR module needs to declare that boundary directly."
order: 12
---

Expand Down
1 change: 1 addition & 0 deletions extensions/ext-observability-opentelemetry/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@1.43.0",
"@std/assert": "jsr:@std/assert@1.0.19",
"@std/testing/bdd": "jsr:@std/testing@1.0.17/bdd",
"protobufjs": "npm:protobufjs@7.6.5",
"veryfront/extensions": "../../src/extensions/index.ts",
"veryfront/extensions/observability": "../../src/extensions/observability/index.ts",
"veryfront/utils": "../../src/utils/index.ts"
Expand Down
2 changes: 2 additions & 0 deletions scripts/storybook/storybook-workbench.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ describe("Storybook UI workbench", () => {
assertStringIncludes(main, "createVeryfrontAliases");
assertStringIncludes(aliases, "veryfront\\/chat");
assertStringIncludes(aliases, "#veryfront");
assertStringIncludes(aliases, "#deno-config");
assertStringIncludes(aliases, "deno.json");
assertStringIncludes(
aliases,
"storybook/.storybook/shims/veryfront-utils.ts",
Expand Down
27 changes: 6 additions & 21 deletions src/index.client.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
/**
* Client/SSR-safe mirror of the `veryfront` root barrel ({@link file://./index.ts}).
* Browser- and SSR-safe helpers from the `veryfront` package.
*
* The root barrel re-exports the server bootstrap surface (`createHandler`,
* `startServer`, `toNodeHandler`) from `#veryfront/server`. Because the browser
* and SSR pipelines transform modules per-file (no cross-module tree-shaking),
* an ESM re-export eagerly loads its source module, so pulling the root barrel
* into a client chunk drags the entire server graph in, including
* `src/server/production-server.ts`, which has module top-level `await` and
* cannot be transformed to the es2020 browser target (HTTP 500 on that chunk,
* which aborts hydration).
*
* A client-reachable module doing a used value import from the barrel, for
* example `import { getEnv } from "veryfront"`, is not dead-stripped, so it
* keeps the barrel and the leak. This barrel exposes exactly the same
* browser-safe surface minus the server bootstrap functions, which no
* client/SSR page code legitimately calls. The import rewriter redirects
* `veryfront` to this module for the `browser` and `ssr` targets (see
* `src/transforms/import-rewriter/strategies/veryfront-strategy.ts`), the same
* mechanism `veryfront/workflow` already uses.
*
* Keep the exports below in sync with {@link file://./index.ts}: everything
* except the `createHandler` / `startServer` / `toNodeHandler` value exports.
* This entrypoint exposes the root package's client-safe configuration,
* platform, routing, data, and security helpers without server bootstrap
* functions. Most app code can import these helpers from `veryfront`; use this
* explicit entrypoint when a browser or SSR module needs to declare that
* boundary directly.
*
* @example
* ```ts
Expand Down
4 changes: 2 additions & 2 deletions src/proxy/routing-invalidation-redis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe("proxy routing invalidation Redis bus", () => {
redisUrl: "redis://example.test:6379",
expectedReplicas: 2,
replicaId: "replica-a",
acknowledgementTimeoutMs: 10,
acknowledgementTimeoutMs: 100,
createClient: redis.createClient,
integritySecret,
onInvalidate: () => {},
Expand All @@ -176,7 +176,7 @@ describe("proxy routing invalidation Redis bus", () => {
redisUrl: "redis://example.test:6379",
expectedReplicas: 2,
replicaId: "replica-b",
acknowledgementTimeoutMs: 10,
acknowledgementTimeoutMs: 100,
createClient: redis.createClient,
integritySecret,
onInvalidate: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { JSDOM } from "npm:jsdom@28.0.0";
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 { InlineCitation } from "./inline-citation.tsx";

function installDomGlobals(dom: JSDOM): () => void {
Expand Down Expand Up @@ -74,10 +75,10 @@ describe("InlineCitation", () => {
assert(trigger.className.includes("vf-citation-trigger"));

trigger.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
await new Promise((resolve) => setTimeout(resolve, 170));
flushSync(() => {
// Flush the hover timer's state update before inspecting the DOM.
});
await waitFor(
() => document.querySelector(".vf-citation-card") !== null,
{ interval: 10, message: "Citation card did not render after hover" },
);

const card = document.querySelector(".vf-citation-card");
assert(card, "Expected citation card to render after hover");
Expand Down
6 changes: 5 additions & 1 deletion src/react/runtime/router-provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { renderToStaticMarkup } from "react-dom/server";
import { JSDOM } from "npm:jsdom@28.0.0";
import { assertEquals, assertStringIncludes } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { waitFor } from "#veryfront/testing/deno-compat.ts";
import {
PageContextProvider,
type PageContextValue,
Expand Down Expand Up @@ -395,7 +396,10 @@ describe("react/runtime/RouterProvider (reactive)", () => {
</RouterProvider>,
);
});
await tick();
await waitFor(
() => (rootElement.textContent ?? "").includes("m:true"),
{ interval: 10, message: "RouterProvider did not finish its client mount" },
);
assertStringIncludes(rootElement.textContent ?? "", "m:true");
root.unmount();
} finally {
Expand Down
23 changes: 23 additions & 0 deletions src/server/services/rsc/endpoints/handler-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ describe("server/services/rsc/endpoints/handler-registry", () => {
__destroyRSCHandlerForTests();
});

it("does not inspect project dependencies until a handler is used", async () => {
const cache = createStubCache();
__injectCacheForTests(cache);
const originalStat = Deno.stat;
const projectDir = "/project/lazy-react-version";
let statCalls = 0;

Deno.stat = (path: string | URL) => {
if (String(path).includes(projectDir)) statCalls++;
return originalStat(path);
};

try {
getRSCHandler(projectDir);
await Deno.stat(".");
await new Promise((resolve) => setTimeout(resolve, 0));

assertEquals(statCalls, 0);
} finally {
Deno.stat = originalStat;
}
});

describe("invalidateRSCHandlersForProject", () => {
it("evicts every handler variant for only the changed project", () => {
const cache = createStubCache();
Expand Down
22 changes: 14 additions & 8 deletions src/server/services/rsc/orchestrators/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class RSCDevServerHandler {
private renderer: RSCRenderer | null = null;
private clientManifest: Map<string, ClientComponentMeta> | null = null;
private rendererPromise: Promise<void> | null = null;
private reactVersionPromise: Promise<string> | null = null;
private invalidationGeneration = 0;

private readonly manifestHandler: ManifestHandler;
Expand All @@ -53,10 +54,6 @@ export class RSCDevServerHandler {
fs: options.adapter?.fs,
contentSourceId,
});
this.reactVersionPromise = resolveProjectReactVersion({
projectDir,
config: options.config,
});
this.renderHandler = new RenderHandler(
projectDir,
() => this.renderer,
Expand All @@ -67,21 +64,22 @@ export class RSCDevServerHandler {
projectId: options.projectId,
projectSlug: options.projectSlug,
contentSourceId,
reactVersion: this.reactVersionPromise,
reactVersion: () => this.getReactVersion(),
},
);
this.streamHandler = new StreamHandler(this.renderHandler);
this.appDir = appDir;
this.isLocalProject = isLocalProject;
this.mode = mode;
this.fs = options.adapter?.fs;
this.config = options.config;
}

private readonly appDir: string;
private readonly isLocalProject: boolean;
private readonly mode: "development" | "production";
private readonly reactVersionPromise: Promise<string>;
private readonly fs?: RuntimeAdapter["fs"];
private readonly config?: VeryfrontConfig;

handleManifest(): Promise<Response> {
return this.manifestHandler.handle(this.clientManifest);
Expand Down Expand Up @@ -109,7 +107,7 @@ export class RSCDevServerHandler {
if (!this.pageHandler) {
this.pageHandler = new PageHandler(
this.mode === "development",
await this.reactVersionPromise,
await this.getReactVersion(),
this.isLocalProject ? "fs" : "rsc-module",
);
}
Expand Down Expand Up @@ -140,7 +138,7 @@ export class RSCDevServerHandler {

private async initializeRenderer(generation: number): Promise<void> {
const clientManifest = await buildClientManifest(this.projectDir, this.appDir, this.fs);
const reactVersion = await this.reactVersionPromise;
const reactVersion = await this.getReactVersion();
if (generation !== this.invalidationGeneration) return;

this.clientManifest = clientManifest;
Expand All @@ -153,4 +151,12 @@ export class RSCDevServerHandler {
reactVersion,
});
}

private getReactVersion(): Promise<string> {
this.reactVersionPromise ??= resolveProjectReactVersion({
projectDir: this.projectDir,
config: this.config,
});
return this.reactVersionPromise;
}
}
6 changes: 3 additions & 3 deletions src/server/services/rsc/orchestrators/render-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface RenderHandlerModuleOptions {
projectId?: string;
projectSlug?: string;
contentSourceId?: string;
reactVersion?: Promise<string>;
reactVersion?: () => Promise<string>;
}

const logger = serverLogger.component("rsc");
Expand Down Expand Up @@ -115,7 +115,7 @@ export class RenderHandler {
this.projectDir,
this.moduleOptions.projectSlug,
this.moduleOptions.contentSourceId,
await this.moduleOptions.reactVersion,
await this.moduleOptions.reactVersion?.(),
) as Record<string, unknown>;
}

Expand All @@ -130,7 +130,7 @@ export class RenderHandler {
contentSourceId: this.moduleOptions.contentSourceId,
dev: this.mode === "development",
mode: this.mode === "development" ? "preview" : "production",
reactVersion: await this.moduleOptions.reactVersion,
reactVersion: await this.moduleOptions.reactVersion?.(),
});
}

Expand Down
Loading