How to override a property to be non-nullable in Typescript - node.js

The DefinitelyTyped definition of the Node built-in IncomingMessage (the type of req in the (req, res, next) arguments) has defined url to be nullable. Here's the snipped parts of the definition file:
// #types/node/index.d.ts
declare module "http" {
export interface IncomingMessage {
/**
* Only valid for request obtained from http.Server.
*/
url?: string;
}
}
As the comment says, this is because this property is only valid when you're getting an instance of this IncomingMessage from the http.Server. In other uses it won't exist, hence, it's nullable.
However, in my case, I know that I'm only getting these instances from http.Server, and so it's kinda annoying that I can't just access the property without extra guards.
import { IncomingMessage, ServerResponse } from 'http';
function someMiddleware(req: IncomingMessage, res: ServerResponse, next: Function) {
const myStr: string = req.url; // bzzzt.
// Argument of type 'string | undefined' is not
// assignable to parameter of type 'string'.
}
It's probably good to mention that I'm using TS 2.0.3 with strictNullChecks, which is not enabled on the Typescript Playground.
Here's the question. Is it possible to override that definition across my application so that url is not nullable?
Here's what I've already tried... adding this to one of my files:
declare module 'http' {
interface IncomingMessage {
url: string;
}
}
...however that is disallowed: "Subsequent variable declarations must have the same type". This is explained in the documentation.
The only thing I can think of thus far is to create my own module which imports, extends and then exports the interfaces:
// /src/http.ts
import { IncomingMessage as OriginalIM } from 'http';
export interface IncomingMessage extends OriginalIM {
url: string;
}
// src/myapp.ts
import { IncomingMessage } from './http'; // <-- local def
function someMiddleware(req: IncomingMessage) {
const str: string = req.url; // all good
}
So, this works, but it seems so wrong.

As of TypeScript 2.1, you can use a lookup type to access an interface property.
IncomingMessage['url'] // string | undefined
You can combine that with NonNullable to fit your use case.
NonNullable<IncomingMessage['url']> // string
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

So I found a solution which is slightly less hacky.
TypeScript 2.0 also has added a non-null assertion operator: !
function someMiddleware(req: IncomingMessage) {
const str1: string = req.url; // error, can't assign string | undefined to string
const str2: string = req.url!; // works
}
In my case, it's still a bit annoying, since there are many different files which need to access this property and so this non-null assertion is used in many places.

In your sample case, it's easy because you want to get rid of ALL undefined, therefore use the Required utility type.
interface IncomingMessage { url?: string; }
type ValidMessage = Required<IncomingMessage>;
ValidMessage will have all properties required.
But for those coming here to find out how to get rid of ALL null, you can use this custom utility type.
export type NonNullableFields<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableFields<IncomingMessage>;
ValidMessage will have all properties not null.
And for those coming here to find out how to get rid of null only for specific fields, you can use these custom utility types.
export type NonNullableFields<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
export type NonNullableField<T, K extends keyof T> = T &
NonNullableFields<Pick<T, K>>;
interface IncomingMessage { url: string | null; }
type ValidMessage = NonNullableField<IncomingMessage, 'url'>;
ValidMessage will have the property url not null.

Here's a solution defining a utility type RequiredProperties:
type RequiredProperties<T, P extends keyof T> = Omit<T, P> & Required<Pick<T, P>>;
Example usage:
type Foo = {
a?: any;
b?: any;
c?: any;
};
type Bar = RequiredProperties<Foo, 'a' | 'b'>;
const bar1: Bar = { a: 1, b: 2, c: 3 };
const bar2: Bar = { b: 2, c: 3 }; // fails because `a` is now required
const bar3: Bar = { c: 3 }; // fails because both `a` and `b` are missing

Related

Retrieve inferred string literal from object keys

I'm trying to create a type that defines the value based on the key. If the key extends $${string} (e.g. $foo) the value should be the key without the prefix e.g. foo. If the key doens't extend $${string} (e.g. boo) the values should be null.
Example
const example = {
$foo: 'foo',
boo: null,
}
Here is an isolated example I created to get it done - but it doesn't work as intended when I apply it to the code below. 😕
type Value<T> = T extends `$${infer I}` ? I : null
type ExampleA = Value<'$foo'> // type is 'foo'
type ExampleB = Value<'boo'> // type is null
My current code
type Values = {
[K in string]: K extends `$${infer p}` ? p : null;
}
const values = {
$foo: 'foo', // Type 'string' is not assignable to type 'null'.
foo: null,
$boo: 'boo', // Type 'string' is not assignable to type 'null'.
boo: null,
} satisfies Values;
type Expected = {
readonly $foo: 'foo',
readonly foo: null,
readonly $boo: 'boo',
readonly boo: null,
}
The satisfies Values is used to infer the type later on. Similar approach is acceptable🙂
Thanks for your help and time - cheers
The problem with your Values type is that mapped types over string do not behave the way you expect them to. While conceptually you can think of string as the infinite union of all possible string literal types, a mapped type over string does not even try to iterate over every possible string literal type; it just maps one thing: string:
type Values = {
[K in string]: K extends `\$${infer S}` ? S : null;
}
/* type Values = {
[x: string]: null;
} */
And since string does not extend `\$${infer S}`, then the property type for the string key is null.
This is working as intended, as discussed in microsoft/TypeScript#22509. Mapped types over string are not what you want.
And unfortunately there is no way to write a specific type in TypeScript which behaves the way you want. The closest you could get is something like
type Values = {
[k: `\$${string}`]: string;
[k: string]: string | null;
}
using a template string pattern index signature, but the parts where the property value string needs to match the part after the "$" character (not just string) and the part where other keys need to have a null (not just string | null) cannot be represented:
const values = {
$foo: 'foo',
foo: null,
$boo: 'boo',
boo: null,
$oops: null, // error, not string
oops: 'hmm', // should be error, but isn't!
$whoops: 'oops', // should be error, but isn't!
} satisfies Values;
So we have to give up on the approach using the satisfies operator, because there is no appropriate Values type to use it with.
What you really care about is having the type of values inferred by the compiler but still checked against your desired constraint. We can get behavior like this by replacing satisfies Values with a generic helper function we can call satisfiesValues(). At runtime this function just returns its input, but the compiler can use it to validate the object literal passed in. So instead of const values = {...} satisfies Values; you would write const values = satisfiesValues({...});.
Here's one possible implementation:
const satisfiesValues = <K extends PropertyKey>(
val: { [P in K]: P extends `\$${infer S}` ? S : null }
) => val;
The function is generic in K, the keys of the val value passed in. This will most likely be some union of known keys (none of which will be just string), and then the mapped type behaves as desired:
const values = satisfiesValues({
$foo: 'foo',
foo: null,
$boo: 'boo',
boo: null,
$oops: null, // error, not "oops"
oops: 'hmm', // error, not null
$whoops: 'oops', // error, not "whoops"
});
/* const values: {
foo: null;
$foo: "foo";
boo: null;
$boo: "boo";
oops: null;
$whoops: "whoops";
$oops: "oops";
} */
Looks good. The type of values is what you want it to be, and the compiler allows the valid properties and complains about the invalid ones.
Playground link to code

Typescript: Generic type of method params to match type of callback function params

I'm trying to make a class that accepts a function in the constructor. The function can have arguments of any type. Then I want to put a method on the class that accepts that same arguments as function parameter, as it will be a wrapper around this callback. Here's a simplified example to show what I'm trying to do
interface Options<T> {
callbackFn(...x: any[]) => Promise<T>
}
class ExampleClass<T> {
private options: Options<T>;
result: T;
constructor(options: Options<T>) {
this.options = options;
}
async wrapperFn(...x: any[]) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(x)
// Do some stuff after
}
}
const example = new ExampleClass<string>({
callbackFn: (a: string, b:string) => new Promise((res) => {
res(a + b);
})
});
example.wrapperFn("foo", "bar")
This is basically the way I have it now, and it works but it obviously doesn't enforce the types of the params of wrapperFn which isn't ideal. Is there any way to do something like this?
If you want the compiler to keep track of both the callback return type and the callback argument list type, then you'll want Options to be generic in both the return type (you called it T but I'll call it R for "return") and the argument list type (I'll call it A for "arguments"):
interface Options<A extends any[], R> {
callbackFn(...x: A): Promise<R>
}
Now you can just use A anywhere you were using any[] before, and you'll get stronger typing. This also implies that ExampleClass needs to be generic in A and R too:
class ExampleClass<A extends any[], R> {
private options: Options<A, R>;
result?: R;
constructor(options: Options<A, R>) {
this.options = options;
}
async wrapperFn(...x: A) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(...x)
// Do some stuff after
}
}
Let's test it out:
const example = new ExampleClass({
callbackFn: (a: string, b: string) => new Promise<string>((res) => {
res(a + b);
})
});
// const example: ExampleClass<[a: string, b: string], string>
example.wrapperFn("foo", "bar") // okay
example.wrapperFn("foo", 123); // error!
// --------------------> ~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.
Looks good.
Playground link to code

