1import * as NodeServices from "@effect/platform-node/NodeServices";
2import * as Effect from "effect/Effect";
3import * as FileSystem from "effect/FileSystem";
4import * as Path from "effect/Path";
5import { describe, expect, it } from "vitest";
6import { buildVinextPrerenderKVPairs } from "../PrerenderCache.ts";
7
8const fixture = Effect.gen(function* () {
9 const fs = yield* FileSystem.FileSystem;
10 const path = yield* Path.Path;
11 const root = yield* fs.realPath(
12 path.resolve(
13 import.meta.dirname,
14 "../../../../../examples/cloudflare-website-vinext/node_modules/vinext",
15 ),
16 );
17 const server = yield* fs.makeTempDirectoryScoped({
18 prefix: "vinext-prerender-",
19 });
20 const build = Effect.tryPromise(() =>
21 buildVinextPrerenderKVPairs(root, server),
22 );
23 return { fs, path, server, build };
24});
25
26const run = <A, E>(
27 effect: Effect.Effect<
28 A,
29 E,
30 FileSystem.FileSystem | Path.Path | import("effect/Scope").Scope
31 >,
32) =>
33 Effect.runPromise(
34 effect.pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
35 );
36
37describe("vinext prerender cache", () => {
38 it("returns no seeds for missing manifests or prerender output", () =>
39 run(
40 Effect.gen(function* () {
41 const { fs, path, server, build } = yield* fixture;
42 expect(yield* build).toEqual({
43 routeCount: 0,
44 pairs: [],
45 warnings: [],
46 });
47 yield* fs.writeFileString(
48 path.join(server, "vinext-prerender.json"),
49 JSON.stringify({ buildId: "build", routes: [] }),
50 );
51 expect(yield* build).toEqual({
52 routeCount: 0,
53 pairs: [],
54 warnings: [],
55 });
56 }),
57 ));
58
59 it("encodes HTML, RSC, and metadata with the runtime cache codec", () =>
60 run(
61 Effect.gen(function* () {
62 const { fs, path, server, build } = yield* fixture;
63 const output = path.join(server, "prerendered-routes");
64 yield* fs.makeDirectory(output);
65 yield* fs.writeFileString(
66 path.join(output, "index.html"),
67 "<h1>prerendered</h1>",
68 );
69 yield* fs.writeFileString(
70 path.join(output, "index.rsc"),
71 "rsc payload",
72 );
73 yield* fs.writeFileString(
74 path.join(server, "vinext-prerender.json"),
75 JSON.stringify({
76 buildId: "build",
77 routes: [
78 {
79 route: "/",
80 router: "app",
81 status: "rendered",
82 revalidate: 60,
83 expire: 120,
84 stale: 30,
85 tags: ["home"],
86 },
87 ],
88 }),
89 );
90 const result = yield* build;
91 expect(result.routeCount).toBe(1);
92 expect(result.warnings).toEqual([]);
93 expect(result.pairs).toHaveLength(2);
94 for (const pair of result.pairs) {
95 const entry = JSON.parse(pair.value);
96 expect(entry.value.kind).toBe("APP_PAGE");
97 expect(entry.tags).toContain("home");
98 expect(entry.cacheControl).toEqual({
99 revalidate: 60,
100 expire: 120,
101 stale: 30,
102 });
103 expect(entry.revalidateAt - entry.lastModified).toBe(60_000);
104 expect(entry.expireAt - entry.lastModified).toBe(120_000);
105 expect(pair.expirationTtl).toBe(30 * 24 * 3600);
106 }
107 expect(JSON.parse(result.pairs[0]!.value).value.html).toBe(
108 "<h1>prerendered</h1>",
109 );
110 expect(JSON.parse(result.pairs[1]!.value).value.rscData).toBe(
111 "cnNjIHBheWxvYWQ=",
112 );
113 }),
114 ));
115});