I am trying to experiment with typescript namespaces. It works perfectly when i dont import other modules but when I import other modules in my case sort the exported function get cant be used in other file even though they are in same namespace.
import { sort } from "../sort"
namespace Api {
export async function get(url:string){
const res = await fetch("api/"+url)
return await res.json()
}
export class Users {
private uid: string
constructor(uid: string){
this.uid = uid
}
public async getUserProfile(){
return await get(`/u/${this.uid}/profile`)
}
public async getUserSongs(){
return await get(`/u/${this.uid}/musics`)
}
}
}
/// <reference path="api.ts" />
namespace Api {
export class Track{
private track_id: string
constructor(track_id){
this.track_id = track_id
}
public async hasLiked(){
return await get("/track/"+this.track_id+"/hasLiked")
}
}
}
It says could not find name get
But when i dont import sort it works perfectly
namespace Api {
export async function get(url:string){
const res = await fetch("api/"+url)
return await res.json()
}
export class Users {
private uid: string
constructor(uid: string){
this.uid = uid
}
public async getUserProfile(){
return await get(`/u/${this.uid}/profile`)
}
public async getUserSongs(){
return await get(`/u/${this.uid}/musics`)
}
}
}
When you add import statement to the file it turns that .ts file into a module, and "moves" all declarations in the file into the scope of that module.
So namespace Api is not seen outside of its module definition and removes it from global namespace. There are a few ways to deal with it:
You can turn you main Api file into definition file by changing its extension to d.ts and wrap namespace Api into declare global {}. By that you don't even need triple slash import, it will still be available in global scope. Your new api.d.ts should look like this:
import { sort } from "./sort";
declare global {
namespace Api {
export async function get(url: string) {
const res = await fetch("api/" + url);
return await res.json();
}
export class Users {
private uid: string;
constructor(uid: string) {
this.uid = uid;
}
public async getUserProfile() {
return await get(`/u/${this.uid}/profile`);
}
public async getUserSongs() {
return await get(`/u/${this.uid}/musics`);
}
}
}
}
Ommiting top-level import and using dynamic import like this:
namespace Api {
export async function get(url: string) {
const sort = await import("./sort");
const res = await fetch("api/" + url);
return await res.json();
}
export class Users {
private uid: string;
constructor(uid: string) {
this.uid = uid;
}
public async getUserProfile() {
return await get(`/u/${this.uid}/profile`);
}
public async getUserSongs() {
return await get(`/u/${this.uid}/musics`);
}
}
}
Simply turning namespace Api with Track into d.ts resolves the issue
Working with declaration merging and modules in typescript is pretty complex and i think there are still many workarounds and pitfalls about it so my answer can definitely be extended.
Related
Help me to get fetch information from blockchain and display in the browser. i want know how to call this thirdweb functions in react.
Below code is a solidity code used to create a user in our system.
function createUser(string memory _userId, string memory _fName, string memory _lName, string memory _mobile, string memory _dob, uint256 _age, string memory _nationality, string memory _gender) public {
if(!chkexisitinguserId(_userId)){
users[_userId] = User(_fName, _lName, _mobile, _dob, _age,_nationality,_gender);
noofUser++;
allUserId[k] = _userId;
k++;
}
}
function getUser(string memory _userId) public view returns (string memory, string memory, string memory, string memory, uint256, string memory, string memory) {
User memory user = users[_userId];
return (user.fName, user.lName, user.mobile, user.dob, user.age, user.nationality, user.gender);
}
The below code is thirdweb libary code to interact with smart contract. The below code is stored in refer.js file.
import { useContract, useContractWrite } from "#thirdweb-dev/react";
export default function Component() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { mutateAsync: createUser, isLoading } = useContractWrite(contract, "createUser")
const call = async () => {
try {
const data = await createUser([ "John0312", "John", "s", "8090890367", "03-11-2000", 20, "India", "M" ]);
console.info("contract call successs", data);
} catch (err) {
console.error("contract call failure", err);
}
}
}
export default function Component() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { data, isLoading } = useContractRead(contract, "getUser", _userId)
}
The smart contract is deployed in thirdweb and trying to access it. I am stuck at how to call this "call" async function from app.js.
import React, { useEffect } from 'react'
function App(){
const handleclick = async (e) => {
await call();
}
return (
<button onClick={handleclick}>click me</button>
)
}
export default App
it generates error like undefined function call().
I would create a new hook (useCall.js) who's job is simply to instantiate the useContract and useContractWrite hooks, then define the call() method for you to use in any component.
In this example, call() is the only thing returned from the hook. It's wrapped inside useCallback to ensure it's only defined when createUser is defined.
export default function useCall() {
const { contract } = useContract("0xBB417720eBc8b76AdeAe2FF4670bbc650C3E791f");
const { mutateAsync: createUser, isLoading } = useContractWrite(contract, "createUser")
const call = React.useCallback(async () => {
try {
const data = await createUser([ "John0312", "John", "s", "8090890367", "03-11-2000", 20, "India", "M" ]);
console.info("contract call successs", data);
} catch (err) {
console.error("contract call failure", err);
}
}, [createUser]);
return call;
}
Now inside of any component you can use the hook and get the call() function:
import useCall from './useCall';
export default function Component() {
const call = useCall();
useEffect(() => {
(async () => {
await call();
})();
}, []);
}
I'm having doubts about which is the best strategy to manage the many service clients in this web app.
"Best" in terms of a good compromise between user's device RAM and Javascript execution speed (main thread ops).
This is what I'm doing right now, this is the main file:
main.ts:
import type { PlayerServiceClient } from './player.client';
import type { TeamServiceClient } from './team.client';
import type { RefereeServiceClient } from './referee.client';
import type { FriendServiceClient } from './friend.client';
import type { PrizeServiceClient } from './prize.client';
import type { WinnerServiceClient } from './winner.client';
import type { CalendarServiceClient } from './calendar.client';
let playerService: PlayerServiceClient;
export const player = async (): Promise<PlayerServiceClient> =>
playerService ||
((playerService = new (await import('./player.client')).PlayerServiceClient()),
playerService);
let teamService: TeamServiceClient;
export const getTeamService = (): TeamServiceClient =>
teamService ||
((teamService = new (await import('./team.client')).TeamServiceClient()),
teamService);
let refereeService: RefereeServiceClient;
export const getRefereeService = (): RefereeServiceClient =>
refereeService ||
((refereeService = new (await import('./referee.client')).RefereeServiceClient()),
refereeService);
let friendService: FriendServiceClient;
export const getFriendService = (): FriendServiceClient =>
friendService ||
((friendService = new (await import('./friend.client')).FriendServiceClient()),
friendService);
let prizeService: PrizeServiceClient;
export const getPrizeService = (): PrizeServiceClient =>
prizeService ||
((prizeService = new (await import('./prize.client')).PrizeServiceClient()),
prizeService);
let winnerService: WinnerServiceClient;
export const getWinnerService = (): WinnerServiceClient =>
winnerService ||
((winnerService = new (await import('./winner.client')).WinnerServiceClient()),
winnerService);
let calendarService: CalendarServiceClient;
export const getCalendarService = (): CalendarServiceClient =>
calendarService ||
((calendarService = new (await import('./calendar.client')).CalendarServiceClient()),
calendarService);
// and so on... a lot more...
As you can see there are many service clients.
I'm using this code because I thought it was better given my web app structure based on routes almost overlapping with client services:
I mean, if the player goes from /home to /players page I can use it like this:
components/players.svelte
import { getPlayerService } from "main";
const playerService = await getPlayerService();
const players = await playerService.queryPlayers();
In this way, if the PlayerService does not exist, it is imported at the moment and returned, otherwise it returns the one imported and instantiated before.
Since the user switches pages frequently this way I can avoid the sudden creation and destruction of those clients, right?
But in this way I am using global variables which I don't like to use and I'm using verbose, DRY and long code in each component.
Is there a way to use the below code in components instead?
import { playerService } from "main";
const players = await playerService.queryPlayers();
What do you suggest me to do?
The patterns you are implementing are "lazy loading" and "singleton".
You could have a single service factory which implements those patterns and use it for every service:
File serviceFactory.js
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = import(serviceName).then(x => new x.default));
}
The ECMAScript modules standard will take care of executing the serviceFactory.js code only once in the application (no matter how many times you import it), so you can hold the singletons in a map assigned to a private top-level variable of the serviceFactory.js module.
This service factory implies that every service is exported with the default keyword like that:
export default class SomeService {
constructor() {
// ...
}
fetchSomething() {
// ...
}
}
Then, use the services everywhere in your application with this code:
import { getService } from './serviceFactory.js';
const service = await getService('./services/some.service.js');
const something = await service.fetchSomething();
If you really want to remove the double await, you can encapsulate it in the service factory like that:
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = resolveService(serviceName));
}
function resolveService(name) {
const futureInstance = import(name).then(x => new x.default);
const handler = {
get: function (target, prop) {
return function (...args) {
return target.then(instance => instance[prop](...args));
}
}
}
return new Proxy(futureInstance, handler);
}
Which allows you to write this code:
const something = await getService('./services/some.service.js').fetchSomething();
This allows the service to be loaded at the exact line of code where you need it.
If it doesn't bothers you to load it with a static import because you need the import { playerService } from "main"; syntax, you can expose every service like this in one file per service:
export const playerService = getService('./services/player.service.js');
I have published the full working demo here: https://github.com/Guerric-P/lazy-singletons-demo
I don't think the code from #Guerric will work with build tools (like webpack.)
Specifically dynamic string imports import(modulePath) is not supported.
My recommendation is to reduce the repeating bits of code to their smallest representation... Hopefully, it'll end up feeling less noisy.
Solution #1/2
Here's an example using a higher-order memoize function to help with the caching.
// Minimal definition of service loaders
export const getPlayerService = memoize<PlayerServiceClient>(async () => new (await import('./player.client')).PlayerServiceClient());
export const getTeamService = memoize<TeamServiceClient>(async () => new (await import('./team.client')).TeamServiceClient());
export const getRefereeService = memoize<RefereeServiceClient>(async () => new (await import('./referee.client')).RefereeServiceClient());
export const getFriendService = memoize<FriendServiceClient>(async () => new (await import('./friend.client')).FriendServiceClient());
export const getPrizeService = memoize<PrizeServiceClient>(async () => new (await import('./prize.client')).PrizeServiceClient());
export const getWinnerService = memoize<WinnerServiceClient>(async () => new (await import('./winner.client')).WinnerServiceClient());
// Mock hacked together memoize fn
// TODO: Replace with some npm library alternative
const fnCache = new WeakMap();
function memoize<TReturn>(fn): TReturn {
let cachedValue = fnCache.get(fn);
if (cachedValue) return cachedValue;
cachedValue = fn();
fnCache.set(fn, cachedValue);
return cachedValue;
}
Solution #2/2
Depending on the version of the JS engine & transpiler, you could possibly cut out some code and use the nature of modules to cache singletons of your services.
(Note: I've occasionally run into gotchas here around how ES Modules rely on deterministic exports. The workaround is to assign the exports to pending promises which return the instance.)
The important feature to know about Promises: they are only resolved once, and can be used to effectively cache their result.
Each await or .then will get the initial resolved value.
// SUPER minimal definition of services
export const playerService = (async (): PlayerServiceClient => new (await import('./player.client')).PlayerServiceClient())();
export const teamService = (async (): TeamServiceClient => new (await import('./team.client')).TeamServiceClient())();
export const refereeService = (async (): RefereeServiceClient => new (await import('./referee.client')).RefereeServiceClient())();
export const friendService = (async (): FriendServiceClient => new (await import('./friend.client')).FriendServiceClient())();
export const prizeService = (async (): PrizeServiceClient => new (await import('./prize.client')).PrizeServiceClient())();
export const winnerService = (async (): WinnerServiceClient => new (await import('./winner.client')).WinnerServiceClient())();
Calling the Service Wrapper
import { playerService } from "./services";
// Example: Using async/await IIFE
const PlayerService = (async () => await playerService)();
function async App() {
// Example: Function-scoped service instance:
// const PlayerService = await playerService
const players = await PlayerService.queryPlayers();
}
I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.
I'm trying to fetch HTML from a webpage, using a class with a single async method. I use Typescript 3.4.3, request-promise 4.2.4.
import * as rp from 'request-promise';
class HtmlFetcher {
public uri: string;
public html: string;
public constructor(uri: string) {
this.uri = uri;
}
public async fetch() {
await rp(this.uri).then((html) => {
this.html = html;
}).catch((error) => {
throw new Error('Unable to fetch the HTML page');
});
}
}
export { HtmlFetcher };
I use following code to test my class with Jest 24.8.0. The address at line 6 is used for the sole purpose of testing, I also tried with different URIs.
import { HtmlFetcher } from './htmlFetcher.service';
describe('Fetch HTML', () => {
it('should fetch the HTMl at the given link', () => {
const uri = 'http://help.websiteos.com/websiteos/example_of_a_simple_html_page.htm';
const fetcher = new HtmlFetcher(uri);
fetcher.fetch();
expect(fetcher.html).toBeDefined();
});
});
I expect the html property to contain the HTML string fetched at the given address after the fetch() method has been called. However, the test code fails, and logs that fetcher.html is undefined. The Typescript, Jest and request-promise docs did not provide any help. What am I doing wrong?
Found the answer thanks to TKoL's comments, and another look at a doc I already read 50 times, namely: Jest async testing. I should've RTFM more carefully...
The test code must be async as well.
import { HtmlFetcher } from './htmlFetcher.service';
describe('Fetch HTML', () => {
it('should fetch the HTMl at the given link', async () => { // Added async keyword
const uri = 'http://help.websiteos.com/websiteos/example_of_a_simple_html_page.htm';
const fetcher = new HtmlFetcher(uri);
await fetcher.fetch(); // Added await keyword
expect(fetcher.html).toBeDefined();
});
});
I'm trying to extends the KUZZLE JavaScript SDK in order to call some controllers on kuzzle servers, implemented via plugins.
I'm following that guide: add controller
Here is my controller which extends from the BaseController:
const { BaseController } = require('kuzzle-sdk');
export class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And here is where I specify the controller in order to use it further in the App, on the creation of the singleton.
const {UserController} = require('./UserController');
const { Kuzzle, WebSocket } = require('kuzzle-sdk');
class KuzzleService {
static instance = null;
static async createInstance() {
var object = new KuzzleService();
object.kuzzle = new Kuzzle(
new WebSocket('localhost'),{defaultIndex: 'index'}
);
object.kuzzle.useController(UserController, 'user');
await object.kuzzle.connect();
const credentials = { username: 'admin', password: 'pass' };
const jwt = await object.kuzzle.auth.login('local', credentials);
return object;
}
static async getInstance () {
if (!KuzzleService.instance) {
KuzzleService.instance = await KuzzleService.createInstance();
}
return KuzzleService.instance;
}
}
export default KuzzleService;
Somehow I'm getting the following error:
Controllers must inherit from the base controller
Is there something wrong with the imports ?
I've found out the solution to that issue. Firstly, I was not on the right version of the kuzzle SDK released recently (6.1.1) and secondly the controller class must be exported as default:
const { BaseController } = require('kuzzle-sdk');
export default class UserController extends BaseController {
constructor (kuzzle) {
super(kuzzle, 'plugins-user/userController');
}
/**
* Method to call the action "CreateAccount" on the UserController
* #param {*} user
*/
async createAccount(user) {
const apiRequest = {
action: 'new',
body: {
user
}
};
try {
const response = await this.query(apiRequest);
return response.result.user;
}
catch (error) {
//Manage errors
}
}
}
And then the UserController needs to be importer that way:
import UserController from './UserController.js'
Then, as specified in the documentation, we need just inject the kuzzle object into the controller that way:
kuzzle.useController(UserController, 'user');