pi-mono/packages/chord/src/next/store/claims.ts
Mario Zechner 14b4c56e57 feat(chord,durable): Chord v2 slice 6b, Durable and jobs on the store
StoreStorage (Durable's storage on Chord v2's store, @earendil-works/pi-durable/storage/store) passes
Durable's storage conformance suite and runs Durable's harness tests (npm run test:store). The store
gets owner names and claims, one id allocator, Durable's Storage semantics for refused steps, history
none/latest/rewindable with reads at a point, archive revisions and applyBatch. self.follow follows
Durable's synchronous observers as Chord sources. Persisted jobs: records and state in the owner's
store, restarts as Durable's tasks, abort stored, deferred state. Chord's ./next export returns for
the adapter surface.
2026-10-28 19:18:30 +00:00

57 lines
2.3 KiB
TypeScript

// Claims (slice 6b "owner names and claims", "claims per table and per document namespace"): what can collide in a
// store is claimed once, in memory: a table by its SQL name, documents by `(owner, namespace)`. A claim is held by its
// claimant (an object, a `StoreStorage`) until its end releases every claim it holds. While a batch applies, nothing
// is claimed: batch apply and owners exclude each other both ways.
import { invalid } from "../data/internal.ts";
/** Who holds claims: named in the refusal of a second claim. */
export type Claimant = { readonly label: string };
/** A store's claims, by key; one claimant per key. */
export class Claims {
readonly #holders = new Map<string, Claimant>();
readonly #keys = new Map<Claimant, string[]>();
/** Whether a batch applies now: no claim is taken meanwhile. */
#locked = false;
/** Whether any claimant holds a claim (batch apply is refused then: its memory would go stale). */
get held(): boolean {
return this.#keys.size > 0;
}
/** Claims table `table` (its SQL name) for `claimant`; held by another: `invalid/args`. */
table(table: string, claimant: Claimant): void {
this.#claim(`table ${JSON.stringify(table)}`, claimant);
}
/** Claims the documents of `owner` in `namespace` for `claimant`; held by another: `invalid/args`. */
documents(owner: string, namespace: string, claimant: Claimant): void {
this.#claim(`documents ${JSON.stringify([owner, namespace])}`, claimant);
}
/** Refuses every claim (`invalid/args`) until the returned unlock: a batch applies meanwhile. */
lock(): () => void {
this.#locked = true;
return () => {
this.#locked = false;
};
}
/** Releases every claim `claimant` holds. */
release(claimant: Claimant): void {
for (const key of this.#keys.get(claimant) ?? []) this.#holders.delete(key);
this.#keys.delete(claimant);
}
#claim(key: string, claimant: Claimant): void {
if (this.#locked) throw invalid("args", `store: ${key} cannot be claimed while a batch applies`);
const holder = this.#holders.get(key);
if (holder === claimant) return;
if (holder !== undefined) throw invalid("args", `store: ${key} is claimed by ${holder.label}`);
this.#holders.set(key, claimant);
const keys = this.#keys.get(claimant);
if (keys === undefined) this.#keys.set(claimant, [key]);
else keys.push(key);
}
}