Skip to main content

Real-world patterns

ShapeWire works best when each transform has one visible responsibility and the complete pipeline converts one transport model into one application model.

Choosing the right transform

NeedTransform
Change transport field namesrename
Create an allowlisted modelpick
Remove a known set of fieldsomit
Fill missing or nullish valuesdefaults
Convert field representationsnormalize
Target one known nested objectat
Apply one transform to a collectionmapEach
Add or overwrite model metadatamerge
Run several transforms left to rightpipe

User response with a nested profile

The transport response contains private data, snake-case names, and inconsistent nested values:

const rawUser = {
user_id: 42,
full_name: 'Ada Lovelace',
profile: {
created_at: '2026-07-01',
newsletter: 'yes',
timezone: null,
},
password_hash: 'private',
};

Transform the root and profile independently:

const toUser = pipe(
omit(['password_hash']),
rename({user_id: 'id', full_name: 'name'}),
at(
'profile',
pipe(
rename({created_at: 'createdAt'}),
defaults({timezone: 'UTC'}),
normalize({createdAt: 'isoDate', newsletter: 'boolean'}),
),
),
);

const user = toUser(rawUser);

The application model is:

{
id: 42,
name: 'Ada Lovelace',
profile: {
createdAt: '2026-07-01T00:00:00.000Z',
newsletter: true,
timezone: 'UTC',
},
}

Paginated product response

A product endpoint returns pagination and item fields in transport formats:

const rawPage = {
page_no: '2',
total_count: '48',
results: [
{
product_id: 'p-1',
product_name: 'Desk',
price: '129.50',
in_stock: 'yes',
internal_cost: '70',
},
{
product_id: 'p-2',
product_name: 'Lamp',
price: 'invalid',
in_stock: null,
internal_cost: '20',
},
],
};

Define the item transform once, then apply it at the renamed collection path:

const toProduct = pipe(
omit(['internal_cost']),
rename({
product_id: 'id',
product_name: 'name',
in_stock: 'inStock',
}),
defaults({inStock: false}),
normalize({price: 'number', inStock: 'boolean'}),
);

const toProductPage = pipe(
rename({page_no: 'page', total_count: 'total', results: 'items'}),
normalize({page: 'number', total: 'number'}),
at('items', mapEach(toProduct)),
);

const page = toProductPage(rawPage);

The final page model is:

{
page: 2,
total: 48,
items: [
{id: 'p-1', name: 'Desk', price: 129.5, inStock: true},
{id: 'p-2', name: 'Lamp', price: null, inStock: false},
],
}

Account model with application metadata

The final example removes transport-only fields, applies a default plan, converts a count, and adds fixed provenance metadata:

const rawAccount = {
account_id: 7,
display_name: 'Research',
plan_code: null,
member_count: '12',
debug: 'trace-123',
};

const toAccount = pipe(
omit(['debug']),
rename({
account_id: 'id',
display_name: 'name',
plan_code: 'plan',
member_count: 'memberCount',
}),
defaults({plan: 'free' as const}),
normalize({memberCount: 'number'}),
merge({source: 'crm' as const, selected: false as const}),
pick(['id', 'name', 'plan', 'memberCount', 'source', 'selected']),
);

const account = toAccount(rawAccount);

The resulting model is:

{
id: 7,
name: 'Research',
plan: 'free',
memberCount: 12,
source: 'crm',
selected: false,
}

These examples intentionally stop at data shaping. Fetching, validation, permissions, pricing decisions, localization, and rendering remain outside ShapeWire's documented scope.

Continue with the focused mapEach guide or merge guide for deeper collection and multi-source patterns.