From fa63757beebacc26218d7dd9c8bec8c7ba9563ec Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Fri, 4 Sep 2026 07:14:42 -0700 Subject: [PATCH 1/3] perf(server): cache and stream static web assets (#9669) (cherry picked from commit 27e6cc27fe0f3cff53a44905e40615b7db99c80c) --- apps/server/src/http.ts | 146 +++++++++++++--- apps/server/src/server.test.ts | 304 +++++++++++++++++++++++++++++++++ apps/web/vite.config.ts | 1 + 3 files changed, 429 insertions(+), 22 deletions(-) diff --git a/apps/server/src/http.ts b/apps/server/src/http.ts index e20477c582..e9f22f9edf 100644 --- a/apps/server/src/http.ts +++ b/apps/server/src/http.ts @@ -12,6 +12,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import { cast } from "effect/Function"; import { @@ -431,10 +432,66 @@ export const attachmentUploadRouteLayer = HttpRouter.add( }), ); -export const staticAndDevRouteLayer = HttpRouter.add( - "GET", - "*", - Effect.gen(function* () { +const decodeBuildManifest = Schema.decodeUnknownEffect( + Schema.fromJsonString( + Schema.Record( + Schema.String, + Schema.Struct({ + file: Schema.String, + css: Schema.optional(Schema.Array(Schema.String)), + assets: Schema.optional(Schema.Array(Schema.String)), + }), + ), + ), +); + +const loadImmutableBuildAssets = Effect.gen(function* () { + const config = yield* ServerConfig.ServerConfig; + const staticDir = + config.staticDir ?? (config.devUrl ? yield* ServerConfig.resolveStaticDir() : undefined); + if (!staticDir) return new Set(); + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + return yield* fileSystem.readFileString(path.join(staticDir, ".vite", "manifest.json")).pipe( + Effect.flatMap(decodeBuildManifest), + Effect.map( + (manifest) => + new Set( + Object.values(manifest).flatMap((entry) => [ + entry.file, + ...(entry.css ?? []), + ...(entry.assets ?? []), + ]), + ), + ), + Effect.orElseSucceed(() => new Set()), + ); +}); + +const openStaticFile = Effect.fn("openStaticFile")(function* (filePath: string) { + const fileSystem = yield* FileSystem.FileSystem; + // Reject directories and special files before opening. Response metadata comes from the handle. + const pathInfo = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null)); + if (pathInfo?.type !== "File") return null; + const file = yield* fileSystem.open(filePath, { flag: "r" }); + const info = yield* file.stat; + return info.type === "File" ? { file, info } : null; +}); + +const streamStaticFile = (file: FileSystem.File, size: bigint) => + Stream.unfold( + 0n, + Effect.fnUntraced(function* (offset: bigint) { + if (offset >= size) return; + const remaining = size - offset; + const bytes = yield* file.readAlloc(remaining < 65_536n ? remaining : 65_536n); + if (Option.isNone(bytes)) return; + return [bytes.value, offset + BigInt(bytes.value.byteLength)] as const; + }), + ); + +const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( + function* (immutableBuildAssets: ReadonlySet) { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); @@ -461,7 +518,6 @@ export const staticAndDevRouteLayer = HttpRouter.add( }); } - const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const staticRoot = path.resolve(staticDir); const staticRequestPath = url.value.pathname === "/" ? "/index.html" : url.value.pathname; @@ -495,30 +551,76 @@ export const staticAndDevRouteLayer = HttpRouter.add( } } - const fileInfo = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null)); - if (!fileInfo || fileInfo.type !== "File") { - const indexPath = path.resolve(staticRoot, "index.html"); - const indexData = yield* fileSystem - .readFile(indexPath) - .pipe(Effect.orElseSucceed(() => null)); - if (!indexData) { + let opened = yield* openStaticFile(filePath); + if (!opened) { + filePath = path.resolve(staticRoot, "index.html"); + opened = yield* openStaticFile(filePath); + if (!opened) { return HttpServerResponse.text("Not Found", { status: 404 }); } - return HttpServerResponse.uint8Array(indexData, { - status: 200, - contentType: "text/html; charset=utf-8", - }); + } + const fileInfo = opened.info; + + // A hash-like name is not enough: custom static files can use the same naming pattern. + const relativePath = path.relative(staticRoot, filePath).replaceAll("\\", "/"); + const immutable = + /^assets\/.+-[\w-]{8}\.[^/]+$/.test(relativePath) && immutableBuildAssets.has(relativePath); + const headers: Record = { + "Cache-Control": immutable ? "public, max-age=31536000, immutable" : "no-cache", + }; + const modifiedAt = Option.getOrUndefined(fileInfo.mtime); + const etag = modifiedAt + ? `W/"${fileInfo.size.toString(16)}-${modifiedAt.getTime().toString(16)}"` + : undefined; + if (etag !== undefined && modifiedAt !== undefined) { + headers.ETag = etag; + headers["Last-Modified"] = modifiedAt.toUTCString(); } - const contentType = Mime.getType(filePath) ?? "application/octet-stream"; - const data = yield* fileSystem.readFile(filePath).pipe(Effect.orElseSucceed(() => null)); - if (!data) { - return HttpServerResponse.text("Internal Server Error", { status: 500 }); + // If-None-Match takes precedence over dates and uses weak comparison for + // GET/HEAD, including when compression changes the transferred bytes. + const ifNoneMatch = request.headers["if-none-match"]; + const ifModifiedSince = request.headers["if-modified-since"]; + const unchanged = + ifNoneMatch !== undefined + ? ifNoneMatch.split(",").some((value) => { + const candidate = value.trim(); + return ( + candidate === "*" || + (etag !== undefined && candidate.replace(/^W\//i, "") === etag.slice(2)) + ); + }) + : ifModifiedSince !== undefined && + modifiedAt !== undefined && + Date.parse(modifiedAt.toUTCString()) <= Date.parse(ifModifiedSince); + if (unchanged) { + return HttpServerResponse.empty({ + status: 304, + headers: { ...headers, Vary: "Accept-Encoding" }, + }); } - return HttpServerResponse.uint8Array(data, { - status: 200, + const contentType = + path.extname(filePath) === ".html" + ? "text/html; charset=utf-8" + : (Mime.getType(filePath) ?? "application/octet-stream"); + // The request scope closes the handle for GET, HEAD, 304, errors, and cancellation. + // HEAD still passes through compression, which selects headers without reading the stream. + return HttpServerResponse.stream(streamStaticFile(opened.file, fileInfo.size), { + headers, contentType, + contentLength: Number(fileInfo.size), }); + }, + Effect.catchTags({ + PlatformError: () => + Effect.succeed(HttpServerResponse.text("Internal Server Error", { status: 500 })), }), ); + +// Read the installed build's manifest once. Unknown files use revalidation. +export const staticAndDevRouteLayer = Layer.unwrap( + loadImmutableBuildAssets.pipe( + Effect.map((assets) => HttpRouter.add("GET", "*", handleStaticAndDevRequest(assets))), + ), +); diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index abf9323803..7d34832065 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -1628,6 +1628,310 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("revalidates static files without sending unchanged bodies", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-cache-" }); + const indexPath = path.join(staticDir, "index.html"); + yield* fileSystem.writeFileString(indexPath, "first build"); + yield* buildAppUnderTest({ config: { staticDir } }); + + const initial = yield* HttpClient.get("/"); + assert.equal(initial.status, 200); + assert.equal(initial.headers["cache-control"], "no-cache"); + assert.include(yield* initial.text, "first build"); + const etag = initial.headers.etag; + assert.isDefined(etag); + assert.isDefined(initial.headers["last-modified"]); + + for (const headers of [ + { "if-none-match": etag! }, + { "if-none-match": `"older", ${etag!.replace(/^W\//, "")}` }, + { "if-none-match": "*" }, + { "if-modified-since": initial.headers["last-modified"]! }, + ]) { + const response = yield* HttpClient.get("/", { headers }); + assert.equal(response.status, 304); + assert.equal(response.headers.etag, etag); + assert.equal(response.headers["cache-control"], "no-cache"); + assert.equal(yield* response.text, ""); + } + + const mismatched = yield* HttpClient.get("/", { + headers: { + "if-none-match": '"another-build"', + "if-modified-since": initial.headers["last-modified"]!, + }, + }); + assert.equal(mismatched.status, 200); + assert.include(yield* mismatched.text, "first build"); + + yield* fileSystem.writeFileString(indexPath, "the next build is available"); + const changed = yield* HttpClient.get("/", { headers: { "if-none-match": etag! } }); + assert.equal(changed.status, 200); + assert.notEqual(changed.headers.etag, etag); + assert.include(yield* changed.text, "next build"); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + + it.effect("caches hashed static assets without freezing mutable files or SPA fallbacks", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-hashes-" }); + yield* fileSystem.makeDirectory(path.join(staticDir, "assets")); + yield* fileSystem.makeDirectory(path.join(staticDir, ".vite")); + yield* fileSystem.writeFileString( + path.join(staticDir, ".vite", "manifest.json"), + `{ + "index.html": { "file": "assets/index-AbCd0123.js", "isEntry": true }, + "large.js": { "file": "assets/large-aBcD9876.js" } + }`, + ); + yield* fileSystem.writeFileString(path.join(staticDir, "index.html"), "app"); + yield* fileSystem.writeFileString( + path.join(staticDir, "assets", "index-AbCd0123.js"), + "export const app = true;", + ); + yield* fileSystem.writeFileString(path.join(staticDir, "assets", "config.json"), "{}"); + const largeAsset = "export const value = 123;\n".repeat(8192); + yield* fileSystem.writeFileString( + path.join(staticDir, "assets", "large-aBcD9876.js"), + largeAsset, + ); + yield* buildAppUnderTest({ config: { staticDir } }); + + const asset = yield* HttpClient.get("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/assets/index-AbCd0123.js"); + assert.equal(asset.status, 200); + assert.equal(asset.headers["cache-control"], "public, max-age=31536000, immutable"); + assert.equal(yield* asset.text, "export const app = true;"); + + const head = yield* HttpClient.head("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/assets/index-AbCd0123.js", { + headers: { "accept-encoding": "identity" }, + }); + assert.equal(head.status, 200); + assert.equal(head.headers.etag, asset.headers.etag); + assert.equal(head.headers["content-length"], String("export const app = true;".length)); + assert.equal(yield* head.text, ""); + + const compressed = yield* HttpClient.get("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/assets/large-aBcD9876.js", { + headers: { "accept-encoding": "gzip" }, + }); + assert.equal(compressed.headers["content-encoding"], "gzip"); + assert.equal(compressed.headers.vary, "Accept-Encoding"); + assert.equal(yield* compressed.text, largeAsset); + const compressedHead = yield* HttpClient.head("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/assets/large-aBcD9876.js", { + headers: { "accept-encoding": "gzip" }, + }); + assert.equal(compressedHead.status, 200); + assert.equal(compressedHead.headers["content-encoding"], "gzip"); + assert.equal(compressedHead.headers.vary, "Accept-Encoding"); + assert.equal(compressedHead.headers.etag, compressed.headers.etag); + assert.equal(compressedHead.headers["content-length"], compressed.headers["content-length"]); + assert.equal(yield* compressedHead.text, ""); + const unchanged = yield* HttpClient.get("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/assets/large-aBcD9876.js", { + headers: { "accept-encoding": "identity", "if-none-match": compressed.headers.etag! }, + }); + assert.equal(unchanged.status, 304); + assert.equal(unchanged.headers.vary, "Accept-Encoding"); + assert.equal(yield* unchanged.text, ""); + + for (const resource of [ + "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/assets/config.json", + "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/threads/example", + "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/assets/old-ZyXw9876.js", + ]) { + const response = yield* HttpClient.get(resource); + assert.equal(response.status, 200); + assert.equal(response.headers["cache-control"], "no-cache"); + assert.equal( + yield* response.text, + resource.endsWith("config.json") ? "{}" : "app", + ); + } + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + + for (const manifest of [ + { label: "missing", contents: null }, + { label: "nonmatching", contents: '{"other.js":{"file":"assets/other-AbCd0123.js"}}' }, + { label: "malformed", contents: "{not-json" }, + ]) { + it.effect(`revalidates hash-like static filenames with a ${manifest.label} manifest`, () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-static-mutable-", + }); + yield* fileSystem.makeDirectory(path.join(staticDir, "assets")); + if (manifest.contents !== null) { + yield* fileSystem.makeDirectory(path.join(staticDir, ".vite")); + yield* fileSystem.writeFileString( + path.join(staticDir, ".vite", "manifest.json"), + manifest.contents, + ); + } + const filePath = path.join(staticDir, "assets", "config-20260904.js"); + yield* fileSystem.writeFileString(filePath, "first config"); + yield* buildAppUnderTest({ config: { staticDir } }); + + const initial = yield* HttpClient.get("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/assets/config-20260904.js"); + assert.equal(initial.headers["cache-control"], "no-cache"); + assert.equal(yield* initial.text, "first config"); + + yield* fileSystem.writeFileString(filePath, "replacement config"); + const changed = yield* HttpClient.get("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/assets/config-20260904.js", { + headers: { "if-none-match": initial.headers.etag! }, + }); + assert.equal(changed.status, 200); + assert.equal(changed.headers["cache-control"], "no-cache"); + assert.notEqual(changed.headers.etag, initial.headers.etag); + assert.equal(yield* changed.text, "replacement config"); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + } + + it.effect("binds static metadata and bytes to one file across atomic replacement", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-replace-" }); + const beforeOpenPath = path.join(staticDir, "before-open.txt"); + const afterOpenPath = path.join(staticDir, "after-open.txt"); + const original = "original bytes"; + const replacement = "replacement bytes with a different size"; + for (const filePath of [beforeOpenPath, afterOpenPath]) { + yield* fileSystem.writeFileString(filePath, original); + yield* fileSystem.writeFileString(`${filePath}.next`, replacement); + } + const replaced = new Set(); + const replaceOnce = Effect.fnUntraced(function* (filePath: string) { + if (replaced.has(filePath)) return; + replaced.add(filePath); + yield* fileSystem.rename(`${filePath}.next`, filePath); + }); + const replacingFileSystem = FileSystem.FileSystem.of({ + ...fileSystem, + stat: (filePath) => + fileSystem + .stat(filePath) + .pipe( + Effect.tap(() => (filePath === beforeOpenPath ? replaceOnce(filePath) : Effect.void)), + ), + open: (filePath, options) => + fileSystem + .open(filePath, options) + .pipe( + Effect.tap(() => (filePath === afterOpenPath ? replaceOnce(filePath) : Effect.void)), + ), + }); + yield* buildAppUnderTest({ config: { staticDir } }).pipe( + Effect.provideService(FileSystem.FileSystem, replacingFileSystem), + ); + + for (const [name, expected] of [ + ["before-open.txt", replacement], + ["after-open.txt", original], + ] as const) { + const response = yield* HttpClient.get(`/${name}`, { + headers: { "accept-encoding": "identity" }, + }); + assert.equal(response.status, 200); + assert.equal(response.headers["content-length"], String(expected.length)); + assert.isTrue(response.headers.etag?.startsWith(`W/"${expected.length.toString(16)}-`)); + assert.equal(yield* response.text, expected); + assert.isTrue(replaced.has(path.join(staticDir, name))); + assert.equal(yield* fileSystem.readFileString(path.join(staticDir, name)), replacement); + } + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + + it.effect("closes static file handles after GET, HEAD, 304, and request cancellation", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-close-" }); + const filePath = path.join(staticDir, "index.html"); + const body = "

file content

".repeat(1024); + yield* fileSystem.writeFileString(filePath, body); + const closed = yield* Queue.unbounded(); + const blocked = yield* Deferred.make(); + const active = new Set(); + let blockAfterOpen = false; + let bodyReads = 0; + const trackedFileSystem = FileSystem.FileSystem.of({ + ...fileSystem, + open: (candidate, options) => + Effect.gen(function* () { + if (candidate !== filePath) return yield* fileSystem.open(candidate, options); + let opened: FileSystem.File | undefined; + // Registered first, so this signal runs after the real descriptor-close finalizer. + yield* Effect.addFinalizer(() => + Effect.gen(function* () { + if (opened === undefined) return; + active.delete(opened); + yield* Queue.offer(closed, opened); + }), + ); + const file = yield* fileSystem.open(candidate, options); + opened = file; + active.add(file); + if (blockAfterOpen) { + yield* Deferred.succeed(blocked, undefined); + return yield* Effect.never; + } + return new Proxy(file, { + get(target, key) { + if (key === "readAlloc") { + return (size: FileSystem.SizeInput) => { + bodyReads += 1; + return target.readAlloc(size); + }; + } + return Reflect.get(target, key, target); + }, + }); + }), + }); + yield* buildAppUnderTest({ config: { staticDir } }).pipe( + Effect.provideService(FileSystem.FileSystem, trackedFileSystem), + ); + + const get = yield* HttpClient.get("/"); + assert.equal(yield* get.text, body); + yield* Queue.take(closed); + assert.equal(active.size, 0); + assert.isAbove(bodyReads, 0); + const readsAfterGet = bodyReads; + + const head = yield* HttpClient.head("/", { headers: { "accept-encoding": "gzip" } }); + assert.equal(head.status, 200); + assert.equal(head.headers["content-encoding"], "gzip"); + assert.equal(yield* head.text, ""); + yield* Queue.take(closed); + assert.equal(active.size, 0); + assert.equal(bodyReads, readsAfterGet); + + const unchanged = yield* HttpClient.get("/", { + headers: { "if-none-match": get.headers.etag! }, + }); + assert.equal(unchanged.status, 304); + yield* Queue.take(closed); + assert.equal(active.size, 0); + assert.equal(bodyReads, readsAfterGet); + + blockAfterOpen = true; + const cancelled = yield* HttpClient.get("/").pipe(Effect.forkChild); + yield* Deferred.await(blocked); + assert.equal(active.size, 1); + yield* Fiber.interrupt(cancelled); + yield* Queue.take(closed); + assert.equal(active.size, 0); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("redirects to dev URL when configured", () => Effect.gen(function* () { yield* buildAppUnderTest({ diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index d2b4e9c4f6..5f8ac8d903 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -279,6 +279,7 @@ export default defineConfig(() => { build: { outDir: "dist", emptyOutDir: true, + manifest: true, sourcemap: buildSourcemap, }, test: { From b58c61826d5d54a27ff418bc82944add6b14a6c3 Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Sun, 6 Sep 2026 12:59:57 -0600 Subject: [PATCH 2/3] fix(server): always refresh HTML across static deployments Selectively ports the HTML validator correction from pingdotgg/t3code commit ce4712d5b04fb998f79fe132245289191147e5d5 (#9799). Restores the Queue test import required after Pylon cleanup. --- apps/server/src/http.ts | 16 ++++---- apps/server/src/server.test.ts | 71 +++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/apps/server/src/http.ts b/apps/server/src/http.ts index e9f22f9edf..d893f50070 100644 --- a/apps/server/src/http.ts +++ b/apps/server/src/http.ts @@ -560,15 +560,20 @@ const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( } } const fileInfo = opened.info; + const mimeType = Mime.getType(filePath) ?? "application/octet-stream"; + const isHtml = mimeType === "text/html"; // A hash-like name is not enough: custom static files can use the same naming pattern. const relativePath = path.relative(staticRoot, filePath).replaceAll("\\", "/"); const immutable = - /^assets\/.+-[\w-]{8}\.[^/]+$/.test(relativePath) && immutableBuildAssets.has(relativePath); + !isHtml && + /^assets\/.+-[\w-]{8}\.[^/]+$/.test(relativePath) && + immutableBuildAssets.has(relativePath); const headers: Record = { "Cache-Control": immutable ? "public, max-age=31536000, immutable" : "no-cache", }; - const modifiedAt = Option.getOrUndefined(fileInfo.mtime); + // Deployments can preserve HTML size and mtime while changing its bundle URLs. + const modifiedAt = isHtml ? undefined : Option.getOrUndefined(fileInfo.mtime); const etag = modifiedAt ? `W/"${fileInfo.size.toString(16)}-${modifiedAt.getTime().toString(16)}"` : undefined; @@ -593,17 +598,14 @@ const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( : ifModifiedSince !== undefined && modifiedAt !== undefined && Date.parse(modifiedAt.toUTCString()) <= Date.parse(ifModifiedSince); - if (unchanged) { + if (!isHtml && unchanged) { return HttpServerResponse.empty({ status: 304, headers: { ...headers, Vary: "Accept-Encoding" }, }); } - const contentType = - path.extname(filePath) === ".html" - ? "text/html; charset=utf-8" - : (Mime.getType(filePath) ?? "application/octet-stream"); + const contentType = isHtml ? "text/html; charset=utf-8" : mimeType; // The request scope closes the handle for GET, HEAD, 304, errors, and cancellation. // HEAD still passes through compression, which selects headers without reading the stream. return HttpServerResponse.stream(streamStaticFile(opened.file, fileInfo.size), { diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 7d34832065..1e755644a0 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -62,6 +62,7 @@ import * as ManagedRuntime from "effect/ManagedRuntime"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as PubSub from "effect/PubSub"; +import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; @@ -1633,14 +1634,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-cache-" }); - const indexPath = path.join(staticDir, "index.html"); - yield* fileSystem.writeFileString(indexPath, "first build"); + const assetPath = path.join(staticDir, "app.js"); + yield* fileSystem.writeFileString(assetPath, 'export const build = "first";'); yield* buildAppUnderTest({ config: { staticDir } }); - const initial = yield* HttpClient.get("/"); + const initial = yield* HttpClient.get("/app.js"); assert.equal(initial.status, 200); assert.equal(initial.headers["cache-control"], "no-cache"); - assert.include(yield* initial.text, "first build"); + assert.include(yield* initial.text, "first"); const etag = initial.headers.etag; assert.isDefined(etag); assert.isDefined(initial.headers["last-modified"]); @@ -1651,30 +1652,70 @@ it.layer(NodeServices.layer)("server router seam", (it) => { { "if-none-match": "*" }, { "if-modified-since": initial.headers["last-modified"]! }, ]) { - const response = yield* HttpClient.get("/", { headers }); + const response = yield* HttpClient.get("/app.js", { headers }); assert.equal(response.status, 304); assert.equal(response.headers.etag, etag); assert.equal(response.headers["cache-control"], "no-cache"); assert.equal(yield* response.text, ""); } - const mismatched = yield* HttpClient.get("/", { + const mismatched = yield* HttpClient.get("/app.js", { headers: { "if-none-match": '"another-build"', "if-modified-since": initial.headers["last-modified"]!, }, }); assert.equal(mismatched.status, 200); - assert.include(yield* mismatched.text, "first build"); + assert.include(yield* mismatched.text, "first"); - yield* fileSystem.writeFileString(indexPath, "the next build is available"); - const changed = yield* HttpClient.get("/", { headers: { "if-none-match": etag! } }); + yield* fileSystem.writeFileString(assetPath, 'export const build = "the next build";'); + const changed = yield* HttpClient.get("/app.js", { headers: { "if-none-match": etag! } }); assert.equal(changed.status, 200); assert.notEqual(changed.headers.etag, etag); assert.include(yield* changed.text, "next build"); }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("serves changed HTML with the same size and timestamp", () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-html-" }); + const indexPath = path.join(staticDir, "index.html"); + const modifiedAt = DateTime.toDateUtc(DateTime.makeUnsafe("1985-10-26T08:15:00.000Z")); + yield* fileSystem.writeFileString(indexPath, "old build"); + yield* fileSystem.utimes(indexPath, modifiedAt, modifiedAt); + yield* buildAppUnderTest({ config: { staticDir } }); + + const initial = yield* HttpClient.get("/"); + assert.equal(yield* initial.text, "old build"); + const previousEtag = initial.headers.etag ?? '"previous-html"'; + const nextHtml = "new build"; + yield* fileSystem.writeFileString(indexPath, nextHtml); + yield* fileSystem.utimes(indexPath, modifiedAt, modifiedAt); + + for (const [resource, headers] of [ + ["/", { "if-none-match": previousEtag }], + ["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/threads/example", { "if-modified-since": modifiedAt.toUTCString() }], + ["/", { "if-none-match": "*" }], + ] as const) { + const response = yield* HttpClient.get(resource, { headers }); + assert.equal(response.status, 200); + assert.equal(yield* response.text, nextHtml); + assert.equal(response.headers["cache-control"], "no-cache"); + assert.isUndefined(response.headers.etag); + assert.isUndefined(response.headers["last-modified"]); + } + + const head = yield* HttpClient.head("/", { + headers: { "if-none-match": previousEtag, "accept-encoding": "identity" }, + }); + assert.equal(head.status, 200); + assert.equal(head.headers["content-length"], String(Buffer.byteLength(nextHtml))); + assert.equal(yield* head.text, ""); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("caches hashed static assets without freezing mutable files or SPA fallbacks", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; @@ -1853,8 +1894,8 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const staticDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-static-close-" }); - const filePath = path.join(staticDir, "index.html"); - const body = "

