Types of property 'cacheLocation' are incompatible - javascript

I have an old app in react with javascript, but I started a new one, to slowly migrate the .JS code to Typescript.
The first file I want to migrate its a configuration file, when its .js build succeeds.
WHen renamed to .TS I get this error
/Users/xx/yy/lulo/src/adalConfig.ts
(13,54): Argument of type '{ tenant: string; clientId: string; endpoints: { api: string; }; 'apiUrl': string; cacheLocation: string; }' is not assignable to parameter of type 'AdalConfig'.
Types of property 'cacheLocation' are incompatible.
Type 'string' is not assignable to type '"localStorage" | "sessionStorage" | undefined'
The file is this:
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: 'xxxx-c220-48a2-a73f-1177fa2c098e',
clientId: 'xxxxx-bd54-456d-8aa7-f8cab3147fd2',
endpoints: {
api:'xxxxx-abaa-4519-82cf-e9d022b87536'
},
'apiUrl': 'https://xxxxx-app.azurewebsites.net/api',
cacheLocation: 'localStorage'
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);

The issue is that the type of adalConfig gets asserted instead of being defined. You can read more about it in the docs but it basically means that TypeScript guesses the type. Short example:
type FooBar = 'foo' | 'bar';
const fooBar1 = 'foo'; // fooBar1: 'foo'
const fooBar2: FooBar = 'foo'; // fooBar2: FooBar
TypeScript playground link
Type assertion depends on a whole bunch of stuff and it's really hard to guess by hand which type TypeScript is going to assert. It's really useful to write TS code quickly, though. In any case - the problem in your code is that adalConfig.cacheLocation gets asserted to a string, but instead you want TypeScript to understand that its type is compatible with "localStorage" | "sessionStorage" | undefined.
Two ways to do that:
cacheLocation: 'localStorage' as 'localStorage': will precise to TypeScript that cacheLocation is of type 'localStorage', thus compatible with what you want
export const adalConfig: AdalConfig = ... will precise to TypeScript that the whole adalConfig object is of type AdalConfig, so it has basically the same effect
Kudos to #explosion-pills and #zerkms who contributed in the comments to this question

I know this is an oldie but always helpful to post an update. You can import configuration from the msal library to set the type of the config variable.
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
import { Configuration } from 'msal';
// Msal Configurations
const config: Configuration = {
auth: {
authority: 'https://login.microsoftonline.com/',
clientId: '<YOUR APPLICATION ID>'
},
cache: {
cacheLocation:"localStorage",
storeAuthStateInCookie: true,
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
`<your app registartion app id>/.default`
]
}
// Options
const options = {
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + '/auth.html'
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)

import { CacheLocation } from "#auth0/auth0-react";
const AUTH0_CASH_LOCATION: CacheLocation | unefined = "localstorage";
this will help you. Auth0 provider makes sure that caching should save in memory or localStorage so you should provide either localStorage or memory for cacheLocation option

Related

Type declaration file doesn't work with commonjs module syntax

Today I was writing a type declaration file for a JavaScript file but despite my hours of trying I couldn't make it work inside a node application. Then I tried switching to es6 module syntax and surprisingly it worked.
Then I discovered that I can also make it work with commonjs module syntax if I add ".default" before accessing any properties of that module. For example I've to use person.default.name instead of person.name to get the intellisence.
I wrote a small module to demonstrate the problem. I've created an object with identical typings of my actual module.
index.js
const names = {
firstname: "Alex",
middlename: "Blex",
lastname: "Clex",
};
const state = {
isAlive() {
return true;
},
isWalking() {
return false;
},
};
function talk(speech) {
console.log(speech);
}
const person = {
names,
state,
talk
};
module.exports = person;
index.d.ts
declare type predicate = (v: unknown) => boolean;
declare function talk(speech: string): void;
declare const person: {
names: {
[key: string]: string;
};
state: {
[key: string]: predicate;
};
talk: typeof talk;
};
export default person;
test1.js
const person = require("./index");
const isAlive = person.default.state.isAlive();
//---------------------^^^^^^^----------------
// The above line throws an error as expected.
// I've to use "person.default.whatever" to get intellisence
// In the editor it shows "typeof isAlive = boolean".
const isReallyAlive = person.state.isAlive();
// EditorError: Property 'state' does not exist on type
// 'typeof import(./index.js)'.
// In the editor it shows typeof "isReallyAlive = any"
// But infact isReallyAlive is boolean.
test2.js
Using es6 module syntax it works perfectly.
I'm fairly new to Typescript so kindly give me some hint where I'm making the mistake.
Thanks in advance, I highly appreciate your time on StackOverflow <3.
So as explained by this post https://stackoverflow.com/a/40295288/10315665 the export default option only creates a default key inside of the exports object. That's why you can still have normal exports besides it.
Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.
So your definition file is wrong. It should be:
declare type predicate = (v: unknown) => boolean;
export declare const names: {
[key: string]: string;
};
export declare const state: {
[key: string]: predicate;
};
export declare function talk(speech: string): void;
And if you respect that, you can actually utilize typescript and it's awesome type checking by simply writing typescript in the first place!:
export const names = {
firstname: "Alex",
middlename: "Blex",
lastname: "Clex",
};
export const state = {
isAlive() {
return true;
},
isWalking() {
return false;
},
};
export function talk(speech: string) {
console.log(speech);
}
Just remember to enable "declaration": true in the tsconfig to also generate declaration files 😉.

