Converting Typescript Code to Javascript Code? - javascript

I'm using Shopify's Node Api tutorial to create a Redis store. However, the code block provided is in typescript and my entire project is written in javascript (React/nextjs). I've been working for a few hours to try and convert the code to be useable, but am unable to get it to work properly in my project. Seriously struggling with this.
How would I convert the below code block from typescript to javascript?
/* redis-store.ts */
// Import the Session type from the library, along with the Node redis package, and `promisify` from Node
import {Session} from '#shopify/shopify-api/dist/auth/session';
import redis from 'redis';
import {promisify} from 'util';
class RedisStore {
private client: redis.RedisClient;
private getAsync;
private setAsync;
private delAsync;
constructor() {
// Create a new redis client
this.client = redis.createClient();
// Use Node's `promisify` to have redis return a promise from the client methods
this.getAsync = promisify(this.client.get).bind(this.client);
this.setAsync = promisify(this.client.set).bind(this.client);
this.delAsync = promisify(this.client.del).bind(this.client);
}
/*
The storeCallback takes in the Session, and sets a stringified version of it on the redis store
This callback is used for BOTH saving new Sessions and updating existing Sessions.
If the session can be stored, return true
Otherwise, return false
*/
storeCallback = async (session: Session) => {
try {
// Inside our try, we use the `setAsync` method to save our session.
// This method returns a boolean (true if successful, false if not)
return await this.setAsync(session.id, JSON.stringify(session));
} catch (err) {
// throw errors, and handle them gracefully in your application
throw new Error(err);
}
};
/*
The loadCallback takes in the id, and uses the getAsync method to access the session data
If a stored session exists, it's parsed and returned
Otherwise, return undefined
*/
loadCallback = async (id: string) => {
try {
// Inside our try, we use `getAsync` to access the method by id
// If we receive data back, we parse and return it
// If not, we return `undefined`
let reply = await this.getAsync(id);
if (reply) {
return JSON.parse(reply);
} else {
return undefined;
}
} catch (err) {
throw new Error(err);
}
};
/*
The deleteCallback takes in the id, and uses the redis `del` method to delete it from the store
If the session can be deleted, return true
Otherwise, return false
*/
deleteCallback = async (id: string) => {
try {
// Inside our try, we use the `delAsync` method to delete our session.
// This method returns a boolean (true if successful, false if not)
return await this.delAsync(id);
} catch (err) {
throw new Error(err);
}
};
}
// Export the class
export default RedisStore;

Just save all that typescript code in a .ts file (probably redis-store.ts).
then use typescript compiler to convert to your version of javascript by just running tsc command as below
tsc redis-store.ts
for more compiler options, please visit below
https://www.typescriptlang.org/docs/handbook/compiler-options.html

You basically need to get rid of all the types (Session and string) and switch private to #, maybe something like this:
/* redis-store.js */
import redis from 'redis';
import {promisify} from 'util';
class RedisStore {
#client;
#getAsync;
#setAsync;
#delAsync;
constructor() {
// Create a new redis client
this.client = redis.createClient();
this.getAsync = promisify(this.client.get).bind(this.client);
this.setAsync = promisify(this.client.set).bind(this.client);
this.delAsync = promisify(this.client.del).bind(this.client);
}
storeCallback = async (session) => {
try {
// Inside our try, we use the `setAsync` method to save our session.
// This method returns a boolean (true if successful, false if not)
return await this.setAsync(session.id, JSON.stringify(session));
} catch (err) {
// throw errors, and handle them gracefully in your application
throw new Error(err);
}
};
/*
The loadCallback takes in the id, and uses the getAsync method to access the session data
If a stored session exists, it's parsed and returned
Otherwise, return undefined
*/
loadCallback = async (id) => {
try {
// Inside our try, we use `getAsync` to access the method by id
// If we receive data back, we parse and return it
// If not, we return `undefined`
let reply = await this.getAsync(id);
if (reply) {
return JSON.parse(reply);
} else {
return undefined;
}
} catch (err) {
throw new Error(err);
}
};
/*
The deleteCallback takes in the id, and uses the redis `del` method to delete it from the store
If the session can be deleted, return true
Otherwise, return false
*/
deleteCallback = async (id) => {
try {
// Inside our try, we use the `delAsync` method to delete our session.
// This method returns a boolean (true if successful, false if not)
return await this.delAsync(id);
} catch (err) {
throw new Error(err);
}
};
}
// Export the class
export default RedisStore;

