I'm using this library in a TypeScript project.
And this is how my class looks like:
import OnvifManager from 'onvif-nvt'
Class OnvifApi {
// device: any = undefined
device = {} as OnvifDevice
constructor (...params) {
// definition
}
connect (): Promise<any> {
OnvifManager.connect(...params).then((response: OnvifDevice) => {
this.device = response
resolve(response)
}
}
coreService(): Promise<Type[]> {
this.device.add('core')
}
}
And this this interface I created for the response from onvif-nvt
interface OnvifDevice {
address: string
// type
// type
// type
add(name: string): void
}
This class is always giving an error in coreService saying that
this.device.add is not a function.
EDIT: This is the return of the connect method, which returns the whole Camera object.
Things are working when device is defined to any.
How can I do this interface mapping from JavaScript to TypeScript?
The reason is, in the interface, OnvifDevice, add is a mandatory parameter, but the device object is initialised with an empty object.
The dynamic mapping at this.device = response is not happening.
Also please check if the response has add method or not while you debug.
These are the two possible symptoms I see.
Related
I want to define whether a function should contain an argument via an interface. The library I'm developing calls for many different methods to be generated, and hardcoding those methods would require too much maintenance; so I figured that types would be a good place to define such things.
Perhaps this is best explained with code. Here's a library that abstracts some rest API:
interface RequestInterface {
endpoint: string
body?: unknown
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
body: never
}
interface CreatePost extends RequestInterface {
endpoint: '/posts'
body: string
}
function Factory<R extends RequestInterface> (endpoint: R['endpoint']) {
return (body?: R['body']): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => I want this to error
In the above, I'm defining the endpoint and body of a particular type of request in my interfaces. Although the TypeScript compiler correctly guards me against passing the wrong argument types, it doesn't guard me against not passing a value when one is required.
I understand why TypeScript doesn't error (because the method defined in factory can be undefined according to my typings), but I figured the above code was a good way of describing what I want to achieve: a quick, declarative library of methods which satisfy a particular type.
A Possible Solution
If I'm willing to extend my interfaces from two separate interfaces (one or the other) then I can achieve something close to what I want using Construct Signatures:
interface RequestInterface {
endpoint: string
call: () => void
}
interface RequestInterfaceWithBody {
endpoint: string
call: {
(body: any): void
}
}
interface GetPosts extends RequestInterface {
endpoint: '/posts'
}
interface CreatePost extends RequestInterfaceWithBody {
endpoint: '/posts'
call: {
(body: string): void
}
}
function Factory<R extends RequestInterface|RequestInterfaceWithBody> (endpoint: R['endpoint']): R['call'] {
return (body): void => {
console.log(`Hitting ${endpoint} with ${body}`)
}
}
const myLibrary = {
getPosts: Factory<GetPosts>('/posts'),
createPosts: Factory<CreatePost>('/posts'),
}
myLibrary.getPosts() // => Correctly passes
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999) // => Correctly errors
myLibrary.createPosts() // => Correctly errors
myLibrary.createPosts('hi') // => Correctly passes
Aside from the fact that I need to pick between two "super" types before extending anything, a major problem with this is that the Construct Signature argument is not very accessible.
Although not demonstrated in the example, the types I create are also used elsewhere in my codebase, and the body is accessible (i.e GetPosts['body']). With the above, it is not easy to access and I'll probably need to create a separate re-usable type definition to achieve the same thing.
You almost hit the spot with your initial types. Two changes required:
Make body of GetPosts of type void
Make body of returned function required
interface RequestInterface {
endpoint: string;
body?: unknown;
}
interface GetPosts extends RequestInterface {
endpoint: "/posts";
body: void;
}
interface CreatePost extends RequestInterface {
endpoint: "/posts";
body: string;
}
function Factory<R extends RequestInterface>(endpoint: R["endpoint"]) {
return (body: R["body"]): void => {
console.log(`Hitting ${endpoint} with ${body}`);
};
}
const myLibrary = {
getPosts: Factory<GetPosts>("/posts"),
createPosts: Factory<CreatePost>("/posts"),
};
// #ts-expect-error
myLibrary.getPosts("something");
// #ts-expect-error
myLibrary.createPosts(999);
// #ts-expect-error
myLibrary.createPosts();
myLibrary.getPosts();
myLibrary.createPosts("Hello, StackOverflow!");
TS Playground
Explanation
never type tells compiler that this should never happen. So, it someone tries to use GetPosts, it's an error, since it should never happen. void (undefined in this case should be also fine) tells that value should not be there.
Making body required in returned function makes it required. But since it is void for GetPosts, you can call it like myLibrary.getPosts(undefined) or simply myLibrary.getPosts() which is equivalent
I have a service that connects with api
export class ConsolidadoApi {
constructor(private http: HttpClient) { }
getInvestiments(search?: any): Observable<any> {
return this.http.get<any>(`${environment.basePosicaoConsolidada}`);
}
}
Response this api:
https://demo5095413.mockable.io/consolidado
This one is responsible for the logic before reaching the component
#Injectable({
providedIn: 'root'
})
export class CoreService {
public test;
constructor(private api: ConsolidadoApi, private state: StateService) { }
public createMenu() {
this.api.getInvestiments()
.subscribe(response => {
console.log(response.carteiras[0])
this.products = response.carteiras[0]
return this.products;
})
}
In my component
export class MenuComponent implements OnInit {
constructor( private coreService : CoreService ) {}
ngOnInit(): void {
console.log(this.coreService.createMenu())
}
}
But when createMenu is called in menu.component.ts it comes undefined.
The raw response is an object. forEach works only on an array. If you are aiming for forEach in 'categorias', you should try
this.test.categorias.forEach()
When you return Observable<any>, that means the argument of the lambda you create when you do subscribe (which you named response) is type any. This doesn't necessary have the function forEach defined (unless the API returns an object with that prototype). That's generally why using any is not good practice; you can't have any expectations on what the object can contain. In fact, it's possible that it's not on object (it could be an array since any is not exclusively an object). If you do want to use forEach, you will want to make sure that response is type array. You can inspect the object's type before using it (e.g. using typeof) and make a judgement on what to call or even just check if the function you're trying to use is defined first, e.g. if (response.forEach !== undefined). You don't actually need to compare to undefined though, so if (response.forEach) suffices. In the examples, I used response, but you can use this.test since they are the same object after the first line in the lambda.
Based on the link you shared, the response is an object. You can log it to the console to confirm.
You can only call for each on an array, so for example, based on the response api, you can call forEach on the property ‘categorias’ and on that array’s children property ‘produtus’
Edit: this answer was based on the op original api and question
https://demo5095413.mockable.io/carteira-investimentos
public createMenu() {
return this.api.getInvestiments()
}
ngOnit() {
this.coreService.createMenu().subscribe(x => console.log(x.categorias))};
{
"codigo":1,
"categorias":[
{
"nome":"Referenciado",
"valorTotal":23000.0,
"codigo":"2",
"produtos":[
{
"nome":"CDB Fácil Bradesco",
"valor":2000.0,
"codigo":1,
"quantidade":0.0,
"porcentagem":0.5500,
"aplicacaoAdicional":500.0,
"codigoInvest":1,
"salaInvestimento":"CDB",
"permiteAplicar":true,
"permiteResgatar":true,
"movimentacaoAutomatica":false,
"ordemApresentacao":37,
"horarioAbertura":"08:30",
"horarioFechamento":"23:59",
"codigoGrupo":0,
"codigoMF":"001
I have a class that has two methods for generating and decoding jsonwebtokens. This is what the class looks like.
interface IVerified {
id: string
email?: string
data?: any
}
export default class TokenProvider implements ITokenProvider {
public async generateToken({ id, email }: ITokenDetails): Promise<string> {
return sign({ id, email }, CONFIG.JWT.secret, {
subject: id,
expiresIn: CONFIG.JWT.expires,
})
}
public async decodeToken(token: string): Promise<IVerified> {
const user = verify(token, CONFIG.JWT.secret)
return user as IVerified
}
}
In the decodeToken method, as you can see from above, it takes in a token and is meant to return the decoded values embedded in the token. But vscode Intellisense shows that jwt.verify() method returns a string | object. How can I override this to enforce the method to return some user attributes? Instead of getting string | object, I want to return the attributes described by the IVerified interface stated above. Any help is appreciated! Thank you very much.
You generally have two options:
Coerce the result to a IVerified like you do there (you might have to do return (user as any) as IVerified; though to get typescript to do what you want. This is fine as long as you can guarantee that the object returned from jwt.verify adheres to the IVerified interface.
Create a helper function that takes in a string | object and does the necessary logic to do runtime validation, deserialization, etc, in order to ensure you get an IVerified back:
private function validateDecodedToken(input: string | object): IVerified {
// ... do whatever you need to parse/deserialize/validate/etc here
}
public async decodeToken(token: string): Promise<IVerified> {
const user = verify(token, CONFIG.JWT.secret);
return this.validateDecodedToken(user);
}
This is the more "robust" approach, but might be overkill if there
are other guarantees in the system.
I am a bit confused about how you are supposed to model APIs in Typescript.
I am trying to model the hexo API, which for example usage, looks a bit like this:
hexo.extend.tag.register(name, function(args, content){
// ...
}, options);
So far I have got something which looks like this:
//hexo.d.ts
declare module 'hexo' {
namespace extend {
export class tag {
public register: _register;
}
}
}
declare class _register {
name: string;
callback(args: any, content: any);
options: _options;
}
declare class _options {
ends: boolean;
}
However, I am getting issues like:
Property 'register' does not exist on type 'typeof tag'.
So how should I model this, it seems as though Typescript becomes a bit tricky after you have declared a class in a namespace in a module, and none of those types can exist within themselves?
What would a.b.c.d.e.f() look like in a .d.ts file?
In general, something is only a class if it is called with new. Otherwise it is
just a plain object. I'd do something like that:
declare module 'hexo' {
class Hexo {
constructor( cwd, opts )
load() : Promise<any>
extend : {
console : any //declare me later, set any for now
// etc
tag : {
register(name:string, cb: (args,content) => void , opts : any)
}
}
}
export = Hexo;
}
export = something is used when the module exports a single object (class Hexo becomes the module itself, not an object inside it).
PS: callback types (and actually any type) can be referenced using the type keyword. The type keyword may even replace the interface keyword...
type someCallback = ( err : Error , resp : HttpResponse ) => Promise<any>
Disclaimer: I know, Parse.com shuts down it's hosted service. Still, we will continue to use the framework for a while, so this question is still important to us.
Recently, I started playing around with TypeScript and figured it might enhance my productivity for parse cloud code a lot. So I did some testing and was successfully able to use typescript to write cloud functions and so on. I even included the typing definition for parse via typings.
However, I still don't get one thing: How can I extend Parse.Object in a type-safe manner?
In normal js I would write:
var Foo = Parse.Object.extend("Foo", {
// instance methods
}, {
// static members
});
In order to get type safety, I would like to write something like this in typescript:
class Foo extends Parse.Object {
// static members
// instance methods
}
Is something like this possible? Am I missing out on something?
Yes, this is possible. There are a few steps necessary for this. First, you need to pass the classname to the parent constructor
class Foo extends Parse.Object {
// static members
// instance methods
constructor() {
// Pass the ClassName to the Parse.Object constructor
super('Foo');
}
}
Furthermore, you need to register your class as an Parse object:
Parse.Object.registerSubclass('Foo', Foo);
After this, you can just use it for your query as:
var query = new Parse.Query(Foo);
query.find({
success: obj: Foo[] => {
// handle success case
},
error: error => {
// handle error
}
});
This also works for the new parse server open source project: https://github.com/ParsePlatform/parse-server
If someone like my stumble across the same question, here are my findings so far:
import Parse from "parse";
interface IBase {
readonly createdAt?: Date;
readonly updatedAt?: Date;
}
interface IMyInterface extends IBase {
name: string;
age?: number;
}
class MyClass extends Parse.Object<IMyInterface> {
constructor(attributes: IMyInterface) {
super("MyClass", attributes);
}
}
export default MyClass;
You can then use it like this:
const newObject = new MyClass({ name: theName, age: theAge });
const result = await newObject.save();
const { attributes } = result;
console.log("Save result", attributes);
You will have full TS support for the attributes then.
Hopefully the work on https://www.npmjs.com/package/#parse/react will proceed quickly.