Release candidate — 1.0.0-rc.7
StitchAPI
GuidesData shaping

transform

Reshape a response before pick and validation — and reshape a request on the way out, through its input schema.

Use transform when the raw response isn't yet the shape the rest of the pipeline expects — scrape HTML text into a structured object, or flatten an odd envelope — so a single function reshapes the body before pick and validation ever see it.

Example

import {  } from 'stitchapi';

const  = ({
    : 'https://api.example.com',
    : '/listings',
    // The body is `unknown` — narrow it, then reshape the raw envelope into a
    // plain array before pick and validation run.
    : () => ( as { : unknown[] }).,
});

The raw body arrives as a wrapped { results: [...] }; transform returns the inner array, and everything downstream works against that array instead.

Options

transform?: (body: unknown) => unknown runs first in the response pipeline — before pick and before validation (and drift). That ordering is the point: reshape a raw body — scrape HTML text into a structured object, or flatten an odd envelope — into the shape pick and validation are written for, rather than bending them around the source's quirks.

body is typed unknown, so narrow it before reading (a cast or a type guard) and return any reshaped value. See Reference → Config types for every field.

Anti-pattern: don't reach for transform to pull the inner field out of an envelope — a cast like reading .results off the body is an opaque function whose output is unchecked, so a wrong narrowing slips past and only surfaces later as drift. Name the path with pick instead, which is declarative, round-trips as JSON, and feeds validation and drift directly. See pick.

Reshaping the request

transform is a response slot. To reshape a body on the way out — your field names in, the API's on the wire — put the mapping in the input schema. A declared slot doesn't only validate: the request is built from the value the schema returned, so a schema that transforms is a request transform.

import {  } from 'stitchapi';
import {  } from 'zod';

const  = ({
    : 'POST',
    : 'https://api.example.com',
    : '/users',
    : {
        // Callers pass the house shape; the API's shape goes on the wire.
        : 
            .({ : .(), : .() })
            .(() => ({
                : .,
                : ..(),
            })),
    },
});

await ({ : { : 'mango', : new () } });

The call argument is typed from the schema's input side, so callers keep { userName, joinedAt } and never see { user_name, joined_at }. The mapping lives on the stitch once, instead of at every call site.

It runs in the right place, too: input schemas resolve before the request is built, so the reshaped body is what auth signs and what the cache key is derived from. Every slot works this way — params, query, body, headers, and GraphQL variables — and each is transformed independently.

Why transform is response-only

An output schema can reshape too, by the same rule: the stitch serves the value the schema returned. So why does transform exist as its own slot — and why only on the response?

Because on the response side the reshaping has to happen somewhere a schema can't reach, and has to stay visible to machinery a schema would hide it from:

  • Ordering. transform runs before pick, so pick addresses the reshaped body. A schema's own transform runs at validation — after pick has already read the raw shape.
  • Drift. Drift is anchored on the difference between the raw body and the validated value. A transform inside a drift() schema sits on the validated side, so every field it renames is reported as drift.
  • Caching. A transform is a declared opacity: a stitch that has one refuses to cache until you set cache.fingerprint.transform, because re-validation cannot detect that the transform itself changed. A transform buried inside output is invisible to that check, so it silently defeats it.

None of the three has a request-side counterpart. There is no request pick and no request drift, and the cache key is derived from the request after the input schema shaped it, so the reshaping is already folded in. A request transform slot would add a second spelling and no guarantee — which is why there isn't one.

The corollary for the response side: prefer transform over a transform inside output. Keeping the two apart is what keeps pick, drift, and the cache fingerprint honest about what reshaped the body.

See also

On this page