sitegen/framework/assets.ts
chloe caruso af60d1172f i accidentally deleted the repo, but recovered it. i'll start committing
it was weird. i pressed delete on a subfolder, i think one of the
pages.off folders that i was using. and then, suddenly, nvim on windows
7 decided to delete every file in the directory. they weren't shred off
the space time continuum, but just marked deleted. i had to pay $80 to
get access to a software that could see them. bleh!

just seeing all my work, a little over a week, was pretty heart
shattering. but i remembered that long ago, a close friend said i could
call them whenever i was feeling sad. i finally took them up on that
offer. the first time i've ever called someone for emotional support.
but it's ok. i got it back. and the site framework is better than ever.

i'm gonna commit and push more often. the repo is private anyways.
2025-06-06 23:38:02 -07:00

99 lines
2.4 KiB
TypeScript

interface Loaded {
map: BuiltAssetMap;
buf: Buffer;
}
let assets: Loaded | null = null;
export type StaticPageId = string;
export async function reload() {
const [map, buf] = await Promise.all([
fs.readFile(".clover/static.json", "utf8"),
fs.readFile(".clover/static.blob"),
]);
assets = {
map: JSON.parse(map),
buf,
};
}
export async function reloadSync() {
const map = fs.readFileSync(".clover/static.json", "utf8");
const buf = fs.readFileSync(".clover/static.blob");
assets = {
map: JSON.parse(map),
buf,
};
}
export async function assetMiddleware(c: Context, next: Next) {
if (!assets) await reload();
const asset = assets!.map[c.req.path];
if (asset) {
return assetInner(c, asset, 200);
}
return next();
}
export async function serveAsset(
c: Context,
id: StaticPageId,
status: StatusCode,
) {
assets ?? await reload();
return assetInner(c, assets!.map[id], status);
}
export function hasAsset(id: string) {
if (!assets) reloadSync();
return assets!.map[id] !== undefined;
}
export function etagMatches(etag: string, ifNoneMatch: string) {
return ifNoneMatch === etag || ifNoneMatch.split(/,\s*/).indexOf(etag) > -1;
}
function subarrayAsset([start, end]: View) {
return assets!.buf.subarray(start, end);
}
function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) {
const ifnonematch = c.req.header("If-None-Match");
if (ifnonematch) {
const etag = asset.headers.ETag;
if (etagMatches(etag, ifnonematch)) {
c.res = new Response(null, {
status: 304,
statusText: "Not Modified",
headers: {
ETag: etag,
},
});
return;
}
}
const acceptEncoding = c.req.header("Accept-Encoding") ?? "";
let body;
let headers = asset.headers;
if (acceptEncoding.includes("zstd") && asset.zstd) {
body = subarrayAsset(asset.zstd);
headers = {
...asset.headers,
"Content-Encoding": "zstd",
};
} else if (acceptEncoding.includes("gzip") && asset.gzip) {
body = subarrayAsset(asset.gzip);
headers = {
...asset.headers,
"Content-Encoding": "gzip",
};
} else {
body = subarrayAsset(asset.raw);
}
c.res = new Response(body, { headers, status });
}
import * as fs from "./fs.ts";
import type { Context, Next } from "hono";
import type { StatusCode } from "hono/utils/http-status";
import type { BuiltAsset, BuiltAssetMap, View } from "./incremental.ts";