npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ineedthis

v0.0.27

Published

Stateful dependency management

Downloads

243

Readme

ineedthis

npm version

Implements an opinionated, simple management layer for stateful dependencies, directly akin to component, mount, yoyo etc. It provides tooling to describe services and use those descriptions to automatically start and stop collections of those services: eg, an HTTP server that in turn uses a database connection, a cache connection, a configuration service, etc.

It is designed to be flexible enough to handle all sorts of services (stateless, constant state (eg static configuration), network connections, thread pools, etc.). It handles describing, starting, and linking together those services while not imposing any structure on the shape or further use of those services.

The API is designed with an opinionated default usage, but also with the flexibility to support any custom semantics.

  • Promises are used pervasively to handle (potentially) asynchronus services.
  • Since we don't have clojure's namespaced keywords, instead we just use strings that (by convention, any convention could be use) include the fully qualified package name, mimicing require/import paths.

It comes with two bin utils for eaily running programs built with this library:

  • ineedthis-run file1.js file2.js: the listed files should be modules each with an ineedthis service as its default export. This command will then automatically start all the services and their dependencies, and handles graceful shutdown on Ctrl-C (SIGINT).
  • ineedthis-debug file1.js file2.js behaves the same as ineedthis-run, but automatically watches all loaded sources files and will hot reload the changed code + gracefully restart the affected services.

Finally, ineedthis promotes code reuse and makes it easier to encapsulate best practices for common libraries:

  • @ineedthis/express makes it easy to start an express server with graceful startup, shutdown, and hot reloading.

Status: Alpha; Working with test suite; Used in prod!

TODO:

  • Decide which of the many "extra" features of component/mount we want
  • Write more wrappers around common node libraries

Example

// Static config
var A = createService(
  'package/Configuration', {
    start: (config = process.env) => () => config
  }
);

// Connect to a remote server, eg a DB, based on the configuration
// service
var B = createService(
  'package/ConnectionService', {
    dependencies: ['package/Configuration'],
    start: () => ({'package/Configuration': config}) => {
      return db.connect(config.url, config.key);
    },
    stop: (dbConnection) => dbConnection.close()
  }
);

var C = createService(
  'package/OverlayService', {
    dependencies: [
      'package/Configuration',
      'package/ConnectionService'
    ],
    start: () => ({
      'package/Configuration': config,
      'package/ConnectionService': underlyingConn
    }) => {
      return {
        _config: config,
        doSomething: function (msg) {
          console.log('Hello, ' + msg + '!');
        }
      };
    }
  }
);

var App = createService(
  'package/AppServer', {
    dependencies: [
      'package/Configuration',
      'package/OverlayService'
    ],
    start: () => ({
      'package/Configuration': config,
      'package/OverlayService': overlay
    }) => {
      const app = express();

      app.use(coolMiddleware());

      // 'global' request DI; possible example:
      app.use((req, res, next) => {
        req.config = config;
        req.ourFancyService = overlay;
        next();
      });

      return new Promise((reject, resolve) => {
        let server;
        app.on('error', reject);
        server = app.listen(config.httpPort, () => resolve(server));
      });
    },
    stop: server => new Promise(resolve => server.close(resolve))
  }
);

// If the service names all follow the intended nameing pattern, a
// simple start() suffices.
start(App)
  // Cleanly shutdown after 1 second:
  .then(system => setTimeout(() => stop(system), 1000))
  // uhoh
  .catch(err => { /* */ });

// Starts A then B then C and finally App; then 1 second later shuts
// them down in reverse order (more complex dependency graphs will be
// handled via topological sort with maximal concurrency at every
// step). Cyclic dependencies trigger an error.

// Starting multiple services that can dynamically share common
// dependencies
start([App, App2]);

// Manually specifying some (or all) dependency services:
const FakeB = MockB();
start(App, {
  'package/Configuration': A({key: 'ABC'}),
  'package/ConnectionService': FakeB,
}).catch(err => { /* */ });

Example project

https://github.com/tf2stadium/qgs is a WIP project; but it demonstrates a frontend+backend website implemented with this library. Most stateful aspects of both the frontend and backend are split into individual ineedthis services and then composed to produce the final system.

For example, the frontend uses a helper to delay the initial React render and inject the launched services as props.

The backend has many stateful components such as a database connection, a postgraphql server, and a job queue.

In development, this runs all the services with hot reloading and shared DB connections, etc:

ineedthis-debug -r localenv -r babel-polyfill ./dist/systems/server ./dist/systems/jobqueue ./dist/systems/monitor

In production, we can easily split the services into separate deployments:

# Run just the web server:
ineedthis-run -r localenv -r babel-polyfill ./dist/systems/server

# Run just the backend monitoring processes:
ineedthis-run -r localenv -r babel-polyfill ./dist/systems/jobqueue ./dist/systems/monitor

Runners

Often JS apps have a slightly awkward need for a separate startup script to actually launch the desired services. ineedthis provides two scripts you can use to do this automatically for you:

  • ineedthis-run starts services that are the default exports of all files listed on the command lines.
  • ineedthis-debug is the same as -run, except it also automatically watches for changes in any files used by the started systems. When a change is detected, those files are hot-reloaded and the affected services are restarted to pickup the new code. This skips having to restart db connections, etc. and thus can be much faster than a full restart. For example, even large monolithic webservers with DB and other stateful connections can typically hot reload route file changes in a fraction of a second.
    • When using a compiler like babel or TypeScript; I highly recommend having an incremental compiler running in the background and running the built files directly instead of using babel-register, babel-node, or their TS equivalents. This is generally more stable because syntax errors pop up in the compiler process instead of inside the running ineedthis-debug process.

API

Types: (ala TypeScript)

type ServiceName = String;
type AliasedServiceName = {type: String, as: String};
type Service = {(): () => any, ...any}
type System = {[ServiceName]: any}

createService

(types are rough/not final yet)

createService<T, StartFn: (...any) => (System) => Promise<T>>( ServiceName: String, { dependencies?: [ServiceName | AliasedServiceName], start: StartFn, stop?: T => Promise<()> } ): StartFn

Creates a service, registering it under the given ServiceName.

  • dependencies can be specified, and they will be linked in before this service gets started; a map of them being passed to the second call of start. AliasedServiceNames allow asking for multiple services of a specific type (TODO: example).
  • start must be specified to initialize the server; curried so as to be called in two steps:
    1. Arbitrary initialization step: called internally or explicitly by the user to configure an instance of this service
    2. Actual start/linking step: the initialized dependencies are passed in, to actually startup this service
  • stop given a running instance, stop it

start

start(targets: (Service | [Service]), dependencies: System): Promise<System>

Starts the specified service(s), resolving all their dependencies. Returns a system--a map of all the initialized services.a

partial restarting

See stopPartial and startPartial for partially reloading only certain bits of a system without having to restart every individual service.

Development

npm run test/clean/build work as expected.

npm run dev gets a full stack of builds watching + test cases running on change.

To filter tests, run with MOCHA_OPTS set to, eg, '-g somepattern'.

License

Released under the MIT License. See the LICENSE file for full text

Copyright © 2017 George Pittarelli