Skip to content

Documentation / @etherfold/processor-entities / StateStore

Interface: StateStore ​

Defined in: packages/state-store/dist/store.d.ts:41

THE SEAM AS A CONSUMER HOLDS IT: the READS a store answers, plus what it declares and how it is opened.

This is the half a consumer needs to RENDER: the four reads, the bounded listing among them, the cursor read, the capability report, the declarations, and migrate. A value of this type cannot mutate the store, and that is the point -- "a reader cannot write" is a fact of the TYPE rather than a rule to remember, which is the move ADR-0044 already made for the stream seam by handing a follower a read-only stream view instead of asking it to behave. (Same structural move, OPPOSITE arbiter: a stream's writer is the OLDEST surviving generation, derived and never raced, while a state store's writer is the LAST claimant.)

migrate is here DELIBERATELY, and it is the one member that looks out of place. It is SCHEMA rather than data, it is idempotent, and it runs on every open -- including from createBrowserStateStore, which several tabs of one app all call. Putting it on the writing side would make merely OPENING a second handle take the store away from the writer that has it (ADR-0075 states the same exclusion for the writer token, for the same reason).

The three names, and which one you want ​

  • StateStore (this one) is what a CONSUMER holds: reads only.
  • StateStoreBackend is what an IMPLEMENTOR provides: this plus the five mutating verbs. A factory hands one over; nobody folds through one.
  • WritableStateStore is what a CLAIM hands back (openForWriting): a backend plus the token that says the claim was taken rather than assumed.

So the ability to mutate is obtainable only by claiming, and a mutation without a claim cannot be expressed (ADR-0077).

Extended by ​

Properties ​

capabilities ​

readonly capabilities: StateStoreCapabilities

Defined in: packages/state-store/dist/store.d.ts:43

What this store keeps and what it can answer. Readable before migrate.


declarations ​

readonly declarations: ReadonlyMap<string, NormalizedEntity>

Defined in: packages/state-store/dist/store.d.ts:45

The declared entities, after validation.

Methods ​

getAsOf() ​

getAsOf<T>(entity, id, at): Promise<T | undefined>

Defined in: packages/state-store/dist/store.d.ts:108

One entity as of a block NUMBER.

undefined means the entity was absent at that block. A store that cannot answer historical reads at all reports asOf: false and refuses rather than answering from the tip.

Type Parameters ​

T ​

T = Record<string, unknown>

Parameters ​

entity ​

string

id ​

EntityId

at ​

number

Returns ​

Promise<T | undefined>


getCurrent() ​

getCurrent<T>(entity, id): Promise<T | undefined>

Defined in: packages/state-store/dist/store.d.ts:81

One entity as it stands at the tip.

Type Parameters ​

T ​

T = Record<string, unknown>

Parameters ​

entity ​

string

id ​

EntityId

Returns ​

Promise<T | undefined>


listAsOf() ​

listAsOf<T>(entity, prefix, at, limit): Promise<Listing<T>>

Defined in: packages/state-store/dist/store.d.ts:100

The same listing as of a block NUMBER: the children that were live then.

Refused, not answered from the tip, by a store whose retention does not cover that block -- the same contract as getAsOf, for the same reason.

Type Parameters ​

T ​

T = Record<string, unknown>

Parameters ​

entity ​

string

prefix ​

EntityIdPrefix

at ​

number

limit ​

number

Returns ​

Promise<Listing<T>>


listCurrent() ​

listCurrent<T>(entity, prefix, limit): Promise<Listing<T>>

Defined in: packages/state-store/dist/store.d.ts:93

The rows whose declared id starts with prefix, at the tip, in ascending id order, at most limit of them. The id order is UTF-8 byte order, each column in turn (compareIds, ADR-0021), on every backend.

This is the derived collection a one-to-many is read through, and the REQUIRED limit is the whole reason it can be asked of any backend: the operation is a key-prefix range with a bound, which is an indexed range scan on every substrate. truncated says whether more matched, because a set that exactly fills the limit is otherwise indistinguishable from a cut-off one.

Type Parameters ​

T ​

T = Record<string, unknown>

Parameters ​

entity ​

string

prefix ​

EntityIdPrefix

limit ​

number

Returns ​

Promise<Listing<T>>


migrate() ​

migrate(): Promise<void>

Defined in: packages/state-store/dist/store.d.ts:53

Bring the storage to the declared shape. Idempotent, so it is safe on every boot.

It is NOT a mutating path in the writer-token sense and must never claim: opening a store is not writing to it, and every tab of one app opens.

Returns ​

Promise<void>


readCursor() ​

readCursor(key): Promise<string | undefined>

Defined in: packages/state-store/dist/store.d.ts:60

The cursor stored under key, or undefined if nothing was ever written there.

Opaque: whatever string the caller last wrote, byte for byte.

Parameters ​

key ​

string

Returns ​

Promise<string | undefined>


readRetentionEnforcement() ​

readRetentionEnforcement(): Promise<RetentionEnforcement>

Defined in: packages/state-store/dist/store.d.ts:79

Whether this store's retention is actually being ENFORCED against its storage: no floor to enforce, a floor and never pruned, or a floor and pruned to a block.

The second half of retention only happens if a host schedules it (prune, below), so a host that rolled its own loop and never does gets the refusals of a bounded store and the footprint of an unbounded one. This is what makes that state discoverable instead of silent.

It is ASYNCHRONOUS and separate from capabilities because the answer is DURABLE: a store pruned before the process died must not come back saying never, so it lives in storage, and the capability getter is synchronous and readable before the storage is even open. See enforcement.ts.

It is a read, so it never claims the writer token and never writes -- which is why it is on THIS side of the split and prune is not.

Returns ​

Promise<RetentionEnforcement>