Defjs

Keep GraphQL as HTTP 200 + application unwrap; Core does not ship a GraphQL protocol.

Unwrap a GraphQL HTTP envelope

Defjs does not implement GraphQL errorPolicy. Model the wire as an HTTP POST whose builder places the query document and variables. Declare envelope data as .nullish() so both missing data and data: null reach your helper, then unwrap errors[] before checking data.

See Design Decisions.

import {
  createClient,
  defineRequest,
  struct,
  withEndpoint,
  withHTTPHandle,
  type Client,
} from "@defjs/core";

const VIEWER_QUERY = "query Viewer { viewer { id login } }" as const;

const runViewer = defineRequest({
  method: "POST",
  path: "/graphql",
  builder: () => ({
    body: { query: VIEWER_QUERY, operationName: "Viewer" },
  }),
  output: struct.object({
    data: struct
      .object({
        viewer: struct
          .object({ id: struct.string(), login: struct.string() })
          .null(),
      })
      .nullish(),
    errors: struct
      .array(struct.object({ message: struct.string() }))
      .optional(),
  }),
});

async function loadViewer(client: Client) {
  const [error, result] = await runViewer(client);
  // A Defjs fault is outside the HTTP 200 envelope this helper returns.
  if (error) throw error;
  if (result.errors?.length) {
    throw new Error(result.errors.map((item) => item.message).join("; "));
  }
  if (!result.data)
    throw new Error("GraphQL response contained neither data nor errors");
  return result.data.viewer;
}

let calls = 0;
const responses = [
  { data: { viewer: { id: "v-1", login: "ada" } } },
  { errors: [{ message: "viewer unavailable" }] },
  { data: null, errors: [{ message: "viewer unavailable" }] },
];
const client = createClient(
  withEndpoint("https://api.example.test"),
  withHTTPHandle(async () => {
    calls += 1;
    return Response.json(responses[calls - 1], { status: 200 });
  }),
);
const viewer = await loadViewer(client);
const graphQlErrors: string[] = [];
for (let attempt = 0; attempt < 2; attempt += 1) {
  try {
    await loadViewer(client);
  } catch (error) {
    graphQlErrors.push(error instanceof Error ? error.message : String(error));
  }
}
console.log(viewer?.login, ...graphQlErrors);
ada viewer unavailable viewer unavailable

HTTP success only means the envelope arrived. Application errors stay in errors[] until your unwrap throws or returns, including a response with data: null. The example handles both missing data and data: null with errors[] as application errors, not Struct mismatches.

if (error) throw error adapts a Defjs fault to that helper. HTTP 200 GraphQL failures stay application errors in errors[] and are thrown on their own.

Edit this page on GitHub
Released under the MIT License. · Copyright © 2026 MunMunMiao

On this page