extending a service to create a single definition of a database throughout application - node.js

I've created a nodejs service to create/interact with databases.
class CRUDService {
private section: string;
private db: any;
constructor(section: string) {
this.section = section;
this.db = new DB('.' + section);
}
public create(data: any): Promise<string> {
...
}
public retrieve(id: string): Promise<string> {
...
}
public update(id: any, data: any): Promise<string> {
...
}
public delete(item: any): Promise<string> {
...
}
public all(): Promise<string> {
...
}
}
export {CRUDService};
And then I was using it by passing a database name to it:
this.db1 = new DatabaseService('database-one');
this.db2 = new DatabaseService('database-two');
This has been working well for me but recently I noticed that I am defining the same databases over and over again (in different modules) and figured there has got to be a better way to doing it so that the database can be defined once and shared across everything.
Is there a way I can extend my current service to define/initialize the database once and reference it?
export class DbOneService extends CRUDservice {
protected section = 'database-one';
}
And then use it like:
this.db1 = DbOneService;
I'm just a bit lost and the above doesn't seem to work.
UPDATE
By leaving the CRUDService as-is I was able to achieve what I by
import {CRUDService} from '../crud.service';
function DbOneService() {
return new CRUDService('database-one');
}
export {DbOneService};
Then I use it as follows:
import {DbOneService} from 'db/database-one';
const DbOneServiceDB = DbOneService();
DbOneServiceDB.all(){...}
Is there anything wrong with this approach?

Related

NestJS lifecycle methods invoked without implementing their interface

I am having a small question about NestJS. In my code, there is a service which looks something like:
`
import { Inject, Injectable } from '#nestjs/common';
import neo4j, { Driver, int, Result, Transaction } from 'neo4j-driver';
import { Neo4jConfig } from './neo4j-config.interface';
import { NEO4J_CONFIG, NEO4J_DRIVER } from './neo4j.constants';
#Injectable()
export class Neo4jService {
constructor(
#Inject(NEO4J_CONFIG) private readonly config: Neo4jConfig,
#Inject(NEO4J_DRIVER) private readonly driver: Driver,
) {}
onApplicationBootstrap() {
console.log('Hello');
}
getDriver(): Driver {
return this.driver;
}
getConfig(): Neo4jConfig {
return this.config;
}
int(value: number) {
return int(value);
}
beginTransaction(database?: string): Transaction {
const session = this.getWriteSession(database);
return session.beginTransaction();
}
getReadSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.READ,
});
}
getWriteSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.WRITE,
});
}
read(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getReadSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
write(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getWriteSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
private onApplicationShutdown() {
console.log('Goodbye')
return this.driver.close();
}
}
`
Then in my main.ts file I have this method called:
`
await app.listen(port);
`
As you can see my service does not implement neither onApplicationBootstrap nor onApplicationShutdown.
How does it come that those methods still get invoked? Should I implement onApplicationBootstrap and onApplicationShutdown or not?
As you can also see I' d like that my onApplicationBootstrap is a private method which would not be possible if I implement the interface.
So, I would like to ask you:
Why the two lifecycle methods get called event without implementing the interface?
Should I implement those interfaces at all or just go on and use the methods which would allow me to define them as private?
I expected those methods to not work without implementing the interfaces
The Typescript interface is there to help us as devs. It doesn't exist at runtime, there's no information about it, so the only thing Nest can do is just check "Hey, does this class have the onModuleInit method?" If yes, add it to a list of classes to call onModuleInit. Do the same with the other lifecycle methods.
The interfaces aren't explicitly necessary, but they do give us devs a better idea of the class by just looking at the export class... line because we can see what is implemented/extended.

NestJs: Why we don't use DTOs to replace all interfaces?

Can we make DTOs source of truth for the validations, and use it in both controller and service?
If I already have DTOs, why do I need interfaces ?
You don't need interfaces if you don't want to use them. For DTO's which are meant to be basic models, I don't use interfaces either. That being said, they are really powerful so I'm definitely not discouraging you from using them, take this example:
interface ILogger {
log(message: string) : Promise<void>;
}
class ConsoleLogger implements ILogger {
log(message: string) : Promise<void> {
return Promise.resolve(console.log(message));
}
}
class DatabaseLogger implements ILogger {
private dbConnection;
constructor() {
dbConnection = new DBConnection(); //Fake but it drives the point across
}
async log(message: string) : Promise<void> {
return await this.dbConnection.saveLog(message);
}
}
class DoTheThing {
private _logger: ILogger;
//You can have nest inject the best logger for this situation and your code doesn't have to care
//about the actual implementation :)
constructor(logger: ILogger) {
this._logger = logger;
}
async myMethod() {
const tweetMessage = await sendTweet('...');
this._logger.log(tweetMessage);
}
}