NestJs: Make sure your class is decorated with an appropriate decorator

I am using graphql-request as a GraphQL client to query a headless CMS to fetch stuff, modify and return to the original request/query. headless cms is hosted separately fyi.
I have the following code :
#Query(returns => BlogPost)
async test() {
const endpoint = 'https://contentxx.com/api/content/project-dev/graphql'
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: 'Bearer xxxxxxx',
},
})
const query = gql`
{
findContentContent(id: "9f5dde89-7f9b-4b9c-8669-1f0425b2b55d") {
id
flatData {
body
slug
subtitle
title
}
}
}`
return await graphQLClient.request(query);
}
BlogPost is a model having the types :
import { Field, ObjectType } from '#nestjs/graphql';
import { BaseModel } from './base.model';
import FlatDateType from '../resolvers/blogPost/types/flatDatatype.type';
#ObjectType()
export class BlogPost extends BaseModel {
#Field({ nullable: true })
id!: string;
#Field((type) => FlatDateType)
flatData: FlatDateType;
}
and FlatDateType has the following code
export default class FlatDateType {
body: string;
slug: string;
subtitle: string;
title: string;
}
it throws the following exception :
Error: Cannot determine a GraphQL output type for the "flatData". Make
sure your class is decorated with an appropriate decorator.
What is missing in here?
How is your graphql server supposed to understand the type of FlatDataType when there's no information about it being passed to the graphql parser? You need to add the graphql decorators to it as well. #ObjectType(), #Field(), etc.
FlatDataType is not defined as #ObjectType(), therefore type-graphql (or #nestjs/graphql) can't take it as an output in GraphQL.

Object is possibly undefined is not accepted with question mark

Consider the following ocde:
app-config.json
{
"auth": {
"clientId": "acb610688b49",
},
"cache": {
"cacheLocation": "localStorage"
},
"scopes": {
"loginRequest": ["openid", "profile", "user.read"]
},
"resources": {
"gatewayApi": {
"resourceUri": "https://localhost:44351/api",
"resourceScope": ["api://0e01a2d8/access_as_user"]
}
}
}
authService.js
import { isInternetExplorer } from 'src/services/utils/utilsService'
import * as Msal from 'msal'
import * as configJson from 'src/app-config.json'
type resourceType = {
resourceUri: string
resourceScope: string | string[]
}
type resourcesType = {
[key: string]: resourceType
}
interface JsonConfigInterface extends Msal.Configuration {
scopes: {
loginRequest: string[]
}
resources: resourcesType
}
const config: JsonConfigInterface = configJson as JsonConfigInterface
function MSALConfigFactory(): Msal.Configuration {
return {
auth: {
clientId: config.auth.clientId,
},
cache: {
cacheLocation?: config.cache.cacheLocation as Msal.CacheLocation,
},
}
}
The reported error for cacheLocation?: config.cache.cacheLocation as Msal.CacheLocation is:
(property) cache?: CacheOptions | undefined Object is possibly
'undefined'.ts(2532)
When looking at the msal documentation it says:
export type CacheLocation = "localStorage" | "sessionStorage";
export type CacheOptions = {
cacheLocation?: CacheLocation;
storeAuthStateInCookie?: boolean;
};
The question mark in cacheLocation? indicates that this parameter is optional, which is fine. It can or cannot be defined within the JSON file. So I do not understand why TypeScript complains that it can be undefined when that is an accepted value? Of course, the TS null check is in place, but should it not allow this as there is a question mark?
The current workaround for now is below, but I don't know if that is the correct approach:
// eslint-disable-next-line #typescript-eslint/no-non-null-assertion
cacheLocation: config.cache!.cacheLocation as Msal.CacheLocation,
I'm still a beginner, so thank you for your help.
Typescript is trying to tell you that this property access might result in an error:
config.cache.cacheLocation
If either config or config.cache turn out undefined, an exception will be raised.
You can do this:
config?.cache?.cacheLocation
It seems this is the type:
export type Configuration = {
auth?: BrowserAuthOptions,
cache?: CacheOptions,
system?: BrowserSystemOptions
};

