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
This commit is contained in:
1 parent
c5e433cc9c
commit
6f409fc83a
15 files changed
+1664
-211
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { publicOnly, toRows, usernameOf } from "../src/items.js";
|
||||
import type { Item } from "../src/items.js";
|
||||
|
||||
/** a load_user_items row: diigo's actual field names (observed
|
||||
* 2026-10-07), made-up values */
|
||||
function diigoItem(linkId: number, url: string): Item {
|
||||
return {
|
||||
link_id: linkId,
|
||||
url,
|
||||
title: `title ${linkId}`,
|
||||
description: "",
|
||||
private: false,
|
||||
u_name: "alice_99",
|
||||
first_by: "bob-the-first",
|
||||
user_id: 4242,
|
||||
real_name: "Alice A.",
|
||||
tags: "",
|
||||
created_at: 1700000000,
|
||||
updated_at: 1700000100 + linkId,
|
||||
annotations: [],
|
||||
comments: [],
|
||||
};
|
||||
}
|
||||
|
||||
function makeLog() {
|
||||
const entries: { level: string; args: unknown[] }[] = [];
|
||||
const at = (level: string) => (...args: unknown[]) => entries.push({ level, args });
|
||||
return { log: { warn: at("warn") }, entries };
|
||||
}
|
||||
|
||||
describe("toRows", () => {
|
||||
it("keys each item on diigo's link_id, so a page of distinct items stays distinct", () => {
|
||||
const { log } = makeLog();
|
||||
const rows = toRows([diigoItem(11, "https://a.example/"), diigoItem(12, "https://b.example/")], log);
|
||||
expect(rows.map((r) => r.itemId)).toEqual(["11", "12"]);
|
||||
});
|
||||
|
||||
it("logs, rather than silently drops or merges, items it cannot key", () => {
|
||||
const { log, entries } = makeLog();
|
||||
const noId = diigoItem(13, "https://c.example/");
|
||||
delete noId.link_id;
|
||||
const noUrl = diigoItem(14, "");
|
||||
const rows = toRows([noId, noUrl, diigoItem(15, "https://d.example/")], log);
|
||||
expect(rows.map((r) => r.itemId)).toEqual(["15"]);
|
||||
expect(entries.map((e) => e.level)).toEqual(["warn", "warn"]);
|
||||
// diagnostics name the fields present, never their values
|
||||
expect(JSON.stringify(entries)).not.toContain("https://c.example/");
|
||||
});
|
||||
});
|
||||
|
||||
describe("usernameOf", () => {
|
||||
it("reads the owner's username from diigo's u_name", () => {
|
||||
expect(usernameOf(diigoItem(1, "https://a.example/"))).toBe("alice_99");
|
||||
});
|
||||
|
||||
it("answers empty for an item without one", () => {
|
||||
expect(usernameOf(undefined)).toBe("");
|
||||
expect(usernameOf({ u_name: "has spaces" })).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("publicOnly", () => {
|
||||
it("keeps shared rows and drops private ones, before anything is stored", () => {
|
||||
const { log } = makeLog();
|
||||
const shared = diigoItem(21, "https://shared.example/");
|
||||
const secret = { ...diigoItem(22, "https://secret.example/"), private: true };
|
||||
const rows = publicOnly(toRows([shared, secret], log));
|
||||
expect(rows.map((r) => r.itemId)).toEqual(["21"]);
|
||||
expect(JSON.stringify(rows)).not.toContain("secret.example");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user