A way to shutdown my IPFS node when nextjs recompiles the API? - node.js

Using NextJS, I have a Db.js file:
import { create } from 'ipfs'
let dbs = null
export async function createDbs() {
if(!dbs){
console.log('starting up IPFS node and databases...')
const node = await create({
preload: { enabled: false },
repo: './ipfs',
EXPERIMENTAL: { pubsub: true },
config: {
Bootstrap: [],
Addresses: { Swarm: [] }
}
})
dbs = { node }
console.log('Startup successful.')
}
return Promise.resolve(dbs)
}
So that the files in my api folder use it in their handlers. Example:
import { createDbs } from '../../lib/Db.js'
export default async function handler(req, res) {
return new Promise(async resolve => {
try {
const dbs = await createDbs()
...
} catch(e) {
res.status(500).json({ error: 'Unexpected error : ' + e })
} finally {
resolve()
}
})
}
Works fine, until I change a source file in the api folder (happens quite a lot during development), and NextJs auto-recompiles the api server.
Then the reference to the Ipfs node is GC'ed, and the node initialization code in 'Db.js' runs again when the Api handler is called.
Problem: when the node was first created, it locked the files using some standard Ipfs mechanism, and as the node was not gracefully stopped before NextJs auto-recompiled the Api, I get an exception when trying to create the Ipfs node l {"code":"ERR_LOCK_EXISTS","level":"error","name":"LockExistsError"}.
I can't find a place where to stop the node gracefully.
For example:
// shutdown calls 'node.stop()'
process.on('exit', () => shutdown(node, 'exit'))
does work when I exit the nextJs dev process, but not when it recompiles.
Where could I call node.stop() so that the lock is released when NextJs recompiles the Api ?

Related

Testing Angular Services with MSW causes timeout

EDIT: I should mention, that I only have problems during testing. When I run ng serve and use msw to serve the data everything works correctly.
I stumbled upon mswjs recently and wanted to use the mock service workers to test my frontend services without waiting on the backend team and avoid having to write mock-service classes. I setup everything according to the examples provided in the documentation.
At first I got the message that stating spec 'UserService should get list of users' has no expectations.
I researched this and added a done() function call at the end of my subscribe callback. After doing that, I get the following error:
Error: Timeout - Async function did not complete within 3000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 7609)
I already tried increasing the default_timout in Karma but even setting it to 30.000 did not change the result.
I also tried working around by using waitForAsync without any success. This way I get no error and the test succeeds but only because it still finds no expectations within the spec.
Most example I found online do not deal with mock service workers and instead resort to using mock-services and fakeasync which does not help in my case.
This is how my code looks like:
My Angular Service:
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = 'http://localhost:3000/api/users';
constructor(private http: HttpClient) { }
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
listUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
}
My Test Code:
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
worker.start();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
});
service = TestBed.inject(UserService);
});
afterAll(() => {
worker.stop();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get list of users', (done) => {
service.listUsers().subscribe((data) => {
expect(data.length).toBe(5);
done();
});
})
});
The Worker setup:
const handlers = [
rest.get('http://localhost:3000/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(users))
})
]
export const worker = setupWorker(...handlers)
I managed to solve my own problem by using firstValueFrom and waitForAsync.
I changed the code in my tests to the following:
it('should get list of users', waitForAsync(async () => {
const source$ = service.listUsers();
const result = await firstValueFrom(source$);
expect(result.length).toEqual(5);
}));

How to properly handle errors on subscriptions with Apollo Server?

