Skip to content

feat(types): type the __dbis__/seq store so sync get() misuse is a compile error (#484 follow-up) #485

Description

@kriszyp

Problem

We have now fixed the store.get() RocksDB MaybePromise misuse three times by hand:

Each was a non-awaited someStore.get(key) whose result is consumed synchronously (?.field, truthiness, ?? default-as-object, for…of). On a block-cache miss get() returns a Promise — truthy, no fields, not iterable — so the code silently takes the wrong branch once the store grows past block-cache size or right after a cold restart. A manual grep audit (the one that produced #484) confirmed the active paths are currently clean, but a grep is a snapshot: nothing stops the next reader from reintroducing it.

#477 already proved the durable fix for one store: type the store honestly so synchronous consumption of get() is a compile error.

Proposed work

Extend the #477 pattern to the __dbis__ / seq store (the one #484 just fixed at runtime).

Mirror NodeStore from #477: a narrow typed handle that constrains only the read path and lets everything else fall through an index signature (no broad retype / blast radius):

type DbisStore = {
  // get() is a MaybePromise on RocksDB — a cache miss returns a Promise.
  get(key: DbisKey): MaybePromise<DbisValue>;
  getSync(key: DbisKey): DbisValue | undefined;
  [k: string]: any; // put/remove/getRange/etc. fall through
};

DbisValue is the seq row ({ seqId, nodes, lastTxnTime }) ∪ copyCursor ({ copyStartTime, currentTable, afterKey }) ∪ residency, etc. The point is that any synchronous use of get() (store.get(k)?.seqId, if (store.get(k)), spreading it) becomes a TS error, forcing getSync(k) or await.

Design decision to settle (input wanted)

  1. harper-pro boundary only (minimal): type tableSubscriptionToReplicator.dbisDB (currently reached off an any) as DbisStore. Smallest change; covers exactly where the bites have happened (replication).
  2. core-level (most durable): type the dbisDb store where it's created in core (resources/Table.ts / wherever RocksDatabase is exposed as dbisDb) so every consumer — core and harper-pro — gets the compile-time guard. The core audit (fix(replication): use getSync for seq/copyCursor resume-cursor reads (RocksDB MaybePromise) #484) found core's dbisDb reads already disciplined (all getSync), so this is purely a guard-rail, but it's the one that prevents the next core reader from regressing.

Recommendation: (2) if the core dbisDb surface can be typed without a large blast radius; otherwise (1) as a first step. The same reasoning extends to typing primaryStore / system-table stores generally (the broader "stores are typed as lmdb Database (sync get) or any, but are RocksDB at runtime" root cause noted in the #484 audit) — but that can be a later increment; this issue is scoped to the __dbis__/seq store.

Acceptance criteria

The lesson this closes

#477's write-up captured it well: the manual audit under-classified defensive readers as safe because a Promise doesn't throw and is truthy. Typing catches all of them deterministically, no per-site judgment. This issue applies that to the store that caused #484 so we stop finding these one at a time.

Refs: #484 (runtime fix this hardens), #477 (the hdb_nodes typing precedent), #476 / #474 (original hdb_nodes sweep).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions