capsite/lib: MapStore and patterns work-alikes

Enough of @agoric/store's MapStore and @endo/patterns' M for the
credential-rotation design: makeMapStore(label, { keyShape, valueShape },
kv) keeps entries in a DO's sync KV under store:<label>:, and readOnly()
hands out the queries alone. Facets are workerd RpcTargets in place of
endo's Far.

Unlike endo, error messages never include keys or values: the values
here are credentials, and messages cross RPC into logs.

Generated-by: Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-09 02:32:22 -05:00
1 parent 1d8a220bbc
commit 9179bdbf38
10 files changed
+2586

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
/**
* @file a work-alike for enough of @endo/patterns: `M.string()`, `M.any()`,
* and literal patterns (a string, number, boolean, or null matches only
* itself, so `"cookie"` as a key shape admits exactly one key).
* @see https://github.com/endojs/endo/tree/master/packages/patterns
*/
type MatcherKind = "string" | "any";
/** a matcher made by {@link M}; recognized by identity, so it is meaningful
* only in the realm that made it (it does not survive an RPC copy) */
export type Matcher = Readonly<{ toString(): string }>;
export type Pattern = Matcher | string | number | boolean | null;
const kinds = new WeakMap<object, MatcherKind>();
const makeMatcher = (kind: MatcherKind, label: string): Matcher => {
const matcher = Object.freeze({ toString: () => label });
kinds.set(matcher, kind);
return matcher;
};
const STRING = makeMatcher("string", "M.string()");
const ANY = makeMatcher("any", "M.any()");
export const M = Object.freeze({
string: (): Matcher => STRING,
any: (): Matcher => ANY,
});
export function matches(specimen: unknown, pattern: Pattern): boolean {
if (typeof pattern === "object" && pattern !== null) {
const kind = kinds.get(pattern);
if (kind === "string") return typeof specimen === "string";
if (kind === "any") return true;
throw new TypeError("not a pattern");
}
return Object.is(specimen, pattern);
}
/**
* throw unless `specimen` matches `pattern`. Unlike endo's, the message
* names the pattern and `label` but never the specimen: specimens here
* can be credentials, and messages cross RPC and land in logs.
*/
export function mustMatch(specimen: unknown, pattern: Pattern, label: string): void {
if (!matches(specimen, pattern)) {
const shape = typeof pattern === "object" && pattern !== null ? String(pattern) : JSON.stringify(pattern);
throw new TypeError(`${label}: must match ${shape}`);
}
}