I have an Express + Apollo Server backend. I enabled subscriptions on it using ws and graphql-ws. Everything is working fine.
Now, I would like to handle resolvers errors properly: hide backend details in production, change message based on error type, add a unique ID, etc. On regular mutations, I'm able to do so using the formatResponse function.
On subscriptions, I can't find where I could do it. All I need is a function called before sending data to the client where I have access to data and errors.
How can I do that?
Here's how the WS Server is created:
// Create Web Socket Server
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql'
});
const serverCleanup = graphqlWS.useServer(
{
schema: graphqlApp.schema,
context: async (ctx: any) => {
try {
// ...Some auth checking...
return context;
} catch (e) {
throw new ApolloAuthenticationError('you must be logged in');
}
}
},
wsServer
);
And an example of event sending:
import {PubSub} from 'graphql-subscriptions';
// ...
Subscription: {
tree: {
subscribe: withFilter(
() => pubsub.asyncIterator('some_id'),
(payload, variables) => {
const canReturn = true;
//...Some filtering logic...
return canReturn;
}
)
}
},

How to dynamically add CORS sites to Google Cloud App Engine Node API

I am new to API deployment.
I have a Node Express API that has CORS enabled in the root app.js, for the API and a socket.io implementation:
var app = express();
app.use(cors({
origin : ["http://localhost:8080", "http://localhost:8081"],
credentials: true
}))
and
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: ["http://localhost:8080", "http://localhost:8081"],
credentials: true,
methods: ["GET"]
}
});
I will set up a sales website that allows a customer to pay for a license to use the API with their site, i.e. https://www.customersite.com
My question is how can I dynamically add the customer's website (say after they submit a form from another site) to the CORS list? Ideally it would be via an API call. The only option which I can think of (that is not automated) is to manually maintain a global js file (i.e. config.js) with the cors list from within the Google platform using the file explorer / editor, and to iterate over it as an array similar to process.env.customerList. This will not work for me as I need to have this step happen automatically.
Any and all suggestions are appreciated.
Solution: Use a process manager like pm2 to 'reload' the API gracefully with close to no downtime.
PM2 reloads can be triggered programmatically. I made a PUT endpoint for modifying CORS list /cors/modify that sent a programmatic pm2 message when a successful modification was done.
Note: on Windows OS must use programmatic messaging:
pm2.list(function(err, list) {
pm2.sendDataToProcessId(list[0].pm2_env.pm_id,
{
type : 'process:msg',
data : {
msg : 'shutdown'
},
topic: true
},
function(err, res) {
console.log(err);
pm2.disconnect(); // Disconnects from PM2
}
);
if (err) {
console.log(err);
pm2.disconnect(); // Disconnects from PM2
}
});
which can then be caught with
process.on('message', async function(msg) {
if (msg == "shutdown" || msg.data.msg == 'shutdown') {
console.log("Disconnecting from DB...");
mongoose.disconnect((e => {
if (e) {
process.exit(1)
} else {
console.log("Mongoose connection removed");
httpServer.close((err) => {
if (err) {
console.error(err)
process.exit(1)
}
process.exit(0);
})
}
}));
}
});

typedi + fastify - initialize service asynchronously

I'm working on a nodejs fastify based app service and using typedi for dependency injection.
Some services I use need async initialization.
MyService.ts
export class MyService {
constructor() {
}
public async init() {
....
}
}
I am trying to initialize the service at application startup so that any service doing Container.get(MyService) gets this initialized instance of MyService
app.ts
export default async function(fastify: FastifyInstance, opts: Options, next: Function) {
// This loads everything under routes
fastify.register(autoload, {
dir: path.join(__dirname, "routes"),
options: opts,
includeTypeScript: true,
});
await Container.get(MyService);
next();
}
server.ts
import app from "./app";
const server = fastify({
logger: logger
});
server.register(oas, docs);
server.register(app);
server.ready(err => {
if (err) throw err;
server.oas();
});
server.listen(config.port, (err) => {
if (err) {
server.log.error(err);
process.exit(1);
}
server.log.info(`server listening on ${server.server.address()}`);
});
export default server;
My attempt to initialize MyService is failing.
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"
Any hints to what I'm doing wrong? I'm new to nodejs and would really appreciate sample code that is correct for this scenario.
Edit
I tried import
Container.import([CmkeService]);
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"

Load custom configuration runtime

I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};

Resources