capsite/lib: endo's own matcher data, smallcaps, and MapStoreDo

Matchers are now exactly what @endo/patterns makes: tagged records as
from pass-style's makeTagged (M.string() is match:string with payload
[], M.any() is match:any with payload undefined). Structured clone drops
their symbol-keyed and non-enumerable properties, so they cross DO
storage and Workers RPC in marshal's smallcaps encoding (the subset
patterns need).

MapStoreDo is a MapStore as a DO of its own: its creator fixes label and
(encoded) shapes once; its id is full authority, and readOnly() hands
out the queries alone.

Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-09 02:32:22 -05:00
1 parent 10414c2ce6
commit 357e4561c4
5 files changed
+225 -26

No files matched your search

+57 -2
View File
@@ -1,9 +1,13 @@
import { env } from "cloudflare:workers";
import { evictDurableObject } from "cloudflare:test";
import { describe, expect, it } from "vitest";
import { M, matches, mustMatch } from "../src/patterns.js";
import { M, fromSmallcaps, makeTagged, matches, mustMatch, toSmallcaps } from "../src/patterns.js";
import { encodeShapes } from "../src/store-do.js";
type TestEnv = { STORE_DO: DurableObjectNamespace<import("./test-worker.js").StoreHost> };
type TestEnv = {
STORE_DO: DurableObjectNamespace<import("./test-worker.js").StoreHost>;
MAP_STORE_DO: DurableObjectNamespace<import("../src/store-do.js").MapStoreDo>;
};
const testEnv = env as unknown as TestEnv;
const SECRET = "session=s3kr1t";
@@ -27,6 +31,28 @@ describe("patterns", () => {
expect(matches("authorization", "cookie")).toBe(false);
});
it("matchers are endo's tagged records", () => {
const s = M.string() as unknown as Record<string | symbol, unknown>;
expect(s[Symbol.for("passStyle")]).toBe("tagged");
expect(s[Symbol.toStringTag]).toBe("match:string");
expect(Object.keys(s)).toEqual(["payload"]);
expect(s.payload).toEqual([]);
expect((M.any() as unknown as Record<string, unknown>).payload).toBeUndefined();
expect(Object.isFrozen(s)).toBe(true);
});
it("smallcaps: endo's encoding, round-tripped", () => {
expect(toSmallcaps(M.string())).toEqual({ "#tag": "match:string", payload: [] });
expect(toSmallcaps(M.any())).toEqual({ "#tag": "match:any", payload: "#undefined" });
expect(toSmallcaps("#cookie")).toBe("!#cookie");
expect(toSmallcaps([NaN, -0, Infinity])).toEqual(["#NaN", 0, "#Infinity"]);
const back = fromSmallcaps(toSmallcaps(M.string())) as Parameters<typeof matches>[1];
expect(matches("x", back)).toBe(true);
expect(matches(1, back)).toBe(false);
expect(fromSmallcaps("!#cookie")).toBe("#cookie");
expect(() => matches("x", makeTagged("match:nope", []))).toThrow("not a pattern");
});
it("mustMatch names the shape but not the specimen", () => {
expect(() => mustMatch(SECRET, "cookie", "jar: key")).toThrow('jar: key: must match "cookie"');
expect(() => mustMatch(SECRET, "cookie", "jar: key")).not.toThrow(SECRET);
@@ -85,3 +111,32 @@ describe("MapStore over RPC", () => {
expect(await again.keys()).toEqual([]);
});
});
describe("MapStoreDo", () => {
const COOKIE_SHAPES = encodeShapes({ keyShape: "cookie", valueShape: M.string() });
const fresh = () => testEnv.MAP_STORE_DO.get(testEnv.MAP_STORE_DO.newUniqueId());
it("keeps the shapes its creator chose, across eviction", async () => {
const stub = fresh();
await stub.configure("cookie", COOKIE_SHAPES);
await evictDurableObject(stub);
{
using store = await stub.store();
expect(await errorOf(store.init("authorization", SECRET))).toContain('cookie: key: must match "cookie"');
expect(await errorOf(store.init("cookie", 42))).toContain("cookie: value: must match M.string()");
await store.init("cookie", "session=a");
}
using rd = await stub.readOnly();
expect(await rd.entries()).toEqual([["cookie", "session=a"]]);
});
it("configures once: the same shapes again is a no-op, others are refused", async () => {
const stub = fresh();
await stub.configure("cookie", COOKIE_SHAPES);
await stub.configure("cookie", encodeShapes({ keyShape: "cookie", valueShape: M.string() }));
expect(await errorOf(stub.configure("cookie", encodeShapes({ keyShape: M.string() })))).toContain(
"already configured",
);
expect(await errorOf(fresh().store())).toContain("not configured");
});
});