JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q20201F
  • License MIT

All in one solution to gracefully shutdown your application through a simple delayed shutdown process. Optionally supports nodemon. (initialise, free, lock critical sections)

Package Exports

  • graceful-shutdown-manager

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (graceful-shutdown-manager) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

node-graceful-shutdown-manager

All in one solution to gracefully shutdown your application through a simple delayed shutdown process. Optionally supports nodemon. (initialise, free, lock critical sections)

Use for any node.js project which might be negatively impacted by immediate termination.

Locking is a mechanism which avoids beginning the shutdown process until a block of code has finished. Wrap important code such as file writing with lock() and unlock(). This will ensure no corruption occurs or resources are freed when they might be required. Ensure the code within the lock is synchronous. I recommend the fs-extra package for a drop-in fs promise replacement.

Template (example is below this template)

main.js

Program entry

async function main() {
  global.gsm = require("graceful-shutdown-manager").Manager.create();

  try {
    global.database = require("./Database").create();

    // Start
    global.gsm.events.on("init", async () => {
      await global.database.init();
    });

    // Main
    global.gsm.events.on("main", async () => {
      // Run code after everything has been initialised
      global.database.lock();
      // Write to a file or something mission critical. The program won't exit until it's unlocked. Make sure you await the result of a write.
      global.database.unlock();
    });

    // Cleanup
    global.gsm.events.on("free", async () => {
      await global.database.free();
    });

    await global.gsm.main(); // Calls init, and then main, free will only be called when invoked
  } catch (e) {
    // All program errors end up here, even during the initialisation phase
    console.error(e);

    // await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
    global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
  }
}

main();

Database.js

const Base = require('graceful-shutdown-manager').Base;

class Database extends Base {
  constructor() {
    super();

    this.events.on("init", async () => {
      await this.connect();
    });

    this.events.on("free", async () => {
      await this.close();
    });
  }

  connect() {
    return new Promise((resolve, reject)) => {
      // Connect and resolve, mysql uses callbacks
      resolve();
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      // End and resolve, mysql uses callbacks
      resolve();
    });
  }
}

module.exports = Database;

Example (mysql, express, locking)

main.js

async function main() {
  global.gsm = require("graceful-shutdown-manager").Manager.create();

  try {
    global.database = require("./Database").create();
    global.express = require("./Express").create();
    global.app = require("./App").create();

    // Start
    global.gsm.events.on("init", async () => {
      await global.database.init();
      await global.express.init();
      await global.app.init();
    });

    // Main
    global.gsm.events.on("main", async () => {
      // Run code after everything has been initialised
    });

    // Cleanup
    global.gsm.events.on("free", async () => {
      await global.express.free();
      await global.database.free();
      await global.app.free();
    });

    await global.gsm.main(); // Calls init, and then main, free will only be called when invoked
  } catch (e) {
    // All program errors end up here, even during the initialisation phase
    console.error(e);

    // await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
    global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
  }
}

main();

Database.js

const Base = require('graceful-shutdown-manager').Base;
const Mysql = require('mysql');

const connection = Mysql.createConnection({
  host     : 'localhost',
  user     : 'user',
  password : 'pass',
  database : 'db'
});

class Database extends Base {
  constructor() {
    super();

    this.events.on("init", async () => {
      await this.connect();
    });

    this.events.on("free", async () => {
      await this.close();
    });
  }

  connect() {
    return new Promise((resolve, reject)) => {
      connection.connect((err) => {
        if (err) {
          console.error('error connecting: ' + err.stack);
          reject(err);
          return;
        }

        console.log('connected as id ' + connection.threadId);
        resolve();
      });
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      connection.end((err) => {
        if (err) {
          console.error('error: ' + err.stack);
          reject(err);
          return;
        }

        resolve();
      });
    });
  }
}

module.exports = Database;

Express.js

const Base = require('graceful-shutdown-manager').Base;
const Base = require('..').Base;
const express = require('express');
const app = express();

class Express extends Base {
  constructor() {
    super();

    // Initialise
    this.events.on("init", async () => {
      await this.listen();
    });

    // Free
    this.events.on("free", async () => {
      await this.close();
    });
  }

  listen() {
    return new Promise((resolve, reject)) => {
      app.listen(3000, () => {
        console.log(`Listening at http://localhost:3000`);
        resolve();
      })
    });
  }

  close() {
    return new Promise((resolve, reject)) => {
      app.close(() => {
        console.log(`Stopped listening`);
        resolve();
      })
    });
  }
}

module.exports = Express;

App.js

/*
  This file demonstrated the locking mechanism, which
  stops a program exiting while it's doing something important.

  An example would be writing to a file, processing a request, etc.
*/

const Base = require("graceful-shutdown-manager").Base;
const fs = require("fs").promises;

class App extends Base {
  constructor() {
    super();

    // Initialise
    this.events.on("init", async () => {
      this.logs = [];

      // Store logs to an array (for file saving), by overwriting the function
      this.oldConsoleLog = console.log;
      console.log = (...args) => {
        this.logs.push(args.join(" "));
        this.oldConsoleLog(...args);
      };

      // Save every minute
      this.saveInterval = setInterval(() => {
        this.saveLog();
      }, 1 * 60 * 1000);
    });

    // Free
    this.events.on("free", async () => {
      clearInterval(this.saveInterval);
      await this.saveLog();

      console.log = this.oldConsoleLog;
    });
  }

  async saveLog() {
    // This will cause the manager to wait until this object is unlocked before exiting, saving you a corrupted file
    this.lock();
    await fs.writeFile("log.txt", JSON.stringify(this.logs, NULL, 2));
    this.unlock();
  }
}

module.exports = App;

Game id

Visit the workshop page, in the url bar it will contain an 'appid'

The below example shows '4920' as the id, which is for Natural Selection 2: https://steamcommunity.com/workshop/browse/?appid=4920

Install

  1. Install git and nodejs.
  2. Pase in cmd/terminal: git clone https://github.com/c-ridgway/node-workshop-downloader.git && cd node-workshop-downloader
  3. Add your steam API key and game id to config.json
  4. Open run.bat for Windows and run.sh for Linux.