Failure behavior
ShapeWire transforms are synchronous data-shaping functions, not validators. They do not collect errors or return result objects. A transform either produces its documented result, returns an unchanged value in the cases listed below, or lets an exception propagate.
At a glance
| Transform | Missing or nullish data | Invalid or incompatible data | Conflicts and errors |
|---|---|---|---|
pipe | Input handling belongs to the first stage. | Incompatible adjacent stages are rejected by TypeScript. | A thrown stage error stops the pipeline and propagates. |
rename | A mapped key absent from the input produces no field. Root null or undefined is outside the object contract and throws at runtime. | The typed API requires an object and property-key destinations. | When several source keys target one key, the last visited source value wins. |
pick | Missing requested keys are ignored. | Nullish and primitive roots throw TypeError; arrays and functions are treated as objects at runtime. | Duplicate keys are copied once. Later transforms see only selected fields. |
omit | Missing omitted keys are ignored. | Nullish and primitive roots throw TypeError; arrays and functions are treated as objects at runtime. | Duplicate omissions have no additional effect. Later transforms see only retained fields. |
defaults | Missing, null, and undefined fields receive fallbacks. | The public input and fallback contracts require objects. | Existing false, 0, NaN, and '' values win over fallbacks. |
normalize | Missing configured fields are normalized from undefined; built-ins produce null, while callbacks decide their own result. | Invalid built-in values produce null. Unsupported currency codes throw RangeError. | Callback and formatter errors propagate. |
merge | Missing source fields have no effect; nullish field values are copied normally. | The source and pipeline input must satisfy the object contract. | Right-hand fields win, including undefined; source-factory errors propagate. |
mapEach | Root null or undefined produces []. | Any other non-array input throws TypeError at runtime. | An item-transform error stops mapping and propagates. |
at | Missing paths and nullish or primitive targets return the original root reference. | Empty or malformed paths also return the original root. Arrays are valid object targets. | The nested transform is not called for an unusable target; if called and it throws, the error propagates. |
Public contracts and bypassed types
Object transforms are typed for object records. mapEach is typed for arrays or nullish input. These constraints are part of the API.
JavaScript callers can bypass them with any, an unsafe cast, or untyped code. Only the explicit runtime outcomes in this page are stable behavior. Incidental coercion outside a public contract—for example, spreading a primitive into an object—is not a validation feature and should not be relied on.
pipe
pipe runs stages from left to right. TypeScript validates that every stage can accept the preceding output and reports the incompatible stage position during compilation.
const parseCount = (value: string) => Number(value);
const requiresBoolean = (value: boolean) => !value;
// @ts-expect-error Stage 2 cannot accept the number produced by Stage 1
pipe(parseCount, requiresBoolean);
At runtime, pipe does not catch exceptions. If a custom callback, source factory, or transform throws, later stages do not run and the same error reaches the caller.
Object key transforms
rename, pick, and omit operate on own enumerable fields.
renameignores mappings whose source key is absent. If two present source keys map to the same destination, normal property iteration order applies and the later value replaces the earlier one.pickignores missing and duplicate requested keys.omitignores missing and duplicate omissions.- Composing
pickandomitis left-to-right. A field removed by one stage is simply absent for the next stage.
rename({first_name: 'name', display_name: 'name'})({
first_name: 'Ada',
display_name: 'Ada Lovelace',
});
// {name: 'Ada Lovelace'}
pipe(pick(['id', 'name', 'token']), omit(['token']))({
id: 1,
name: 'Ada',
token: 'private',
});
// {id: 1, name: 'Ada'}
Defaults and merge
defaults fills only missing or nullish fields. It does not replace other falsy values.
merge always gives the right-hand source precedence. It is not a conflict detector, and undefined on the right still overwrites the left. A source factory runs once for each transformed input; if it throws, the error propagates.
Normalizers and callbacks
Built-in normalizers use null as their invalid-value result:
isoDatereturnsnullfor empty or invalid dates.numberreturnsnullfor empty, non-numeric,NaN, or infinite values.booleanreturnsnullfor values outside its documented variants.currency:CODEreturnsnullfor invalid numeric values.
An unsupported currency code is different: Intl.NumberFormat throws RangeError when the normalizer is created during transformation. ShapeWire does not replace that configuration error with null.
Custom callbacks receive the current value and key, including undefined for an absent configured field. Their return and error behavior are entirely callback-defined. A thrown callback error propagates and no result object is returned.
Built-in currency output uses the runtime's default locale as a convenience. For locale-sensitive presentation requirements, normalize to a number and format later as described in Scope and non-goals.
Collections and nested targets
mapEach(null) and mapEach(undefined) return []. Other non-array inputs are contract violations and throw TypeError at runtime. Empty arrays return a new empty array, items are processed in order, and sparse holes remain holes.
at is deliberately non-throwing for path lookup failures:
- A missing, inherited, or non-traversable segment returns the original root.
- A
null,undefined, or primitive target returns the original root. - An empty path or a path with an empty segment returns the original root.
- An array is an object target, so
at('items', mapEach(...))is supported.
In every skipped case, the nested transform is not called. If the target is usable, normal transform behavior resumes—including propagated callback errors.