Typescript declaration merging: override? - node.js

Is there a way to "override" when doing declartion merge, eg:
app.ts (express + nodejs):
import * as express from 'express';
var app = express();
app.use(function(req, res, next) {
console.log(req.headers.accept);
});
This fails with error:
error TS2339: Property 'accept' does not exist on type '{ [key: string]: string; }'.
Because in express.d.ts headers are declared like this:
headers: { [key: string]: string; };
So what i've tried to do, is create a definition of 'accept' :
declare module Express {
export interface Headers {
accept: String
}
export interface Request {
headers: Headers
}
}
But that does not work also (i can only add new members, not override them, right?):
error TS2430: Interface 'e.Request' incorrectly extends interface 'Express.Request'.
Types of property 'headers' are incompatible.
Type '{ [key: string]: string; }' is not assignable to type 'Headers'.
Property 'accept' is missing in type '{ [key: string]: string; }'.
So the only way to overcome this is change notation to:
req.headers.accept -> req.headers['accept']
Or i somehow can "redeclare" the headers property?

In Express, you can use the accepts method (accepts documentation):
if (req.accepts('json')) { //...
Or if you want them all, they are available as:
console.log(req.accepted);
To get other headers, use the get method (get method documentation)
req.get('Content-Type');
These are all included the the type definition for Express on Definitely Typed.

Related

How to read properties in typescript after using Object.defineProperty?

I have the following code on typescript playground and a few questions come up that I am not sure how to get working
class PathInfo {
functionName: string;
httpPath: string;
httpMethod: string;
constructor(functionName: string, httpPath: string, httpMethod: string) {
this.functionName = functionName;
this.httpPath = httpPath;
this.httpMethod = httpMethod;
}
toString(): string {
return "PathInfo["+this.functionName+","+this.httpPath+","+this.httpMethod+"]";
}
}
class AuthRequest {}
class AuthResponse {}
class LoginRequest {}
class LoginResponse {}
const path: any = (thePath: string, type: any) => {
return (target: Function, memberName: string, propertyDescriptor: PropertyDescriptor) => {
const pathMeta = new PathInfo(memberName, path, type);
Object.defineProperty(target, memberName+'pathInfo', {
value: pathMeta,
writable: false
});
//How do I access the stored pathMeta
//console.log("target="+target.pathInfo);
console.log("member="+memberName);
console.log("props="+propertyDescriptor);
}
}
class AuthApiImpl {
#path("/authenticate", AuthResponse)
authenticate(request: AuthRequest): Promise<AuthResponse> {
throw new Error("all this is generated by factory.createApiImpl");
}
#path("/login", LoginResponse)
login(request: LoginRequest): Promise<LoginResponse> {
throw new Error("all this is generated by factory.createApiImpl");
}
};
function printMethods(obj: any) {
console.log("starting to print methods");
for (var id in obj) {
console.log("id="+id);
try {
//How do I access the stored pathMeta here FOR EACH METHOD ->
//console.log("target="+target.pathInfo);
if (typeof(obj[id]) == "function") {
console.log(id+":"+obj[id].toString());
}
} catch (err) {
console.log(id + ": inaccessible"+err);
}
}
}
console.log("starting to run")
const temp = new AuthApiImpl();
printMethods(temp);
console.log("done")
line 64-65, how to read the property that I set
line 40-41, how to read the property that I set
line 58-74, why is this not printing any functions? I want to print all functions and I do NOT want to print properties (just functions)
line 33, Can I access the class name at this point?
line 35, I thought target was a function and would be authorize, then login, BUT if I define the property as JUST 'pathInfo', I get an error that the property is already defined on the target(This implies the target is the class not the function?). I am so confused.
Terribly sorry as I try to focus on a single question, but this one test of writing decorators has given me more questions than answers as I delve into the typescript world.
How can I tweak the code to play more here?
A goal here is as developers define the APIs of other microservices, I can capture a bunch of meta information and store it SOMEWHERE I can use later in startup code. I do not care where I store that really, but just need a clean way of knowing the class I want to extend, the methods, the return types, the http path, etc.
How to get methods of a class
You still can't grab the method names even if you remove the decorator. This isn't a TypeScript specific question.
You need to get the properties of the prototype, not just the object itself.
function printMethods(obj: any) {
console.log("starting to print methods");
const objProto = Object.getPrototypeOf(obj);
console.log(Object.getOwnPropertyNames(objProto));
}
How to access class names
Don't think this is possible with decorators at the moment, but it should be straightforward to just pass in your class name as a string.
Similar issue: TypeScript class decorator get class name
Open issue on GitHub: https://github.com/microsoft/TypeScript/issues/1579
"property is already defined on the target"
Notice if you run the code above you get the following in console.log:
["constructor", "authenticate", "login", "authenticatepathInfo", "loginpathInfo"]
I also want to point out that if you don't even initialize an instance of the class, you'll still get the same error.
I want to read this meta data in nodejs and use that to dynamically create a client implementing the api. Basically, developers never have to write clients and only write the api and the implementation is generated for them.
If I were to do that, I'd probably not use decorators, but mapped types:
// library code
interface ApiMethodInfo {
httpPath: string;
httpMethod: string;
}
type ApiInfo<S extends object> = Record<keyof S, ApiMethodInfo>;
type Client<S extends object> = {[key in keyof S]: S[key] extends (req: infer Req) => infer Res ? (req: Req) => Promise<Res> : never};
function generateClient<S extends object>(apiInfo: ApiInfo<S>): Client<S> {
const client = {} as Client<S>;
for (const key in apiInfo) {
const info = apiInfo[key as keyof S];
client[key] = ((param: any) => invokeApi(info, param)) as any;
}
return client;
}
// application code
interface AuthRequest {}
interface AuthResponse {}
interface LoginRequest {
username: string,
password: string,
}
interface LoginResponse {}
interface MyServer {
authenticate(request: AuthRequest): AuthResponse;
login(request: LoginRequest): LoginResponse;
}
const myApiInfo: ApiInfo<MyServer> = { // compiler verifies that all methods of MyServer are described here
authenticate: {
httpPath: '/authenticate',
httpMethod: 'POST'
},
login: {
httpPath: '/login',
httpMethod: 'POST'
}
}
const myClient = generateClient(myApiInfo); // compiler derives the method signatures from the server implementation
const username = "joe";
const password = "secret";
const response = myClient.login({username, password}); // ... and can therefore check that this call is properly typed
(To understand how these type definitions work, you may want to read the section Creating Types from Types in the TypeScript Handbook)
The weakness of this approach is that while the compiler can derive the client signatures from the server signatures, it will not copy any JSDoc, so client devs can not easily access the API documentation.
In the above code, I chose to specify the metadata in a separate object rather than decorators so the compiler can check exhaustiveness (decorators are always optional; the compiler can not be instructed to require their presence), and because decorators are an experimental language feature that may still change in future releases of the language.
It is entirely possible to populate such a metadata object using decorators if that's what you prefer. Here's what that would look like:
// library code
interface ApiMethodInfo {
httpPath: string;
httpMethod: string;
}
const apiMethodInfo = Symbol("apiMethodInfo");
function api(info: ApiMethodInfo) {
return function (target: any, propertyKey: string) {
target[apiMethodInfo] = target[apiMethodInfo] || {};
target[apiMethodInfo][propertyKey] = info;
}
}
type ApiInfo<S extends object> = Record<keyof S, ApiMethodInfo>;
type Client<S extends object> = {[key in keyof S]: S[key] extends (req: infer Req) => infer Res ? (req: Req) => Promise<Res> : never};
function invokeApi(info: ApiMethodInfo, param: any) {
console.log(info, param);
}
function generateClient<S extends object>(serverClass: new() => S): Client<S> {
const infos = serverClass.prototype[apiMethodInfo]; // a decorator's target is the constructor function's prototype
const client = {} as Client<S>;
for (const key in infos) { // won't encounter apiMethodInfo because Symbol properties are not enumerable
const info = infos[key];
client[key as keyof S] = ((param: any) => invokeApi(info, param)) as any;
}
return client;
}
// application code
interface AuthRequest {}
interface AuthResponse {}
interface LoginRequest {
username: string,
password: string,
}
interface LoginResponse {}
class MyServer {
#api({
httpPath: '/authenticate',
httpMethod: 'POST'
})
authenticate(request: AuthRequest): AuthResponse {
throw new Error("Not implemented yet");
}
#api({
httpPath: '/login',
httpMethod: 'POST'
})
login(request: LoginRequest): LoginResponse {
throw new Error("Not implemented yet");
}
}
const myClient = generateClient(MyServer); // compiler derives the method signatures from the server implementation
const username = "joe";
const password = "secret";
const response = myClient.login({username, password}); // ... and can therefore check that this call is properly typed
Notice how using a Symbol prevents name collisions, and ensures that other code doesn't see this property (unless they look for that particular Symbol), and therefore can not be tripped up by its unexpected presence.
Also notice how MyServer, at runtime, contains the constructor of the class, whose prototype holds the declared instance methods, and it being passed as target to any decorators thereof.
General Advice
May I conclude with some advice for the recovering Java programmer? ;-)
EcmaScript is not Java. While the syntax may look similar, EcmaScript has many useful features Java does not, which often allow writing far less code. For instance, if you need a DTO, it is wholly unnecessary to declare a class with a constructor manually copying each parameter into a property. You can simply declare an interface instead, and create the object using an object literal. I recommend looking through the Modern JavaScript Tutorial to familiarize yourself with these useful language features.
Also, some features behave differently in EcmaScript. In particular, the distinction between class and interface is quite different: Classes are for inheriting methods from a prototype, interfaces for passing data around. It's quite nonsensical to declare a class for a Response that will be deserialized from JSON, because prototypes don't survive serialization.

