Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
311f7cf
fix(security): close the destructured server-value leak and fail clos…
kojiwakayama Aug 17, 2026
9a23d93
fix(build): ignore intra-pattern liveness reads
kojiwakayama Aug 17, 2026
15208fc
fix(security): fail closed on reassigned hook bindings and verify the…
kojiwakayama Aug 17, 2026
9a88a21
fix(build): ignore shadowed client bindings in strip liveness
kojiwakayama Aug 17, 2026
b38fd7b
fix(build): make stripped import liveness scope-aware
kojiwakayama Aug 17, 2026
0a26945
fix(build): bind class names during import liveness
kojiwakayama Aug 17, 2026
4de2aa4
fix(security): fail closed on hoisted var redeclarations of hook bind…
kojiwakayama Aug 17, 2026
0417603
fix(transforms): model TypeScript and static-block scopes
kojiwakayama Aug 17, 2026
09ad671
fix(transforms): retain decorator imports in parameter properties
kojiwakayama Aug 17, 2026
68cdb72
fix(transforms): model switch lexical scope
kojiwakayama Aug 17, 2026
41823c1
fix(transforms): compute strip liveness as reachability from survivin…
kojiwakayama Aug 17, 2026
4297844
test(transforms): pin the half-dead repeated var fail-closed path
kojiwakayama Aug 17, 2026
c991de8
fix(transforms): track runtime TypeScript bindings
kojiwakayama Aug 17, 2026
2aa3fda
test(transforms): cover reference-only syntax
kojiwakayama Aug 17, 2026
dc571b8
fix(transforms): model decorator and namespace scopes
kojiwakayama Aug 17, 2026
a7dfa3b
fix(transforms): stop dead code from pinning the hooks' closure
kojiwakayama Aug 17, 2026
a8b2770
fix(transforms): read decorators on ordinary parameters
kojiwakayama Aug 17, 2026
b95f439
fix(transforms): retain ordinary parameter decorators
kojiwakayama Aug 17, 2026
3a56737
fix(transforms): preserve shared client initializers
kojiwakayama Aug 17, 2026
1f8b805
fix(transforms): model private elements and parameter decorators
kojiwakayama Aug 17, 2026
4fede09
fix(transforms): model auto-accessor properties
kojiwakayama Aug 17, 2026
0079166
fix(transforms): separate what a declaration evaluates from what it r…
kojiwakayama Aug 17, 2026
a346934
fix(transforms): classify JSX tag references
kojiwakayama Aug 17, 2026
b48049b
fix(transforms): preserve class heritage evaluation
kojiwakayama Aug 17, 2026
dbd48ff
fix(transforms): preserve mixed hoisted initializers
kojiwakayama Aug 17, 2026
7b7161a
fix(transforms): defer callback initialization reads
kojiwakayama Aug 17, 2026
85a1efd
test(transforms): pin module binding reachability
kojiwakayama Aug 17, 2026
4e234d3
fix(transforms): root the names a surviving export clause publishes
kojiwakayama Aug 17, 2026
6cdbd2d
fix(transforms): recognize call and apply IIFEs
kojiwakayama Aug 17, 2026
95f29b1
fix(transforms): preserve raw client roots
kojiwakayama Aug 17, 2026
8654195
fix(transforms): root default export expressions
kojiwakayama Aug 17, 2026
9162148
fix(build): preserve analyzer lexical context
kojiwakayama Aug 17, 2026
7fc14e1
fix(build): reject dynamic name helper keys
kojiwakayama Aug 17, 2026
fd2341d
fix: tighten compiler name helper detection
kojiwakayama Aug 17, 2026
fb0c8c6
fix: reject mutated name helper intrinsics
kojiwakayama Aug 17, 2026
1831933
fix: reject shadowed name helper intrinsics
kojiwakayama Aug 17, 2026
a8fa844
fix(transforms): guard compiler name helper recognition
kojiwakayama Aug 17, 2026
90851f6
fix: reject escaped intrinsics and hoisted helper rebinds
kojiwakayama Aug 17, 2026
4e7a886
fix: reject indirect intrinsic writes
kojiwakayama Aug 17, 2026
e1d6acc
fix(transforms): preserve unrelated defineProperty writes
kojiwakayama Aug 17, 2026
24673f4
fix(transforms): reject global Object replacement
kojiwakayama Aug 17, 2026
40167d5
fix: reject global object replacement paths
kojiwakayama Aug 17, 2026
f3294cc
fix(transforms): resolve intrinsic writes by scope
kojiwakayama Aug 17, 2026
9c8771a
fix(transforms): resolve global intrinsic aliases by scope
kojiwakayama Aug 17, 2026
fdf5bec
fix: resolve browser global object aliases
kojiwakayama Aug 17, 2026
63fe5f1
fix(transforms): cover transparent browser globals
kojiwakayama Aug 17, 2026
cc3ec40
fix(transforms): recognise compiler name metadata by allowlist
kojiwakayama Aug 17, 2026
d23da54
test(transforms): pin the shapes the allowlist must still admit
kojiwakayama Aug 17, 2026
10c6f8b
test(server): update dev dashboard error catalog counts
kojiwakayama Aug 17, 2026
1be3d6b
fix(transforms): resolve helper mutations lexically
kojiwakayama Aug 17, 2026
c98006e
fix(transforms): preserve exported helper rebindings
kojiwakayama Aug 17, 2026
1cbcae9
fix(transforms): close intrinsic mutation aliases
kojiwakayama Aug 17, 2026
36430c6
fix(transforms): reject unreadable descriptor maps on the intrinsic
kojiwakayama Aug 17, 2026
e63feba
fix(transforms): track intrinsic routes lexically
kojiwakayama Aug 17, 2026
b422181
fix(transforms): resolve var aliases and nested invocation wrappers
kojiwakayama Aug 17, 2026
6049e3c
fix(transforms): close intrinsic invocation gaps
kojiwakayama Aug 17, 2026
1d6ef35
fix(transforms): follow invoked intrinsic routes
kojiwakayama Aug 17, 2026
60ab605
fix(transforms): cover destructured intrinsic routes
kojiwakayama Aug 18, 2026
8a966bd
docs(data): keep hook guidance self-contained
kojiwakayama Aug 18, 2026
4f88f0b
test(errors): cover server export remediation
kojiwakayama Aug 18, 2026
dfac8e7
fix(transforms): follow intrinsic iterator flows
kojiwakayama Aug 18, 2026
f8fe5be
fix(transforms): narrow intrinsic reflection routes
kojiwakayama Aug 18, 2026
8659020
fix(transforms): close reflection iterator review gaps
kojiwakayama Aug 18, 2026
2b56419
fix(transforms): follow class and destructured routes
kojiwakayama Aug 18, 2026
ac2155b
fix(transforms): follow remaining dynamic routes
kojiwakayama Aug 18, 2026
cc089a1
fix(transforms): follow method factories and pattern defaults
kojiwakayama Aug 18, 2026
bfdfb4e
fix(transforms): tighten callable value flow
kojiwakayama Aug 18, 2026
9651e9d
fix(transforms): follow callable owner aliases
kojiwakayama Aug 18, 2026
3da6e9e
fix(transforms): track member owner flow by occurrence
kojiwakayama Aug 18, 2026
e14bc69
fix(transforms): account for hoisted function owners
kojiwakayama Aug 18, 2026
9dd4cdc
fix(transforms): preserve execution-scope flow order
kojiwakayama Aug 18, 2026
578208c
fix(transforms): resolve active member values
kojiwakayama Aug 18, 2026
d085250
fix(transforms): retain non-direct assignment flows
kojiwakayama Aug 18, 2026
a398a80
fix(transforms): preserve structured owner flows
kojiwakayama Aug 18, 2026
e597cf7
fix(transforms): preserve mixed computed key flows
kojiwakayama Aug 18, 2026
2a9e3d7
fix(transforms): bound incomplete member flows
kojiwakayama Aug 18, 2026
ca026f3
fix(transforms): model computed member flows
kojiwakayama Aug 18, 2026
ff6fb95
fix(transforms): guard recursive value traversal
kojiwakayama Aug 18, 2026
919f16f
fix(transforms): preserve uncertain member lookup
kojiwakayama Aug 18, 2026
d4efcce
fix(transforms): bound computed key resolution
kojiwakayama Aug 18, 2026
991e309
fix(security): fail closed on unprovable compiler name registrations
kojiwakayama Aug 18, 2026
8832879
Merge origin/main into refactor/issue-112-esbuild-server-hook-dce
kojiwakayama Aug 18, 2026
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
1 change: 1 addition & 0 deletions docs/api-reference/veryfront/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ throw INVALID_WIDGET.create({ detail: "The widget id is malformed." });
| `SECURITY_VIOLATION` | Path traversal / secure-fs violations (replaces SecurityError) | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/general.ts#L77) |
| `SEMAPHORE_TIMEOUT` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L60) |
| `SERVER_ERROR_CATALOG` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/catalog/server-errors.ts#L4) |
| `SERVER_EXPORT_STRIP_FAILED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/build.ts#L75) |
| `SERVER_ONLY_IN_CLIENT` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/boundary.ts#L11) |
| `SERVER_START_ERROR` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L12) |
| `SERVICE_OVERLOADED` | | [source](https://github.com/veryfront/veryfront-code/blob/main/src/errors/error-registry/server.ts#L44) |
Expand Down
87 changes: 87 additions & 0 deletions docs/guides/data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,93 @@ entirely, including their top-level side effects. Put client initialization in a
separate client-referenced module or a bare side-effect import that is not only
used by a server data hook.

### Declare server data hooks directly

Veryfront must find a local declaration for each server data export so it can
empty it before the module reaches the browser. Declare the hook in the route
module as a function declaration or as an initializer on a `const`, `let`, or
`var`:

```tsx
// Supported
export async function getServerData(ctx: DataContext) {
return { props: { query: ctx.query.toString() } };
}

// Also supported
export const getStaticData = async () => ({ props: { generated: true } });
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

These forms have no declaration to empty and fail the build with
`server-export-strip-failed`:

```tsx
// Not supported: the hook is a re-exported import
import { loadIt } from "./loader.ts";
export { loadIt as getServerData };

// Not supported: the hook is a class
export class getServerData {}
```

Move the import inside a directly declared hook to migrate:

```tsx
export async function getServerData(ctx: DataContext) {
const { loadIt } = await import("./loader.ts");
return loadIt(ctx);
}
```

The same build error reports a value that only a stripped hook reads when that
value is declared in a position Veryfront cannot remove, such as a loop head:

```tsx
// Not supported: the binding is declared by the loop, not at module scope
for (var KEY of getEnv("SECRET_KEY")) {}

// Supported
const KEY = getEnv("SECRET_KEY");
```

### Modules that rewrite the Object intrinsic

Compiled input carries name registrations such as `__name(loadUser, "loadUser")`.
Veryfront reads them as build metadata, which is what lets it see that
`loadUser` is read only by a stripped hook and remove it along with the server
import and the secret behind it.

A module that rewrites `Object.defineProperty`, or reaches it through
`.constructor`, `__proto__`, `eval`, or `Function`, makes that reading
unprovable. Veryfront must not delete a call the module can observe, and it must
not emit a module that still holds a server-only binding, so the build fails
with `server-export-strip-failed`.

