Skip to content

Commit

Permalink
feat(cache): cache event handler stream response
Browse files Browse the repository at this point in the history
  • Loading branch information
kerwanp committed Dec 12, 2024
1 parent f982f99 commit 789bb93
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 1 deletion.
46 changes: 45 additions & 1 deletion src/runtime/internal/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
fetchWithEvent,
handleCacheHeaders,
isEvent,
isStream,
splitCookiesString,
} from "h3";
import type { EventHandlerRequest, EventHandlerResponse, H3Event } from "h3";
Expand Down Expand Up @@ -67,6 +68,8 @@ export function defineCachedFunction<T, ArgsT extends unknown[] = any[]>(
useNitroApp().captureError(error, { event, tags: ["cache"] });
})) as unknown) || {};

entry.key = cacheKey;

// https://github.com/nitrojs/nitro/issues/2160
if (typeof entry !== "object") {
entry = {};
Expand Down Expand Up @@ -272,6 +275,32 @@ export function defineCachedEventHandler<
}
return true;
},
// transform(entry) {
// if (!entry.value?.body) return entry.value;
//
// if (!(entry.value.body instanceof ReadableStream)) return entry.value;
//
// const [stream1, stream2] = entry.value.body.tee();
//
// const td = new TextDecoder();
// let res = "";
// stream2
// .pipeTo(
// new WritableStream({
// write: (chunk) => {
// res += td.decode(chunk);
// },
// })
// )
// .then(() => {
// const en = entry as any;
// en.value.body = res;
// useStorage().setItem(entry.key!, entry);
// });
//
// entry.value.body = stream1 as any;
// return entry.value;
// },
group: opts.group || "nitro/handlers",
integrity: opts.integrity || hash([handler, opts]),
};
Expand Down Expand Up @@ -406,11 +435,26 @@ export function defineCachedEventHandler<
headers["cache-control"] = cacheControl.join(", ");
}

let cachedBody = body as any;
// When handler response is a stream, we cache the result of this stream
if (body instanceof ReadableStream) {
const td = new TextDecoder();
let buffer = "";
await body.pipeTo(
new WritableStream({
write(chunk) {
buffer += td.decode(chunk);
},
})
);
cachedBody = buffer;
}

// Create cache entry for response
const cacheEntry: ResponseCacheEntry<Response> = {
code: event.node.res.statusCode,
headers,
body,
body: cachedBody,
};

return cacheEntry;
Expand Down
1 change: 1 addition & 0 deletions test/fixture/nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default defineNitroConfig({
"/rules/_/cached/noncached": { cache: false, swr: false, isr: false },
"/rules/_/cached/**": { swr: true },
"/api/proxy/**": { proxy: "/api/echo" },
"/stream-cached": { swr: true },
},
prerender: {
crawlLinks: true,
Expand Down
13 changes: 13 additions & 0 deletions test/fixture/routes/stream-cached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default eventHandler(() => {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("nitro"));
controller.enqueue(encoder.encode("is"));
controller.enqueue(encoder.encode("awesome,"));
controller.enqueue(encoder.encode(Date.now().toString()));
controller.close();
},
});
return stream;
});
1 change: 1 addition & 0 deletions test/presets/netlify-legacy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe("nitro:preset:netlify-legacy", async () => {
/rules/isr-ttl/* /.netlify/builders/server 200
/rules/isr/* /.netlify/builders/server 200
/rules/dynamic /.netlify/functions/server 200
/stream-cached /.netlify/builders/server 200
/build/* /build/:splat 200
/* /.netlify/functions/server 200"
`);
Expand Down
4 changes: 4 additions & 0 deletions test/presets/vercel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ describe("nitro:preset:vercel", async () => {
"dest": "/__nitro--rules-swr-ttl?url=$url",
"src": "(?<url>/rules/swr-ttl/.*)",
},
{
"dest": "/stream-cached?url=$url",
"src": "/stream-cached",
},
{
"dest": "/__nitro",
"src": "/(.*)",
Expand Down
28 changes: 28 additions & 0 deletions test/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,34 @@ export function testNitro(
}
}
);

it.skipIf(
ctx.isIsolated ||
(isWindows && ctx.preset === "nitro-dev") ||
ctx.isLambda
)("should cache stream result", async () => {
const { data } = await callHandler({
url: "/stream-cached",
});

const [str, timestamp] = data.split(",") as string[];

expect(str).toBe(
ctx.isLambda ? btoa("nitroisawesome") : "nitroisawesome"
);

const calls = await Promise.all([
callHandler({ url: "/stream-cached" }),
callHandler({ url: "/stream-cached" }),
callHandler({ url: "/stream-cached" }),
]);

for (const call of calls) {
expect(call.data).toBe(
ctx.isLambda ? btoa(`${str},${timestamp}`) : `${str},${timestamp}`
);
}
});
});

describe("scanned files", () => {
Expand Down

0 comments on commit 789bb93

Please sign in to comment.