Files
dckc 6f409fc83a diigo-cache: bookmark cache in Durable Objects (goal 1c)
The dump worker becomes a page cache: a hit is served from DO SQLite
storage with no diigo call; a miss asks the webapp instance in the
cache's wiring and etches the page.

- two independent caches, one behavior (PagedCache): DiigoCache keeps
  every item; PublicCache keeps shared items only, dropping private
  rows before they are stored; served at /public/<username> (HTML page
  form, or JSON)
- create flow on one port: /_connect relays webapp's mint form, /_mint
  proxies the mint, /_wire wires a private or public cache; /_publish
  adds a public cache later
- ids are webkeys: kept out of URLs (POST bodies), shown and logged
  only as a hash fingerprint; saved to the password manager under the
  holder's petname (never sent to the server)
- items keyed on diigo's link_id; username from u_name; unkeyable
  items and every failure path are logged; each page read logs hit/miss
- tests run the real worker + DOs in the vitest pool against a fake
  webapp (tests/fake-webapp.js)

Generated-by: opencode (GLM)
Generated-by: Claude Opus 5.5
2026-10-07 04:19:54 -05:00

139 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
yurl32,
unyurl32,
hexToBytes,
bytesToHex,
glyphsFor,
bytesInGlyphs,
toWebkey,
idFromWebkey,
isWebkey,
shashKey,
type DurableObjectIdLike,
} from "../src/webkeys.js";
// @ts-expect-error vite raw import
import webkeysSource from "../src/webkeys.ts?raw";
const sampleHex = "ab9d14667a05453602b28218c3dc60995d890e49c1502ee1fffa2d0815b367ee";
/** mock a Cloudflare DurableObjectId */
function mockDurableObjectId(hex = sampleHex): DurableObjectIdLike {
return {
toString: () => hex,
equals: (other: unknown) =>
typeof other === "object" &&
other !== null &&
"toString" in other &&
(other as { toString(): string }).toString() === hex,
};
}
describe("yurl32 and hex codecs", () => {
it("matches the paper's alphabet sample", () => {
expect("mhbqcmmva5ja3").toMatch(/^[a-z2-7]{13}$/);
});
it("encodes 5 bits per glyph", () => {
expect(glyphsFor(64)).toBe(13);
expect(glyphsFor(128)).toBe(26);
expect(glyphsFor(160)).toBe(32);
expect(glyphsFor(256)).toBe(52);
expect(bytesInGlyphs(26)).toBe(16);
expect(bytesInGlyphs(52)).toBe(32);
});
it("round-trips hex bytes", () => {
const bytes = hexToBytes(sampleHex);
expect(bytes.length).toBe(32);
expect(bytesToHex(bytes)).toBe(sampleHex);
});
it("round-trips bytes through yurl32", () => {
const bytes = hexToBytes(sampleHex);
const encoded = yurl32(bytes);
expect(encoded).toMatch(/^[a-z2-7]+$/);
expect(encoded.length).toBe(52);
expect(unyurl32(encoded)).toEqual(bytes);
});
it("handles leftovers: 0xff encodes to '74'", () => {
expect(yurl32(new Uint8Array([0xff]))).toBe("74");
});
it("refuses garbage", () => {
expect(() => unyurl32("0OIl")).toThrow(/not YURL32/);
expect(() => unyurl32("AAAA-")).toThrow();
expect(() => hexToBytes("abc")).toThrow(/invalid hex length/);
expect(() => hexToBytes("zz")).toThrow(/invalid hex byte/);
});
});
describe("toWebkey and idFromWebkey (aligning with Worker unique ID)", () => {
it("converts a DurableObjectId to a 52-glyph YURL32 webkey", () => {
const doId = mockDurableObjectId();
const webkey = toWebkey(doId);
expect(webkey.length).toBe(52);
expect(webkey).toMatch(/^[a-z2-7]{52}$/);
expect(isWebkey(webkey)).toBe(true);
});
it("round-trips DurableObjectId -> webkey -> hex string", () => {
const doId = mockDurableObjectId();
const webkey = toWebkey(doId);
const hex = idFromWebkey(webkey);
expect(hex).toBe(sampleHex);
});
it("accepts a raw 64-char hex string as input to toWebkey", () => {
const webkey = toWebkey(sampleHex);
expect(webkey.length).toBe(52);
expect(idFromWebkey(webkey)).toBe(sampleHex);
});
it("accepts an already-hex ID in idFromWebkey for backward compatibility", () => {
expect(idFromWebkey(sampleHex)).toBe(sampleHex);
expect(idFromWebkey(sampleHex.toUpperCase())).toBe(sampleHex);
});
it("encodes raw 16-byte (128-bit) entropy directly", () => {
const bytes = new Uint8Array(16).fill(42);
const webkey = toWebkey(bytes);
expect(webkey.length).toBe(26);
expect(isWebkey(webkey)).toBe(true);
expect(isWebkey(webkey, 128)).toBe(true);
expect(unyurl32(webkey)).toEqual(bytes);
});
it("refuses malformed or wrong-sized webkeys in idFromWebkey", () => {
expect(() => idFromWebkey("short")).toThrow();
// 26-char (128-bit) webkey is valid YURL32, but not a 256-bit Worker ID:
const short128 = toWebkey(new Uint8Array(16));
expect(() => idFromWebkey(short128)).toThrow(/expected 32 bytes/);
});
});
describe("shashKey (swissHash discipline)", () => {
it("is one-way: identity, not authority", async () => {
const doId = mockDurableObjectId();
const h = await shashKey(doId);
expect(h).toMatch(/^[a-z2-7]+$/);
expect(h.length).toBe(52);
expect(h).not.toBe(toWebkey(doId));
});
it("is deterministic (joinable in logs)", async () => {
const doId = mockDurableObjectId();
expect(await shashKey(doId)).toBe(await shashKey(doId));
});
});
describe("no ambient authority in this module", () => {
it("the module's functions take their effects as arguments", () => {
// strip comments before inspecting executable code
const code = webkeysSource.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
expect(code.includes("getRandomValues")).toBe(false);
expect(code.match(/crypto\.subtle/g)?.length ?? 0).toBe(1); // shashKey only
});
});