Extract "defined" type from property in TypeScript at runtime

What I want to do
I'm currently looping over an object's keys and transferring the values to another object.
interface From {
[key: string]: string;
}
let from: From = {
prop1: "foo",
prop2: "23",
};
interface To {
[key: string]: string | number | boolean | undefined;
prop1: string;
prop2: number;
prop3: boolean;
}
let to: To = {} as To;
const initEnv = async () => {
const keys: string[] = Object.keys(from);
for (let i = 0; i < keys.length; i++) {
let key: string = keys[i];
let keyType = typeof (key as keyof To); // This only returns "string". Which kind of makes sense to me
let keyType = typeof keyof key; // SyntaxError: ',' expected
let keyType: typeof to[key]; // Error: 'key' refers to a value, but is being used as a type
to[key] = from[key];
}
};
I would want to be able to, say switch the value, so I don't want to just extract the type of the key. I want to assign it to a variable for use in the code, thus at runtime, as a string for instance.
So I think things like this wouldn't work.
let keyType2: typeof env[key]; // Error: 'key' refers to a value, but is being used as a type
Maybe, but the question is then; what do I assign to this variable?
The reason for all this, is that I want to convert the from variables to the correct type before, assigning them to the to object.
So yeah, basically, my question is how I would extract the type (as a string, at runtime, dynamically) from the key. Or is it even possible in the first place? And if it isn't why not? I like understanding things.
Thanks for putting up with my bad english, as well.
There are no interfaces at runtime; TypeScript's type system is erased from the emitted JavaScript. Your from and to values will be evaluated like this at runtime:
let from = {
prop1: "foo",
prop2: "23",
};
let to = {};
There's no From or To, and no way to use To to figure out how to coerce from's properties into the right types. The type system has no runtime effects.
The usefulness of TypeScript's type system comes from describing what will happen at runtime and not from affecting things at runtime. Imagine how you would have to write your code in pure JavaScript, and then give types to that code. Here's one way I might do it. Instead of a To interface, let's make a To object whose properties are functions that coerce inputs to other types:
const To = {
prop1: String,
prop2: Number,
prop3: Boolean
}
This is enough information to proceed at runtime.
Now, if you were going to build to manually, the compiler would be able to understand that the resulting value has a prop1 property of type string and a prop2 property of type number:
const toManual = { prop1: To.prop1(from.prop1), prop2: To.prop2(from.prop2) };
/* const toManual: { prop1: string; prop2: number; } */
But you don't want to do it manually; you'd like to write a loop that walks through the keys of from and uses To to produce properties of to. This is harder for the compiler to understand, but with judicious use of type assertions and type annotations you can write an objMap function that works programmatically:
function objMap<T, F extends { [K in keyof T]: (arg: T[K]) => any }>(
obj: T, fMap: F) {
const ret = {} as { [K in keyof T]: ReturnType<F[K]> };
const fM: { [K in keyof T]: (arg: T[K]) => ReturnType<F[K]> } = fMap;
(Object.keys(obj) as Array<keyof T>).forEach(<K extends keyof T>(k: K) => {
ret[k] = fM[k](obj[k]);
})
return ret;
}
The objMap function takes an object obj and a mapping object fMap which has at least all the same keys as obj and whose properties are functions that map obj's properties. The return type of objMap is an object whose properties are all the returned values of the fMap function for each property in obj. The actual work is being done by ret[k] = fM[k](obj[k]);. It's the programmatic equivalent of ret.prop1 = To.prop1(from.prop1); and ret.prop2 = To.prop2(from.prop2).
Let's see if it works:
const to = objMap(from, To);
/* const to: { prop1: string; prop2: number; } */
console.log(JSON.stringify(to));
// {"prop1":"foo","prop2":23}
That looks correct; the type of to is inferred by the compiler to be {prop1: string, prop2: number}, and the actual value of to is computed at runtime to be {prop1: "foo", prop2: 23}.
Okay, hope that helps; good luck!
Playground link to code

