Documentation / @etherfold/core / createDirectIngestion
Function: createDirectIngestion() ​
createDirectIngestion(
ingestion):IngestionTarget
Defined in: packages/core/src/directIngestion.ts:81
The ADR-0004 wire, with no wire.
createHttpIngestion puts a network between the two halves; this one puts nothing between them, so a single deployable can fetch and process in one place while keeping every property the split has. That is possible only because both sides of the contract are INTERFACES rather than an HTTP client and an HTTP route: the fetcher pushes into an IngestionTarget, the receiver implements LogIngestion, and this is the eighteen lines that make one the other.
What is kept, which is nearly all of it ​
The receiver is still authoritative about the cursor, still derives every reorg, and still refuses a batch that does not start where it says. The fetcher still holds no cursor, still asks before its first fetch, and is still corrected rather than crashed when it asks from the wrong place. None of that came from HTTP; it came from the contract, so none of it is lost by removing the transport.
What IS lost is only what the transport was carrying: a network hop, a shared secret, and the two failure modes that go with them (an unreachable server, a wrong token). A combined deployment has none of those, and the code that would have handled them costs nothing because it is keyed off errors that can no longer be thrown.
The one thing to get right ​
A cursor refusal must come back as a CursorCorrection and not as a throw. UnexpectedFromBlockError is the ONE resumable refusal in the contract, and a sender that received it as an exception would treat the ordinary case (a restart, a lost acknowledgement, a second fetcher) as a fault. The HTTP transport does this by mapping a 409; here it is done by recognising the error, STRUCTURALLY rather than with instanceof, for the same reason isRetryable is structural: two copies of this package in one dependency tree would otherwise turn the correction path into a crash, and it would happen only in the deployments that bundle awkwardly.
ONE RECEIVER, or WHICHEVER OF SEVERAL IS LIVE AT THE ASK ​
A single LogIngestion is the shape that has always been here and is what a caller holding one receiver passes. A LiveIngestions function is the other arm, and it is what a caller holding a GENERATION CONTAINER passes, because such a caller has no single receiver to hand over: a container holds several folds and only some of them have one -- a FOLLOWER re-folds a stored stream and is deliberately not addressable on the wire (ADR-0044) -- and WHICH of them is live moves while the process runs.
It is one function with a widened parameter rather than a sibling beside it, because there is ONE concept here ("the ADR-0004 wire, with no wire") and a second name for it would be a second thing to keep in step. On the resolving arm this routes exactly as the HTTP route does: the batch's own {source, config} selects among the live receivers, using @etherfold/core's own comparison, so a target cannot select a receiver that then refuses the batch.
What it will NOT do is invent an answer when nothing matches. See NoLiveReceiverError for why the empty case and the foreign-context case are the same refusal carrying different retryable flags.