This proposal is two-part because the use of extend ties in with the use of exact. We will see why below.
*1) struct.exact
At the moment, struct only validates loose objects; when there are more props than required, they're simply ignored. This is probably safe for most use-cases, but some times you might want a strict mode where additional props are disallowed.
The proposed API for this is: struct(S).exact(x) -> x is exact S
There will be no change in the type guard. At compile time, exact is similar to loose validation, because any x will always be accepted as input, and it's unsafe to use un-validated props either way. It's only a runtime change where exact fails if there were more props than expected.
Example
const Message = struct({ from: string, to: string, content: string });
const x = { from, to, content };
if (Message.exact(x)) {
// x is valid
}
const y = { from, to, content, isForward };
if (Message.exact(y)) {
// y is not valid Message, since it has an unexpected prop `isForward`
}
*2) struct.extend
Currently, extend is its own Higher Order Predicate. Its behaviour is such that it accepts a struct predicate as first param, and a struct as second param. The definition of the first struct is enforced at compile-time; you cannot extend from an arbitrary type (use and for that). x must satisfy both structs, while the second struct may not contradict the first; only extend it.
Problem
Because struct.exact as defined previously is also a predicate, extend will have to disallow exact structs as a parameter, because its runtime validation will fail before being extensible.
Because extend is very tied in to struct, moving it to a method makes extend and exact mutually exclusive. I'd also expect extend to be used as an extension of a previously defined struct, rather than inline definition like extend(struct(...), ...). Making it a method makes a lot of sense.
Example
Existing:
const Message = struct({ from: string, to: string, content: string });
// .. elsewhere
const ForwardedMessage = extend(Message, { isForward: true });
Proposed:
const ForwardedMessage = Message.extend({ isForward: bool });
Unaddressed issues
There are still some concerns to be worried about, namely deeply strict structs, like this one:
const MessageContent = struct({
type: oneOf("html", "markdown"),
value: string
}).exact; // notice that MessageContent has been exact-ed
const Message = struct({
from: string,
to: string,
content: MessageContent,
});
Say Message is extended like so:
const MessageWithAttachment = Message.extend({
content: {
attachment: string,
},
});
This check fails to behave as expected at runtime because MessageWithAttachment will always fail as the original struct's content is exacted. This could be solved by having a way to extract the non-exact predicate out of an exact predicate, but we're yet to decide whether implicitly un-exacting structs when extending them is the right way to go.
This proposal is two-part because the use of
extendties in with the use ofexact. We will see why below.*1) struct.exactAt the moment,
structonly validates loose objects; when there are more props than required, they're simply ignored. This is probably safe for most use-cases, but some times you might want a strict mode where additional props are disallowed.The proposed API for this is:
struct(S).exact(x) -> x is exact SThere will be no change in the type guard. At compile time,
exactis similar to loose validation, because anyxwill always be accepted as input, and it's unsafe to use un-validated props either way. It's only a runtime change whereexactfails if there were more props than expected.Example
*2) struct.extendCurrently,
extendis its own Higher Order Predicate. Its behaviour is such that it accepts a struct predicate as first param, and a struct as second param. The definition of the first struct is enforced at compile-time; you cannot extend from an arbitrary type (useandfor that).xmust satisfy both structs, while the second struct may not contradict the first; only extend it.Problem
Because
struct.exactas defined previously is also a predicate,extendwill have to disallow exact structs as a parameter, because its runtime validation will fail before being extensible.Because
extendis very tied in tostruct, moving it to a method makesextendandexactmutually exclusive. I'd also expectextendto be used as an extension of a previously defined struct, rather than inline definition likeextend(struct(...), ...). Making it a method makes a lot of sense.Example
Existing:
Proposed:
Unaddressed issues
There are still some concerns to be worried about, namely deeply strict structs, like this one:
Say
Messageis extended like so:This check fails to behave as expected at runtime because
MessageWithAttachmentwill always fail as the original struct'scontentisexacted. This could be solved by having a way to extract the non-exact predicate out of an exact predicate, but we're yet to decide whether implicitly un-exacting structs when extending them is the right way to go.