Documentation / @etherfold/processor-entities / openForWriting
Function: openForWriting() ​
openForWriting(
store,options?):Promise<WritableStateStore>
Defined in: packages/state-store/dist/store.d.ts:431
CLAIM a store, and get back the handle that may write to it.
const store = await openForWriting(await createBrowserStateStore(processor.entities));
await store.applyBlock(block, mutations, cursor);What it DOES, in order ​
It migrates (the same reason openSnapshotAware does: claiming is a write, and a store that has not been migrated has nothing to write to), then it takes the claim by clearing the seam's writerClaim record, which changes nothing and is a mutation, so a backend that enforces a single writer SWAPS its stored token there. That record lives in the seam's OWN keyspace (records.ts), so the act of claiming is invisible in the cursor port a caller writes to -- which is not merely tidy: while it was a reserved cursor key, a caller that chose the same name was clearing its own progress marker every time a writer opened. An earlier writer's next mutation is then refused (StoreWriterChangedError), and it is refused from the moment this call returns rather than from the moment this writer gets round to writing.
It does not QUEUE and it does not wait its turn. There is no lease: a loser is not waiting behind anybody, it has lost. A writer whose claim is taken learns so on its next mutation and demotes itself to a reader.
It does, however, await ONE round trip to the storage -- the mutation that takes the claim -- and a storage that never answers makes this call hang. That is not hypothetical: on WebKit a database can be left permanently unable to run any transaction, in which case the clear never settles and an application sits with nothing to render and nothing to act on (work/notes/findings/webkit-does-not-abort-a-terminated-workers-indexeddb-transaction.md). Hand a signal to bound it:
try {
const store = await openForWriting(backend, {signal: AbortSignal.timeout(10_000)});
} catch (error) {
if (error instanceof StoreClaimAbandonedError) {
// the storage did not answer. Render something; offer a rebuild.
}
}The bound is the caller's and never this seam's. No timeout is invented here and none is defaulted, because there is no number that is right for a cold mobile browser, a contended database and a server at once, and because turning "slow" into "failed" on a guess is how a working deployment acquires a mystery. What abandoning does NOT do is cancel the claim: the mutation was issued and may still commit, so a later openForWriting on the same store joins the same attempt rather than issuing a second one.
It is IDEMPOTENT per store instance, and that is load-bearing ​
A second call on the same instance returns the SAME handle and does not claim again. A host MAY hand ONE store instance to several generations (createState: () => store), and then, if each generation claimed independently, building a successor would invalidate the canonical generation and the guard would refuse the process against ITSELF. One storage, one claim. (Generations that fold SIDE BY SIDE need their own storage, a table namespace or a database each, or they collide on rows and on the sync cursor; that is what the shipped hosts do. Sharing one instance is legal, not the pattern to copy.)
Construction, not a lease ​
The claim belongs to the store INSTANCE and is released by nothing, so there is no expiry to tune, no held state a crash can strand, and no answer needed to "what does a dead lease do mid-fold". A demoted writer builds a new store and opens that, which forces the re-read correctness wants anyway. A handle that has already LOST cannot re-claim through here: the backend never re-mints a claim it has committed (ADR-0075), so the clear is refused and that refusal travels out of this call.
What it takes: a BACKEND, never a store narrowed to its reads ​
The argument is StateStoreBackend, so a value already narrowed to StateStore cannot be handed here and widened back. That is the point of the narrowing: a reader that could re-open its handle for writing would have the type say nothing at all. What a demoted writer does instead is what ADR-0077 already says it does -- build a NEW store and open that, which forces the re-read correctness wants anyway.
Parameters ​
store ​
options? ​
OpenForWritingOptions
Returns ​
Promise<WritableStateStore>