/** * The FeltDB freshness capability. * * FeltDB does not require every runtime to produce a revision number, because * not every runtime has a serialization boundary that could make one * authoritative. `get_sequence()` looked like that number and is not: it is a * hardcoded `0` in the authority runtime and counts only this handle's writes * elsewhere, so a cache built on it would look correct with one writer and * serve stale data the moment a second one exists. * * The contract instead makes freshness a capability a runtime advertises, and * holds it to one rule: * * A runtime may advertise revision-based cache validation only if its * revision is authoritative for the consistency domain it represents. * * A runtime that cannot say that says so, and callers fall back to explicit * refresh. Declining is a correct answer here; fabricating a scalar is not. * * See docs/architecture/collection-freshness.md for the decision and * docs/architecture/freshness-revision-investigation.md for the evidence. */ import type { JsDb } from './feltdb.js'; /** * The consistency domain a runtime is operating in. * * This is a property of the deployment, not of the storage engine: the same * runtime moves between domains when replication is switched on. */ export type ConsistencyDomain = /** One process, one handle graph. Nothing writes this state from outside. */ 'single-process' /** A single server serializes every commit behind one lock. */ | 'single-authority' /** One storage engine serializes competing local writers (IndexedDB across tabs). */ | 'local-first' /** A shared directory with no serialization authority over its writers. */ | 'multi-process-file' /** Causally ordered replicas. There is no global sequencer, by design. */ | 'replicated' /** The runtime did not declare one. Treated as the least trustworthy case. */ | 'undeclared'; /** * A committed-state revision. * * `value` is meaningful **only** against another revision carrying the same * `scope`. That restriction is the whole point: it is what stops a scalar from * being compared across replicas, where the authoritative order is partial and * two replicas can each reach "revision 7" holding different state. */ export interface Revision { value: number; scope: string; } export interface FreshnessCapability { domain: ConsistencyDomain; /** * How a cache holder is permitted to decide its copy is current. * * - `revision`: ask for the current revision and compare it against the one * the cache was built from. Cheap, and it answers "am I stale?". * - `refresh`: re-read the state. It is the only honest answer when no * authoritative revision exists. */ validation: 'revision' | 'refresh'; /** * Whether the runtime pushes a change signal. * * Invalidation tells a cache that it *might* be stale. It is never proof * that a cache *is* current, and it never upgrades `validation`. */ invalidation: boolean; /** Comparison scope for revisions. Present exactly when validation is `revision`. */ revisionScope?: string; /** Why revision validation is not offered. Present exactly when validation is `refresh`. */ reason?: string; } export declare class FreshnessContractViolation extends Error { constructor(message: string); } /** * Check a capability against the rule before anyone acts on it. * * A runtime that claims revision validation without being able to produce a * revision is worse than one that claims nothing, so this throws rather than * degrading quietly. The failure belongs to whoever wrote the runtime. */ export declare function assertFreshnessCapability(capability: FreshnessCapability, db: Pick): FreshnessCapability; /** Ask a runtime what freshness it can honestly support, and hold it to the rule. */ export declare function describeFreshness(db: Pick): Promise; /** * Read a revision, but only from a runtime that advertised one. * * The capability is consulted on every read rather than cached, because a * runtime's domain can change underneath a caller: switching on replication * revokes revision authority, and a cache that had already captured the * capability would keep trusting a number that stopped meaning anything. * * This is the accessor callers should use. Calling `db.revision()` directly * skips the check that makes the answer trustworthy. */ export declare function readRevision(db: Pick): Promise; /** * Compare two revisions from the same scope. * * Throws on a scope mismatch instead of returning a comparison. Two revisions * from different scopes are not ordered, and silently picking an answer is * exactly the mistake a scalar revision invites once replication exists. */ export declare function isNewer(candidate: Revision, baseline: Revision): boolean; /** * Whether a cache built at `cached` is still current against `current`. * * Deliberately not "is it newer": equal revisions mean current, and anything * else — including a revision that moved backwards, which should not happen — * means refetch. */ export declare function isCacheCurrent(cached: Revision, current: Revision): boolean; //# sourceMappingURL=freshness.d.ts.map