Skip to content

Commit

Permalink
Add support to deno deploy cache api implementation (#741)
Browse files Browse the repository at this point in the history
* Add support to deno deploy cache api implementation

Documentation for deno's Edge Cache are available at https://docs.deno.com/deploy/manual/edge-cache/

---------

Co-authored-by: Matheus Gaudencio do Rêgo <[email protected]>
  • Loading branch information
ItamarRocha and matheusgr authored Sep 26, 2024
1 parent 6f5f110 commit 68e0621
Show file tree
Hide file tree
Showing 12 changed files with 456 additions and 1,299 deletions.
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,12 @@ Here is a table with the integrations that we have built and the statuses of the
## Cache env vars (WIP)
| Environment Variable | Description | Example Value |
|-----------------------------------|---------------------------------------------------------|--------------------------------------------------------|
| `CACHE_UPLOAD_BUCKET` | The AWS S3 bucket name for cache uploads | `BUCKET-NAME` |
| `CACHE_AWS_REGION` | AWS region where the cache bucket is located | `sa-east-1` |
| `CACHE_AWS_ACCESS_KEY_ID` | AWS access key ID for authentication | `` |
| `CACHE_AWS_SECRET_ACCESS_KEY` | AWS secret access key for authentication | `` |
| `ENABLE_LOADER_CACHE` | Flag to enable or disable the loader cache | `true` |
| `LOADER_CACHE_START_TRESHOLD` | Cache start threshold | `0` |
| `WEB_CACHE_ENGINE` | Defines the cache engine(s) to use | `"FILE_SYSTEM,S3"` |
| `FILE_SYSTEM_CACHE_DIRECTORY` | Directory path for file system cache | `` |
| `MAX_CACHE_SIZE` | Maximum size of the file system cache (in bytes) | `1073741824` (1 GB) |
| `TTL_AUTOPURGE` | Flag to automatically delete expired items from the file system cache (cpu intensive) | `false` |
| `TTL_RESOLUTION` | Time interval to check for expired items in the file system cache (in milliseconds) | `30000` (30 seconds) |
| `WEB_CACHE_ENGINE` | Defines the cache engine(s) to use | `"FILE_SYSTEM,CACHE_API"` |
| `CACHE_MAX_SIZE` | Maximum size of the file system cache (in bytes) | `1073741824` (1 GB) |
| `CACHE_TTL_AUTOPURGE` | Flag to automatically delete expired items from the file system cache (cpu intensive) | `false` |
| `CACHE_TTL_RESOLUTION` | Time interval to check for expired items in the file system cache (in milliseconds) | `30000` (30 seconds) |
| `CACHE_MAX_AGE_S` | Time for cache to become stale | `60` (60 seconds) |


Expand Down
25 changes: 18 additions & 7 deletions blocks/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ export const wrapCaughtErrors = async <
};

export const LOADER_CACHE_START_TRESHOLD =
Deno.env.get("LOADER_CACHE_START_TRESHOLD") ?? 5;
Deno.env.get("LOADER_CACHE_START_TRESHOLD") ?? 0;

export const LOADER_CACHE_SIZE = Deno.env.get("LOADER_CACHE_SIZE") ?? 1_024;
export const LOADER_CACHE_SIZE = Deno.env.get("LOADER_CACHE_SIZE") ?? 1_024_000;

const stats = {
cache: meter.createCounter("loader_cache", {
Expand Down Expand Up @@ -291,13 +291,24 @@ const wrapLoader = (

const callHandlerAndCache = async () => {
const json = await handler(props, req, ctx);
const jsonStringEncoded = new TextEncoder().encode(
JSON.stringify(json),
);

const headers: { [key: string]: string } = {
expires: new Date(Date.now() + (MAX_AGE_S * 1e3))
.toUTCString(),
"Content-Type": "application/json",
};

if (jsonStringEncoded && jsonStringEncoded.length > 0) {
headers["Content-Length"] = "" + jsonStringEncoded.length;
}

cache.put(
request,
new Response(JSON.stringify(json), {
headers: {
"expires": new Date(Date.now() + (MAX_AGE_S * 1e3))
.toUTCString(),
},
new Response(jsonStringEncoded, {
headers: headers,
}),
).catch((error) => logger.error(`loader error ${error}`));

Expand Down
43 changes: 2 additions & 41 deletions runtime/caches/common.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,6 @@
import { ValueType } from "../../deps.ts";
import { tracer } from "../../observability/otel/config.ts";
import { meter } from "../../observability/otel/metrics.ts";
import { sha1 } from "../utils.ts";

export const assertNoOptions = (
{ ignoreMethod, ignoreSearch, ignoreVary }: CacheQueryOptions = {},
) => {
if (ignoreMethod || ignoreSearch || ignoreVary) {
throw new Error("Not Implemented");
}
};

export const requestURL = (request: RequestInfo | URL): string => {
return typeof request === "string"
? request
: request instanceof URL
? request.href
: request.url;
};

export const withCacheNamespace =
(cacheName: string) => (request: RequestInfo | URL): Promise<string> => {
return requestURLSHA1(request).then((key) => `${key}${cacheName}`);
};

export const requestURLSHA1 = (request: RequestInfo | URL): Promise<string> => {
return sha1(requestURL(request));
};

export const assertCanBeCached = (req: Request, response: Response) => {
if (!/^http(s?):\/\//.test(req.url)) {
throw new TypeError(
"Request url protocol must be 'http:' or 'https:'",
);
}
if (req.method !== "GET") {
throw new TypeError("Request method must be GET");
}

if (response.status === 206) {
throw new TypeError("Response status must not be 206");
}
};

export interface CacheMetrics {
engine: string;
Expand All @@ -63,6 +22,8 @@ export const withInstrumentation = (
const cacheImpl = await cache.open(cacheName);
return {
...cacheImpl,
delete: cacheImpl.delete.bind(cacheImpl),
put: cacheImpl.put.bind(cacheImpl),
match: async (req, opts) => {
const span = tracer.startSpan("cache-match", {
attributes: { engine },
Expand Down
Loading

0 comments on commit 68e0621

Please sign in to comment.