shopify_draft_proxy/proxy/upstream_query

Single chokepoint that operation handlers use to ask Shopify a question.

The substrate exists so the parity runner can install a recorded cassette as the proxy’s upstream transport (with_upstream_transport) and have every per-operation upstream call deterministically replay from that cassette. Production callers leave the transport unset and fall through to the real HTTP shims (upstream_client.send_sync on Erlang; the JS-async path is not yet wired here — JS production handlers needing upstream must wait for a Promise-flavoured fetch helper, which lands when the first JS-only domain needs it).

There is intentionally no domain-wide hydration helper. Each operation calls fetch_sync itself and decides what to do with the response: persist into base state, use it once and discard, or transform it into the caller-facing reply. The choice is documented per-handler — see the per-domain migration playbook in docs/parity-runner.md.

Types

What can go wrong when asking upstream a question. TransportFailed reports the underlying network shim’s error message; HttpStatusError surfaces non-2xx responses (the caller decides whether to swallow or propagate); MalformedResponse covers JSON the proxy can’t parse; NoTransportInstalled is what JS production handlers see today (they can’t issue sync upstream calls because production fetch is async, and no async helper exists yet on this seam).

pub type FetchError {
  TransportFailed(message: String)
  HttpStatusError(status: Int, body: String)
  MalformedResponse(message: String)
  NoTransportInstalled
}

Constructors

  • TransportFailed(message: String)
  • HttpStatusError(status: Int, body: String)
  • MalformedResponse(message: String)
  • NoTransportInstalled

Shared upstream-call context. Bundles the three pieces every handler needs to issue an upstream GraphQL call: the optional SyncTransport (set by parity tests, unset in production), the origin to address, and the inbound request’s headers (so the proxy can forward auth tokens etc.). process_request builds one of these per inbound request and threads it into any handler that wants to reach upstream.

pub type UpstreamContext {
  UpstreamContext(
    transport: option.Option(upstream_client.SyncTransport),
    origin: String,
    headers: dict.Dict(String, String),
  )
}

Constructors

Values

pub fn empty_upstream_context() -> UpstreamContext

Context whose fetch_sync calls fall through to the live HTTP shim on Erlang and fail with NoTransportInstalled on JS. Useful for tests and callers that don’t have headers or origin in scope.

pub fn fetch_sync(
  origin: String,
  transport: option.Option(upstream_client.SyncTransport),
  inbound_headers: dict.Dict(String, String),
  operation_name: String,
  query: String,
  variables: json.Json,
) -> Result(commit.JsonValue, FetchError)

Synchronous upstream call. Returns the parsed response body as a commit.JsonValue AST so callers can walk it cheaply.

variables is a Json tree (the write-only kind produced by json.object, json.string, etc.). The body sent upstream is the canonical {"operationName":..,"query":..,"variables":..} envelope.

On Erlang the real HTTP shim is used as the fallback when no transport is installed. On JS, no fallback exists yet — call sites that need upstream from JS must install a SyncTransport (cassette) or use a future async helper.

Search Document