How can I use typescript generics for dynamic function arguments

I am trying to create a wrapper method around node's gRPC bindings. I'd like to make a method called rpc on WrapperClient that invokes a method on the underlying GrpcClient class but also type check both the method and the request arguments.
Here is an example I'll cross post to the TS playground.
type ReqA = { type: 'a' }
type ReqB = { type: 'b' }
class GrpcClient {
findA(request: ReqA) { };
findB(request: ReqB) { };
}
class WrapperClient {
rpc<GrpcClient, TMethod extends keyof GrpcClient>(client: GrpcClient, method: TMethod, req: any) {
}
}
const grpcClient = new GrpcClient()
const client = new WrapperClient()
// This works
grpcClient.findA({ type: 'a' }) // correct
grpcClient.findB({ type: 'b' }) // correct
// This doesn't.
// It Matches the method name. That's good.
// But it does not check the request type.
client.rpc(grpcClient, 'findA', 1) // should fail
client.rpc(grpcClient, 'findB', 1) // should fail
client.rpc(grpcClient, 'findC', 1) // double fail, the method check works though
I'm able to use the extends keyof generic expression to typecheck the method names. I'm not able to type check the request arguments though.
I could hard code a union as the request argument type.
rpc<GrpcClient, TMethod extends keyof GrpcClient>(client: GrpcClient, method: TMethod, req: ReqA | ReqB) {
The gRPC bindings are dynamically generated and I don't want to maintain a list of possible request types that could change when I regenerate the bindings.
Thoughts?
You can use a conditional type to determine the request type:
type ReqA = { type: 'a' }
type ReqB = { type: 'b' }
class PeopleServiceClient {
findA(request: ReqA) { };
findB(request: ReqB) { };
}
class WrapperClient {
rpc<PeopleServiceClient, TMethod extends keyof PeopleServiceClient>(
client: PeopleServiceClient, method: TMethod,
req: PeopleServiceClient[TMethod] extends (arg: infer T) => void ? T : never) {
}
}
const grpcClient = new PeopleServiceClient()
const client = new WrapperClient()
grpcClient.findA({ type: 'a' }) // correct
grpcClient.findB({ type: 'b' }) // correct
client.rpc(grpcClient, 'findA', {type: 'a'}) // correct
client.rpc(grpcClient, 'findA', {type: 'b'}) // fails
client.rpc(grpcClient, 'findA', 1) // fails
client.rpc(grpcClient, 'findB', 1) // fails
client.rpc(grpcClient, 'findC', 1) // fails

Unexpected "Spread types may only be created from object types" error when using generics

I've got this typescript class that requires a generic type to be provided on construction:
type Partial<T> = {
[P in keyof T]?: T[P];
};
class Foo<Bar> {
bis: Partial<Bar> = {}; // (1)
constructor() {
console.log(typeof this.bis); // object
this.bis = {...this.bis}; // (2) Spread types may only be created from object types
}
}
How ever, as you can see above, i don't get an error at (1), but i do at (2).
Why is this? And how do i fix it?
Edit1:
I've opened an issue over at the Typescript github.
A workaround for this is typecasting the object explicitely with <object>,<any> or <Bar> in your case.
I don't know if your requirements allow this or not but have a look -
type Partial<T> = {
[P in keyof T]?: T[P];
};
class Foo<Bar> {
bis: Partial<Bar> = {}; // (1)
constructor() {
console.log(typeof this.bis); // object
this.bis = {...<Bar>this.bis};
}
}

Resources