Hello I struggle with the logical organization of code in my project.
I encountered a certain challenge, I have to check database table with some time interval of time if there are any new actions to be performed. Actions can be different and their logic is not related to each other, so I wanted to separate the code of each action into separate files.
I created an example files structure and I think it works the way I would like. I just wonder if it is a good thing that I am passing an instance of the database every time I call a await action.invoke(this.databaseInstance) function repeatedly?
My files:
// index.js
const { Lion } = require('./Lion');
const databaseInstance = {
database: 'Postgress',
dialect: 'SQL'
};
const actionrunner = new Lion({ databaseInstance });
actionrunner.startProcessing();
// Lion.js
const { reoccuringActions } = require('./reoccuringActions');
class Lion {
constructor({ databaseInstance }) {
this.interval = 10000;
this.intervalId = null;
this.databaseInstance = databaseInstance;
}
startProcessing() {
this.recuringInvoke();
this.intervalId = setInterval(this.recuringInvoke.bind(this), this.interval);
}
async recuringInvoke() {
for (const action of reoccuringActions) {
await action.invoke(this.databaseInstance);
}
}
}
module.exports = { Lion };
// reoccuringActions.js
const { BabyLion } = require('./babyLion');
const { BabyCat } = require('./babyCat');
const reoccuringActions = [new BabyLion(), new BabyCat()];
module.exports = { reoccuringActions };
// babyLion.js
class BabyLion {
constructor() {}
async invoke(databaseInstance) {
console.log('invoke BabyLion');
// do something
}
}
module.exports = { BabyLion };
//babyCat.js
class BabyCat {
constructor() {}
async invoke(databaseInstance) {
console.log('invoke babyCat');
// do something
}
}
module.exports = { BabyCat };
Well i don't think there is anything wrong with passing "databaseInstance" every time. But if you want to do in another way you can create "databaseInstance" as variable in each of your action and pass "databaseInstance" in constructor of your action. and use it there, in that way you don't have to pass every time.
// babyLion.js
class BabyLion {
constructor({databaseInstance}) {
this.databaseInstance;
}
async invoke() {
console.log('invoke BabyLion');
// do something
}
}
module.exports = { BabyLion };
//babyCat.js
class BabyCat {
constructor({databaseInstance}) {
this.databaseInstance;
}
async invoke() {
console.log('invoke babyCat');
// do something
}
}
module.exports = { BabyCat };
// reoccuringActions.js
const { BabyLion } = require('./babyLion');
const { BabyCat } = require('./babyCat');
const databaseInstance = require("FromSomewhere");
const reoccuringActions = [
new BabyLion(databaseInstance),
new BabyCat(databaseInstance)
];
module.exports = { reoccuringActions };
Related
when i call the function .get returns an empty object. When i use console.log returns the database but when the function reach returns result is {}.
let { connection, mongoose } = require("../../../config/mongodb");
let { Schema, model } = mongoose;
let { productosSchema } = require("../../../schemas/productos");
let productosSchemaModel = new Schema(productosSchema);
let Productos = new model("productos", productosSchemaModel);
class ProductosService {
constructor(url) {
this.url = url;
}
async getProds() {
try {
const allProds = await Productos.find({}).sort({ producto: 1 });
return prods;
} catch (error) {
console.log(error);
}
}
}
As #CertainPerformance said, your variable name is allProds, not prods. Simply change the return from return prods; to return allProds; and it should work. See below for the complete code:
const { mongoose } = require('../../../config/mongodb');
const { Schema, model } = mongoose;
const { productosSchema } = require('../../../schemas/productos');
const productosSchemaModel = new Schema(productosSchema);
const Productos = new model('productos', productosSchemaModel);
class ProductosService {
constructor(url) {
this.url = url;
}
async getProds() {
try {
const allProds = await Productos.find({}).sort({
producto: 1
});
return allProds;
} catch (error) {
console.log(error);
}
}
}
I'm working on standing up a simple Relay/GraphQL app using Node.
At the root level, I have a connection called 'notes' that paginates all notes in the database. On my user object, I have a notes connection that paginates all notes that user has created.
const rootNotesId = ConnectionHandler.getConnectionID('client:root', 'RootNotesConnection_notes');
const userNotesId = ConnectionHandler.getConnectionID(queryData?.me?.id, 'UserNotesConnection_notes');
const rootNotes = usePaginationFragment(graphql `
fragment NotesRoot_notes on Query #refetchable(queryName: "NotesRootQuery") {
notes(first: $count, after: $cursor) #connection(key: "RootNotesConnection_notes") {
edges {
node {
...Note_note
}
}
}
}
`, queryData);
const userNotes = usePaginationFragment(graphql`
fragment NotesUser_notes on Query #refetchable(queryName: "NotesUserQuery") {
me {
id
notes(first: $count, after: $cursor) #connection(key: "UserNotesConnection_notes") {
edges {
node {
...Note_note
}
}
}
}
}
`, queryData);
How do I add or delete a note to both connections at once client-side? I have two different edge types and I thought this code would work:
const [commit, isInFlight] = useMutation(graphql `
mutation NotesCreateMutation($input: createNoteInput!) {
createNote(input: $input) {
noteEdge {
cursor,
node {
id
user {
username
}
content
}
}
}
}
`);
const handleButtonClick = () => {
if (!isInFlight) {
commit({
variables: {
input: {
content: newNoteInput
}
},
updater: store => {
const rootCon = ConnectionHandler.getConnection(store.get('client:root'), 'RootNotesConnection_notes');
const userCon = ConnectionHandler.getConnection(store.get(userId), 'UserNotesConnection_notes');
const payload = store.getRootField('createNote');
const newEdge = payload.getLinkedRecord('noteEdge');
const newNote = newEdge.getLinkedRecord('node');
debugger;
const newRootEdge = ConnectionHandler.createEdge(store, rootCon, newNote, 'QueryNotesEdge');
const newUserEdge = ConnectionHandler.createEdge(store, userCon, newNote, 'UserNotesEdge');
ConnectionHandler.insertEdgeAfter(rootCon, newRootEdge);
ConnectionHandler.insertEdgeAfter(userCon, newUserEdge);
}
});
setNewNoteInput('');
}
}
The only thing I can find in my debugging is that the cursor never gets set for the new edge. Stepping through this code in the debugger reveals that all variables before newRootEdge resolve just fine
This worked. Thanks to this thread for a good workaround: https://github.com/facebook/relay/issues/2761
const handleButtonClick = () => {
if (!isInFlight) {
commit({
variables: {
input: {
content: newNoteInput
}
},
updater: store => {
const rootCon = ConnectionHandler.getConnection(store.get('client:root'), 'RootNotesConnection_notes');
const payload = store.getRootField('createNote');
const newRootEdge = payload.getLinkedRecord('noteEdge');
const prevRootEdges = rootCon.getLinkedRecords('edges');
const nextRootEdges = [...prevRootEdges, newRootEdge];
rootCon.setLinkedRecords(nextRootEdges, 'edges');
const userCon = ConnectionHandler.getConnection(store.get(userId), 'UserNotesConnection_notes');
const newNote = newRootEdge.getLinkedRecord('node');
const newUserEdge = ConnectionHandler.createEdge(store, userCon, newNote, 'UserNotesEdge');
newUserEdge.setValue(newRootEdge.getValue('cursor'), 'cursor');
const prevUserEdges = userCon.getLinkedRecords('edges');
const nextUserEdges = [...prevUserEdges, newUserEdge];
userCon.setLinkedRecords(nextUserEdges, 'edges');
}
});
setNewNoteInput('');
}
}
I have several JavaScript files that I create enums. for example:
source.enum.js
const enumUtils = require('../enum.utils');
const EmailAddressesSourceType = enumUtils.createEnum([
['DIRECTORY', 'directory'],
['FILE', 'file'],
['ARRAY', 'array']
]);
module.exports = { EmailAddressesSourceType };
The enum.utils.js is just a file that do the simple function of creating an enum from array:
class EnumUtils {
constructor() { }
// This method takes a map of elements and converts them to freeze objects (an enum-like object).
createEnum(mapItems) {
if (!mapItems || mapItems.length <= 0) {
throw new Error(`No array received: ${mapItems} (1000000)`);
}
const mapList = new Map([...mapItems]);
const symbolMap = {};
mapList.forEach((value, key) => { symbolMap[key] = value; });
return Object.freeze(symbolMap);
}
}
const enumUtils = new EnumUtils();
module.exports = enumUtils;
Now since I have 5-6 js files with enums, I want to avoid 'const enumUtils = require('../enum.utils');' in each of them, and do it all together in index.js file, something like this:
const { EmailAddressStatus, EmailAddressType, SendEmailStepName } = require('./files/emailAddress.enum');
const { Placeholder } = require('./files/placeholder.enum');
const { EmailAddressesSourceType } = require('./files/sources.enum');
const { Mode, Status, Method } = require('./files/system.enum');
const { StatusIcon, Color, ColorCode } = require('./files/text.enum');
const createEnum = (mapItems) => {
if (!mapItems || mapItems.length <= 0) {
throw new Error(`No array received: ${mapItems} (1000000)`);
}
const mapList = new Map([...mapItems]);
const symbolMap = {};
mapList.forEach((value, key) => { symbolMap[key] = value; });
return Object.freeze(symbolMap);
};
module.exports = {
createEnum(Color), createEnum(ColorCode), createEnum(EmailAddressStatus), createEnum(EmailAddressType), createEnum(EmailAddressesSourceType),
createEnum(Method), createEnum(Mode), createEnum(Placeholder), createEnum(SendEmailStepName), createEnum(Status), createEnum(StatusIcon)
};
But, there are compilation error in:
module.exports = {
createEnum(Color), createEnum(ColorCode), createEnum(EmailAddressStatus), createEnum(EmailAddressType), createEnum(EmailAddressesSourceType),
createEnum(Method), createEnum(Mode), createEnum(Placeholder), createEnum(SendEmailStepName), createEnum(Status), createEnum(StatusIcon)
};
My question is, there is a workaround so enable me to reduce the 'const enumUtils = require('../enum.utils');' in each file of the enums js file?
Thanks!
UPDATE 1
The error I'm getting is this:
The current status of the file (before I was trying to refactor) - It works OK:
index.js
const { EmailAddressStatus, EmailAddressType, SendEmailStepName } = require('./files/emailAddress.enum');
const { Placeholder } = require('./files/placeholder.enum');
const { EmailAddressesSourceType } = require('./files/sources.enum');
const { Mode, Status, Method } = require('./files/system.enum');
const { StatusIcon, Color, ColorCode } = require('./files/text.enum');
module.exports = {
Color, ColorCode, EmailAddressStatus, EmailAddressType, EmailAddressesSourceType,
Method, Mode, Placeholder, SendEmailStepName, Status, StatusIcon
};
This guy, guy-incognito, solved for me the issue. Now it works like a charm. Thanks man!
const { EmailAddressStatus, EmailAddressType, SendEmailStepName } = require('./files/emailAddress.enum');
const { Placeholder } = require('./files/placeholder.enum');
const { EmailAddressesSourceType } = require('./files/sources.enum');
const { Mode, Status, Method } = require('./files/system.enum');
const { StatusIcon, Color, ColorCode } = require('./files/text.enum');
const createEnum = (mapItems) => {
if (!mapItems || mapItems.length <= 0) {
throw new Error(`No array received: ${mapItems} (1000000)`);
}
const mapList = new Map([...mapItems]);
const symbolMap = {};
mapList.forEach((value, key) => { symbolMap[key] = value; });
return Object.freeze(symbolMap);
};
module.exports = {
Color: createEnum(Color),
ColorCode: createEnum(ColorCode),
EmailAddressStatus: createEnum(EmailAddressStatus),
EmailAddressType: createEnum(EmailAddressType),
EmailAddressesSourceType: createEnum(EmailAddressesSourceType),
Method: createEnum(Method),
Mode: createEnum(Mode),
Placeholder: createEnum(Placeholder),
SendEmailStepName: createEnum(SendEmailStepName),
Status: createEnum(Status),
StatusIcon: createEnum(StatusIcon)
};
I'm following a tutorial which is using jest to test the javascript. The instructor created a static function called genesis() on a class called Block and it worked for him just fine, but when I tried to do it I got TypeError: block.genesis is not a function. If I remove the static keyword it recognises the function and the test passes.
Here is the class:
const { GENESIS_DATA } = require('./config');
class Block {
constructor({ timestamp, lastHash, hash, data }) {
this.timestamp = timestamp;
this.lastHash = lastHash;
this.hash = hash;
this.data = data;
}
static genesis() {
return new Block(GENESIS_DATA);
}
}
module.exports = Block;
And the test:
const Block = require('./block');
const { GENESIS_DATA } = require('./config');
describe('Block', () => {
const timestamp = 'a-date';
const lastHash = 'a-hash';
const hash = 'another-hash';
const data = ['blockchain', 'data'];
const block = new Block({ timestamp, lastHash, hash, data });
describe('genesis()', () => {
const genesisBlock = block.genesis();
it('returns a block instance', () => {
expect(genesisBlock instanceof Block).toBe(true);
});
it('returns the genesis data', () => {
expect(genesisBlock).toEqual(GENESIS_DATA);
});
});
});
The genesis method is part of the class, not the instance. You want to call Block.genesis() instead of block.genesis()
So I'm creating a singleton class and when I require it from my server.js file it works fine, but when I require it from another file it returns as undefined. I'll try to post relevant code but some will have to be cut out due to work.
server.js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
const config = require('config');
const FBConfigsListener = require('./server/amq_listeners/fb_configs.listener');
const FBConfigs = require('./server/models/FBConfigs');
//Api file for interacting with mongodb
const api = require('./server/routes/api.routes');
//Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
//Angular Dist output folder
app.use(express.static(path.join(__dirname, 'dist')));
//Api location
app.use('/api', api);
//Send all other requests to angular
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
//set port
var port = config.get('webserver.port');
app.set('port', port);
const server = http.createServer(app);
server.listen(port, () => console.log(`Running on localhost:${port}`));
models/FBConfigs.js
var ConfigModel = require('./config');
var config = require('config');
var _ = require('lodash');
var FBConfigsListener = require('../amq_listeners/fb_configs.listener');
var AMQAdapter = require('../adapters/amq.adapter');
var uniqid = require('uniqid');
const connectionOptions = config.get('activemq.connectionOptions');
class FBConfigs {
constructor() {
console.log(config.get('environments'));
this.listener = FBConfigsListener;
this.configs = {};
this.unique_keys = ['id'];
this.update_topic = '/topic/fusebuilder.update.config.';
console.log(FBConfigsListener);
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
}
add(key, config) {
if (!(key in this.configs)) {
this.configs[key] = new ConfigModel(this.unique_keys);
}
this.configs[key].add(config);
}
get(key) {
let configs_json = {};
if (key) {
configs_json[key] = JSON.parse(this.configs[key].toString());
} else {
for (let key in this.configs) {
configs_json[key] = JSON.parse(this.configs[key].toString());
}
}
return configs_json;
}
updateByID(key, id, input_config) {
let configs = this.configs[key].get();
for (let config of configs) {
if (input_config.id === config.id) {
this.update(key, _.merge(config, input_config));
}
}
}
//Send update to config topic
update(key, config) {
let topic = this.update_topic + key;
var update_object = {};
if (Array.isArray(config)) {
update_object[key] = [...config];
} else {
update_object[key] = [config];
}
console.log(`Sending ${key} update:${JSON.stringify(update_object)}`);
this.amq_client.sendMessage(topic, update_object);
}
copyTo(key, id, env) {
let selected_env = config.get('environments.' + env);
// let tmp_amq_client = new AMQAdapter(selected_env.host, selected_env.port);
let selected_config = this.configs[key].getByID(id);
console.log(this);
if (key === 'fuses') {
console.log('In FBConfig Copy to for fuses');
const get_fuse_topic = '/topic/fusebuilder.get_fuse';
const tran_id = uniqid();
const sendObj = { fuseName: id, tran_id };
this.amq_client.sendMessage(get_fuse_topic, sendObj);
var startTime = process.hrtime()[0];
var timeout = false;
while (!this.listener.get_copy_fuse_data(tran_id)) {
console.log('Waiting for config');
sleep(100);
if (process.hrtime()[0] - startTime > 3) {
console.log('Timed out');
timeout = true;
break;
}
}
console.log(JSON.stringify(FBConfigsListener.get_copy_fuse_data(tran_id)));
} else {
tmp_amq_client.sendMessage(this.update_topic, selected_config);
}
console.log(`Copy ${key} id:${id} to ${env}`);
}
}
module.exports = new FBConfigs();
amq_listener/fb_configs.listener.js
const config = require('config');
var AMQAdapter = require('../adapters/amq.adapter');
var FBConfigs = require('../models/FBConfigs');
**removed for work**
class FBConfigsListener {
constructor() {
this.instance;
this.copy_fuse_data = {};
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
//Subscribe to configs
this.amq_client.subscribe(config_subscribe_topic, this.config_topic_callback.bind(this));
//Request Tables
this.amq_client.sendMessage(config_request_topic, { tables: config_tables });
//Subscribe to Copy Fuse topic
this.amq_client.subscribe(subscribe_fuse_copy_topic, this.copy_fuse_callback.bind(this));
}
config_topic_callback(err, message) {
let dest = this.amq_client.getDestination(message);
let key = this.get_key_from_topic(dest);
this.amq_client.readMessage(message, body => {
let configs = JSON.parse(body);
if (key in configs) {
for (let config of configs[key]) {
FBConfigs.add(key, config);
}
}
});
}
copy_fuse_callback(err, message) {
this.amq_client.readMessage(message, body => {
const config = JSON.parse(body);
this.copy_fuse_data[config.tran_id] = config;
});
}
//Get Key from the topic and convert using key map if needed
get_key_from_topic(topic) {
let key = topic.split('.')[topic.split('.').length - 1];
key = key in key_map ? key_map[key] : key;
return key;
}
get_copy_fuse_data(id) {
if (id in this.copy_fuse_data) {
return this.copy_fuse_data[id];
} else {
return false;
}
}
}
module.exports = new FBConfigsListener();
Error happens in FBConfigs. FBConfigsListener returns {} so all functions in there are undefined. Even if I do console.log(require('../amq_listeners/fb_configs.listener')) it prints {} But doing the same thing in server.js (with updated path) it prints the module.
Also tips on how to improve my coding style would be appreciated too.
Edit
So I found out that I have a circular dependency between these classes. How can this be fixed while allowing me to call one from the other.
I would suggest you to instantiate your dependencies firstly and store them in some object which you can pass then to your dependent classes. The structure can be
factories/services.js
/*
* Instantiates passed services and passes injector object to them
*/
module.exports = function createServices(injector, services) {
return Object.entries(services)
.reduce((aggregator, [name, serv]) => {
const name_ = camelCase(name);
aggregator.set(name_, new serv(injector));
return aggregator;
}, new Map());
};
lib/service.js
/**
* Base class for classes need any injections
*/
module.exports = class Service {
constructor(injector) {
this.injector = injector;
}
get dependencies() {
return this.injector.dependencies;
}
/*
* Background jobs can be ran here
*/
async startService() {}
/*
* Background jobs can be stopped here
*/
async stopService() {}
};
lib/injector.js
const Service = require('./service');
/*
* Contains all dependencies
*/
module.exports = class Injector {
constructor() {
this.services = new Map();
this._dependencies = {};
}
has(name) {
return this.services.has(name);
}
register(name, service) {
if (this.has(name)) {
throw new Error(`Service ${name} already exists`);
}
if (service instanceof Service === false) {
throw new Error('Argument #2 should be an instance of Service');
}
this.services.set(name, service);
this._dependencies[name] = service;
}
unregister(name) {
if (! this.has(name)) {
throw new Error(`Service ${name} not found`);
}
this.services.delete(name);
delete this._dependencies[name];
}
get dependencies() {
return { ...this._dependencies };
}
/*
* Starts all registered services
*/
async start() {
for (let service of this.services.values()) {
await service.startService();
}
}
/*
* Stops all registered services
*/
async stop() {
for (let service of this.services.values()) {
await service.stopService();
}
}
};
Then import, initialize and bind your services in the main file (don't forget to export just a class, not an object like you do it now).
server.js
const createServices = require('./factories/services.js');
const injector = require('./lib/injector');
const Injector = new injector();
const services = createServices(Injector, [require('./server/amq_listeners/fb_configs.listener'), require('./server/models/FBConfigs')]);
services.forEach((service, name) => {
Injector.register(name, service);
});
// Start services
Injector.start();
Inherit required classes to Service class and you will get an access to all dependencies there (don't forget to call super() from constructor). Like
models/FBConfigs.js
const Service = require('../lib/service');
class FBConfigs extends Service {
constructor(injector) {
super(injector);
const { FBConfigsListener } = this.dependencies;
...your code here
}
async startService() {
...run bg job or init some connection
}
async stopService() {
...stop bg job or close some connection
}
}
module.exports = FBConfigs;
Also you can pass some config object to createServices (I didn't include it here) with keys equal to service names and values containing config object and pass config to appropriate service.
It is caused by that circular dependency. You should avoid it or used very carefully.
In your case the fix is probably pretty simple, move the line var FBConfigs = require('../models/FBConfigs'); from listener at the end of the file as the last line (yes, even after the module.exports).
Edit: Actually it maybe is not enough as I checked the code more in detail. As you are not using Listener in FBConfig constructor, you can create method assignListener, remove this.listener from that constructor and call it later in server.js which will do the this.listener
Or the last solution, which is also "best practice". Do not export the instances. Export the classes only. Then in server.js create these instances after both are required.