Related

javascript callback is called twice in an impossible invocation

I built a TS, MongoDB Client wrapper. for some reason when I call the function that gets the connection, its callback is called twice.
There are 2 calls in total to the get() function, 1 before the export as you can see and another from a mocha test.
I am pretty new to TS and JS in general, but this seems a bit off.
import {Db, MongoClient} from "mongodb";
import {MongoConfig} from '../config/config'
class DbClient {
private cachedDb : Db = null;
private async connectToDatabase() {
console.log('=> connect to database');
let connectionString : string = "mongodb://" + MongoConfig.host + ":" + MongoConfig.port;
return MongoClient.connect(connectionString)
.then(db => {
console.log('=> connected to database');
this.cachedDb = db.db(MongoConfig.database);
return this.cachedDb;
});
}
public async get() {
if (this.cachedDb) {
console.log('=> using cached database instance');
return Promise.resolve(this.cachedDb);
}else{
return this.connectToDatabase();
}
}
}
let client = new DbClient();
client.get();
export = client;
where the console output is:
=> connect to database
=> connected to database
=> connected to database
Any particular reason this is misbehaving?
There are 2 calls in total to the get() function, 1 before the export as you can see and another from a mocha test.
I suspect the output has an additional => connect to database. As I said in the comments: There's a "race condition" where get() could be called multiple times before this.cachedDb is set which would lead to multiple connections/instances of Db being created.
For example:
const a = client.get();
const b = client.get();
// then
a.then(resultA => {
b.then(resultB => {
console.log(resultA !== resultB); // true
});
});
Solution
The problem can be fixed by storing the promise as the cached value (also, no need to have the async keyword on the methods as Randy pointed out, as there's no values being awaited in any of the methods so you can just return the promises):
import {Db, MongoClient} from "mongodb";
import {MongoConfig} from '../config/config'
class DbClient {
private cachedGet: Promise<Db> | undefined;
private connectToDatabase() {
console.log('=> connect to database');
const connectionString = `mongodb://${MongoConfig.host}:${MongoConfig.port}`;
return MongoClient.connect(connectionString);
}
get() {
if (!this.cachedGet) {
this.cachedGet = this.connectToDatabase();
// clear the cached promise on failure so that if a caller
// calls this again, it will try to reconnect
this.cachedGet.catch(() => {
this.cachedGet = undefined;
});
}
return this.cachedGet;
}
}
let client = new DbClient();
client.get();
export = client;
Note: I'm not sure about the best way of using MongoDB (I've never used it), but I suspect connections should not be so long lived as to be cached like this (or should probably only be cached for a short time and then disconnected). You'll need to investigate that though.

not able to access class methods using the class instance returned by a static method

I have created a subscriber class to store subscriber details and use a static method to return the instance of the class, but I am not able to set the values using the instance
Here is the subscriber class:
let _instance;
export class Subscriber {
constructor(username, password) {
this._username = username;
this._password = password;
}
setSubscriberId(subscriberId) {
cy.log(subscriberId);
this._subscriberId = subscriberId;
}
setSessionId(sessionId) {
this.sessionId = sessionId;
}
getUserName = () => {
return this._username;
}
getPassword = () => {
return this._password;
}
getSubsciberId() {
return this._subscriberId;
}
getSessionId() {
return this.sessionId;
}
static createSubscriber(username, password) {
if (!_instance) {
_instance = new Subscriber(username, password);
}
return _intance;
}
static getSubscriber() {
return _instance;
}
}
I am creating a instance of the class in before block and accessing the instance in Given block
before("Create a new subscriber before the tests and set local storage", () => {
const username = `TestAutomation${Math.floor(Math.random() * 1000)}#sharklasers.com`;
const password = "test1234";
subscriberHelpers.createSubscriber(username, password, true).then((response) => {
cy.log(response);
Subscriber.createSubscriber(username, password);
Subscriber.getSubscriber().setSubscriberId(response.Subscriber.Id);
Subscriber.getSubscriber().setSessionId(response.SessionId);
}).catch((error) => {
cy.log(error);
});
});
Given(/^I launch selfcare app$/, () => {
cy.launchApp();
});
Given(/^I Set the environemnt for the test$/, () => {
cy.log(Subscriber.getSubscriber());
cy.log(Subscriber.getSubscriber().getSubsciberId());
});
here is the output on the cypress console
Questions:
Why the subscriberID is null even though I am setting it in the before block
if I print the subscriber Object why am I not seeing subscriberID
Here is the output of subscriber object
Properties username and password are defined synchronously in before(), so are present on the object when tested.
But subscriberId is obtained asynchronously, so you will need to wait for completion inside the test, e.g
cy.wrap(Subscriber.getSubscriber()).should(function(subscriber){
expect(subscriber.getSubsciberId()).not.to.be.null
})
Refer to wrap - Objects to see how to handle an object with Cypress commands.
and see should - Differences
When using a callback function with .should() or .and(), on the other hand, there is special logic to rerun the callback function until no assertions throw within it.
In other words, should will retry (up to 5 seconds) until the expect inside the callback does not fail (i.e in your case the async call has completed).

