JS: blending two classes - javascript

Using Apollo server, have a pretty standard setup:
In an index.js file that manages my Apollo server state:
const { ApolloServer } = require('apollo-server-express');
const databaseAPI = require('../database/database.datasource');
...
module.exports = new ApolloServer({
...
dataSources: () => {
return {
databaseAPI: new databaseAPI()
};
},
...
});
And then my database.datasource.js file:
const { RESTDataSource } = require("apollo-datasource-rest");
// imports and config here
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
// methods here like createUser({email, password, name})
}
module.exports = databaseAPI;
This database.datasource.js file is getting a bit lengthy, in part because it is managing the db layer for many types, so I was thinking about how to refactor it.
I want to break it apart by major types using a repository pattern, something like:
UserRepository.js (and similar abstract classes as reference and to enforce an internal API)
class UserRepository {
createUser({ email, password, name }) {
throw new Error("Not implemented");
}
...
}
export default UserRepository;
and then implementations of these in by-type directories:
/UserRepository/db.datasource.js
import UserRepository from "../UserRepository";
import utils from './utils';
class UserDatabaseAPI extends UserRepository {
constructor(db) {
super();
this.db = db;
}
createUser({ email, password, name }) {
// logic here
}
}
export default UserDatabaseAPI;
My question is how best to manage the multiple classes inside of my root database.datasource.js file with multiple inheritance sources.
I could see doing something like this:
const { RESTDataSource } = require("apollo-datasource-rest");
const UserDatabaseAPI = require("./UserRepository/db.datasource.js");
// imports and config here, including a db instance
const userDBAPI = new UserDatabaseAPI(db);
class databaseAPI extends RESTDataSource {
constructor() {
super();
}
createUser = userDBAPI.createUser;
...
}
Though I think that this might become kind of a headache. Is there another approach that might work better here, or is there at least a way to better map the methods in my userDBAPI instance to methods in my root database.datasource.js file?

Instead of having a single RESTDataSource for the endpoint you're wrapping, you can split it up into multiple RESTDataSource instances, one per resource. For example, https://swapi.co/api can be split into 6 difference RESTDataSource instances with the following baseURLs:
https://swapi.co/api/people
https://swapi.co/api/films
https://swapi.co/api/starships
https://swapi.co/api/vehicles
https://swapi.co/api/species
https://swapi.co/api/planets
And your config would look something like:
dataSources: () => ({
people: new PeopleAPI(),
films: new FilmsAPI(),
starships: new StarshipsAPI(),
vehicles: new VehiclesAPI(),
species: new SpeciesAPI(),
planets: new PlanetsAPI(),
})

Related

How to create factories with existing relationship on model in MIKRO-ORM

Hi I am trying to figure out how to create factory and define relationship between models.
For example I have UserFactory with User entity and this entity has connection to userType table. In factory I have not access to EntityManager so I couldnĀ“t find any existing.
export class UserFactory extends Factory<User> {
model = User
definition(faker: Faker): Partial<User> {
const user = {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
...
userType: // Here I need do something like this:
// EntityManager.findOne(UserType, {id: 1}}
// But EntityManager is private in Factory class
}
return user
}
}
Itried also something like this but this return me an error:
ValidationError: Value for User.type is required, 'undefined' found
DatabaseSeeder
export class DatabaseSeeder extends Seeder {
async run(em: EntityManager): Promise<void> {
const users: User[] = new UserFactory(em).each(async user => {
const userType : UserType| null = await em.findOne(UserType, 1)
console.log(tenant)
const userType = await em.findOne(UserType, 1)
if (userType !== null) {
user.type = userType
} else {
user.type = em.create(UserType, {
type: 'test'
})
}
}).make(10)
}
}
What is the proper way to achieve this please?
You can use the shared seeder context as describer in the docs:
https://mikro-orm.io/docs/seeding#shared-context
export class AuthorSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
// save the entity to the context
context.author = em.create(Author, {
name: '...',
email: '...',
});
}
}
export class BookSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
em.create(Book, {
title: '...',
author: context.author, // use the entity from context
});
}
}
I guess this shared context should be also available in the seeder factories, but you can always handle this yourself, as both the seeder and factory is your implementation, so you can pass any additional options in there. Its you who initializes the factory so I dont think there is a better way than doing it in your code.
I would suggest not to flush and findOne things in your seeder, you should aim for a single flush and use the shared context instead for entity look up.