Typescript extending a generic type

I have the following generic interface in my typescript code:
interface BaseResponse<T> {
status_code: string;
data: T;
}
I thought I would be able to use that base interface, without specifying the base's type parameter, in a generic function like this:
class MyService {
static async post<T extends BaseResponse>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this gives the following error:
Generic type 'BaseResponse<T>' requires 1 type argument(s).(2314)
I can fix this error by updating the code like so:
class MyService {
static async post<T extends BaseResponse<U>, U>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this requires me to pass two type parameters when I call the function as below. I was hoping I could only pass one and it could infer the second, but that gives me the error Expected 2 type arguments, but got 1.(2558). Is there any way to accomplish this?
// What I want to be able to do (Causes error mentioned above):
const response1 = await MyService.post<CustomerResponse>('/customers', postData);
// What I have to do instead (note the two type parameters)
const response2 = await MyService.post<CustomerResponse, CustomerData>('/customers', postData);

How to add custom Request type to Express?

I want to add custom Request type to express.
There is way to just extend Request. But this way I have to check is auth undefined.
I cant find how to use AuthorizedRequest with app.get('/path/, ...)
How can I declare AuthorizedRequest properly?
// need to check undefined, auth can not exists so i cant remove "?"
declare module 'express-serve-static-core' {
interface Request {
auth?: {
test: string
}
}
}
// error with routing app.get(...)
declare module 'express-serve-static-core' {
interface AuthorizedRequest<P extends Params = ParamsDictionary> extends Request<P> {
auth: {
test: string
}
}
}
But I got the following error:
"- error TS2769: No overload matches this call."
Create ./typings/index.d.ts
Set tsconfig: "typeRoots": ["./typings", "./node_modules/#types"]
index.d.ts example:
type Params = {
test1: {
test2: string
},
test3: number
}
declare global {
namespace Express {
export interface Request extends Params {}
}
}
declare module 'express' {
export type ExtendableRequest = Request<any, any, any, any> &
}
export {}

How to customise firebase DocumentData interface?

I'm using firebase#5.5.8 and typescript#3.1.4
whenever I create a document from firestore i get an object of type firebase.firestore.DocumentReference as expected
If I call the get(options?: firebase.firestore.GetOptions|undefined) I'll get a object of type firebase.firestore.DocumentSnapshot as expected
If i call data(options?: firebase.firestore.DataOptions|undefined) I'll get either a firebase.firestore.DocumentData object or undefined, as expected
Now, on my manager object, I know what I'm writing to the databse, so I can make the assertion that whatever DocumentData you get out of my manager, you'll get a Client as shown
export interface Client {
name: string;
website?: string;
description?: string;
visible: boolean;
}
I want to create an interface for my manager object that expresses that. So I've tried:
export interface FirebaseDocumentSnapshot<T> extends $firebase.firestore.DocumentSnapshot {
data(options?: $firebase.firestore.SnapshotOptions | undefined): T|undefined
}
export interface FirebaseDocumentReference<T> extends $firebase.firestore.DocumentReference {
get(options?: $firebase.firestore.GetOptions | undefined): Promise<FirebaseDocumentSnapshot<T>>
}
The problem i've got is here:
const client: Client = mapClient(args);
const result: FirebaseDocumentReference<Client> = await $db.add(client);
the error is:
[ts]
Type 'DocumentReference' is not assignable to type 'FirebaseDocumentReference<Client>'.
Types of property 'get' are incompatible.
Type '(options?: GetOptions | undefined) => Promise<DocumentSnapshot>' is not assignable to type '(options?: GetOptions | undefined) => Promise<FirebaseDocumentSnapshot<Client>>'.
Type 'Promise<DocumentSnapshot>' is not assignable to type 'Promise<FirebaseDocumentSnapshot<Client>>'.
Type 'DocumentSnapshot' is not assignable to type 'FirebaseDocumentSnapshot<Client>'.
Types of property 'data' are incompatible.
Type '(options?: SnapshotOptions | undefined) => DocumentData | undefined' is not assignable to type '(options?: SnapshotOptions | undefined) => Client | undefined'.
Type 'DocumentData | undefined' is not assignable to type 'Client | undefined'.
Type 'DocumentData' is not assignable to type 'Client'.
Property 'name' is missing in type 'DocumentData'. [2322]
const result: FirebaseDocumentReference<Client>
How can i declare the interfaces so I can know the type of the resulting object?
A simple solution would be to cast the return data:
const client = snapshot.data() as Client
#gal-bracha's solution will "work" in the sense that the TypeScript compiler will assume client is of type Client, but it doesn't prevent a runtime error if bad data ended up in Firestore. A safer solution when dealing with any data external to your app is to use something like Yup to validate data explicitly:
import * as yup from "yup";
const clientSchema = yup.object({
name: yup.string(),
website: yup.string().url().notRequired(),
description: yup.string().notRequired(),
visible: yup.boolean()
});
// You can use this elsewhere in your app
export type Client = yup.InferType<typeof clientSchema>;
// Usage
const client = await clientSchema.validate(snapshot.data());
This is more defensive as clientSchema.validate will throw an error if, for some reason, bad data ended up in Firestore. After validate(), you're guaranteed, that client is a Client and not just telling the TypeScript compiler "treat this as a Client even though at runtime, it may not be".
export declare function getDocs<T>(query: Query<T>): Promise<QuerySnapshot<T>>;

eslint-plugin-import how to add flow declared modules to exceptions

I have a file in flow-typed directory with some common type declarations like:
common-types.js
// #flow
declare module 'common-types' {
declare export type RequestState = {
setLoading: () => void,
setFinished: () => void,
setError: (error: AxiosFailure) => void,
reset: () => void,
status: LoadingStatus,
error: AxiosFailure | void,
};
declare export type LoadingStatus = '' | 'loading' | 'finished';
declare export type ErrorObject = { [key: string]: string | string[] | Error };
declare export type AxiosFailure = {
status: number,
data: ErrorObject,
}
}
Now I import it like this in files:
import type { RequestState } from 'common-types';
but I get eslint-plugin-import errors about missing file extension as well as unable to resolve path to module 'common-types'
How do I deal with it?
I found a solution. As #logansmyth suggested in comment
Your types should just pass your code along with your data
The problem I had was with webpack aliases. Flow pointed me errors about modules not being installed. Hovewer I found out that I can use mappers in .flowconfig like:
module.name_mapper='^common' ->'<PROJECT_ROOT>/src/common'
along with webpack aliases, which makes eslint-plugin-import and flow happy as well properly type-checking. Now I import types along with common components, no messing with flow-typed directory.

Resources