HttpClient, Make Action String Type Safe - javascript

Is there anyway to make the 'post' action string type safe below? Right now it accepts Any word to substitute in for 'post', example 'abcd', will Not create compilation error.
Example:
saveUsers(body?: UpdateIdentityUserDto): Observable<any> {
return this.httpClient.request<IdentityUserDtoBaseRequestResponse>('post',`${this.baseUserUrl}`,
{
body: body
}
);
}
Option:
Here is another option below, however, I prefer to use string option above since those are auto generated from Swagger IO proxy generator.
saveUsers(body?: UpdateIdentityUserDto): Observable<any> {
return this.httpClient.post<IdentityUserDtoBaseRequestResponse>(`${this.baseUserUrl}`,body);
}
Currently using Angular 10

I suggest wrapping it around and making it a generic method. Make a separate file for these generics.
type HttpMethods = 'post' | 'get' | 'patch' | 'delete';
request<T>(method: HttpMethods, body: any): Observable<any> {
this.httpClient.request<T>(method, this.baseUrl, { body: body });
}
Then if you want to make a call an api call in a separate file, call this
import { request } from '...'
saveUsers(body?: BodyInterface): Observable<ResponseInterface> {
return request<IdentityUserDtoBaseRequestResponse>('post', body)
}

Create a namespace and export constants for the different types of request methods.
export namespace RequestMethod {
export const GET:string = "get";
export const HEAD:string = "head";
export const POST:string = "post";
export const PUT:string = "put";
export const DELETE:string = "delete";
export const CONNECT:string = "connect";
export const OPTIONS:string = "options";
export const TRACE:string = "trace";
export const PATCH:string = "patch";
}
Then you can utilize this namespace inside of a service
import { RequestMethod } from '../request-method.ts';
saveUsers(body?: UpdateIdentityUserDto): Observable<any> {
return this.httpClient.request<IdentityUserDtoBaseRequestResponse>(RequestMethod.POST,`${this.baseUserUrl}`,
{
body: body
}
);
}
Understand that this does not prevent a developer from typing any string they want. But it does give you a type-safe way of ensuring your strings are consistent through the app when utilized as a standard within your team.

Related

Prevent copy of class instance when importing in two different files - JavaScript?

I have a class like this: Code Sandbox
(In the sandbox, I have imported file1 so that that part of code gets executed first and then trying to access the same using file2 however it returns undefined)
import crypto from 'crypto';
const deleteMillis = 3600 * 1000;
class Security {
activeSessions: Record<string, { ua: string }>;
constructor() {
this.activeSessions = {};
}
getSession(session: string, ua: string): { ua: string } | undefined {
const currentSession = this.activeSessions[session];
console.log(this.activeSessions, this.activeSessions[session], session);
if (!currentSession) return;
if (ua !== currentSession.ua) return;
return currentSession;
}
addSession(ua: string): string {
const session = crypto.randomUUID();
this.activeSessions[session] = {
ua
};
setTimeout(() => {
this.removeSession(session);
}, deleteMillis);
return session;
}
removeSession(session: string) {
delete this.activeSessions[session];
}
}
const security = new Security();
export default security;
I want to use security instance of this class into multiple es module files. Let's say file1.js and file2.js.
Now the problem is that when i do an import:
import security from "#/security"; // file-1
import security from "#/security"; // file-2
The problem with the above is that the data doesn't remain synced in both the files and they operate independently.
these two files create two different instances however I wanted to use one instance in both the files. How can I achieve the same? Do I need to create a third file or what exactly should be the approach?
As suggested in the answer, I tried this:
class Security {
activeSessions: Record<string, { ua: string }>;
private static instance: Security;
constructor() {
this.activeSessions = {};
}
getSession(session: string, ua: string): { ua: string } | undefined {
const currentSession = this.activeSessions[session];
console.log(this.activeSessions, this.activeSessions[session], session);
if (!currentSession) return;
if (ua !== currentSession.ua) return;
return currentSession;
}
addSession(ua: string): string {
const session = crypto.randomUUID();
this.activeSessions[session] = {
ua
};
setTimeout(() => {
this.removeSession(session);
}, deleteMillis);
return session;
}
removeSession(session: string) {
delete this.activeSessions[session];
}
static getInstance(): Security {
if (!Security.instance) {
Security.instance = new Security();
}
console.log('Security instance', Security.instance);
return Security.instance;
}
}
const security = Security.getInstance();
export default security;
However this also fails to preserve the instance.
Here's an approach, we define a static method getInstance in the Security class and call it to get Security instance in other files
class Security {
private static instance: Security;
// other methods and variables
static getInstance(): Security {
if (!Security.instance) {
Security.instance = new Security();
}
return Security.instance;
}
}
export default Security;
Usage example
let securityInstance = Security.getInstance();
securityInstance.getSession();
This is called the Singleton design pattern, you can read more about it here Singleton design pattern
The shown code works for me (the session object is the same for both files). It is, however, not clear what you want to achieve with the code.
In file1.js you have security.addSession("abc", "deg") in file2.js you have security.getSession("abc", "def"). (deg vs def).
After calling security.addSession("abc", "deg") the contents of this.activeSessions['abc'] is: {ua : "deg"}.
When you call security.getSession("abc", "def") you have the test if (ua !== currentSession.ua) return; which compares ua which is def with currentSession.ua which is deg and that is not equal, so you return undefined.

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.

Pass value to extends plugin ng2 smart table