```tsx
// Not supported: the module rebinds Object, so the name registration
// cannot be proven to be compiler metadata
const Object = globalThis.Object;

export async function getServerData() {
return { props: { user: await loadUser() } };
}
```

Move the code that reaches or rewrites the intrinsic into a module that exports
no server data hook, then import what you need from it:

```tsx
import { isPlainObject } from "../lib/is-plain-object.ts";

export async function getServerData() {
return { props: { user: await loadUser() } };
}
```

Ordinary client code that reads `.constructor` or `__proto__` on a value, such
as an `isPlainObject` helper or `error.constructor.name` logging, does not
trigger this failure.

The `props` you return are passed to the page component. To read the same props
data from a layout or nested component without prop-drilling, use
`usePageContext().data` (see
Expand Down
7 changes: 7 additions & 0 deletions docs/guides/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ Compilation failed.
- **HTTP status:** 500
- **What to do:** Review compiler output for specific errors

### server-export-strip-failed

Server-only export cannot be removed from the client build.

- **HTTP status:** 500
- **What to do:** Declare the hook directly in the route module and keep its values module scope

## Runtime

Raised while executing project code.
Expand Down
28 changes: 26 additions & 2 deletions src/errors/catalog/build-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe("errors/catalog/build-errors", () => {
"ssg-generation-error",
"sourcemap-error",
"compilation-error",
"server-export-strip-failed",
];

for (const slug of expectedSlugs) {
Expand All @@ -38,8 +39,8 @@ describe("errors/catalog/build-errors", () => {
}
});

it("should have 9 entries", () => {
assertEquals(Object.keys(BUILD_ERROR_CATALOG).length, 9);
it("should have 10 entries", () => {
assertEquals(Object.keys(BUILD_ERROR_CATALOG).length, 10);
});

it("build-failed should have tips", () => {
Expand All @@ -52,5 +53,28 @@ describe("errors/catalog/build-errors", () => {
const solution = BUILD_ERROR_CATALOG["mdx-compile-error"]!;
assertEquals(typeof solution.example, "string");
});

it("documents server export stripping remediation", () => {
const solution = BUILD_ERROR_CATALOG["server-export-strip-failed"]!;
assertEquals(
solution.title,
"Server-only export cannot be removed from the client build",
);
assertEquals(solution.message.includes("getServerData"), true);
assertEquals(
solution.steps?.includes(
"Declare the hook directly as a function declaration or a const, let, or var declaration",
),
true,
);
assertEquals(
solution.steps?.includes(
"Keep a browser-needed value in a client-referenced module before importing it into the hook",
),
true,
);
assertEquals(solution.example?.includes("export { loadIt as getServerData };"), true);
assertEquals(solution.example?.includes("export async function getServerData(ctx)"), true);
});
});
});
32 changes: 32 additions & 0 deletions src/errors/catalog/build-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,36 @@ title: My Post
"Verify TypeScript configuration",
],
),