Using Typescript interface as a variable

I am working on a Node Js (TypeScript) architecture and for some reason, I want to bind my interface to a specific object. I am making a general class that is extended by other subclasses and it will have a very general code. So my code looks like
interface User {
name: string;
}
interface Profile {
title: string;
}
class Parent {
name: string;
interface: Interface; // Help required here, getting error can't use type as a variable
constructor( name, interface ) {
// Load schema and store here
this.name = name
this.interface = interface
}
// Though this is not correct I hope you get the idea of what I am trying to do
get (): this.interface {
// fetch the data and return
return data
}
set (data: this.interface): void {
// adding new data
}
}
class UserSchema extends Parent {
// Class with custom functions for UserSchema
}
class ProfileSchema extends Parent {
// Class with custom functions for ProfileSchema
}
// Config file that saves the configs for different modules
const moduleConfig = [
{
name: "User Module",
class: UserSchema,
interface: User
},
{
name: "Profile Module",
class: ProfileSchema,
interface: Profile
},
]
const allModules = {}
// Loading the modules
moduleConfig.map(config => {
allModules[config.name] = new config.class(
config.name,
config.interface
)
})
export allModules;
I need suggestions on how should I bind my interfaces with their respective configs. Till now I have had no luck with that.
PS: All this code is separated into their respective files.
This is the use case for generics. You can even see them as "variable for types".
Instead of having an interface property in your Parent class, the latter would have a generic type:
class Parent<T> { // T is the generic type
name: string;
// interface: Interface; // generic is already provided at class level
constructor( name ) {
// Load schema and store here
this.name = name
}
get (): T {
// fetch the data and return
return data
}
set (data: T): void {
// adding new data
}
}
// Here you specify the concrete generic type
class UserSchema extends Parent<User> {
// Class with custom functions for UserSchema
}
class ProfileSchema extends Parent<Profile> {
// Class with custom functions for ProfileSchema
}

JavaScript ES6 instantiating with dependency injection classes which depend on each other

Still trying to get the hang of dependency injection, I've run into a problem. I have three classes: Main, Store, Model. Store does as the name suggests: stores data which is accessible elsewhere. Model retrieves data from the DB, in my case local/Chrome storage. I'm trying to write the classes in a decoupled way so I can test/modify it later. But of course, I keep getting Maximum call stack size exceeded error because I think Store and Model just keep going round and round...circular dependancy. What's a possible solution?
store.js
export class Store { //ok there's also a config class but that's not the problem
constructor(config, model) {
this._config = config;
this._model = model;
}
//...
}
model.js
export class Model {
constructor(config, store) {
this._config = config;
this._store = store;
}
//...
}
main.js
import { Store } from './store.js'
import { Config } from './config.js'
import { Model } from './model.js'
export class Main {
constructor(config, store, model) {
this._store = store;
this._model = model;
}
//...
}
const config = new Config();
const model = new Model(config, ???);
const store = new Store(config, ???);
const main = new Main(config, model, store);
Is there a fix, workaround, anything? Thanks in advance!

How to define custom query helper in mongoose model with typescript?

