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.