I have a function like:
function getBlogs(limit?: number) { ... }
The limit can only be a positive integer and it cant be 0.
How do i tell typescript that this number can only be greater then 0?
There is an open suggestion, microsoft/TypeScript#15480 to support "range types" where you could say something like, say, type PositiveWholeNumbers = 1..9999999 to represent your type. There's also a (closed) suggestion microsoft/TypeScript#4639 to support int and uint-like types, which could also possibly work here. Sadly, neither of these are implemented, so if you want to pursue this in the type system you need to do something else.
If there's a reasonably small upper limit (in the thousands at most), you can manually generate a union of the allowable values and shove it in a library file somewhere. This can be done programmatically so you don't have to physically type it out, but it's the same as if you did:
// Evaluate the following in JS and copy into your code somewhere
// "type AllowableValues = "+
// Array.from({length: 5000}, (x, i) => String(i+1)+((i+1)%100?"":"\n")).join(" | ")+";";
// which produces:
type AllowableValues = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | // and so on
function getBlogs(limit?: AllowableValues) {
console.log((limit || 0).toFixed());
}
This will work in the sense that the compiler will see that limit is some numeric value in the union (or undefined), and calling getBlogs() with a numeric argument will be validated against it:
getBlogs(100); // okay
getBlogs(1); // okay
getBlogs(1.5); // error!
// ----> ~~~
// Argument of type '1.5' is not assignable to
// parameter of type 1 | 2 | 3 | ...
getBlogs(0); // error!
// ----> ~
// Argument of type '0' is not assignable to
// parameter of type 1 | 2 | 3 | ...
getBlogs(-4);
// ----> ~~
// Argument of type '-4' is not assignable to
// parameter of type 1 | 2 | 3 | ...
Of course anything outside the range will fail:
getBlogs(1234567); // error (too big)
getBlogs(1e100); // error (too big)
And this really only works when people call getBlogs() with some numeric literal. Mathematical operations tend to produce number, which is too wide to be validated:
getBlogs(3+1); // error: safe but the compiler can't tell
Also I've noticed that big unions like the above bog things down enough to make my IDE sluggish, so I'd really only recommend doing this for numbers up to several hundred.
Another possibility is to make getBlogs() a generic function where the argument is validated to make sure it's a positive whole number. You can, I think, get this done in TypeScript 4.1 using template literal types:
type AsPositiveWholeNumber<N extends number> =
N extends 0 ? PositiveWholeNumber :
number extends N ? PositiveWholeNumber :
`${N}` extends `${infer F}${"." | "-"}${infer L}` ?
PositiveWholeNumber : N
type PositiveWholeNumber = {
["Please choose a value that is a positive whole number"]: number
} & number
function getBlogs<N extends number>(limit?: AsPositiveWholeNumber<N>) {
console.log((limit || 0).toFixed());
}
Here, the getBlogs() function accepts a limit parameter of type AsPositiveWholeNumber<N>, where N is a number-constrained type parameter inferred by the value of limit passed in. The AsPositiveWholeNumber<N> type is designed so that if N is a positive whole number, then AsPositiveWholeNumber<N> will just be N. Otherwise, it will be PositiveWholeNumber, a dummy type whose purpose is to give the user a plausibly useful error message.
The important work is done inside AsPositiveWholeNumber<N>. First we make sure that N is neither number nor 0. Then we convert it to a string via the template literal type `${N}` and then make sure that this string version of N contains no "." or "-" characters. This should mostly distinguish positive whole numbers from other numbers, at least until we get to huge values which are integers but whose string representation is rendered in exponential notation with a decimal point (such as 1.23456789e+23).
Let's see if it works:
getBlogs(100); // okay
getBlogs(1); // okay
getBlogs(1.5); // error!
// ----> ~~~
// Argument of type '1.5' is not assignable to
// parameter of type 'PositiveWholeNumber | undefined'
getBlogs(0); // error!
// ----> ~
// Argument of type '0' is not assignable to
// parameter of type 'PositiveWholeNumber | undefined'
getBlogs(-4);
// ----> ~~
// Argument of type '-4' is not assignable to
// parameter of type 'PositiveWholeNumber | undefined'
getBlogs(1234567); // okay
getBlogs(1e100); // okay, but
getBlogs(11e98); // error: becomes 1.1e+99
getBlogs(3 + 1); // error: safe but the compiler can't tell
So that works also. It has the same problem where you need to call getBlogs() with a numeric literal and that any number-typed parameter will not be accepted. This solution definitely has more weird moving parts than the pure union, so I'm not sure I'd really recommend this in any production code. But I did want to show how close TypeScript can currently get to your desired behavior.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With