"server-export-strip-failed": createErrorSolution("server-export-strip-failed", {
title: "Server-only export cannot be removed from the client build",
message:
"A route module exports getServerData, getStaticData, or getStaticPaths in a form the " +
"client build cannot empty. Emitting the module would send the loader, its imports, and " +
"the values it reads to the browser, so the build stops instead.",
steps: [
"Declare the hook directly as a function declaration or a const, let, or var declaration",
"Replace a re-export such as `export { loadIt as getServerData }` with a direct declaration",
"Replace a class or an alias export of the hook with an exported async function",
"Declare any value the hook reads once, at module scope, not inside a loop head",
"Keep a browser-needed value in a client-referenced module before importing it into the hook",
"Move code that rewrites or reaches the `Object` intrinsic into a module with no server data hook",
],
tips: [
"The error message names the export and the declaration form that blocked the removal",
"A hook declared directly is stripped from the client bundle with everything only it read",
"A module that rewrites `Object.defineProperty`, or reaches it through `.constructor`, " +
"`__proto__`, `eval` or `Function`, stops the build from proving which name registrations " +
"the compiler emitted, so a server-only binding one of them names cannot be removed",
],
example: `// Not supported: no local declaration to empty
import { loadIt } from "./loader.ts";
export { loadIt as getServerData };

// Supported
export async function getServerData(ctx) {
const { loadIt } = await import("./loader.ts");
return loadIt(ctx);
}`,
}),
});
8 changes: 4 additions & 4 deletions src/errors/error-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ describe("error-registry", () => {
assertEquals(slugs.length, uniqueSlugs.size, "Duplicate slugs detected");
});

it("should have 113 registered errors", () => {
it("should have 114 registered errors", () => {
const slugs = getAllSlugs();
assertEquals(slugs.length, 113);
assertEquals(slugs.length, 114);
});
});

Expand Down Expand Up @@ -180,7 +180,7 @@ describe("error-registry", () => {

it("should return BUILD errors", () => {
const errors = getErrorsByCategory("BUILD");
assertEquals(errors.length, 9);
assertEquals(errors.length, 10);
for (const error of errors) {
assertEquals(error.category, "BUILD");
}
Expand Down Expand Up @@ -322,7 +322,7 @@ describe("error-registry", () => {
describe("error categories coverage", () => {
const expectedCategoryCounts: Record<string, number> = {
CONFIG: 12,
BUILD: 9,
BUILD: 10,
RUNTIME: 11,
ROUTE: 6,
MODULE: 8,
Expand Down
9 changes: 9 additions & 0 deletions src/errors/error-registry/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ export const COMPILATION_ERROR = defineError({
suggestion: "Review compiler output for specific errors",
});

export const SERVER_EXPORT_STRIP_FAILED = defineError({
slug: "server-export-strip-failed",
category: "BUILD",
status: 500,
title: "Server-only export cannot be removed from the client build",
suggestion: "Declare the hook directly in the route module and keep its values module scope",
});

/** Registry fragment for BUILD errors (slug → definition). */
export const BUILD_REGISTRY = {
"build-failed": BUILD_FAILED,
Expand All @@ -83,4 +91,5 @@ export const BUILD_REGISTRY = {
"ssg-generation-error": SSG_GENERATION_ERROR,
"sourcemap-error": SOURCEMAP_ERROR,
"compilation-error": COMPILATION_ERROR,
"server-export-strip-failed": SERVER_EXPORT_STRIP_FAILED,
} as const;
1 change: 1 addition & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export {
SCHEDULE_CONFIG_INVALID,
SECURITY_VIOLATION,
SEMAPHORE_TIMEOUT,
SERVER_EXPORT_STRIP_FAILED,
SERVER_ONLY_IN_CLIENT,
SERVER_START_ERROR,
SERVICE_OVERLOADED,
Expand Down
4 changes: 2 additions & 2 deletions src/server/handlers/dev/dashboard/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,10 @@ describe("Dashboard API - GET endpoints", () => {
assertEquals("errors" in body, true);
assertEquals("categories" in body, true);
assertEquals("count" in body, true);
assertEquals(body.count, 66);
assertEquals(body.count, 67);
assertEquals(body.categories, {
config: 7,
build: 9,
build: 10,
runtime: 7,
route: 6,
server: 8,
Expand Down
Loading