Repository navigation
Cache: add the OpenNextCache entrypoint (Cloudflare) #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
fa74e70
refactor: remove createServerBundle.ts and integrate its functionalit…
conico974 33a6eff
feat: implement default overrides for bundle configurations and add t…
conico974 fe002e1
format
conico974 9ee9515
fix ts issue
conico974 6fad0c6
feat: add default overrides for AWS adapter and integrate Cloudflare …
conico974 84397ab
Revert "feat: add default overrides for AWS adapter and integrate Clo…
conico974 f22d37f
feat: enhance AWS adapter with default overrides and improve middlewa…
conico974 a3165f0
refactor(core): return ValidateConfigResult from validateConfig inste…
conico974 6296e94
refactor(core): extract buildOpenNextOutput, export OpenNextOutput type
conico974 4e82821
refactor(core): branch on ValidateConfigResult in compileOpenNextConfig
conico974 0af2e73
feat(core): overridable validateConfig and generic generateOutput on …
conico974 bab47f1
fix(core): use require.resolve for full-path overrides in resolve plugin
conico974 51b5e3b
format
conico974 a437de1
feat(core): enforce mandatory externals in serverBundle and update re…
conico974 cd8f83e
refactor(core): enhance resolve plugin to support dynamic override re…
conico974 c200d7a
feat(core): run the cache in a dedicated handler function
conico974 8b63860
feat(cloudflare): add the OpenNextCache entrypoint
conico974 c606b91
Merge origin/main into cache entrypoint branch
vicb 3c16e0c
test(cloudflare): cover flattened Wrangler environments
vicb 9cdc1b4
fix(cloudflare): surface cache service failures
vicb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| "@opennextjs/cloudflare": patch | ||
| --- | ||
|
|
||
| Add the `OpenNextCache` named entrypoint | ||
|
|
||
| The build now emits the OpenNext cache handler function and exposes it from the worker as the | ||
| `OpenNextCache` named entrypoint, together with a `service-cache` override that reaches it over a | ||
| service binding. | ||
|
|
||
| This requires a self referencing service binding in the wrangler configuration: | ||
|
|
||
| ```jsonc | ||
| "services": [ | ||
| { | ||
| "binding": "NEXT_CACHE_SERVICE", | ||
| "service": "<your-worker-name>", | ||
| "entrypoint": "OpenNextCache" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| The cache runs in the same worker by default. Pointing the binding at another worker is enough to | ||
| run the cache as a service of its own. | ||
|
|
||
| Nothing selects this override yet - `defineCloudflareConfig` still runs the cache in the server | ||
| function. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
packages/cloudflare/src/api/overrides/cache/service-cache.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
|
||
| import serviceCache, { BINDING_NAME } from "./service-cache.js"; | ||
|
|
||
| const fetchMock = vi.fn<(input: string, init?: RequestInit) => Promise<Response>>(); | ||
| const env: Record<string, unknown> = {}; | ||
|
|
||
| vi.mock("../../cloudflare-context.js", () => ({ | ||
| getCloudflareContext: () => ({ env }), | ||
| })); | ||
|
|
||
| function lastRequest() { | ||
| const [url, init] = fetchMock.mock.calls.at(-1)!; | ||
| return { url: new URL(url), method: init?.method ?? "GET", body: init?.body }; | ||
| } | ||
|
|
||
| describe("serviceCache", () => { | ||
| beforeEach(() => { | ||
| fetchMock.mockReset(); | ||
| fetchMock.mockResolvedValue(new Response("", { headers: { "x-opennext-cache-found": "false" } })); | ||
| env[BINDING_NAME] = { fetch: fetchMock }; | ||
| }); | ||
|
|
||
| it("throws when the service is not bound", async () => { | ||
| delete env[BINDING_NAME]; | ||
|
|
||
| await expect(serviceCache.get("key")).rejects.toThrow(BINDING_NAME); | ||
| }); | ||
|
|
||
| describe("get", () => { | ||
| it("requests the key and the cache type", async () => { | ||
| await serviceCache.get("key/with/slashes", "fetch"); | ||
|
|
||
| const { url, method } = lastRequest(); | ||
| expect(method).toBe("GET"); | ||
| expect(url.pathname).toBe(`/cache/${encodeURIComponent("key/with/slashes")}`); | ||
| expect(url.searchParams.get("type")).toBe("fetch"); | ||
| }); | ||
|
|
||
| it("returns null on a cache miss", async () => { | ||
| await expect(serviceCache.get("key")).resolves.toBeNull(); | ||
| }); | ||
|
|
||
| it("parses a cache hit", async () => { | ||
| fetchMock.mockResolvedValue( | ||
| new Response("body", { | ||
| headers: { | ||
| "x-opennext-cache-found": "true", | ||
| "x-opennext-cache-type": "cache", | ||
| "x-opennext-cache-sub-type": "route", | ||
| "x-opennext-cache-last-modified": "1234", | ||
| }, | ||
| }) | ||
| ); | ||
|
|
||
| await expect(serviceCache.get("key")).resolves.toEqual({ | ||
| lastModified: 1234, | ||
| value: expect.objectContaining({ type: "route", body: "body" }), | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("set", () => { | ||
| // The cache type is part of the key for the incremental caches, it has to be forwarded | ||
| // or entries would be written where they are not read from. | ||
| it("sends the value and the cache type", async () => { | ||
| await serviceCache.set("key", { kind: "FETCH", data: { headers: {}, body: "b", url: "u" } }, "fetch"); | ||
|
|
||
| const { url, method, body } = lastRequest(); | ||
| expect(method).toBe("PUT"); | ||
| expect(url.pathname).toBe("/cache/key"); | ||
| expect(url.searchParams.get("type")).toBe("fetch"); | ||
| expect(JSON.parse(body as string)).toEqual({ | ||
| value: { kind: "FETCH", data: { headers: {}, body: "b", url: "u" } }, | ||
| }); | ||
| }); | ||
|
|
||
| it("rejects unsuccessful responses", async () => { | ||
| fetchMock.mockResolvedValue(new Response(null, { status: 500 })); | ||
|
|
||
| await expect( | ||
| serviceCache.set("key", { kind: "FETCH", data: { headers: {}, body: "b", url: "u" } }, "fetch") | ||
| ).rejects.toThrow("Failed to set cache entry: cache handler returned 500"); | ||
| }); | ||
| }); | ||
|
|
||
| it("deletes a key", async () => { | ||
| await serviceCache.delete("key"); | ||
|
|
||
| const { url, method } = lastRequest(); | ||
| expect(method).toBe("DELETE"); | ||
| expect(url.pathname).toBe("/cache/key"); | ||
| }); | ||
|
|
||
| it("rejects an unsuccessful delete", async () => { | ||
| fetchMock.mockResolvedValue(new Response(null, { status: 503 })); | ||
|
|
||
| await expect(serviceCache.delete("key")).rejects.toThrow( | ||
| "Failed to delete cache entry: cache handler returned 503" | ||
| ); | ||
| }); | ||
|
|
||
| it("revalidates tags", async () => { | ||
| await serviceCache.revalidateTags(["tag1", "tag2"]); | ||
|
|
||
| const { url, method, body } = lastRequest(); | ||
| expect(method).toBe("POST"); | ||
| expect(url.pathname).toBe("/cache/revalidate-tags"); | ||
| expect(JSON.parse(body as string)).toEqual({ tags: ["tag1", "tag2"] }); | ||
| }); | ||
|
|
||
| it("rejects an unsuccessful tag revalidation", async () => { | ||
| fetchMock.mockResolvedValue(new Response(null, { status: 502 })); | ||
|
|
||
| await expect(serviceCache.revalidateTags(["tag"])).rejects.toThrow( | ||
| "Failed to revalidate cache tags: cache handler returned 502" | ||
| ); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.