Skip to content

Documentation / @etherfold/browser / PortCases

Type Alias: PortCases ​

PortCases = object

Defined in: packages/browser/src/host/envelope.ts:417

EVERY SURFACE THE PORT CARRIES, as a map from case name to what it takes and what it answers.

A later task adds a key and gets its request typed, its response typed and its clone-safety checked, with no change to the transport, the correlation or either end's plumbing -- which is the property ADR-0082 asked for, and which the four reads below are the first demonstration of.

The store's FOUR reads, as four cases ​

One case per read rather than one read case with a verb inside it: the requests differ in what they carry (an id or a prefix, with or without a block), so a single case would be a union a host has to narrow by hand, and the envelope's own dispatch already is that narrowing. The names are the seam's own (getCurrent / getAsOf / listCurrent / listAsOf), so there is one vocabulary from the store, through the port, to the surface a tab holds.

There are FOUR and there will not be a fifth: the seam has no predicate and no ordering, because a handler runs once per event on a substrate with no query planner (ADR-0021). Richer queries arrive on this same port as the EXECUTOR the-same-query-runs-against-a-worker-and-a-server defines, which owns its own serialisation -- not as more methods on this proxy.

The LIFECYCLE calls, as cases of their own ​

startIndexing, stopIndexing, reconfigure, generations and promotion are the control half of ADR-0082's "status and control" surface, and they are REQUESTS WITH ANSWERS rather than fire-and-forget messages: a tab that asks for a reconfigure learns whether it was accepted, what it created, or -- through the refusal path -- why not. There is deliberately no case that advances the fold by ONE step: a round trip per cycle is the polling this port replaced, and the driver that does the advancing lives in the host.

Properties ​

checkTxInclusion ​

readonly checkTxInclusion: object

Defined in: packages/browser/src/host/envelope.ts:585

DOES THE INDEXED STATE ALREADY ACCOUNT FOR THESE TRANSACTIONS? One verdict per hash, from the window the host's canonical generation maintains.

A LIST in, a record out, because an app with a pending queue asks about the QUEUE: a call per transaction would be a round trip per transaction for a question every one of them answers off the same window, built once per call (indexWindow).

The verdict crosses WHOLE, and must never become a boolean ​

TxInclusionVerdict is @etherfold/core's own type, carried unchanged: a STATUS and the BASIS for it. Narrowing it at this boundary would destroy the distinction the type exists for -- unknown has two causes (not-synced, window-not-covering) and an app renders those differently from an honest absent, because dropping an optimistic update on "I cannot tell" is the double-count this whole surface exists to prevent.

minedAtBlock crosses PER QUERY and is not decoration: the window is SPARSE, so absent means only "not in the window", and a caller holding a RECEIPT closes that through the below-window branch. A tab is exactly where a caller has a receipt.

A SNAPSHOT, and deliberately not a subscription ​

It is answered against the state at the moment of the call, so an app watching a transaction ASKS AGAIN rather than being handed something live. The window moves as the fold advances and as reorgs are concluded, and a verdict pushed at a tab would be a second progress signal with the same cadence as the one already on this port -- progress is what tells an app that asking again is worth it.

request ​

readonly request: object

request.queries ​

readonly queries: readonly TxInclusionQuery[]

response ​

readonly response: Record<string, TxInclusionVerdict>


connect ​

readonly connect: object

Defined in: packages/browser/src/host/envelope.ts:462

WHAT THE TAB HANDS ITS HOST WHEN IT CONNECTS: the cloneable settings, and the chain as a MessagePort speaking @eip-1193/over-port (ADR-0082, amended).

Sent by the port itself, FIRST, on every host it obtains (the first one and every restart), so it is ahead of anything else on the wire and a restarted host is told again what the dead one was told. The provider port rides in the TRANSFER list, which is why this is the one request whose payload is not a plain clone.

