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

yokohama

v1.4.3

Published

Opinionated? dependency resolution library

Downloads

7

Readme

yokohama Build Status NPM Version License

Yokohama is a dependency resolution library inspired by di.js and Relay. It uses ES7 decorators for annotating dependencies and supports injecting with React.

Usage

Yokohama is a stand-alone library and doesn't use global singleton modules for tracking dependency instances. Instead, dependency constructors declare their dependencies by referencing the constructors of other dependencies, which can be crawled like a tree to access the constructors for every dependency in the system. This avoids the need for shared injector instances as well as avoiding the use of strings for identifying dependencies.

To use yokohama, you can use @dependencies and @inject to start plugging together dependencies in your system. Dependencies are instantiated, but they can return any non-null values. Yokohama is built around asynchronous intialization, any dependency can return a promise and it will be factored in and properly parallelized.

@dependencies

@dependencies is used to list all dependencies of the target. The shape of the arguments passed to the call become the same shape when passed to the constructor.

@dependencies({foo: Foo}, Bar, [Baz, Quux])
class Something {
    // The tokens map 1:1 with the arguments passed to the constructor
    constructor({foo}, bar, [baz, quux]) {
        // ...
    }
}

@inject

@inject is similar to @dependencies but is used for creating a wrapper for React components that inject dependencies by passing them through props. @inject assumes you have provided the dependency cache in the context. All props passed to this wrapper component are forwarded to the original component.

@inject({
    foo: Foo,
    baz: Baz
})
class SomeComponent extends React.Component {
    static propTypes = {
        foo: React.PropTypes.instanceOf(Foo).isRequired,
        baz: React.PropTypes.instanceOf(Bar).isRequired
    };

    // ...
}

@provide

One of the core features of yokohama is the ability to provide mock implementations to customize the dependency tree under certain contexts. @provide is used to annotate that a specific constructor is a "mock" that will replace the constructor of whatever token is passed to @provide. The mock constructor can then inject it's own dependencies.

This is really useful for treating dependency tokens as interfaces, where the implementation is specified in the entrypoint instead of being hard-coded into whatever module it is declared. This allows for inverting the dependency tree and writing clean, isolated and maintainable modules.

class CurrentUser {}

@provide(CurrentUser)
class ClientCurrentUser {
    constructor() {
        return fetch('/users/me').then(res => res.json());
    }
}

@provide(CurrentUser)
@dependencies(http.IncomingRequest)
class ServerCurrentUser {
    constructor(req) {
        return req.user;
    }
}

// in the server entry point
const injector = new Injector([ServerCurrentUser]);

// in the client entry point
const injector = new Injector([ClientCurrentUser]);

new Injector([mocks])

The injector instance is where instances of depenencies get cached. It's also where you specify what mocks to use. If you use the same injector instance more than once, it will re-use cached instances of dependencies from the previous load. You can leverage this by creating a shared injector instance and creating child injectors where temporary instances will be cached. That means the temporary instances are discarded when the child is discarded, but the permanent/shared instances will stay cached in the parent.

Injector#createChild([mocks])

This, along with the mock system itself, is where yokohama is very powerful. Only dependencies that depend on the child's mocks will be cached in the child injector. This means any other dependencies will be cached in the parent injector instead.

For example, you can create a shared injector for initializing HTTP controllers, but create a child injector with mocks for the HTTP request and response objects. If the request and response dependencies are mocked in the child injector, then anything that depends on those will be cached in the child injector, and will only live for the duration of the request. So resources that are initialized per-request would stay in the child, but shared resources like the server configuration would stay cached in the parent and shared across all requests.

You could go even further by caching connection pools in the parent injector, and having a mock for the "database connection" in the children that would query from the pool on every request. This would also ensure that connections are established on an as-needed basis, instead of wastefully connecting for every request made. There's even more you could with mocks and child injectors that would allow you control the order of instantiation to further optimize and avoid unecessary connections or queries.

Example

class User {
    constructor(attrs) {
        const {id, name, email} = attrs;

        this.id = id;
        this.name = name;
        this.email = email;
    }
}

class CurrentUser {}

@provide(CurrentUser)
class CurrentUserResolver {
    constructor() {
        return fetch('/users/me')
            .then(res => res.json())
            .then(body => new User(body));
    }
}

@dependencies(Dispatcher, CurrentUser)
class CurrentUserStore extends Store {
    constructor(dispatcher, currentUser) {
        // ...
    }
}

@inject({
    dispatcher: Dispatcher,
    currentUser: CurrentUser
})
class HelloComponent extends React.Component {
    render() {
        const {currentUser} = this.props;

        return (
            <div>Hello {currentUser.name}!</div>
        );
    }
}

Documentation

Detailed API documentation can be found in the JSDoc annotations and TypeScript definitions in src/. Otherwise see the Usage section of this README

Pending features

See all open feature issues