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

@honout/functionality

v0.0.7

Published

#### Creating a custom functionality

Downloads

5

Readme

Honout Functionality

Creating a custom functionality

Start by importing the IFunctionality interface from @honout/system

import { IFunctionality } from '@honout/system';

The interface has 3 generic parameters.

  • ExtendableLogic
    • Internal logic interfaces that may be extended
  • LogicIdentifiers
    • Identifiers to the above.
  • Configuration
    • Configuration of this functionality
IFunctionality<ExtendableLogic, LogicIdentifiers, Configuration>;

Next create a class that implements this interface and mark it as injectable. As an example, let´s create a functionality for event handling. First declare an interface for your event handlers.

interface IEventHandler {
    // called when an event is received
    onEvent(message: string): void;
    // the type of event this handler should listen to
    getType(): string;
}

Next, create your publicly extendable logics. In this case, we only want to be able to add event handlers so this interface will be the only logic extension.

type ExtendableLogic = IEventHandler;

Next define your public logic identifiers.

enum MyFunctionalityIdentifiers {
    EVENT_HANDLER = 'IEventHandler',
}

If your functionality may be configured, declare an interface for the configuration also

interface IConfiguration {
    foo: string;
}

Finally we want to create a separate interface that will be used to consume all event handlers and to start actually listening to events

interface IEventManager {
    start(): void;
}

Now that all of our types and interfaces have been declared, let´s piece the logic together. Let´s create a class that implements the IFunctionality interface.

@injectable()
class EventFunctionality
    implements
        IFunctionality<
            ExtendableLogic,
            MyFunctionalityIdentifiers,
            IConfiguration
        >
{
    // Receives any extensions, it is up to each functionality to handle this.
    onLogicExtensions(
        extensions: ILogicExtension<
            ExtendableLogic,
            MyFunctionalityIdentifiers
        >[],
        container: Container
    ): void {}

    // Called with the configuration given to this functionality
    onConfigure(configuration: IConfiguration): void {}

    // Called when it is time for this functionality to bind its´ internal dependencies.
    bindInternals(container: Container): void {}

    // This will be called when all of the above has been resolved
    async start(container: Container): Promise<void> {}
}

Lets start by implementing some logic in these methods. Lets begin with onLogicExtensions

onLogicExtensions(
        extensions: ILogicExtension<ExtendableLogic, MyFunctionalityIdentifiers, IConfiguration>[],
        container: Container
    ): void {
        // we loop through each possible extension even though we only have one in this case, the IEventHandler
        extensions.forEach((extension) => {
            // We use the identifier to verify the type of extension
            if (extension.identifier === MyFunctionalityIdentifiers.EVENT_HANDLER) {
                // And finally we go thorugh all the IEventHandlers and bind them to the di container
                extension.definitions.forEach((definition) => {
                    container
                        .bind(extension.identifier)
                        .to(definition)
                        .inSingletonScope()
                })
            }
        })
    }

Before we implement the bindInternals method, we need to implement the IEventManager since bindInternals will use this dependency. So let´s to that.

@injectable()
class EventManager implements IEventManager {
    constructor(
        // Inject all event handlers
        @multiInject(MyFunctionalityIdentifiers.EVENT_HANDLER)
        // The number of event handlers could be 0, meaning we declare this as optional
        @optional()
        private eventHandlers: IEventHandler[]
    ) {}

    start() {
        // Go through each injected event handler
        this.eventHandlers.forEach((eventHandler) => {
            // Bind the handler to the emitter
            EventEmitter.on(eventHandler.getType(), eventHandler.onEvent);
        });
    }
}

Now we can finally implement the bindInternals method

bindInternals(container: Container): void {
        container.bind<IEventManager>('IEventManager').to(EventManager).inSingletonScope()
    }

And lastly, let´s implement the start method

async start(container: Container): Promise<void> {
    container.get<IEventManager>('IEventManager').start()
}

Let´s use this functionality in an application now. First we need to create an event handler

@injectable()
class MyEventHandler implements IEventHandler {
    onEvent(message: string): void {
        console.log('Message received', message);
    }

    getType(): string {
        return 'message';
    }
}

In order to add this to an application, there is a decorator available from the @honout/system that we can use

import { WithFunctionality } from '@honout/system';

@injectable()
@WithFunctionality({
    functionality: EventFunctionality,
    extend: [
        {
            identifier: MyFunctionalityIdentifiers.EVENT_HANDLER,
            definitions: [MyEventHandler],
        },
    ],
})
class MyApp implements IApplication {}

And that´s it. When the System starts, this functionality will automatically run and starts listening to 'message' events.

existing @honout functionalities