Get Nodejs Promise value for use in existing Framework object

Amateur JavaScript guy here. I've written a private NodeJS module that manages our DB connection strings (Decrypt passwords & connection string construction), but due to the nature of the decrypt, the module returns a promise for the db connection string.
We are using Sails, and the config happens in the export of a variables object:
module.exports.variables = { dbstring: 'mongodb://user:password#host/mydb' }
But now with the promise, it's a little trickier to squeeze a string in here. I've tried putting the 'module.exports.variables' block, inside a '.then' block:
myConfigModule.getDBString('mysql-master').then( result => {
module.exports.variables = { dbstring: result }
}
but then the rest of the sails app fails to start up, with it trying to access variables inside 'module.exports.variables', and only gets 'undefined'. I assume because the rest of the app isn't waiting for the promise to be fulfilled.
Any suggestions?
I'd export a function from your module getDBString, e.g.
Config Module (config.js)
var getDBString = function() {
/* Query DB, returning a promise. */
return queryDB('mysql-master');
}
module.exports = {
getDBString: getDBString
};
Main Module
var config = require('/config.js');
config.getDBString().then((dbString) => {
console.log('DB string: ', dbString)
});
Managed to resolve this, but it's mostly sails.js specific. The sails.lift() method allows you to pass override configs, so in the app.js, I was then able to use my promise to pass the db configs to sails app:
myConfigModule.getDBString('mysql-master').then( result => {
const sailsOverrideSettings.connections = {
someMySQLServer: {
url: result
}
};
sails.lift(sailsOverrideSettings);
})
.catch ( err => {
// Throw error up the stack.
throw (err)
});
Reference:
https://sailsjs.com/documentation/reference/application/sails-lift

How to connect to mongoDB and test for drop collection?

This is how I connect to a mongoDB using monk(). I'll store it in state.
Assume we want to drop some collections, we call dropDB.
db.js
var state = {
db: null
}
export function connection () {
if (state.db) return
state.db = monk('mongdb://localhost:27017/db')
return state.db
}
export async function dropDB () {
var db = state.db
if (!db) throw Error('Missing database connection')
const Users = db.get('users')
const Content = db.get('content')
await Users.remove({})
await Content.remove({})
}
I'm not quite sure if it is a good approach to use state variable. Maybe someone can comment on that or show an improvement.
Now I want to write a unit test for this function using JestJS:
db.test.js
import monk from 'monk'
import { connection, dropDB } from './db'
jest.mock('monk')
describe('dropDB()', () => {
test('should throw error if db connection is missing', async () => {
expect.assertions(1)
await expect(dropDB()).rejects.toEqual(Error('Missing database connection'))
})
})
This part is easy, but the next part gives me two problems:
How do I mock the remove() methods?
test('should call remove() methods', async () => {
connection() // should set `state.db`, but doesn't work
const remove = jest.fn(() => Promise.resolve({ n: 1, nRemoved: 1, ok: 1 }))
// How do I use this mocked remove()?
expect(remove).toHaveBeenCalledTimes(2)
})
And before that? How do I setup state.db?
Update
As explained by poke the global variable makes the problem. So I switched to a class:
db.js
export class Db {
constructor() {
this.connection = monk('mongdb://localhost:27017/db');
}
async dropDB() {
const Users = this.connection.get('users');
const Content = this.connection.get('content');
await Users.remove({});
await Content.remove({});
}
}
which results in this test file:
db.test.js
import { Db } from './db'
jest.mock('./db')
let db
let remove
describe('DB class', () => {
beforeAll(() => {
const remove = jest.fn(() => Promise.resolve({ n: 1, nRemoved: 1, ok: 1 }))
Db.mockImplementation(() => {
return { dropDB: () => {
// Define this.connection.get() and use remove as a result of it
} }
})
})
describe('dropDB()', () => {
test('should call remove method', () => {
db = new Db()
db.dropDB()
expect(remove).toHaveBeenCalledTimes(2)
})
})
})
How do I mock out any this elements? In this case I need to mock this.connection.get()
Having a global state is definitely the source of your problem here. I would suggest to look for a solution that does not involve global variables at all. As per Global Variables Are Bad, global variables cause tight coupling and make things difficult to test (as you have noticed yourself).
A better solution would be to either pass the database connection explicitly to the dropDB function, so it has the connection as an explicit dependency, or to introduce some stateful object that holds onto the connection and offers the dropDB as a method.
The first option would look like this:
export function openConnection() {
return monk('mongdb://localhost:27017/db');
}
export async function dropDB(connection) {
if (!connection) {
throw Error('Missing database connection');
}
const Users = connection.get('users');
const Content = connection.get('content');
await Users.remove({});
await Content.remove({});
}
This would also make it very easy to test dropDB as you can now just pass a mocked object for it directly.
The other option could look like this:
export class Connection() {
constructor() {
this.connection = monk('mongdb://localhost:27017/db');
}
async dropDB() {
const Users = this.connection.get('users');
const Content = this.connection.get('content');
await Users.remove({});
await Content.remove({});
}
}
A test for the first option could look like this:
test('should call remove() methods', async () => {
const usersRemove = jest.fn().mockReturnValue(Promise.resolve(null));
const contentRemove = jest.fn().mockReturnValue(Promise.resolve(null));
const dbMock = {
get(type) {
if (type === 'users') {
return { remove: usersRemove };
}
else if (type === 'content') {
return { remove: contentRemove };
}
}
};
await dropDB(dbMock);
expect(usersRemove).toHaveBeenCalledTimes(1);
expect(contentRemove).toHaveBeenCalledTimes(1);
});
Basically, the dropDB function expects an object that has a get method which when called returns an object that has a remove method. So you just need to pass something that looks like that, so the function can call those remove methods.
For the class, this is a bit more complicated since the constructor has a dependency on the monk module. One way would be to make that dependency explicit again (just like in the first solution), and pass monk or some other factory there. But we can also use Jest’s manual mocks to simply mock the whole monk module.
Note that we do not want to mock the module containing our Connection type. We want to test that, so we need it in its un-mocked state.
To mock monk, we need to create a mock module of it at __mocks__/monk.js. The manual points out that this __mocks__ folder should be adjacent to the node_modules folder.
In that file, we simply export our custom monk function. This is pretty much the same we already used in the first example, since we only care about getting those remove methods in place:
export default function mockedMonk (url) {
return {
get(type) {
if (type === 'users') {
return { remove: mockedMonk.usersRemove };
}
else if (type === 'content') {
return { remove: mockedMonk.contentRemove };
}
}
};
};
Note that this refers to the functions as mockedMonk.usersRemove and mockedMonk.contentRemove. We’ll use this in the test to configure those function explicitly during the test execution.
Now, in the test function, we need to call jest.mock('monk') to enable Jest to mock the monk module with our mocked module. Then, we can just import it too and set our functions within the test. Basically, just like above:
import { Connection } from './db';
import monk from 'monk';
// enable mock
jest.mock('./monk');
test('should call remove() methods', async () => {
monk.usersRemove = jest.fn().mockReturnValue(Promise.resolve(null));
monk.contentRemove = jest.fn().mockReturnValue(Promise.resolve(null));
const connection = new Connection();
await connection.dropDB();
expect(monk.usersRemove).toHaveBeenCalledTimes(1);
expect(monk.contentRemove).toHaveBeenCalledTimes(1);
});

Angular Transfer State not preventing repeat http calls

I have the http request being made a service which is injected onto my component and subscribed to from there. Since I introduced server side rendering with angular universal to my application, the results on the page are repeated at least twice.
I have method which is called on click, which performs the http request to facebook's api
getAlbum(albumId: number) {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY, null as any);
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, res as any);
});
}
}
I declared the const variable below the imports
const ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
And I also declared the property
albumNames: any;
I am assuming I have done all of the imports right I have the code on github in the gallery component.
You are on the right pass, you just need to handle your service differently if you are on the server or the browser side to perform your queries only once and not twice.
Pseudo logic:
If server -> Do http request -> Set value in transfer-state
If browser -> Get value from transfer-state
To do so, you could for example enhance your Service like following:
#Injectable()
export class FacebookEventsService {
const ALBUM_PHOTOS_KEY: StateKey<number>;
constructor(#Inject(PLATFORM_ID) private platformId: Object, private http: HttpClient) {
this.ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
}
getBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
// Here we check if server or browser side
if (isPlatformServer(this.platformId)) {
return this.getServerBachaDiffFacebookEvents();
} else {
return this.getBrowserBachaDiffFacebookEvents();
}
}
getServerBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return this.http.get(this.facebookEventsUrl)
.map(res => {
// Here save also result in transfer-state
this.transferState.set(ALBUM_PHOTOS_KEY, calendarEvents);
});
}
getBrowserBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return new Observable(observer => {
observer.next(this.transferState.get(ALBUM_PHOTOS_KEY, null));
});
}
}
UPDATE
To use this logic you would also need:
TransferHttpCacheModule (to be initialized in app.module.ts).
TransferHttpCacheModule installs a Http interceptor that avoids
duplicate HttpClient requests on the client, for requests that were
already made when the application was rendered on the server side.
https://github.com/angular/universal/tree/master/modules/common
ServerTransferStateModule on the server side and BrowserTransferStateModule on the client side to use TransferState
https://angular.io/api/platform-browser/TransferState
P.S.: Note that if you do so and enhance your server, of course you would not need anymore to set the value in transfer-state in your getAlbum() method you displayed above
UPDATE 2
If you want to handle the server and browser side as you did in your gallery.component.ts, you could do something like the following:
getAlbum(albumId: number) {
if (isPlatformServer(this.platformId)) {
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, null);
});
}
} else {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY,null);
}
}
UPDATE 3
The thing is, your action getAlbum is never called on the server side. This action is only used on the browser side, once the page is rendered, when the user click on a specific action. Therefore, using transfer-state in that specific case isn't correct/needed.
Furthermore not sure that the Observable in your service was correctly subscribed.
Here what to change to make it running:
gallery.component.ts
getAlbum(albumId: number) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.albumPhotos = res;
});
}
facebook-events.service.ts
getBachadiffAlbumPhotos(albumId: number): Observable<Object> {
this.albumId = albumId;
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
return Observable.fromPromise(this.getPromiseBachaDiffAlbumPhotos(albumId));
}
private getPromiseBachaDiffAlbumPhotos(albumId: number): Promise<{}> {
return new Promise((resolve, reject) => {
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
let facebookPhotos: FacebookPhoto[] = new Array();
let facebookPhoto: FacebookPhoto;
const params: HttpParams = new HttpParams();
this.http.get(this.facebookAlbumPhotosUrl, {params: params})
.subscribe(res => {
let facebookPhotoData = res['data'];
for (let photo of facebookPhotoData) {
facebookPhotos.push(
facebookPhoto = {
id: photo.id,
image: photo.images[3].source,
link: photo.link,
height: photo.height,
width: photo.width
});
}
resolve(facebookPhotos);
}, (error) => {
reject(error);
});
});
}
UPDATE 4
ngOnInit is executed on the server side, this means that my very first answer here has to be use in this case.
Furthermore, also note that on the server side you doesn't have access to the window, therefore calling $
With gallery.component.ts you could do something like this to run only the http queries once but this won't solve all your problems, I think it will still need further improvements.
ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.facebookService.getBachadiffFacebookVideos().subscribe(res => {
this.bachataVidsArray = res;
this.state.set(VIDEOS_KEY, res as any);
});
this.facebookService.getBachadiffFacebookLastClassPictures().subscribe(res => {
this.bachataPicsArray = res;
this.state.set(LAST_CLASS_PICTURES_KEY, res as any);
});
this.facebookService.getBachadiffAlbumNames().subscribe(res => {
this.bachataAlbumHeaderNames = res;
this.state.set(ALBUM_NAMES_KEY, res as any);
});
} else {
$('ul.tabs').tabs();
this.bachataVidsArray = this.state.get(VIDEOS_KEY, null as any);
this.bachataPicsArray = this.state.get(LAST_CLASS_PICTURES_KEY, null as any);
this.bachataAlbumHeaderNames = this.state.get(ALBUM_NAMES_KEY, null as any);
}
}

Categories

Resources