file content

".repeat(1024); + const filePath = path.join(staticDir, "app.txt"); + const body = "file content\n".repeat(1024); yield* fileSystem.writeFileString(filePath, body); const closed = yield* Queue.unbounded(); const blocked = yield* Deferred.make(); @@ -1899,14 +1940,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { Effect.provideService(FileSystem.FileSystem, trackedFileSystem), ); - const get = yield* HttpClient.get("/"); + const get = yield* HttpClient.get("/app.txt"); assert.equal(yield* get.text, body); yield* Queue.take(closed); assert.equal(active.size, 0); assert.isAbove(bodyReads, 0); const readsAfterGet = bodyReads; - const head = yield* HttpClient.head("/", { headers: { "accept-encoding": "gzip" } }); + const head = yield* HttpClient.head("/app.txt", { headers: { "accept-encoding": "gzip" } }); assert.equal(head.status, 200); assert.equal(head.headers["content-encoding"], "gzip"); assert.equal(yield* head.text, ""); @@ -1914,7 +1955,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { assert.equal(active.size, 0); assert.equal(bodyReads, readsAfterGet); - const unchanged = yield* HttpClient.get("/", { + const unchanged = yield* HttpClient.get("/app.txt", { headers: { "if-none-match": get.headers.etag! }, }); assert.equal(unchanged.status, 304); @@ -1923,7 +1964,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { assert.equal(bodyReads, readsAfterGet); blockAfterOpen = true; - const cancelled = yield* HttpClient.get("/").pipe(Effect.forkChild); + const cancelled = yield* HttpClient.get("/app.txt").pipe(Effect.forkChild); yield* Deferred.await(blocked); assert.equal(active.size, 1); yield* Fiber.interrupt(cancelled); From 07cd53056d7b052aa8c91e8c537b39a1f6dc5b18 Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Sun, 6 Sep 2026 13:01:57 -0600 Subject: [PATCH 3/3] fix(server): integrate static caching with current Pylon streams --- .agents/upstream-review.md | 8 ++++++++ apps/server/src/server.test.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.agents/upstream-review.md b/.agents/upstream-review.md index 58cfa6c1de..da0d780d0e 100644 --- a/.agents/upstream-review.md +++ b/.agents/upstream-review.md @@ -15,6 +15,14 @@ Two standing sections outlive any single batch and must be read on every review: ## Review batches +## 2026-09-06 — stream and cache static responses without stale HTML (partial source) + +Adopts upstream #9669, `27e6cc27fe0f3cff53a44905e40615b7db99c80c`, plus the HTML validator correction from #9799, `ce4712d5b04fb998f79fe132245289191147e5d5`, under the maintainer's standing approval. Complete source patches, PR descriptions, and relevant reviews were read. #9799 is one source commit shared with the separately adopted live-stream fix; its remaining mobile, highlighting, animation, marketing, and CI concerns are not included. The full review cursor remains unchanged. + +Static responses stream bounded chunks from one request-scoped handle, using that same handle for metadata. Immutable caching requires both a hashed asset name and membership in the Vite build manifest. Mutable assets can revalidate; HTML always returns the current shell without size/mtime validators, including SPA fallback and conditional HEAD requests. This preserves Pylon authentication, routing, compression, desktop identity, and remote origins. Hosted clients use their hosting layer; the bundled and standalone Pylon servers share this route. No mobile native dependency changes. + +Verification: the same-size/same-timestamp HTML regression failed with a stale 304 before the correction. All 174 server routing tests pass after integration, including atomic file replacement, GET/HEAD/304/cancellation handle cleanup, manifest lookalikes, authentication, and live streams. Server and web typechecks, targeted lint, formatting, and diff checks pass. Implementation branch: `upstream/2026-09-06-static-response-caching` based on `origin/pylon` at `1e7ffff246bf6ad4c31236e6b242a91d5c2275b0`. + ## 2026-09-06 — project live updates before applying buffer limits (partial source) Adopted the live-stream portion of upstream #9799, `ce4712d5b04fb998f79fe132245289191147e5d5`, under the maintainer's standing approval for compatible fixes. The complete upstream patch, PR description, and review context were read. This port changes only the live budget/coalescer, shell stream metadata, and focused WebSocket regression tests. The upstream HTML, mobile outbox, highlighting, animation, marketing, and CI changes are separate concerns and are not claimed as adopted here. The full range cursor remains unchanged. diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 1e755644a0..bf43938a05 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -62,7 +62,6 @@ import * as ManagedRuntime from "effect/ManagedRuntime"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as PubSub from "effect/PubSub"; -import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema";