const unduck = require(('unduck');
const ud = unduck.withTypes(
{ /* first type description */ },
{ /* secondtype description */ },
);
creates a duck pond with two duck types. You can then call
const instanceOfFirstType = ud({ /* properties described by first type */ });
const instanceOfSecondType = ud({ /* properties described by second type */ });
First, a complete usage example:
const unduck = require('unduck');
// Defining a class.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// Defining a duck type.
const ud = unbock.withType({
// Specifies which constructor ud will use.
classType: Point,
// Creates a constructor argument list given properties.
toConstructorArguments({ x, y }) { return [x, y]; },
// The properties that can appear on a bag of properties that specifies a
// point along with property metadata.
properties: {
x: { type: 'number' },
y: { type: 'number' },
}
};
// These two declarations create equivalent points.
const point1 = ud({ x: 7, y: -2 });
const point2 = new Point(7, -2);
unduck only recognizes own properties in type and property descriptors.
Required Function
Arguments
classType
must be a function usable with new
.
...constructorArguments
are the output of .toConstructorArguments
.
Returns any value.
Required Function
Called to convert processed properties to arguments for .classType
.
Arguments
properties
: Object -- The processed properties.trusted
: boolean -- True iff invoked as a result ofud.trusted
with a bag of properties from a trusted source.userContext
: any -- If the originalud
call had two arguments, this is the second argument. Otherwise it isundefined
. This allows passing some (possibly mutable) context to every custom function for a particular top-levelunduck
call.
Returns an Array to indicate that properties are suitable, or any other value to veto and try any another applicable duck type. (Future versions may assume a promise is a promise for an array.)
This function may be called speculatively for nested duck type values if there are multiple applicable duck types. It should not throw an exception, and instead return null to failover to alternatives.
Maps keys to property descriptor like
({
x: { /* descriptor for property x */ },
y: { /* descriptor for property y */ },
});
Symbol keys are ok. The key __proto__
is not allowed since it has a
special meaning in JavaScript.
A property descriptor may have the following fields.
boolean -- required; false
means that the property need not appear
in the bag for the type to be applicable. By default, properties
are required, unless the property descriptor has a default
property.
string or Function -- If the value is a string, it must be one
of the outputs of the typeof
operator. If a function, it should be a value
that makes sense to the right of instanceof
.
any -- If present specifies the only acceptable value. This enables specifying types that are applicable based on boilerplate values.
Values compare using ===
(modulo NaN).
A property may be both optional and have a required value. For example
{ properties: { version: { value: '1.0', required: false } } }
which means that the type is only applicable if there is no version or
if it is exactly '1.0'
.
any -- The value assumed if the property is not present in the input.
boolean -- By default, property values are recursively unducked.
recurse: false
prevents recursive unducking.
Note: unduck may call user functions like toConstructorArguments
and convert
speculatively. This may happen if there are multiple
applicable types which differ on whether unduck should recursively
process a property's value.
boolean -- Should be true if downstream code might trust the value, so it is important that untrusted properties not control it. Defaults to false.
Calling ud.trust(x)
instead of ud(x)
indicates that the caller trusts x
.
any -- A value to use if the input is not clearly trustworthy,
but downstream code might trust the parts.
Not used unless trusted: true
.
Function -- called to convert a value for the current property to a
value for the bag passed to toConstructorArguments
.
Arguments
value
: any -- The raw property value to convert.trusted
: boolean -- Whether the top level call asserts value's trustworthiness. Seetrusted
andinnocuous
below. The top level unduck call specifies this argument. It is independent of thetrusted
property descriptor field, soconvert
can pick their own safe value instead of relying on a singleinnocuous
value.userContext
: any -- If the originalud
call had two arguments, this is the second argument. Otherwise it isundefined
. This allows passing some (possibly mutable) context to every custom function for a particular top-levelud
call.notApplicable
: Object -- a sentinel value thatconvert
may return to indicate that the duck type is not applicable to the input.
Return
The processed value for the property, or notApplicable
to abort further
processing using the current duck type.
convert
calls happen after substituting default
or innocuous
values as
appropriate, after the value is recursively unducked if appropriate,
and after and only after the value passes any type
check.
const unduck = require('unduck')
brings an unduck function into scope
that has zero duck types.
myUnduck.withTypes(additionalType)
produces a new unduck function
that inherits the duck types from myUnduck
and also recognizes the
type descriptor additionalType
.
const unduck = require('unduck');
// Calls can happen separately as long as you use the return value.
let ud = unduck.withTypes(typeDescriptor1);
ud = ud.withTypes(typeDescriptor2);
// Can pass ud out to library code.
ud = myFavoriteLibrary.registerSomeTypes(ud);
((ud) => {
// A narrower scope.
ud = ud.withTypes(typeDescriptorVisibleInNarrowScope);
...
})(ud);
Application code shouldn't naively convert any bag of properties to an object. "JSON object forgery" explains why not.
JSON.parse makes it easy to unintentionally turn untrustworthy strings into untrustworthy objects.
By default, unduck replaces trusted property values with
innocuous values, and by default the trusted
parameter
to convert functions and to toConstructorArguments
is false.
This avoids problems when all or part of the bag of properties comes from
an untrusted source like ud(JSON.parse(untrustedString)
.
When you know that the input is trustworthy, use ud.trust(input)
.
Prefer using convert
functions that sanitize inputs to explicitly
trusting inputs.