How to extend built-in function/add a property to a function? - node.js

process.hrtime.bigint() does not exist in the standard lib. I'm trying to add it in.
Here's what I've got:
types/bigint.d.ts
// https://github.com/Microsoft/TypeScript/issues/15096#issuecomment-400186862
type BigInt = number
declare const BigInt: typeof Number;
declare namespace NodeJS {
export interface Process {
hrtime: HighResTime
}
export interface HighResTime {
(time?: [number, number]): [number, number];
bigint(): BigInt
}
}
Which I've added to my typeRoots in tsconfig.json:
"typeRoots": ["./node_modules/#types","./types"]
Using like:
///<reference path="../types/bigint.d.ts"/>
export default class ProgressBar {
private startTime?: BigInt;
start() {
this.startTime = process.hrtime.bigint();
this.render();
}
But I get:
/home/me/Projects/xxx/node_modules/ts-node/src/index.ts:261
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
src/ProgressBar.ts(19,41): error TS2339: Property 'bigint' does not exist on type '(time?: [number, number] | undefined) => [number, number]'.
If I remove the <reference> to bigint.d.ts then it gets even worse, and complains the type BigInt doesn't exist (TS2304) -- which doesn't make much sense to me, i thought that was the point of the typeRoot?
But the bigger issue is that even with the reference, TS still doesn't like process.hrtime.bigint(), so I'm guessing I didn't extend the Process interface properly. How do I do it right?

Related

interfaces in typescript: use function parameter on a nested object reference

I have this object model:
export interface UpdateDocument {
updated_at?: string;
actions?: Actions;
}
export interface Actions {
update?: Update;
create?: Create;
}
export interface Update {
name?: Values;
priority?: Values;
engine?: Values;
fact?: Fact;
}
export interface Fact {
brand?: Values;
model?: Values;
version?: Values;
year?: Values;
km?: Values;
color?: Values;
}
export interface Values {
old?: any;
new?: any;
}
export interface Create {
conditions?: Object;
recipe?: Object;
}
In this function i tried to pass a parameter to references an objects field and do an assignment:
async buildUpdateDocument (updateDocument: UpdateDocument) {
let fields: Array<string> = ['name','priority','engine','fact','adjustment'];
fields.forEach((field: string) =>{
updateDocument.actions!.update![field]!.old = await this.getValue(field)
})
}
but i hav this ts-error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Update'.
No index signature with a parameter of type 'string' was found on type 'Update'.ts(7053)
How can i pass the parameter in this kind of reference to do the assignment?
First of you have specified a wrong key adjustment that doesn't exist on Update. This example uses a explicit type (as const):
let fields = ['name','priority','engine','fact'] as const;
Make sure to not add a type definition to the variable when using as const.
Here is the modified function to better fit TS standards. This also addresses the forEach-async problem in the original code. The real correct structure would be null checks for each of the x | undefined types, but to override the type errors the following is the way to go.
async function buildUpdateDocument (updateDocument: UpdateDocument) {
const fields: Array<keyof Update> = ['name','priority','engine','fact'];
await Promise.all(fields.map(async (field) => {
(((updateDocument.actions as {update: Update}).update)[field] as Values).old = await this.getValue(field);
}));
}
Your current code has bugs that the type system would help you find if you let it. First, the adjustment field doesn't exist on the Update type, and old field doesn't exist on the Fact type.
To implement this properly, I would use a Record for the data type instead:
const updateFields = ['name', 'priority', 'engine', 'fact'] as const
export type UpdateFields = typeof updateFields[number]
export type Update = Record<UpdateFields, Values>
And then, your function will look like this:
async buildUpdateDocument (updateDocument: UpdateDocument) {
updateFields.forEach((field) =>{
updateDocument.actions!.update![field]!.old = await this.getValue(field)
})
}

Fix for Typescript warnings for type ‘any’ with Cloud Functions for Firebase

I’m getting a number of warnings all relating to the use of ‘any’ as a return type for functions in my Typscript code. I am trying to write a node.js backend with Cloud Functions for Firebase, to manage Google Play Billing purchases and subscriptions.
I am following the examples given in the Classy Taxi Server example here:
https://github.com/android/play-billing-samples/tree/main/ClassyTaxiServer
For example, the following:
function purchaseToFirestoreObject(purchase: Purchase, skuType: SkuType): any {
const fObj: any = {};
Object.assign(fObj, purchase);
fObj.formOfPayment = GOOGLE_PLAY_FORM_OF_PAYMENT;
fObj.skuType = skuType;
return fObj;
}
Gives the warning
Unexpected any. Specify a different type. #typescript-eslint/no-explicit-any)
I have tried to change ‘any’ to ‘unknown’, but then I get an error
Property 'formOfPayment' does not exist on type 'unknown'.ts(2339)
and
Property 'skuType' does not exist on type 'unknown'.ts(2339)
In another function
export function mergePurchaseWithFirestorePurchaseRecord(purchase: Purchase, firestoreObject: any) {
// Copy all keys that exist in Firestore but not in Purchase object, to the Purchase object (ex. userID)
Object.keys(firestoreObject).map(key => {
// Skip the internal key-value pairs assigned by convertToFirestorePurchaseRecord()
if ((purchase[key] === undefined) && (FIRESTORE_OBJECT_INTERNAL_KEYS.indexOf(key) === -1)) {
purchase[key] = firestoreObject[key];
}
});
}
I get the following warnings
Missing return type on function. #typescript-eslint/explicit-module-boundary-types
Argument 'firestoreObject' should be typed with a non-any type. #typescript-eslint/explicit-module-boundary-types
Unexpected any. Specify a different type. #typescript-eslint/no-explicit-any
In this function, if I change ‘any’ to ‘unknown’, I still get a warning for
Missing return type on function.
In another example I get an error for the use of ‘any’ in this constructor:
export default class PurchaseManager {
constructor(private purchasesDbRef: CollectionReference, private playDeveloperApiClient: any) { }
And again the warning is
Argument 'playDeveloperApiClient' should be typed with a non-any type. #typescript-eslint/explicit-module-boundary-types
In this case, if I follow the suggestion to use ‘unknown’ instead of ‘any’, then I get an error for ‘purchases’ in the following function:
const apiResponse = await new Promise((resolve, reject) => {
this.playDeveloperApiClient.purchases.products.get({
packageName: packageName,
productId: sku,
token: purchaseToken,
}, (err, result) => {
if (err) {
reject(this.convertPlayAPIErrorToLibraryError(err));
} else {
resolve(result.data);
}
})
});
The error created by changing ‘any’ to ‘unknown’ in the constructor is:
Property 'purchases' does not exist on type 'unknown'.ts(2339)
If I understand correctly, I could prevent all of these (and other similar) warnings without creating errors, by disabling explicit-module-boundary-types, and/or no-explicit-any for the entire file, but I am not sure if this is bad practice?
Is there another (better) way to specify the return types to avoid using ‘any’?
Or is it fine to just go ahead and disable explicit-module-boundary-types or no-explicit-any?
If you do not want to use any, you will have to declare an interface for your type and set that as the return type of the function.
You can do something like:
interface MyType {
formOfPayment: string;
skyType: SkyType;
foo: //<any other pre-existing type>;
}
Then you can use this as the return type of any other function that requires you to return an object with above properties. e.g.
function purchaseToFirestoreObject(purchase: Purchase, skuType: SkuType): MyType {
const fObj: any = {};
Object.assign(fObj, purchase);
fObj.formOfPayment = GOOGLE_PLAY_FORM_OF_PAYMENT;
fObj.skuType = skuType;
return fObj;
}

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.

Use commander in typescript

I try to use commander in typescript and I could like to give a proper type to my cli. So I start with this code:
import * as program from "commander";
const cli = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
But I get this error:
example.ts(9,17): error TS2339: Property 'debug' does not exist on type 'Command'.
So I tried to add an interface, as documented here:
import * as program from "commander";
interface InterfaceCLI extends commander.Command {
debug?: boolean;
}
const cli: InterfaceCLI = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
and I get this error:
example.ts(3,32): error TS2503: Cannot find namespace 'commander'.
From what I understand, cli is actually a class of type commander.Command So I tried to add a class:
import * as program from "commander";
class Cli extends program.Command {
public debug: boolean;
}
const cli: Cli = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
Which gives me this error:
example.ts(7,7): error TS2322: Type 'Command' is not assignable to type 'Cli'.
Property 'debug' is missing in type 'Command'.
I don't know how to add a property to the Command class, either in my file or in a new .d.ts file.
With your first code snippet and the following dependencies, I do not get an error:
"dependencies": {
"commander": "^2.11.0"
},
"devDependencies": {
"#types/commander": "^2.9.1",
"typescript": "^2.4.1"
}
Typescript interprets cli.debug as any. I guess the type declarations have been updated. So, if you are fine with any, the problem is solved.
If you really want to tell Typescript the type of debug, declaration merging would in principle be the way to go. It basically works like this:
class C {
public foo: number;
}
interface C {
bar: number;
}
const c = new C();
const fooBar = c.foo + c.bar;
However, there is a problem: program.Command is not a type but a variable. So, you cannot do this:
interface program.Command {
debug: boolean;
}
And while you can do this:
function f1(): typeof program.Command {
return program.Command;
}
type T = typeof program.Command;
function f2(): T {
return program.Command;
}
You can neither do this:
interface typeof program.Command {
}
Nor this:
type T = typeof program.Command;
interface T {
}
I do not know whether this problem could be solved or not.
I think I found the solution, I don't really know if it's a good practice, but nontheless.
import { Command } from 'commander';
const cli = new Command();
interface InterfaceCLI{
debug?: boolean;
}
cli
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
const { debug } : InterfaceCli = <InterfaceCli><unknown>cli;
console.log(debug) // here debug is your cli.debug, I just used object destructuring
In the line before the last one I'm using type casting, I'm first casting to unkown for non-overlapping types, and then, finally, casting to our interface - InterfaceCli.

TypeScript warning => TS7017: Index signature of object type implicitly has any type

I am getting the following TypeScript warning -
Index signature of object type implicitly has any type
Here is the code that cases the warning:
Object.keys(events).forEach(function (k: string) {
const ev: ISumanEvent = events[k]; // warning is for this line!!
const toStr = String(ev);
assert(ev.explanation.length > 20, ' => (collapsed).');
if (toStr !== k) {
throw new Error(' => (collapsed).');
}
});
can anyone determine from this code block why the warning shows up? I cannot figure it out.
If it helps this is the definition for ISumanEvent:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString
}
You could add an indexer property to your interface definition:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString,
[key: string]: string|TSumanToString|ISumanEvent;
}
which will allow you to access it by index as you do: events[k];. Also with union indexer it's better to let the compiler infer the type instead of explicitly defining it:
const ev = events[k];

Resources