@cloudflare/util-en-garde
Advanced tools
Comparing version 6.0.0 to 6.1.0
@@ -6,2 +6,18 @@ # Change Log | ||
# [6.1.0](http://stash.cfops.it:7999/fe/stratus/compare/@cloudflare/util-en-garde@6.0.0...@cloudflare/util-en-garde@6.1.0) (2019-11-26) | ||
### Bug Fixes | ||
* **util-en-garde:** TSX-157 Do *NOT* use typescript property initializat ([29562eb](http://stash.cfops.it:7999/fe/stratus/commits/29562eb)) | ||
### Features | ||
* **util-en-garde:** TSX-158 Extend io-ts classes ([d0e6f97](http://stash.cfops.it:7999/fe/stratus/commits/d0e6f97)) | ||
# [6.0.0](http://stash.cfops.it:7999/fe/stratus/compare/@cloudflare/util-en-garde@4.0.1...@cloudflare/util-en-garde@6.0.0) (2019-11-20) | ||
@@ -8,0 +24,0 @@ |
@@ -16,14 +16,61 @@ import * as t from 'io-ts'; | ||
* | ||
* Additionally, this wrapper provides an `optional` property which unions | ||
* the codec with the `undefined` codec. This provides an API that is both | ||
* easier to use and more readable for a very common scenario. | ||
* The other main thing this wrapper seeks to address is the clunkiness of | ||
* defining objects. | ||
* | ||
* Next is the new `object` type which seeks to replace `t.type` in io-ts and | ||
* which is less immediately obvious. When deriving type information from | ||
* `t.type` with `t.TypeOf`, keys whose values are unioned with undefined | ||
* are still required. The suggested strategy in `io-ts` is to define optional | ||
* keys separately and intersect them with required keys. The `object` type | ||
* addresses this by returning a type that automatically enables optional | ||
* keys where values are unioned with undefined. This combined with the | ||
* `optional` property makes for a much nicer API when defining object shapes. | ||
* If you want to declare an object in io-ts, it looks like this: | ||
* | ||
* const person = t.type({ firstName: t.string, lastName: t.string }) | ||
* | ||
* 'type' here is not obvious that it is representing an object, but that's | ||
* not the only issue. If you want to make one of the keys optional, you must | ||
* manually union the type with t.undefined. | ||
* | ||
* const person = t.type({ | ||
* firstName: t.string, | ||
* lastName: t.union([t.string, t.undefined]) | ||
* }) | ||
* | ||
* This isn't so bad that it warrants a wrapper, but it could be improved | ||
* upon. The real pain is that when deriving types from this, the 'optional' | ||
* key isn't actually optional. | ||
* | ||
* // { firstName: string, lastName: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* Note that lastName does *NOT* have a question mark after it marking it as | ||
* optional. If you wanted to now declare a variable with this type, you MUST | ||
* provide the lastName key, even if the value is undefined. | ||
* | ||
* const p: Person = { firstName: "Kevin", lastName: undefined } | ||
* | ||
* This is where things get really clunky. The solution according to the docs | ||
* is to separate your optional keys (with t.partial) from your required keys | ||
* and then intersect the two types together. | ||
* | ||
* const person = t.intersection([ | ||
* t.type({ | ||
* firstName: t.string, | ||
* }), | ||
* t.partial({ | ||
* lastName: t.string | ||
* }) | ||
* ]) | ||
* | ||
* // Now this is correct | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* This wrapper provides a much nicer API for this *very* common scenario | ||
* addressing the aforementioned quirks of io-ts. | ||
* | ||
* const person = eg.object({ | ||
* firstName: eg.string | ||
* lastName: eg.string.optional | ||
* }) | ||
* | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = TypeFromCodec<typeof person> | ||
* | ||
* The `optional` property unions the codec with the `undefined` codec, and the | ||
* type information that is derived handles this automatically. | ||
*/ | ||
@@ -49,46 +96,19 @@ declare type KeysWithValueType<O, T> = { | ||
declare type IoTsCodec = t.Any; | ||
export declare class Codec<Codec extends IoTsCodec> { | ||
ioTsCodec: Codec; | ||
constructor(ioTsCodec: Codec); | ||
name: string; | ||
is: Codec['is']; | ||
encode: Codec['encode']; | ||
decode: Codec['decode']; | ||
validate: Codec['validate']; | ||
pipe: Codec['pipe']; | ||
_A: Codec['_A']; | ||
_O: Codec['_O']; | ||
_I: Codec['_I']; | ||
asDecoder: Codec['asDecoder']; | ||
asEncoder: Codec['asEncoder']; | ||
assertDecode: (value: unknown) => Codec["_A"]; | ||
get optional(): import(".").Codec<t.UnionC<[Codec, t.UndefinedC]>>; | ||
export declare class Codec<C extends IoTsCodec> extends t.Type<t.TypeOf<C>, t.OutputOf<C>, t.InputOf<C>> { | ||
constructor(ioTsCodec: C); | ||
assertDecode: (value: unknown) => C["_A"]; | ||
get optional(): Codec<t.UnionC<[this, t.UndefinedC]>>; | ||
} | ||
declare type InterfaceTypeWithOptionalKeys<P extends t.Props> = t.InterfaceType<P, EnableOptionalKeys<{ | ||
export declare class ObjectCodec<P extends t.Props> extends t.InterfaceType<P, EnableOptionalKeys<{ | ||
[K in keyof P]: t.TypeOf<P[K]>; | ||
}>, EnableOptionalKeys<{ | ||
[K in keyof P]: t.OutputOf<P[K]>; | ||
}>, unknown>; | ||
export declare class ObjectCodec<P extends t.Props, CodecWithOptionalKeys extends t.InterfaceType<any, any, any, any> = InterfaceTypeWithOptionalKeys<P>> { | ||
ioTsCodec: t.TypeC<P>; | ||
}>, unknown> { | ||
constructor(ioTsCodec: t.TypeC<P>); | ||
name: string; | ||
is: CodecWithOptionalKeys['is']; | ||
encode: CodecWithOptionalKeys['encode']; | ||
decode: CodecWithOptionalKeys['decode']; | ||
validate: CodecWithOptionalKeys['validate']; | ||
pipe: CodecWithOptionalKeys['pipe']; | ||
_A: CodecWithOptionalKeys['_A']; | ||
_O: CodecWithOptionalKeys['_O']; | ||
_I: CodecWithOptionalKeys['_I']; | ||
asDecoder: CodecWithOptionalKeys['asDecoder']; | ||
asEncoder: CodecWithOptionalKeys['asEncoder']; | ||
assertDecode: (value: unknown) => CodecWithOptionalKeys["_A"]; | ||
get optional(): Codec<t.UnionC<[t.TypeC<P>, t.UndefinedC]>>; | ||
readonly _tag: 'InterfaceType'; | ||
props: CodecWithOptionalKeys['props']; | ||
assertDecode: (value: unknown) => { [Key_2 in keyof ({ [MandatoryKey in { [Key in keyof { [K in keyof P]: P[K]["_A"]; }]: undefined extends { [K in keyof P]: P[K]["_A"]; }[Key] ? never : Key; }[keyof P]]: { [K in keyof P]: P[K]["_A"]; }[MandatoryKey]; } & { [OptionalKey in { [Key_1 in keyof { [K in keyof P]: P[K]["_A"]; }]: Key_1; }[Exclude<keyof P, { [Key in keyof { [K in keyof P]: P[K]["_A"]; }]: undefined extends { [K in keyof P]: P[K]["_A"]; }[Key] ? never : Key; }[keyof P]>]]?: { [K in keyof P]: P[K]["_A"]; }[OptionalKey] | undefined; })]: ({ [MandatoryKey in { [Key in keyof { [K in keyof P]: P[K]["_A"]; }]: undefined extends { [K in keyof P]: P[K]["_A"]; }[Key] ? never : Key; }[keyof P]]: { [K in keyof P]: P[K]["_A"]; }[MandatoryKey]; } & { [OptionalKey in { [Key_1 in keyof { [K in keyof P]: P[K]["_A"]; }]: Key_1; }[Exclude<keyof P, { [Key in keyof { [K in keyof P]: P[K]["_A"]; }]: undefined extends { [K in keyof P]: P[K]["_A"]; }[Key] ? never : Key; }[keyof P]>]]?: { [K in keyof P]: P[K]["_A"]; }[OptionalKey] | undefined; })[Key_2]; }; | ||
get optional(): Codec<t.UnionC<[this, t.UndefinedC]>>; | ||
} | ||
export declare type TypeFromCodec<T extends IoTsCodec> = t.TypeOf<T>; | ||
export declare const eg: { | ||
object: <P extends t.Props>(p: P, name?: string | undefined) => ObjectCodec<P, InterfaceTypeWithOptionalKeys<P>>; | ||
object: <P extends t.Props>(p: P, name?: string | undefined) => ObjectCodec<P>; | ||
tuple: <Codecs extends never[] | [t.Any, ...t.Any[]]>(codecs: Codecs, name?: string | undefined) => Codec<t.TupleC<Exclude<Codecs, never[]>>>; | ||
@@ -95,0 +115,0 @@ intersection: <Codecs_1 extends [t.Any, t.Any, ...t.Any[]]>(codecs: Codecs_1, name?: string | undefined) => Codec<t.IntersectionC<Codecs_1>>; |
152
es/index.js
@@ -46,14 +46,61 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
* | ||
* Additionally, this wrapper provides an `optional` property which unions | ||
* the codec with the `undefined` codec. This provides an API that is both | ||
* easier to use and more readable for a very common scenario. | ||
* The other main thing this wrapper seeks to address is the clunkiness of | ||
* defining objects. | ||
* | ||
* Next is the new `object` type which seeks to replace `t.type` in io-ts and | ||
* which is less immediately obvious. When deriving type information from | ||
* `t.type` with `t.TypeOf`, keys whose values are unioned with undefined | ||
* are still required. The suggested strategy in `io-ts` is to define optional | ||
* keys separately and intersect them with required keys. The `object` type | ||
* addresses this by returning a type that automatically enables optional | ||
* keys where values are unioned with undefined. This combined with the | ||
* `optional` property makes for a much nicer API when defining object shapes. | ||
* If you want to declare an object in io-ts, it looks like this: | ||
* | ||
* const person = t.type({ firstName: t.string, lastName: t.string }) | ||
* | ||
* 'type' here is not obvious that it is representing an object, but that's | ||
* not the only issue. If you want to make one of the keys optional, you must | ||
* manually union the type with t.undefined. | ||
* | ||
* const person = t.type({ | ||
* firstName: t.string, | ||
* lastName: t.union([t.string, t.undefined]) | ||
* }) | ||
* | ||
* This isn't so bad that it warrants a wrapper, but it could be improved | ||
* upon. The real pain is that when deriving types from this, the 'optional' | ||
* key isn't actually optional. | ||
* | ||
* // { firstName: string, lastName: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* Note that lastName does *NOT* have a question mark after it marking it as | ||
* optional. If you wanted to now declare a variable with this type, you MUST | ||
* provide the lastName key, even if the value is undefined. | ||
* | ||
* const p: Person = { firstName: "Kevin", lastName: undefined } | ||
* | ||
* This is where things get really clunky. The solution according to the docs | ||
* is to separate your optional keys (with t.partial) from your required keys | ||
* and then intersect the two types together. | ||
* | ||
* const person = t.intersection([ | ||
* t.type({ | ||
* firstName: t.string, | ||
* }), | ||
* t.partial({ | ||
* lastName: t.string | ||
* }) | ||
* ]) | ||
* | ||
* // Now this is correct | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* This wrapper provides a much nicer API for this *very* common scenario | ||
* addressing the aforementioned quirks of io-ts. | ||
* | ||
* const person = eg.object({ | ||
* firstName: eg.string | ||
* lastName: eg.string.optional | ||
* }) | ||
* | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = TypeFromCodec<typeof person> | ||
* | ||
* The `optional` property unions the codec with the `undefined` codec, and the | ||
* type information that is derived handles this automatically. | ||
*/ | ||
@@ -72,2 +119,3 @@ | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(EnGardeAssertionError).call(this)); | ||
_this.errors = errors; | ||
Object.setPrototypeOf(_assertThisInitialized(_assertThisInitialized(_this)), EnGardeAssertionError.prototype); | ||
@@ -81,36 +129,20 @@ return _this; | ||
/*#__PURE__*/ | ||
function () { | ||
function (_t$Type) { | ||
_inherits(Codec, _t$Type); | ||
function Codec(ioTsCodec) { | ||
var _this2 = this; | ||
var _this2; | ||
_classCallCheck(this, Codec); | ||
_defineProperty(this, "name", this.ioTsCodec.name); | ||
_this2 = _possibleConstructorReturn(this, _getPrototypeOf(Codec).call(this, ioTsCodec.name, ioTsCodec.is, ioTsCodec.validate, ioTsCodec.encode)); | ||
_defineProperty(this, "is", this.ioTsCodec.is); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this2)), "assertDecode", function (value) { | ||
var result = _this2.decode(value); | ||
_defineProperty(this, "encode", this.ioTsCodec.encode); | ||
_defineProperty(this, "decode", this.ioTsCodec.decode); | ||
_defineProperty(this, "validate", this.ioTsCodec.validate); | ||
_defineProperty(this, "pipe", this.ioTsCodec.pipe); | ||
_defineProperty(this, "_A", this.ioTsCodec._A); | ||
_defineProperty(this, "_O", this.ioTsCodec._O); | ||
_defineProperty(this, "_I", this.ioTsCodec._I); | ||
_defineProperty(this, "asDecoder", this.ioTsCodec.asDecoder); | ||
_defineProperty(this, "asEncoder", this.ioTsCodec.asEncoder); | ||
_defineProperty(this, "assertDecode", function (value) { | ||
var result = _this2.ioTsCodec.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
return result.right; | ||
}); | ||
return _this2; | ||
} | ||
@@ -121,3 +153,3 @@ | ||
get: function get() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
@@ -127,36 +159,19 @@ }]); | ||
return Codec; | ||
}(); | ||
}(t.Type); | ||
export var ObjectCodec = | ||
/*#__PURE__*/ | ||
function () { | ||
function (_t$InterfaceType) { | ||
_inherits(ObjectCodec, _t$InterfaceType); | ||
function ObjectCodec(ioTsCodec) { | ||
var _this3 = this; | ||
var _this3; | ||
_classCallCheck(this, ObjectCodec); | ||
_defineProperty(this, "name", this.ioTsCodec.name); | ||
_this3 = _possibleConstructorReturn(this, _getPrototypeOf(ObjectCodec).call(this, ioTsCodec.name, // these MUST be cast as any because the types don't line up and that's | ||
ioTsCodec.is, ioTsCodec.validate, ioTsCodec.encode, ioTsCodec.props)); | ||
_defineProperty(this, "is", this.ioTsCodec.is); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this3)), "assertDecode", function (value) { | ||
var result = _this3.decode(value); | ||
_defineProperty(this, "encode", this.ioTsCodec.encode); | ||
_defineProperty(this, "decode", this.ioTsCodec.decode); | ||
_defineProperty(this, "validate", this.ioTsCodec.validate); | ||
_defineProperty(this, "pipe", this.ioTsCodec.pipe); | ||
_defineProperty(this, "_A", this.ioTsCodec._A); | ||
_defineProperty(this, "_O", this.ioTsCodec._O); | ||
_defineProperty(this, "_I", this.ioTsCodec._I); | ||
_defineProperty(this, "asDecoder", this.ioTsCodec.asDecoder); | ||
_defineProperty(this, "asEncoder", this.ioTsCodec.asEncoder); | ||
_defineProperty(this, "assertDecode", function (value) { | ||
var result = _this3.ioTsCodec.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
@@ -166,5 +181,3 @@ return result.right; | ||
_defineProperty(this, "_tag", 'InterfaceType'); | ||
_defineProperty(this, "props", this.ioTsCodec.props); | ||
return _this3; | ||
} | ||
@@ -175,3 +188,3 @@ | ||
get: function get() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
@@ -181,3 +194,3 @@ }]); | ||
return ObjectCodec; | ||
}(); | ||
}(t.InterfaceType); | ||
var primitiveCodecs = { | ||
@@ -218,3 +231,4 @@ string: new Codec(t.string), | ||
}; | ||
export var eg = _objectSpread({}, primitiveCodecs, higherOrderCodecs); | ||
export var eg = _objectSpread({}, primitiveCodecs, higherOrderCodecs); // re-export io-ts lib | ||
export { t }; |
@@ -55,2 +55,3 @@ "use strict"; | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(EnGardeAssertionError).call(this)); | ||
_this.errors = errors; | ||
Object.setPrototypeOf(_assertThisInitialized(_assertThisInitialized(_this)), EnGardeAssertionError.prototype); | ||
@@ -67,36 +68,20 @@ return _this; | ||
/*#__PURE__*/ | ||
function () { | ||
function (_t$Type) { | ||
_inherits(Codec, _t$Type); | ||
function Codec(ioTsCodec) { | ||
var _this2 = this; | ||
var _this2; | ||
_classCallCheck(this, Codec); | ||
_defineProperty(this, "name", this.ioTsCodec.name); | ||
_this2 = _possibleConstructorReturn(this, _getPrototypeOf(Codec).call(this, ioTsCodec.name, ioTsCodec.is, ioTsCodec.validate, ioTsCodec.encode)); | ||
_defineProperty(this, "is", this.ioTsCodec.is); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this2)), "assertDecode", function (value) { | ||
var result = _this2.decode(value); | ||
_defineProperty(this, "encode", this.ioTsCodec.encode); | ||
_defineProperty(this, "decode", this.ioTsCodec.decode); | ||
_defineProperty(this, "validate", this.ioTsCodec.validate); | ||
_defineProperty(this, "pipe", this.ioTsCodec.pipe); | ||
_defineProperty(this, "_A", this.ioTsCodec._A); | ||
_defineProperty(this, "_O", this.ioTsCodec._O); | ||
_defineProperty(this, "_I", this.ioTsCodec._I); | ||
_defineProperty(this, "asDecoder", this.ioTsCodec.asDecoder); | ||
_defineProperty(this, "asEncoder", this.ioTsCodec.asEncoder); | ||
_defineProperty(this, "assertDecode", function (value) { | ||
var result = _this2.ioTsCodec.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
return result.right; | ||
}); | ||
return _this2; | ||
} | ||
@@ -107,3 +92,3 @@ | ||
get: function get() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
@@ -113,3 +98,3 @@ }]); | ||
return Codec; | ||
}(); | ||
}(t.Type); | ||
@@ -120,33 +105,16 @@ exports.Codec = Codec; | ||
/*#__PURE__*/ | ||
function () { | ||
function (_t$InterfaceType) { | ||
_inherits(ObjectCodec, _t$InterfaceType); | ||
function ObjectCodec(ioTsCodec) { | ||
var _this3 = this; | ||
var _this3; | ||
_classCallCheck(this, ObjectCodec); | ||
_defineProperty(this, "name", this.ioTsCodec.name); | ||
_this3 = _possibleConstructorReturn(this, _getPrototypeOf(ObjectCodec).call(this, ioTsCodec.name, // these MUST be cast as any because the types don't line up and that's | ||
ioTsCodec.is, ioTsCodec.validate, ioTsCodec.encode, ioTsCodec.props)); | ||
_defineProperty(this, "is", this.ioTsCodec.is); | ||
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this3)), "assertDecode", function (value) { | ||
var result = _this3.decode(value); | ||
_defineProperty(this, "encode", this.ioTsCodec.encode); | ||
_defineProperty(this, "decode", this.ioTsCodec.decode); | ||
_defineProperty(this, "validate", this.ioTsCodec.validate); | ||
_defineProperty(this, "pipe", this.ioTsCodec.pipe); | ||
_defineProperty(this, "_A", this.ioTsCodec._A); | ||
_defineProperty(this, "_O", this.ioTsCodec._O); | ||
_defineProperty(this, "_I", this.ioTsCodec._I); | ||
_defineProperty(this, "asDecoder", this.ioTsCodec.asDecoder); | ||
_defineProperty(this, "asEncoder", this.ioTsCodec.asEncoder); | ||
_defineProperty(this, "assertDecode", function (value) { | ||
var result = _this3.ioTsCodec.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
@@ -156,5 +124,3 @@ return result.right; | ||
_defineProperty(this, "_tag", 'InterfaceType'); | ||
_defineProperty(this, "props", this.ioTsCodec.props); | ||
return _this3; | ||
} | ||
@@ -165,3 +131,3 @@ | ||
get: function get() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
@@ -171,3 +137,3 @@ }]); | ||
return ObjectCodec; | ||
}(); | ||
}(t.InterfaceType); | ||
@@ -211,4 +177,5 @@ exports.ObjectCodec = ObjectCodec; | ||
var eg = _objectSpread({}, primitiveCodecs, higherOrderCodecs); | ||
var eg = _objectSpread({}, primitiveCodecs, higherOrderCodecs); // re-export io-ts lib | ||
exports.eg = eg; |
{ | ||
"name": "@cloudflare/util-en-garde", | ||
"description": "", | ||
"version": "6.0.0", | ||
"version": "6.1.0", | ||
"types": "./dist/index.d.ts", | ||
@@ -29,3 +29,3 @@ "main": "lib/index.js", | ||
}, | ||
"gitHead": "2c7116804e9d7e6f2e005ee4d7aa37829d2624a3" | ||
"gitHead": "317eb680b783edefb6c5d673e758800288ab08f4" | ||
} |
144
src/index.ts
@@ -17,14 +17,61 @@ import * as t from 'io-ts'; | ||
* | ||
* Additionally, this wrapper provides an `optional` property which unions | ||
* the codec with the `undefined` codec. This provides an API that is both | ||
* easier to use and more readable for a very common scenario. | ||
* The other main thing this wrapper seeks to address is the clunkiness of | ||
* defining objects. | ||
* | ||
* Next is the new `object` type which seeks to replace `t.type` in io-ts and | ||
* which is less immediately obvious. When deriving type information from | ||
* `t.type` with `t.TypeOf`, keys whose values are unioned with undefined | ||
* are still required. The suggested strategy in `io-ts` is to define optional | ||
* keys separately and intersect them with required keys. The `object` type | ||
* addresses this by returning a type that automatically enables optional | ||
* keys where values are unioned with undefined. This combined with the | ||
* `optional` property makes for a much nicer API when defining object shapes. | ||
* If you want to declare an object in io-ts, it looks like this: | ||
* | ||
* const person = t.type({ firstName: t.string, lastName: t.string }) | ||
* | ||
* 'type' here is not obvious that it is representing an object, but that's | ||
* not the only issue. If you want to make one of the keys optional, you must | ||
* manually union the type with t.undefined. | ||
* | ||
* const person = t.type({ | ||
* firstName: t.string, | ||
* lastName: t.union([t.string, t.undefined]) | ||
* }) | ||
* | ||
* This isn't so bad that it warrants a wrapper, but it could be improved | ||
* upon. The real pain is that when deriving types from this, the 'optional' | ||
* key isn't actually optional. | ||
* | ||
* // { firstName: string, lastName: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* Note that lastName does *NOT* have a question mark after it marking it as | ||
* optional. If you wanted to now declare a variable with this type, you MUST | ||
* provide the lastName key, even if the value is undefined. | ||
* | ||
* const p: Person = { firstName: "Kevin", lastName: undefined } | ||
* | ||
* This is where things get really clunky. The solution according to the docs | ||
* is to separate your optional keys (with t.partial) from your required keys | ||
* and then intersect the two types together. | ||
* | ||
* const person = t.intersection([ | ||
* t.type({ | ||
* firstName: t.string, | ||
* }), | ||
* t.partial({ | ||
* lastName: t.string | ||
* }) | ||
* ]) | ||
* | ||
* // Now this is correct | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = t.TypeOf<typeof person> | ||
* | ||
* This wrapper provides a much nicer API for this *very* common scenario | ||
* addressing the aforementioned quirks of io-ts. | ||
* | ||
* const person = eg.object({ | ||
* firstName: eg.string | ||
* lastName: eg.string.optional | ||
* }) | ||
* | ||
* // { firstName: string, lastName?: string | undefined } | ||
* type Person = TypeFromCodec<typeof person> | ||
* | ||
* The `optional` property unions the codec with the `undefined` codec, and the | ||
* type information that is derived handles this automatically. | ||
*/ | ||
@@ -57,19 +104,13 @@ | ||
export class Codec<Codec extends IoTsCodec> { | ||
constructor(public ioTsCodec: Codec) {} | ||
export class Codec<C extends IoTsCodec> extends t.Type< | ||
t.TypeOf<C>, | ||
t.OutputOf<C>, | ||
t.InputOf<C> | ||
> { | ||
constructor(ioTsCodec: C) { | ||
super(ioTsCodec.name, ioTsCodec.is, ioTsCodec.validate, ioTsCodec.encode); | ||
} | ||
public name = this.ioTsCodec.name; | ||
public is: Codec['is'] = this.ioTsCodec.is; | ||
public encode: Codec['encode'] = this.ioTsCodec.encode; | ||
public decode: Codec['decode'] = this.ioTsCodec.decode; | ||
public validate: Codec['validate'] = this.ioTsCodec.validate; | ||
public pipe: Codec['pipe'] = this.ioTsCodec.pipe; | ||
public _A: Codec['_A'] = this.ioTsCodec._A; | ||
public _O: Codec['_O'] = this.ioTsCodec._O; | ||
public _I: Codec['_I'] = this.ioTsCodec._I; | ||
public asDecoder: Codec['asDecoder'] = this.ioTsCodec.asDecoder; | ||
public asEncoder: Codec['asEncoder'] = this.ioTsCodec.asEncoder; | ||
public assertDecode = (value: unknown): Codec['_A'] => { | ||
const result = this.ioTsCodec.decode(value); | ||
public assertDecode = (value: unknown) => { | ||
const result = this.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
@@ -80,7 +121,7 @@ return result.right; | ||
public get optional() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
} | ||
type InterfaceTypeWithOptionalKeys<P extends t.Props> = t.InterfaceType< | ||
export class ObjectCodec<P extends t.Props> extends t.InterfaceType< | ||
P, | ||
@@ -90,31 +131,17 @@ EnableOptionalKeys<{ [K in keyof P]: t.TypeOf<P[K]> }>, | ||
unknown | ||
>; | ||
export class ObjectCodec< | ||
P extends t.Props, | ||
CodecWithOptionalKeys extends t.InterfaceType< | ||
any, | ||
any, | ||
any, | ||
any | ||
> = InterfaceTypeWithOptionalKeys<P> | ||
> { | ||
constructor(public ioTsCodec: t.TypeC<P>) {} | ||
constructor(ioTsCodec: t.TypeC<P>) { | ||
super( | ||
ioTsCodec.name, | ||
// these MUST be cast as any because the types don't line up and that's | ||
// what we want since we're enabling optional keys | ||
ioTsCodec.is as any, | ||
ioTsCodec.validate as any, | ||
ioTsCodec.encode as any, | ||
ioTsCodec.props | ||
); | ||
} | ||
public name = this.ioTsCodec.name; | ||
public is: CodecWithOptionalKeys['is'] = this.ioTsCodec.is; | ||
public encode: CodecWithOptionalKeys['encode'] = this.ioTsCodec.encode; | ||
public decode: CodecWithOptionalKeys['decode'] = this.ioTsCodec.decode; | ||
public validate: CodecWithOptionalKeys['validate'] = this.ioTsCodec.validate; | ||
public pipe: CodecWithOptionalKeys['pipe'] = this.ioTsCodec.pipe; | ||
public _A: CodecWithOptionalKeys['_A'] = this.ioTsCodec._A; | ||
public _O: CodecWithOptionalKeys['_O'] = this.ioTsCodec._O; | ||
public _I: CodecWithOptionalKeys['_I'] = this.ioTsCodec._I; | ||
public asDecoder: CodecWithOptionalKeys['asDecoder'] = this.ioTsCodec | ||
.asDecoder; | ||
public asEncoder: CodecWithOptionalKeys['asEncoder'] = this.ioTsCodec | ||
.asEncoder; | ||
public assertDecode = (value: unknown): CodecWithOptionalKeys['_A'] => { | ||
const result = this.ioTsCodec.decode(value); | ||
public assertDecode = (value: unknown) => { | ||
const result = this.decode(value); | ||
if (result._tag === 'Left') throw new EnGardeAssertionError(result.left); | ||
@@ -125,6 +152,4 @@ return result.right; | ||
public get optional() { | ||
return new Codec(t.union([this.ioTsCodec, t.undefined])); | ||
return new Codec(t.union([this, t.undefined])); | ||
} | ||
readonly _tag: 'InterfaceType' = 'InterfaceType'; | ||
public props: CodecWithOptionalKeys['props'] = this.ioTsCodec.props; | ||
} | ||
@@ -181,2 +206,3 @@ | ||
// re-export io-ts lib | ||
export { t }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
40525
633
0