Protects your typescript code world from unknown outside input.
const Body = Shape.make((s) =>
s.Struct({
name: s.Constrain(s.String, (s) => s.length < 10, "Nickname"),
age: s.Int, // a refined number
gender: s.Enum("Gender", Gender),
birth: s.Loose.Date, // casting string to date
})
);
// a koa router with json bodyparser
router.post("/profiles", async (ctx) => {
const profile = Body.coerce(ctx.request.body);
// profile is validated and has type
// {
// name: string
// age: Int
// gender: Gender
// birth: Date
// }
const user = await create(profile);
ctx.body = user;
});
- Installation
- Usage
- Define a shape
- Primitive
- Combinators
- The
s.Literal
constructor - The
s.Optional
constructor - The
s.Struct
constructor - The
s.Partial
constructor - The
s.Map
constructor - The
s.Array
constructor - The
s.Tuple
constructor - The
s.Intersect
constructor - The
s.Union
constructor - The
s.Enum
constructor - The
s.Constrain
constructor - The
s.Shape
constructor
- The
- Brand
- Loose Type
- Coericion
- Define a shape
- Roadmap
npm i t-shape
const shape = Shape.make((s) =>
s.Struct({
id: s.OidLiteral,
name: s.String,
age: s.Optional(s.Number)
address: s.Array(s.String)
})
)
// stands for type => {
// id: OidLiteral // string with objectId constraint
// name: string
// age: number | undefined
// address: string[]
// }
s.Never
s.String
s.Number
s.Boolean
s.Unknown
s.Literal(null)
=>null
s.Literal(undefined)
=>undefined
s.Literal('ON')
=>'ON'
s.Literal('ON', 'OFF')
=>'ON' | 'OFF'
s.Optional(s.String)
=> string | undefined
Notice that it DOES NOT mean the key is optional (key?: string
)
s.Struct({ id: s.String, time: s.Number })
=> { id: string, time: number }
s.Partial({ id: s.String, time: s.Number })
=> { id?: string, time?: number }
s.Map(s.Boolean)
=> Record<string, boolean>
s.Array(s.Number)
=> number[]
s.Tuple(s.String, s.Number)
=> [string, number]
s.Intersect(s.Struct({ name: s.String }), s.Partial({ age: s.Number }))
=> { name: string } & { age?: number }
s.Union(s.String, s.Number)
=> string | number
To compatible with Typescript enum when there is no other choice, it's not well supported due to a poor design, highly recommend using s.Literal
instead.
// MUST NOT be a `const enum`, otherwise it will be optimized out.
enum Toggle {
On = "ON",
Off = "OFF",
}
s.Enum("Toggle", Toggle); // => Toggle
// You have to specify enum type when it's a numeric enum,
// however numeric enums are unsafe in typescript, shouldn't be used anyway.
// @see https://github.com/gcanti/io-ts/issues/216#issuecomment-621615906
enum ToggleNumber {
On,
Off,
}
s.Enum<ToggleNumber>("ToggleNumber", ToggleNumber); // => ToggleNumber
s.Constrain(s.Number, n => n > 0, 'Positive')
=> number (with positive constraint)
To add additional constraint to a type.
s.Shape(Shape.make(s => s.Number))
=> number
the s.OidLiteral
is string with ObjectID constraint, it uses ObjectId.valid(i)
.
- the
s.UrlLiteral
is string with Url constraint, it usesnew URL(i)
to validate.
- the
s.Int
is number with Integer constraint, it usesNumber.isInteger(i)
.
the s.Loose.Date
will cast string/number to Date, by using new Date(i)
the s.Loose.Number
will cast string/number to number, cases:
123
->123
NaN
-> throw error"123"
->123
"123abc"
-> throw error""
-> throw error
the s.Loose.Boolean
will cast string/boolean to boolean, cases:
true
->true
"false"
->false
"1"
-> throw error"yes"
-> throw error
the s.Loose.Optional
will cast undefined/null to undefined, cases:
undefined
/null
->undefined
the s.Loose.Int
is based on s.Loose.Number
, and cast the result to s.Int
with Math.trunc
, cases:
123
->123
123.45
->123
-123.45
->-123
Say we are using koa(with body-parser), to coerce body shape you can
import { Shape } from "t-shape";
import { Errors } from "./errors";
const Body = Shape.make((s) =>
S.Struct({ id: s.OidLiteral, text: s.String, time: s.Date })
);
async function handler(ctx: Context) {
// with type { id: OidLiteral, text: string, time: Date }
const { id, text, time } = Body.coerce(
ctx.request.body,
new Errors.InvalidParams()
);
}
The third argument can receive a Error | ErrorContructor
, the Error instance will be threw directly while the ErrorContructor will receive error message as first argument.
By default it's TypeError
constructor.
- Add branded type such as Positive(number), Email(string), etc.