Skip to main content

Scope and non-goals

ShapeWire has one job: turn raw transport data into predictable application models.

Use it at the boundary where an API response enters application code. A ShapeWire transform should be synchronous, deterministic, and concerned with the shape or representation of that data.

In scope

ShapeWire handles:

  • Structural transformation: select, remove, rename, or merge fields.
  • Field mapping: translate transport names such as user_id into application names such as id.
  • Defaults and null handling: replace missing or nullish values with explicit application defaults.
  • Value normalization: coerce inconsistent primitives into stable numbers, booleans, ISO date strings, or other field representations.
  • Nested targeting: apply a transform at one explicit object path with at.
  • Collection mapping: apply one item transform across an array with mapEach.
  • Pipeline composition: combine small synchronous transforms with pipe.

For example, this belongs in ShapeWire:

const toUser = pipe(
omit(['password_hash']),
rename({user_id: 'id', created_at: 'createdAt'}),
defaults({role: 'viewer' as const}),
normalize({createdAt: 'isoDate', balance: 'number'}),
);

Non-goals

ShapeWire is not:

  • A schema validator or recursive schema parser.
  • An HTTP client, cache, query library, or data-fetching layer.
  • A UI renderer or state-management library.
  • A localization or internationalization system.
  • A general-purpose date parsing and formatting toolkit.
  • A business-rules engine or place for domain calculations.
  • A collection of unrelated utilities such as deep cloning, grouping, sorting, or debouncing.

Validate untrusted input with a schema library before or beside the boundary transform. Fetch and cache data with a request library. Apply permissions, pricing rules, eligibility decisions, and other business policy in domain code.

Normalization is not presentation

Normalization chooses a stable application representation. Presentation formatting chooses how that representation appears to a particular user.

Converting "12.50" to 12.5, "yes" to true, or a transport timestamp to an ISO string is normalization. Rendering 12.5 with a user-selected currency and locale, displaying a date in a time zone, or translating a status label is presentation formatting and belongs closer to the UI.

The built-in currency:CODE normalizer is a small convenience for APIs that need an immediately displayable currency string. It uses the runtime's default locale; it is not a localization system. If locale, numbering rules, or user preferences matter, normalize the value with number and format it later with Intl.NumberFormat or an i18n library.

Custom callback or external library?

Use a custom normalizer for a local, synchronous field conversion:

normalize({
tags: value =>
typeof value === 'string' ? value.split(',').map(tag => tag.trim()) : [],
});

Use an external library or application service when the work requires:

  • Runtime schema validation and useful validation errors.
  • Locale-aware number, currency, or message formatting.
  • Time-zone-aware date parsing, arithmetic, or display.
  • Recursive object parsing.
  • Network access or asynchronous enrichment.
  • Business decisions that depend on domain rules.

A callback does not move those concerns into scope. It is an extension point for transport normalization, not a replacement for validation, localization, or domain architecture.

For concrete workflows, see Real-world patterns. For invalid and missing input outcomes, see Failure behavior.