I want to define custom query helper using query helper api .
Here the example:
// models/article.ts
import { Document, Schema, Model, model } from 'mongoose';
interface IArticle extends Document {
name: string;
}
interface IArticleModel extends Model<IArticle> {
someStaticMethod(): Promise<any>;
}
const ArticleSchema = new Schema( { name: String } )
ArticleSchema.query.byName = function(name) {
return this.find({ name })
}
export default model<IArticle, IArticleModel>('Article', ArticleSchema);
// routes/article.ts
import ArticleModel from '../models/article.ts'
router.get('/articles, (req, res) => {
ArticleModel.find().byName('example')
})
Typescript complains about byName method when I chain it with defaults.
I can put it in IArticleModel interface but in that case I could only call it from model.
Where should I put the definition of this method to use it in chainable way?
I've drafted a new version of #types/mongoose that supports query helpers. See this answer for ways to install a modified #types package. With my version, you should be able to write the following in models/article.ts:
import { Document, Schema, Model, model, DocumentQuery } from 'mongoose';
interface IArticle extends Document {
name: string;
}
interface IArticleModel extends Model<IArticle, typeof articleQueryHelpers> {
someStaticMethod(): Promise<any>;
}
const ArticleSchema = new Schema( { name: String } )
let articleQueryHelpers = {
byName(this: DocumentQuery<any, IArticle>, name: string) {
return this.find({ name });
}
};
ArticleSchema.query = articleQueryHelpers;
export default model<IArticle, IArticleModel>('Article', ArticleSchema);
and then routes/article.ts will work. If this works for you, then I will submit a pull request to the original package on DefinitelyTyped.

Load different classes and execute similar function name

I have a problem that I can't resolve.
Let's say we have some classes in a directory named services.
Each of theses classes contain a contructor() and send() method.
We can have differents classes such as Discord, Slack, SMS, etc.
Their goal is just to sent notification through external service.
I think I have to use an interface or an abstract class which contain contructor() and send() method but how can I instanciate every class and call send() in an elegantly way ?
My project structure :
services/
-> discord.js
-> slack.js
-> [...]
index.js
Regards.
I think what you are looking for is a kind of manager where you have a single send() function that chooses a specific service based on a parameter. Something like this:
services/index.js
import SlackService from 'slack.js';
import DiscordService from 'discord.js';
export const TYPES = {
SLACK: 'slack',
DISCORD: 'discord',
};
export class ServiceManager {
services;
constructor() {
this.services = {
[TYPES.DISCORD]: new DiscordService(/* discordConfig */),
[TYPES.SLACK]: new SlackService(/* slackConfig */),
};
}
send(type, data) {
return this.services[type].send(data);
}
}
index.js
import ServiceManager from 'services/index.js';
const serviceManager = new ServiceManager();
serviceManager.send(ServiceManager.TYPES.SLACK, { message: 'Sent to Slack' });
serviceManager.send(ServiceManager.TYPES.DISCORD, { message: 'Sent to Discord' });
Dynamically loading services from files
You can use require-dir to import all files from a directory and then map over those to create each service. The individual service files have to be written in a defined syntax for the manager to use them. Something like this:
services/slack.js (as example for all service files):
export const name = 'slack';
export class Service {
constructor() {
// Set up connection to slack
}
send() {
// Send something to slack
}
}
services/index.js
const requireDir = require('require-dir');
export class ServiceManager {
services;
constructor() {
const serviceObjects = requireDir('.');
this.services = Object.values(serviceObjects).reduce(
(services, { name, Service }) => {
services[name] = new Service();
return services;
}
)
}
getRegisteredServices() {
return Object.keys(this.services);
}
send(name, data) {
return this.services[name].send(data);
}
sendAll(data) {
Object.values(this.services).each(service => service.send(data));
}
}
index.js (stays pretty much the same)
import ServiceManager from 'ServiceManager.js';
const serviceManager = new ServiceManager();
console.log('Registered services are: ', serviceManager.getRegisteredServices());
serviceManager.send('slack', { message: 'Sent to Slack' });
serviceManager.send('discord', { message: 'Sent to Discord' });

Categories