How to create a NestJs Pipe with a config object and dependency?

I would Like to pass a configuration string to a Pipe but also want to inject a service. The NesJs docs describe how to do both of these independent of each other but not together. Take the following example:
pipe.ts
#Injectable()
export class FileExistsPipe implements PipeTransform {
constructor(private filePath: string, db: DatabaseService) { }
async transform(value: any, metadata: ArgumentMetadata) {
const path = value[this.filePath];
const doesExist = await this.db.file(path).exists()
if(!doesExist) throw new BadRequestException();
return value;
}
}
controller.ts
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
#Body( new FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
Basically, I want to be able to pass a property name to my pipe (e.g.'input') and then have the pipe look up the value of the property in the request (e.g.const path = value[this.filePath]) and then look to see if the file exists or not in the database. If it doesn't, throw a Bad Request error, otherwise continue.
The issue I am facing is that I need NestJs to inject my DataBaseService. With the current example, It won't and my IDE gives me an error that new FileExistsPipe('input') only has one argument passed but was expecting two (e.g. DatabaseService).
Is there anyway to achieve this?
EDIT: I just checked your repo (sorry for missing it before). Your DatabaseService is undefined in the FIleExistPipe because you use the pipe in AppController. AppController will be resolved before the DatabaseModule gets resolved. You can use forwardRef() to inject the DatabaseService in your pipe if you are going to use the pipe in AppController. The good practice here is to have feature controllers provided in feature modules.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(
createFileExistPipe
);
function createFileExistPipe(filePath: string): Type<PipeTransform> {
class MixinFileExistPipe implements PipeTransform {
constructor(
// use forwardRef here
#Inject(forwardRef(() => DatabaseService)) private db: DatabaseService
) {
console.log(db);
}
async transform(value: ITranscodeRequest, metadata: ArgumentMetadata) {
console.log(filePath, this.db);
const doesExist = await this.db.checkFileExists(filePath);
if (!doesExist) throw new BadRequestException();
return value;
}
}
return mixin(MixinFileExistPipe);
}
You can achieve this with Mixin. Instead of exporting an injectable class, you'd export a factory function that would return such class.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(createFileExistPipe);
function createFileExistPipe(filePath: string) {
class MixinFileExistPipe implements PipeTransform {
constructor(private db: DatabaseService) {}
...
}
return mixin(MixinFileExistPipe);
}
memoize is just a simple function to cache the created mixin-pipe with the filePath. So for each filePath, you'd only have a single version of that pipe.
mixin is a helper function imported from nestjs/common which will wrap the MixinFileExistPipe class and make the DI container available (so DatabaseService can be injected).
Usage:
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
// notice, there's no "new"
#Body(FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
a mixin guard injecting the MongoDB Connection
the console shows the connection being logged

Unable to use interfaces in fabric-contract-api

I am trying to develop a chaincode for Hyperledger Fabric 1.4 using the IBM Blockchain Platform plugin for Visual Studio Code and the fabric-contract-api v1.4.2. In this situation, I am facing some problems when trying to use interfaces from my chaincode methods. This is the error:
Error: Type not properly specified for parameter myAssetConfig, can not process pure Object types
The asset I am using is called MyAsset. This is the declaration of that element:
#Object()
export class MyAsset {
#Property()
public propertyA: string;
#Property()
public propertyB: string;
#Property()
public propertyC?: string;
constructor(myAssetConfig: IMyAssetConfig) {
this.propertyA = myAssetConfig.propertyA;
this.propertyB = myAssetConfig.propertyB;
if (myAssetConfig.hasOwnProperty('propertyC')) {
this.propertyC = myAssetConfig.propertyC;
}
}
}
Apart from it, this the content of types/index.d.ts (I am using the flag #Object here but I am not exactly sure if I should and why/why not):
#Object
export interface IMyAssetConfig {
propertyA: string;
propertyB: string;
propertyC?: string;
}
export type MyAssetId = string;
Finally, this is the content of myasset-contract.ts
#Info({title: 'MyAssetContract', description: 'My MyAsset Contract'})
export class MyAssetContract extends Contract {
#Transaction(false)
#Returns('boolean')
public async myAssetExists(ctx: Context, myAssetId: MyAssetId): Promise<boolean> {
const buffer = await ctx.stub.getState(myAssetId);
return (!!buffer && buffer.length > 0);
}
#Transaction()
public async createMyAsset(ctx: Context, myAssetConfig: IMyAssetConfig): Promise<MyAssetId> {
const myAssetId: MyAssetId = myAssetConfig.shippingCompanyId + '-' + this.generateInternMyAssetId(ctx);
const exists = await this.myAssetExists(ctx, myAssetId);
if (exists) {
throw new Error(`The myAsset ${myAssetId} already exists`);
}
const myAsset = new MyAsset(myAssetConfig);
const buffer = Buffer.from(JSON.stringify(myAsset));
await ctx.stub.putState(myAssetId, buffer);
return myAssetId;
}
#Transaction(false)
#Returns('MyAsset')
public async readMyAsset(ctx: Context, myAssetId: MyAssetId): Promise<MyAsset> {
const exists = await this.myAssetExists(ctx, myAssetId);
if (!exists) {
throw new Error(`The myAsset ${myAssetId} does not exist`);
}
const buffer = await ctx.stub.getState(myAssetId);
return JSON.parse(buffer.toString()) as MyAsset;
}
#Transaction()
public async splitMyAsset(ctx: Context, myAssetId: MyAssetId, children: IMyAssetConfig[]): Promise<MyAssetId[]> {
// REMOVED because it is actually irrelevant to the problem and makes the post too long.
return [];
}
}
Of course, this is all anonymized and reduced but I think the problem is clear enough. I can not use IMyAssetConfig as I type for the parameter myAssetConfig but there is no problem if I use string. I could understand till some point that fabric-contract-api does not accept Objects as parameters. However, if I comment all the code of createMyAsset I get no errors, and I am also using an object in splitMyAsset and I have no problem there.
Can anyone explain me what this is happening? The problems I get happen when I try to instantiate the chaincode/run tests using npm test.
Thank you very much.

How to extend a class in typescript

My service is designed in nodejs.
Below is my scenario
i have two controllers, one will be extending the other. there is a static function in both the controllers where in a static variable will be assigned some value.
depending on the condition of the data, im trying the make a call to the respective controller so that the static variable gets a appropriate assigned value.
Note:
The below code is just a snippet to explain the scenario and not the actual code of the application. But the order / calling / controller structure of this code snippet is exactly same. Also the listOfDept variable will be having separate business logic in the checkStart function of firstController and secondController.
// firstController.ts
firstController implements IFirstController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = // my logic to fill this object
}
constructor (){}
}
getRelevantData(next: (error: string, response: any) => void): void {
var myObject = firstController.listOfDept;
this.myRepository.uniqueData(myObject, next);
}
}
firstController.checkStart();
export = firstController;
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
secondController extends firstController implements iSecondController {
private static listOfDept: string[];
static checkStart(){
firstController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();
export = secondController;
//isecondController.ts
interface ISecondController implements ifirstController{}
//Controller calling the getRelevantData function
//middlewareController
middlewareController implements IMiddlewareController {
constructor(private firstController: IFirstController, private secondController: ISecondController) {
}
getDepData(data: any, next: (error: string, response: any) => void): void {
if(data.url = "fromParent") {
// im expecting this to make a call to checkStart() of firstController
this.firstController.getRelevantData();
} else {
// im expecting this to make a call to checkStart() of secondController
this.secondController.getRelevantData();
}
}
}
Problem faced with the above code
No matter which way the getRelevantData function is getting called, im always getting the value of listOfDept as computer science. It is never going in the checkStart function of first controller.
In general I would discourage using static methods for this kind of initialization and instead inject the required data into constructors or create factory methods for creating object with necessary data.
But, if you do want to use static properties, the problem is that you need to refer to the right parent class in the getRelevantData implementation. The class that constructed the instance can be accessed through constructor property. TypeScript does not process this scenario well, so you have to make a type cast:
// firstController.ts
class firstController implements IFirstController {
// Need to be `protected` to be accessible from subclass
protected static listOfDept: string[];
static checkStart(){
firstController.listOfDept; // my logic to fill this object
}
constructor (){}
getRelevantData(next: (error: string, response: any) => void): void {
// You need to refer to the constructor
let Class = this.constructor as typeof firstController;
var myObject = Class.listOfDept;
// the rest
}
}
firstController.checkStart();
//ifirstController.ts
interface IFirstController {
getRelevantData(next: (error: string, response: any) => void): void;
}
// secondController.ts
class secondController extends firstController implements ISecondController {
// No `listOfDept` definition here
static checkStart(){
secondController.listOfDept = ["Computer Science"];
}
constructor (){
super();
}
}
secondController.checkStart();

Resources