I've checked the document and source code for pagination implementation (advanced-example-server.component.ts).
And found that the ServerDataSource it used had only implemented pagination via HTTP GET (_sort, _limit, _page, etc parameters expose in URL)..... as my current project worked on required to use POST to send front-end parameters to back-end Restful APIs,
using extends to HTTP post call implement, I don't know how to add the extra parameters in pagination request. I Need To pass the request_server to extendsplugin.ts
import { Observable } from 'rxjs/internal/Observable';
import { ServerDataSource } from 'ng2-smart-table';
export class PostServerDataSource extends ServerDataSource {
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
return this.http.post(this.conf.endPoint, request_server, { observe: 'response' });
}
}
anotherComponent.ts
swiftListTable() {
const request_server = { "userType": this.currentUser.role, "authName": this.currentUser.username }
this.source = new PostServerDataSource(this.http,{endPoint: this.service.apiURL + 'swift/pagination', dataKey: 'content', pagerLimitKey:"_limit",
pagerPageKey:"_page",
sortDirKey: "pageable",
sortFieldKey: "pageable",
totalKey:'totalElements'});
}
There are two ways you can handle it,
one way is attaching the params in query string and append to url like,
this.service.apiURL + 'swift/pagination?param1=p&param2=q'
Other way could be handling it in requestElements and swiftListTable functions like below.
swiftListTable() {
const request_server = {
"userType": this.currentUser.role,
"authName": this.currentUser.username
}
this.source = new PostServerDataSource(http,
{ endPoint: url, dataKey: 'content', pagerLimitKey:'_limit'}, request_server);
export class PostServerDataSource extends ServerDataSource {
params: any;
constructor(http: HttpClient, config: any, params?: any) {
super(http, config);
this.params = params;
}
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
if (this.params) {
let keys = Object.keys(this.params);
keys.forEach((key) => {
httpParams = httpParams.set(key, this.params[key]);
});
}
return this.http.post(this.conf.endPoint, httpParams, { observe: 'response' });
}
}

How to use validation in NestJs with HTML rendering?

NestJS uses validation with validation pipes and
#UsePipes(ValidationPipe)
If this fails it throws an exception. This is fine for REST APIs that return JSON.
How would one validate parameters when using HTML rendering and return
{ errors: ['First error'] }
to an hbs template?
You can create an Interceptor that transforms the validation error into an error response:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(
// Here you can map (or rethrow) errors
catchError(err => ({errors: [err.message]}),
),
);
}
}
You can use it by adding #UseInterceptors(ErrorsInterceptor) to your controller or its methods.
I've been driving myself half mad trying to find a "Nest like" way to do this while still retaining a degree of customisability, and I think I finally have it. Firstly, we want an error that has a reference to the exisiting class-validator errors, so we create a custom error class like so:
import { ValidationError } from 'class-validator';
export class ValidationFailedError extends Error {
validationErrors: ValidationError[];
target: any;
constructor(validationErrors) {
super();
this.validationErrors = validationErrors;
this.target = validationErrors[0].target
}
}
(We also have a reference to the class we tried to validate, so we can return our object as appropriate)
Then, in main.ts, we can set a custom exception factory like so:
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (validationErrors: ValidationError[] = []) => {
return new ValidationFailedError(validationErrors);
},
}),
);
Next, we create an ExceptionFilter to catch our custom error like so:
#Catch(ValidationFailedError)
export class ValidationExceptionFilter implements ExceptionFilter {
view: string
objectName: string
constructor(view: string, objectName: string) {
this.view = view;
this.objectName = objectName;
}
async catch(exception: ValidationFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response.render(this.view, {
errors: exception.validationErrors,
[this.objectName]: exception.target,
url: request.url,
});
}
}
We also add an initializer, so we can specify what view to render and what the object's name is, so we can set up our filter on a controller method like so:
#Post(':postID')
#UseFilters(new ValidationExceptionFilter('blog-posts/edit', 'blogPost'))
#Redirect('/blog-posts', 301)
async update(
#Param('id') postID: string,
#Body() editBlogPostDto: EditBlogPostDto,
) {
await this.blogPostsService.update(postID, editBlogPostDto);
}
Hope this helps some folks, because I like NestJS, but it does seem like the docuemntation and tutorials are much more set up for JSON APIs than for more traditional full stack CRUD apps.

Typescript hierarchy of a sharable module

I want to write a package in Typescript such that once I compile it into .js, the typing is also available if you use it in a Typescript application.
Here is an example of a Http client that is built on top of Axios:
// http.ts
import Axios, {AxiosInstance, AxiosPromise, AxiosRequestConfig} from 'axios';
import {HttpStatic} from './interfaces';
export default class Http implements HttpStatic {
private headers: object;
private _client: AxiosInstance;
constructor(options?: AxiosRequestConfig) {
options = Object.assign({}, options);
this.headers = {};
this._client = Axios.create(options);
}
public get<R>(url: string, params: object = {}, headers: object = {}): AxiosPromise<R> {
return this._client.get(url, {
headers: {
...this.headers,
...headers
}, params
});
}
}
And this is the interface:
// interface.d.ts
import {AxiosPromise, AxiosRequestConfig} from 'axios';
export interface HttpInstance {
new(uri: string, options?: AxiosRequestConfig):HttpStatic
get<R>(url: string, params: object, headers: object): AxiosPromise<R>;
}
export interface HttpStatic {
}
When I compile this, I get http.js and http.d.ts. But http.d.ts is basically the http.ts file and includes the line import {HttpStatic} from './interfaces' which does not exist in the dist folder.
So when I try to use this module, I get errors saying ./interfaces not found.
Now, if I put all the content of the ./interfaces inside the http.ts then everything will compile fine. But I don't want to do that, and I rather to keep the interface/types separate from the source code.
How do I do this?

Categories