How to declare flow types for REST API

=====EDIT=====
Thanks to #kalley I updated my declaration file and made a bit of progress. I don't have a linting error anymore, but I still can't get the type to be recognizes where I import them. I updated the content of the files above
=====end EDIT=====
I'm trying to create flow type for a REST API that I consume for a project I"m working on.
I'm running into an issues:
the types are not being seen when i'm editing the source using the new type
Here is the file which contains the types (src/flow-typed/API.js)
// #flow
declare module "API" {
// type Error = {code:number, message:string};
declare export type ImageJSON = {
url: string,
reference: string
};
declare export type CategoryJSON = {
reference: number,
name: string,
slug: string,
icon: ImageJSON,
children: [],
created_at: string
};
declare export type CompanyJSON = {
reference: number,
name: string,
slug: string,
email: string,
occupational_medicine: string,
psychology_of_work: string,
logo: ImageJSON,
categories: Array<CategoryJSON>,
created_at: string
};
declare export type GetCompanyResponse = {
error: Error,
company: CompanyJSON
};
// declare export default GetCompanyResponse
}
I import the file this way
import typeof { GetCompanyResponse } from "../flow-typed/API";
I get the following flow error:
[flow] Named import from module `../flow-typed/WazzaAPI` (This module only has a default export. Did you mean `import GetCompanyResponse from ...`?)
If I import the file like so
import typeof GetCompanyResponse from "../flow-typed/API";
Then in the code I get an error saying the GetCompanyResponse does not have an error member:
.then((response: GetCompanyResponse) => {
//check to see if there is an error
if (response.error.code !== ERROR_CODE.noError) {
The error:
[flow] property `error` (Property not found in exports)
Any pointers appreciated

How to make sure a typescript module conforms to an interface

I'm kind of writing a specific content scraper for some domains. So for those domains supported I just have some functions declared, let's just say there is a
export function getContent(html: string): string {}
So, I have many files with this exact interface for example ...
export interface Domain {
supportsHttps: boolean;
getContent(html: string): string;
}
And then for simplicity's sake (to make a map of supported hostname and my domains file), I just
// domainsList.ts
import * as domainA from './domains/domainA';
export default {
"www.domainA.com": domainA,
}
Then I import my domain list
// index.ts
import * as url from 'url';
import domains from './domainsList';
const maybeDomain: Domain | undefined = domains[url.parse(someUrl).host];
if (maybeDomain) {
// here I get proper autocompletion ...
const content = maybeDomain.getContent(someHtml);
} else {
throw new Error('domain not supported');
}
But if I refactor the name of the function in the interface from getContent to getContents for example, I actually do not have any compilation error from inside all the domains files.
I want to ensure ./domains/domainA.ts exported structure conforms to my Domain interface. Do I have a way to do that ?
Since you are not actually defining the function have the type of Domain the compiler won't link the two of them together, thus no errors.
Also, the Domain interface suits a class much better than a function.
You can get all the checks by defining classes instead of functions. Like this:
class AwesomeDomain implements Domain {
public supportsHttps: boolean;
getConten(html: string): string {
return '';
}
}
You can find a full example here.
This is my solution, as long as you expose all of your imports exclusively via an index file export.
module/index.ts
import * as A from "./A";
import * as A from "./B";
// type definitions
export type MyFunc = (args: any[]) => any;
export type MyCollection = {
[key: string]: MyModule,
}
export interface MyModule {
KEY: string;
EXAMPLE_ARRAY: string[];
someFn: MyFunc;
}
export const COMMANDS = {
[A.KEY]: A,
[B.KEY]: B,
} as MyCollection;
module/A.ts
import { MyFunc } from ".";
export const KEY = "some-key";
export const EXAMPLES = [
"hello",
"world",
]
export const someFn: CommandFn = async (args: any[]) => {
return ''
};
If you uncomment one of the exports:
Conversion of type '{ "A": typeof A; "B": typeof B; }' to type 'MyCollection' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Property '[A.KEY]' is incompatible with index signature.
Property 'someFn' is missing in type 'typeof import("...") but required in type 'MyModule'.

Categories