Incompatible types due to readonly - node.js

I have a object containing a middleware property but cant get the types to work. It tells me that the two middlewares are incompatible because one of them is readonly. Is there a way to solve this? - thanks :)
Example
type Middleware = (...args: unknown[]) => unknown;
type Router = { middleware?: Middleware[] };
const router = {
middleware: [() => null],
} as const satisfies Router;
Error
type Router = {
middleware?: Middleware[] | undefined;
}
Type '{ readonly middleware: readonly [() => null]; }' does not satisfy the expected type 'Router'.
Types of property 'middleware' are incompatible.
The type 'readonly [() => null]' is 'readonly' and cannot be assigned to the mutable type 'Middleware[]'.

A quick solution would be to remove the as const, which makes the object literals readonly:
const router = {
middleware: [() => null],
} satisfies Router;
Depending on your use case: you can also change the type Router by adding the utility type Readonly:
type Router = { middleware?: Readonly<Middleware[]> };
Though you are not able to call e.g. push on router.middleware.

Related

Typescript - how can i add a custom property to express. Request with two or more types?

I have a problem. I know how to add a custom property to Express.Request, but i dont know how to give that custom property two or more types. For example i have a.ts and b.ts. These file export a function as default. And i import them in c.ts. All those functions add the same property to Express.Request. But those functions, add different types of functions as value! Look at this example:
a.ts:
declare module "express-serve-static-core" {
interface Request {
store: (name: string) => number
}
}
const func1 = (req:Request, res:Response) => {
req.store = (name) => {
return setInStore(name)
}
}
export default func1;
b.ts:
declare module "express-serve-static-core" {
interface Request {
store: (key: number) => string
}
}
const func2 = (req:Request, res:Response) => {
req.store = (key) => {
return getFromStore(key);
}
}
export default func2;
but when i set the type in b.ts, i will get error from a.ts. it says that type (name: string) => number is not assignable to type (key: number) => string.
and i think that there is an other way instead of using conditional types.
so how can i change the type of store property to (name: string) => number if user used the func1 middleware and change the type of store to (key: number) => string if user used func2 thanks for help.
I am not express user. maybe you can config the type through generics too which is easier
anyway, what you want to do is not possible. however, you can go with one of these solutions:
1. unions
if the store functions supports both value, then it is not important which type in which function you are using... so you can use union
(key: string | number) => void
2. more specific function name
split the store function into 2 seperate functions like this
storeByName: (name: string) => void
storeById: (id: number) => void

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)
})
}

Reference imported object based on env

In my TypeScript Node app I wish to reference the exported object that matches my NODE_ENV variable.
config.ts
const test: { [index: string]: any } = {
param1: "x",
param2: {
name: "John"
}
}
const dev: { [index: string]: any } = {
param1: "y",
param2: {
name: "Mary"
}
}
export { test, dev }
main.ts
const environment = process.env.NODE_ENV || "development";
import * as config from "./config.ts";
const envConfig = config[environment]; //gives error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof import("/path_to_config.ts")'.ts(7053)
Just make the implicit any explicit:
const envConfig: any = (config as any)[environment];
This error often arises when you try to access a property of an object via ['propertyName'] instead of .propertyName, since that form bypasses TypeScript's type checking in many cases.
You could do a bit better than any by defining a type which is constrained to all possible values (which you could export from your config.tsx) e.g.
type configType ='test' | 'dev'
const envConfig = config[environment as configType];

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>>;

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