Skip to content
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

[WIP] Cache api support #709

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ The deno project created with Deco is completely standalone — all of the CMS i
This means you can deploy a Deco project easily to any hosting platform you want. By using our integrated hosting partners, you get full first-class environment support an observability inside Deco.

> [!WARNING]
> Self-hosting the editor itself is coming in early 2024. Bear with us as we refactor some innards before we can invite more developers to extend it! We're looking forward to it.
> Self-hosting the editor itself is coming in early 2025. Bear with us as we refactor some innards before we can invite more developers to extend it! We're looking forward to it.

## Deploy to the deco.cx PRO edge

Expand Down
6 changes: 5 additions & 1 deletion blocks/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ const injectAppStateOnManifest = <
),
loaders: mapObjKeys(
manifest.loaders ?? {},
(mod) => ({ ...mod, default: injectAppState(state, mod.default) }),
(mod) => ({
...mod,
default: injectAppState(state, mod.default),
cacheKey: mod.cacheKey && injectAppState(state, mod.cacheKey),
}),
),
};
};
Expand Down
82 changes: 63 additions & 19 deletions blocks/loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// deno-lint-ignore-file no-explicit-any
import JsonViewer from "../components/JsonViewer.tsx";
import { ValueType, weakcache } from "../deps.ts";
import { LoggerProvider, ValueType, weakcache } from "../deps.ts";
import type { Block, BlockModule, InstanceOf } from "../engine/block.ts";
import { FieldResolver } from "../engine/core/resolver.ts";
import { singleFlight } from "../engine/core/utils.ts";
Expand Down Expand Up @@ -62,7 +62,7 @@ export const wrapCaughtErrors = async <
return ctx.next!();
}
try {
return await ctx.next!();
return await ctx.next!(); // Seems like here
} catch (err) {
if (err instanceof HttpError) {
throw err;
Expand Down Expand Up @@ -99,9 +99,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 @@ -181,6 +181,7 @@ const wrapLoader = (
!isCache(maybeCache) ||
cacheKeyValue === null
) {
// console.log("bypassed the cache, cacheKeyValue: ", cacheKeyValue, " mode: ", mode, " ENABLE_LOADER_CACHE: ", ENABLE_LOADER_CACHE, " isCache(maybeCache): ", isCache(maybeCache));
status = "bypass";
stats.cache.add(1, { status, loader });

Expand Down Expand Up @@ -248,47 +249,90 @@ const wrapLoader = (
url.searchParams.set("cacheKey", cacheKeyValue);
const request = new Request(url);

// console.log("loader.ts cacheKeyValue: ", cacheKeyValue, " props: ", props, " req: ", req);
const callHandlerAndCache = async () => {
const json = await handler(props, req, ctx);
let json;
try {
json = await handler(props, req, ctx); // aqui tá rolando o erro
} catch (error) {
console.log("ERROR -> handler error: ", error);
}
const jsonString = JSON.stringify(json);
const jsonStringEncoded = new TextEncoder().encode(jsonString);
console.log("Length of json in call handler and cache: ", jsonString ? jsonString.length : 0);
console.log("Length of jsonEncoded in call handler and cache: ", jsonStringEncoded ? jsonStringEncoded.length : 0);

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;
// headers["Content-Type"] = "application/json; charset=utf-8";
}

const response = new Response(jsonStringEncoded, {
headers: headers,
});
console.log(
"caching the following request: ",
request.url,
"\nAnd response: ",
response.url,
"Request headers: ", request.headers,
);
cache.put(
request,
new Response(JSON.stringify(json), {
headers: {
"expires": new Date(Date.now() + (MAX_AGE_S * 1e3))
.toUTCString(),
},
}),
).catch((error) => logger.error(`loader error ${error}`));
response,
).catch((error) => {
console.log(
"ERROR -> request",
request.url,
"\nAnd response: ",
response.url,
" -- callhandlerandcache",
);
console.error(
`ERROR -> loader error ${error} -- callhandlerandcache`,
);
});

return json;
};

const staleWhileRevalidate = async () => {
const matched = await cache.match(request).catch(() => null);

// console.log(`matched: ${matched}`);
if (!matched) {
status = "miss";
stats.cache.add(1, { status, loader });

return await callHandlerAndCache();
}

console.log("matched headers: ", matched.headers);
const expires = matched.headers.get("expires");
const isStale = expires ? !inFuture(expires) : false;

if (isStale) {
status = "stale";
stats.cache.add(1, { status, loader });

callHandlerAndCache().catch((error) =>
logger.error(`loader error ${error}`)
);
callHandlerAndCache();
} else {
status = "hit";
stats.cache.add(1, { status, loader });
}

return await matched.json();
const matchedText = await matched.text();
console.log("Length of matchedText: ", matchedText.length);
console.log("matchedText: ", matchedText);
try {
return JSON.parse(matchedText);
} catch (error) {
console.error("Error parsing JSON: ", error.message);
console.error("Faulty JSON snippet: ", matchedText.slice(-500)); // Log the last 500 characters of matched text
throw error; // Re-throw the error after logging for debugging purposes
}
};

return await flights.do(request.url, staleWhileRevalidate);
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@deco-cx/deco",
"version": "1.75.1",
"version": "1.75.2",
"exports": "./mod.ts",
"tasks": {
"start": "deno task check",
Expand Down
2 changes: 2 additions & 0 deletions runtime/caches/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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
12 changes: 6 additions & 6 deletions runtime/caches/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
// import { caches as cachesS3, isS3Available } from "./s3.ts";
import { createTieredCache } from "./tiered.ts";

export const ENABLE_LOADER_CACHE =
Deno.env.get("ENABLE_LOADER_CACHE") === "true";
const DEFAULT_CACHE_ENGINE = "KV";
const WEB_CACHE_ENGINES: CacheEngine[] = Deno.env.has("WEB_CACHE_ENGINE")
? Deno.env.get("WEB_CACHE_ENGINE")!.split(",") as CacheEngine[]
: [DEFAULT_CACHE_ENGINE];
export const ENABLE_LOADER_CACHE = true;
const DEFAULT_CACHE_ENGINE = "CACHE_API";
const WEB_CACHE_ENGINES: CacheEngine[] = [DEFAULT_CACHE_ENGINE];
// Deno.env.has("WEB_CACHE_ENGINE")
// ? Deno.env.get("WEB_CACHE_ENGINE")!.split(",") as CacheEngine[]
// : [DEFAULT_CACHE_ENGINE];

export interface CacheStorageOption {
implementation: CacheStorage;
Expand Down