Answered with where the fold is, or refused naming the fields that disagree (HostSettingsConflictError). A main-thread host refuses it outright: it takes its provider and settings from init, on the thread that holds them.

request ​

readonly request: object

request.provider? ​

readonly optional provider?: MessagePort

request.settings ​

readonly settings: HostSettings

response ​

readonly response: HostProgress


declarations ​

readonly declarations: object

Defined in: packages/browser/src/host/envelope.ts:650

WHAT THE HOST'S STORE WAS BUILT WITH, so a tab's surface can be checked against it.

The same-thread surface compares its declarations with the store's at CONSTRUCTION and refuses a disagreement naming both (assertDeclaredBy), because a surface generated from a stale copy types its rows off columns the store does not have. A port cannot answer that question synchronously, so it is asked here -- once, when a surface is built -- and the refusal lands on the first read instead of on the constructor.

request ​

readonly request: undefined

response ​

readonly response: readonly NormalizedEntity[]


generations ​

readonly generations: object

Defined in: packages/browser/src/host/envelope.ts:542

EVERY GENERATION THIS HOST HOLDS, in the order it built them, with the one answering reads marked.

Asked rather than pushed: a generation list changes when a caller RECONFIGURES or a pointer moves, which is a handful of times in a session, and pushing it would be a second signal to keep in step with one nobody is watching between reconfigures.

request ​

readonly request: undefined

response ​

readonly response: readonly HostGeneration[]


getAsOf ​

readonly getAsOf: object

Defined in: packages/browser/src/host/envelope.ts:657

One entity as of a block NUMBER. Refused, never answered from the tip, outside retention.

request ​

readonly request: object

request.at ​

readonly at: number

request.entity ​

readonly entity: string

request.id ​

readonly id: EntityId

response ​

readonly response: PortRow | undefined


getCurrent ​

readonly getCurrent: object

Defined in: packages/browser/src/host/envelope.ts:652

One entity at the tip, or undefined if it is absent.

request ​

readonly request: object

request.entity ​

readonly entity: string

request.id ​

readonly id: EntityId

response ​

readonly response: PortRow | undefined


letGo ​

readonly letGo: object

Defined in: packages/browser/src/host/envelope.ts:508

THIS TAB IS LETTING GO: quiet the host if nothing else holds it, and say whether it did.

Posted by the port itself on its way out (IndexerPort.close), never by an app, and deliberately NOT a stopIndexing: that one is an app asking for the fold to stop, meant for the host, and a SharedWorker host must honour it for every tab. This one is a tab leaving, and a host several tabs hold must not stop for it while the others stay.

The HOST answers it as a stop, always, because a host is never told how many tabs hold it (ADR-0082): what reaches it is only what its endpoint forwards. So the decision is the shape's. A dedicated worker, a main-thread wire and the LAST tab of a SharedWorker forward it, and the host stops once the cycle in flight has landed and answers quiesced: true, which is the only quiet a shape may kill a host on (HostAccess.close). A SharedWorker tab that is NOT the last is detached where it attached (sharedWorker.ts) and answered quiesced: false there, and the fold goes on for the others.

request ​

readonly request: undefined

response ​

readonly response: object

response.quiesced ​

readonly quiesced: boolean


listAsOf ​

readonly listAsOf: object

Defined in: packages/browser/src/host/envelope.ts:667

The same listing as of a block NUMBER.

request ​

readonly request: object

request.at ​

readonly at: number

request.entity ​

readonly entity: string

request.limit ​

readonly limit: number

request.prefix ​

readonly prefix: EntityIdPrefix

response ​

readonly response: Listing<PortRow>


listCurrent ​

readonly listCurrent: object

Defined in: packages/browser/src/host/envelope.ts:662

The rows of an id PREFIX at the tip, ascending, bounded by a REQUIRED limit.

request ​

readonly request: object

request.entity ​

readonly entity: string

request.limit ​

readonly limit: number

request.prefix ​

readonly prefix: EntityIdPrefix

response ​

readonly response: Listing<PortRow>


ping ​

readonly ping: object

Defined in: packages/browser/src/host/envelope.ts:434

ARE YOU STILL THERE? The one case whose ANSWER is its entire content.

It carries nothing and answers nothing, because the only fact being asked for is that something answered at all. A port PROBES with it when its host has been silent (IndexerPortOptions.watch), and a probe that goes unanswered is how a tab learns the host died -- which is the only way it CAN learn, since no browser fires an event when it evicts a dedicated worker.

Deliberately not progress, which would answer this too. What is polled here is LIVENESS and never STATUS: status is PUSHED (ADR-0082), and a port that asked for a cursor on a timer would be the polling that decision exists to replace, with the cost merely moved to the other end of the wire.

request ​

readonly request: undefined

response ​

readonly response: undefined


progress ​

readonly progress: object

Defined in: packages/browser/src/host/envelope.ts:419

How far the fold has got. Takes nothing.

request ​

readonly request: undefined

response ​

readonly response: HostProgress


promotion ​

readonly promotion: object

Defined in: packages/browser/src/host/envelope.ts:552

THE PROMOTION POLICY IN FORCE, as the container resolved it.

Reported rather than re-derived, and NOTHING IS DEFAULTED AT THIS BOUNDARY: there is one default everywhere (on-catch-up), it lives with the type it belongs to, and a second copy of it in the browser -- or in the tab, one step further out -- is how two runtimes come to disagree about which value an app is running under (CONTEXT.md, canonical pointer).

request ​

readonly request: undefined

response ​

readonly response: UsedPromotionConfig


query ​

readonly query: object

Defined in: packages/browser/src/host/envelope.ts:686

ONE QUERY, handed to the handler the host's entry injected, and its answer (ADR-0099). GENERIC: the request and the answer are opaque here, because this package carries the case and never the query language (host/query.ts). The GraphQL executor on this port is workerExecutor in @etherfold/graphql/worker, which owns its own serialisation, so its answers are the bytes every other executor answers.

A host whose entry passed no handler REFUSES it, naming the missing handler.

request ​

readonly request: object

request.request ​

readonly request: unknown

response ​

readonly response: unknown


reconfigure ​

readonly reconfigure: object

Defined in: packages/browser/src/host/envelope.ts:529

RECONFIGURE THE SOURCE: fold it in a generation BESIDE the live one.

The SOURCE and nothing else, because the source is the only half of a generation that is DATA. A generation is a stream and a fold over it; the fold is code, so changing it is a new worker bundle rather than a message (ADR-0082), and the stream CONFIG is one mutable value on the one keeper a container holds, so it is not settable per generation (GenerationSpec.source).

What the host does with it is the generation machinery unchanged: the new generation folds beside the canonical one, which goes on answering every read, and the canonical pointer moves when the promotion policy says so -- on-catch-up once the new fold reaches the cursor the live one has, immediate at once, manual never on its own.

A REFUSAL crosses as the refusal it is (GenerationCapReachedError, which is the one this case meets), carrying its own fields: see PortError. What the new generation meets later, while it FOLDS, is reported on progress like any other driver failure.

request ​

readonly request: object

request.source ​

readonly source: IndexingSource<Abi>

response ​

readonly response: HostReconfigure


startIndexing ​

readonly startIndexing: object

Defined in: packages/browser/src/host/envelope.ts:477

START THE DRIVER, and answer where the fold is now.

Starting a host that is already indexing is an ANSWER and not a refusal: "index" is a state a caller asks for rather than an edge it triggers, so a settings screen that asks twice, or two components that each ask once, leave the host in the state they both asked for. What comes back says which state that is (HostProgress.indexing). One arriving while a STOP is still being honoured waits for that stop to land and then starts afresh, so the pair cannot race into a host that was asked to index and is not.

request ​

readonly request: undefined

response ​

readonly response: HostProgress


stopIndexing ​

readonly stopIndexing: object

Defined in: packages/browser/src/host/envelope.ts:488

STOP THE DRIVER, and answer where the fold stopped.

It ANSWERS WHEN THE CYCLE IN FLIGHT HAS FINISHED, which is what makes it honest: no chain request is made after this response is posted, and the cursor is where a completed cycle would have left it rather than somewhere half a cycle in. A cut-off cycle is not on offer at all -- the cursor is written in the same transaction as the block it describes (ADR-0027), so the consistent thing to do with an advance already under way is to let it land.

request ​

readonly request: undefined

response ​

readonly response: HostProgress


subscribeToProgress ​

readonly subscribeToProgress: object

Defined in: packages/browser/src/host/envelope.ts:602

START PUSHING progress to this tab, and answer where the fold is NOW.

The current value comes back as the RESPONSE rather than as a push chasing it, which is what makes a tab that attached half way through a fold correct immediately: there is no window in which it holds nothing, and no race between a first push and the answer to a question it also asked.

Named for WHAT it subscribes to rather than taking a topic argument, on the same ground the four reads are four cases: the envelope's dispatch already is that narrowing, and a later push would otherwise make one case's response type depend on its request's contents.

request ​

readonly request: undefined

response ​

readonly response: HostProgress


subscribeToStateMoved ​

readonly subscribeToStateMoved: object

Defined in: packages/browser/src/host/envelope.ts:630

START PUSHING THE state-moved signal to this tab, and answer NOTHING.

The pair beside subscribeToProgress, and the one place the two differ is the RESPONSE, which is empty here on purpose. Progress is a STATE -- "where is the fold now" has an answer at every instant, so a subscriber is handed it and is correct immediately. The signal is an EVENT: what it says is that a block was applied AT A MOMENT, and replaying the last one at a tab that attached afterwards would tell it something moved when nothing did, under a token it has never held (ADR-0083: best-effort, at-most-once, and the producer holds NOTHING per client).

So a tab that attaches part way through a fold is told nothing until the fold NEXT moves, and what it does in the meantime is READ -- through the four reads it already holds, which answer from where the fold is now. That is the same residual ADR-0083 records for every transport: a reader on a quiet chain learns nothing until a block moves, and buffering per client to close it is the one thing the signal must not do.

request ​

readonly request: undefined

response ​

readonly response: undefined


unsubscribeFromProgress ​

readonly unsubscribeFromProgress: object

Defined in: packages/browser/src/host/envelope.ts:610

STOP PUSHING progress to this tab.

The host stops POSTING, rather than the tab stopping listening: an unsubscribed tab that still received the messages would be paying the cost the push cadence exists to bound.

request ​

readonly request: undefined

response ​

readonly response: undefined


unsubscribeFromStateMoved ​

readonly unsubscribeFromStateMoved: object

Defined in: packages/browser/src/host/envelope.ts:638

STOP PUSHING the signal to this tab.

The host stops POSTING, exactly as unsubscribeFromProgress does, and with the same consequence one level down: the host lets go of its own subscription to the container's signal when the last tab lets go of this one.

request ​

readonly request: undefined

response ​

readonly response: undefined


visibility ​

readonly visibility: object

Defined in: packages/browser/src/host/envelope.ts:447

THIS TAB IS, OR IS NOT, ON SCREEN: document.visibilityState, told to the host so a worker host can take part in the tab election's FOREGROUND TAKEOVER (ADR-0097, D4 as amended), since a worker has no document of its own.

Sent by the port itself, from a scope that has a document, after the connect and on every visibilitychange (and again to a restarted host). Answered with nothing. A host that does not take part ignores it: the main-thread host (it reads the document it runs beside), a SharedWorker host (one indexer for every tab, not a background tab), and any host with the takeover off or no election at all.

request ​

readonly request: object

request.visibility ​

readonly visibility: TabVisibility

response ​

readonly response: undefined