diff --git a/diigo-cache/src/api.ts b/diigo-cache/src/api.ts index ed360bf..745cd9b 100644 --- a/diigo-cache/src/api.ts +++ b/diigo-cache/src/api.ts @@ -14,13 +14,13 @@ */ import { RpcTarget } from "capnweb"; -import type { DiigoCache, FillStatus, PublicCache } from "./cache.js"; +import type { DiigoCacheLogic, FillStatus, PublicCacheLogic } from "./cache.js"; import { idFromWebkey, isWebkey, shashKey } from "./webkeys.js"; /** the bindings the API reaches (structural) */ export type ApiEnv = { - CACHE_DO: DurableObjectNamespace; - PUBLIC_DO: DurableObjectNamespace; + CACHE_DO: DurableObjectNamespace; + PUBLIC_DO: DurableObjectNamespace; }; /** the fill-job surface of one cache (private or public) */ diff --git a/diigo-cache/src/cache.ts b/diigo-cache/src/cache.ts index e925d10..4d6b5f1 100644 --- a/diigo-cache/src/cache.ts +++ b/diigo-cache/src/cache.ts @@ -1,6 +1,9 @@ /** - * @file the cache Durable Objects: `DiigoCache` (public+private items) and - * `PublicCache` (public-only items, widely shared). They are independent + * @file the cache Durable Objects' logic: `DiigoCacheLogic` (public+private + * items) and `PublicCacheLogic` (public-only items, widely shared). Logic + * only: each requires its {@link Clock}; the worker's entry module + * (src/index.ts) is the edge that makes the live one and exports the + * runtime-constructed `DiigoCache`/`PublicCache`. They are independent * and work the same way ({@link PagedCache}): pages are the unit of * caching; a hit is answered from `ctx.storage.sqlite` with no diigo call; * a miss asks the webapp instance in the cache's wiring and etches the @@ -117,14 +120,9 @@ abstract class PagedCache extends DurableObject { #pause?: Promise; #clock: Clock; - /** the runtime constructs DOs with (ctx, env), so the default — the - * live clock, made here at the edge, nowhere in module scope — applies; - * a subclass (tests) can pass another */ - constructor( - ctx: DurableObjectState, - env: CacheEnv, - clock: Clock = { now: () => Date.now(), sleep: (ms) => new Promise((r) => setTimeout(r, ms)) }, - ) { + /** `clock` is required: time comes from the edge that constructs us + * (src/index.ts in the worker, tests/test-worker.ts in tests) */ + constructor(ctx: DurableObjectState, env: CacheEnv, clock: Clock) { super(ctx, env); this.#clock = clock; } @@ -338,10 +336,11 @@ abstract class PagedCache extends DurableObject { } /** - * the private cache (export name `DiigoCache`): public+private items, - * addressed by its cache id (a webkey, held by the user). + * the private cache's logic (exported to the runtime as `DiigoCache` by + * src/index.ts): public+private items, addressed by its cache id (a + * webkey, held by the user). */ -export class DiigoCache extends PagedCache { +export class DiigoCacheLogic extends PagedCache { protected readonly kind = "private"; protected keep(rows: ItemRow[]): ItemRow[] { @@ -375,7 +374,7 @@ export class DiigoCache extends PagedCache { * (the idFromName("index") instance holds the kv mapping and is never * wired). */ -export class PublicCache extends PagedCache { +export class PublicCacheLogic extends PagedCache { protected readonly kind = "public"; protected keep(rows: ItemRow[]): ItemRow[] { diff --git a/diigo-cache/src/index.ts b/diigo-cache/src/index.ts index 2b13d11..d1979fa 100644 --- a/diigo-cache/src/index.ts +++ b/diigo-cache/src/index.ts @@ -17,16 +17,39 @@ import { newWorkersRpcResponse } from "capnweb"; import { CacheApi } from "./api.js"; import { CAPNWEB_BROWSER_JS } from "./capnweb-browser.js"; import { CACHE_PAGE_HTML } from "./cache-page.js"; -import { DiigoCache, PublicCache } from "./cache.js"; -import type { FillStatus } from "./cache.js"; +import { DiigoCacheLogic, PublicCacheLogic } from "./cache.js"; +import type { CacheEnv, Clock, FillStatus } from "./cache.js"; import type { Item } from "./cache.js"; import { updatedISO, usernameOf } from "./items.js"; import { toWebkey, idFromWebkey, isWebkey, shashKey } from "./webkeys.js"; import { pmSave } from "./pm-save.js"; -// the DO classes must be entrypoint exports (the exports/ binding targets -// the main module) -export { DiigoCache, PublicCache }; +/** + * the live clock. This module is the worker's edge — the runtime's entry, + * like a program script — so ambient time is reached here and delegated + * down; the cache logic only uses the clock it is given. + */ +function liveClock(): Clock { + return { now: () => Date.now(), sleep: (ms) => new Promise((r) => setTimeout(r, ms)) }; +} + +// The DO classes must be exports of the main module, and the runtime +// constructs them with (ctx, env) only: a Cloudflare API constraint, met +// here at the edge by subclasses that supply the live clock. + +/** the private cache, as the runtime constructs it */ +export class DiigoCache extends DiigoCacheLogic { + constructor(ctx: DurableObjectState, env: CacheEnv) { + super(ctx, env, liveClock()); + } +} + +/** the public cache, as the runtime constructs it */ +export class PublicCache extends PublicCacheLogic { + constructor(ctx: DurableObjectState, env: CacheEnv) { + super(ctx, env, liveClock()); + } +} /** worker bindings (structural until wrangler types regenerates) */ type Env = { diff --git a/diigo-cache/tests/caches.test.ts b/diigo-cache/tests/caches.test.ts index c26ae79..2c949e7 100644 --- a/diigo-cache/tests/caches.test.ts +++ b/diigo-cache/tests/caches.test.ts @@ -9,8 +9,8 @@ import { FETCH_BUDGET } from "../src/query.js"; const WEBAPP_ID = "fake-webapp-id"; type TestEnv = { - CACHE_DO: DurableObjectNamespace; - PUBLIC_DO: DurableObjectNamespace; + CACHE_DO: DurableObjectNamespace; + PUBLIC_DO: DurableObjectNamespace; WEBAPP: { calls(): Promise }; }; const testEnv = env as unknown as TestEnv; diff --git a/diigo-cache/tests/fill.test.ts b/diigo-cache/tests/fill.test.ts index d151a69..21e9e67 100644 --- a/diigo-cache/tests/fill.test.ts +++ b/diigo-cache/tests/fill.test.ts @@ -12,9 +12,9 @@ import { FIXED_NOW } from "./test-worker.js"; // of 24, every 3rd private). type TestEnv = { - CACHE_DO: DurableObjectNamespace; + CACHE_DO: DurableObjectNamespace; CLOCKED_DO: DurableObjectNamespace; - PUBLIC_DO: DurableObjectNamespace; + PUBLIC_DO: DurableObjectNamespace; WEBAPP: { calls(): Promise }; }; const testEnv = env as unknown as TestEnv; diff --git a/diigo-cache/tests/test-worker.ts b/diigo-cache/tests/test-worker.ts index 2caba86..eb68698 100644 --- a/diigo-cache/tests/test-worker.ts +++ b/diigo-cache/tests/test-worker.ts @@ -1,9 +1,9 @@ /** - * @file the tests' main module: the real worker, plus a DiigoCache on a - * fixed clock (bound as CLOCKED_DO in wrangler.tests.jsonc), so tests can - * interpose time through the constructor. + * @file the tests' main module — their edge: the real worker (whose own + * edge supplies the live clock), plus the private cache's logic on a fixed + * clock (bound as CLOCKED_DO in wrangler.tests.jsonc). */ -import { DiigoCache } from "../src/cache.js"; +import { DiigoCacheLogic } from "../src/cache.js"; import type { CacheEnv } from "../src/cache.js"; export * from "../src/index.js"; @@ -12,7 +12,7 @@ export { default } from "../src/index.js"; /** 2026-10-07T12:00:00Z */ export const FIXED_NOW = Date.UTC(2026, 9, 7, 12, 0, 0); -export class FixedClockCache extends DiigoCache { +export class FixedClockCache extends DiigoCacheLogic { constructor(ctx: DurableObjectState, env: CacheEnv) { super(ctx, env, { now: () => FIXED_NOW, sleep: async () => {} }); }