@leverage/core
TypeScript icon, indicating that this package has built-in type declarations

4.1.4 • Public • Published

Leverage Logo

What is Leverage?

👩‍💻 A small, fast, flexible system to orchestrate your next application!

With Leverage you can easily create:

Install

# Using NPM
npm install @leverage/core

# Using Yarn
yarn add @leverage/core

Examples

Just getting started with Leverage and want to learn from a real-world example? Take a look at these projects!

Documentation

Table Of Contents

Units

Leverage's job is to help wire together your application, installing things only once all dependencies are satisfied. To accomplish this, Leverage standardizes the "pieces" of an application into three different kinds:

  • Plugin
  • Component
  • Service

Each unit contains a configuration object that specifies two properties. First, config.is tells Leverage what kind of unit it is and can be either "plugin", "component", or "service". Second, config.type tells Leverage what type this unit is for. The config.type value can be any string value and serves as a "name" for a plugin and set of components.

Plugins

A Plugin is a unit that has an install() method which is passed any added components of the same type as the plugin. For example, if you create a plugin where config.type is "http", any components that also have config.type set to "http" will be passed to your plugin's install() method.

Here is an example with a plugin that simply logs out the components it installs:

// index.js
import { add } from "@leverage/core";
import plugin from "./plugin";
import component from "./component";

add(plugin, component);
// plugin.js
import { useConfig } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "plugin",
        type: "example",
    });
};

export const install = (component) => {
    console.log("installing component: ", component);
    // => { init: Function, myValue: 42 }
};
// component.js
import { useConfig } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "component",
        type: "example",
    });
};

export const myValue = 42;

Components

A Component is a unit that is passed to a Plugin of the same type. Components serve as ways to configure the systems that Plugins create. For example, a component with the type "http" may be used to configure a route for the HTTP server that the HTTP plugin manages. Here's an example of an HTTP component from @leverage/plugin-http:

// index.js
import { add } from "@leverage/core";
import { http } from "@leverage/plugin-http";
import component from "./component";

add(http, component);
// component.js
import { useHTTP } from "@leverage/plugin-http";

export const init = () => {
    useHTTP({
        route: "/:name",
        method: "get",
    });
};

// The HTTP plugin calls `component.http` when a request comes in.
export const http = (req, res) => {
    res.write(`Hello, ${req.params.name}!`);
    res.end();
};

Services

A Service is a unit that has access to the same hooks and lifecycle that other units do, but is not directly connected to a Plugin or Component. Services are great for abstracting databases, creating state stores, or other things that don't fit into the Plugin/Component model. Here's an example of how you might create a database service:

// index.js
import { add } from "@leverage/core";
import { http } from "@leverage/plugin-http";
import component from "./component";
import service from "./service";

add(http, component, service);
// service.js
import db from "my-db-library";
import { useConfig, useKeyRef, useInstallEffect } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "service",
        type: "database",
    });

    const connectionRef = useKeyRef("connection");

    useInstallEffect(() => {
        db.connect((connection) => {
            connectionRef.current = connection;
        });
    });
};

export const findUser = (name) => {
    const connection = useKeyRef("connection");

    // Database isn't ready yet!
    if (!connection) return;

    return connection.find("user", { name });
};
// component.js
import { useDependencies, useService } from "@leverage/core";
import { useHTTP } from "@leverage/plugin-http";

export const init = () => {
    useHTTP({
        route: "/:name",
        method: "get",
    });

    useDependencies({
        service: "database",
    });
};

// The HTTP plugin calls `component.http` when a request comes in.
export const http = (req, res) => {
    const db = useService("database");

    const user = db.findUser(req.params.name);

    res.json(user);
    res.end();
};

Lifecycle

A Unit has two different parts to its lifecycle: uninitialized and installed.

Uninitialized

When a Unit is added to Leverage, it starts as uninitialized. Leverage will immediately run the Unit's init function. In the uninitialized phase, a Unit is limited in what hooks it has access to. During this phase, a Unit can use the following hooks:

  • useConfig
  • useDependencies
  • useEvent
  • useHooks
  • useInstallEffect
  • useIs
  • useKeyRef
  • useType
  • useUnit

In order to successfully initialize, a Unit's init function must configure the config.is and config.type properties using useConfig, useIs, or useType.

Installed

Once all dependencies for a Unit are satisfied, a unit will be installed by Leverage. During this phase, any install effects created with useInstallEffect are run. Future useInstallEffect calls will fire immediately. Additionally, plugins are passed components of the same type. During this phase, a Unit can use the following hooks:

  • useConfig
  • useDependencies
  • useEffect
  • useEvent
  • useHooks
  • useInstallEffect
  • useIs
  • useKeyRef
  • usePlugin
  • useRef
  • useService
  • useState
  • useType
  • useUnit

However, an install effect created with useInstallEffect is limited to the following hooks:

  • useConfig
  • useDependencies
  • useEvent
  • useHooks
  • useIs
  • useKeyRef
  • usePlugin
  • useService
  • useType
  • useUnit

Hooks

Hooks are functions that can be used inside of a Unit's methods to interact with Leverage. Common examples of use cases are to:

  • Configure a Unit
  • Run a function after a Unit has been installed
  • Manage Unit state
  • Get an installed Plugin or Service

A Unit's exposed methods are automatically wrapped to support hooks. Any functions that are not exported will not be wrapped with hook support.

useConfig

The useConfig hook can be used in two different ways. It can be used inside of an init function to set or get the configuration. After a unit is initialized, useConfig can be used to get the current configuration.

import { useConfig, useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useConfig` **sets** the configuration to...
     *
     *  {
     *      "is": "component",
     *      "type": "http"
     *  }
     *
     * Then this new configuration is returned and stored in the
     *  `config` variable.
     */
    const config = useConfig({
        is: "component",
        type: "http",
    });

    /*
     * Here, `useConfig` updates the configuration to...
     *
     *  {
     *      "is": "component",
     *      "type": "http",
     *      "http": {
     *          "route": "/",
     *          "method": "get"
     *      }
     *  }
     *
     * Then this new configuration is returned and stored in the
     *  `config` variable.
     */
    const newConfig = useConfig({
        http: {
            route: "/",
            method: "get",
        },
    });

    /*
     * Here, `useConfig` **gets** the configuration and stores it in the
     *  `currentConfig` variable.
     */
    const currentConfig = useConfig();

    useInstallEffect(() => {
        /*
         * Here, `useConfig` **gets** the configuration and stores it in
         *  the `finalConfig` variable.
         */
        const finalConfig = useConfig();
    });
};

export const http = () => {
    /*
     * Here, `useConfig` **gets** the configuration and stores it in the
     *  `config` variable.
     */
    const config = useConfig();
};

useDependencies

The useDependencies hook can be used in two different ways. It can be used inside of an init function to set or get the dependencies. After a unit is initialized, useDependencies can be used to get the current dependencies.

import { useDependencies, useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useDependencies` **sets** the configuration to...
     *
     *  {
     *      "plugins": ["http"],
     *      "services": ["db"]
     *  }
     *
     * Then this new dependency configuration is returned and stored in
     *  the `dependencies` variable.
     */
    const dependencies = useDependencies({
        plugins: ["http"],
        services: ["db"],
    });

    /*
     * Here, `useDependencies` updates the configuration to...
     *
     *  {
     *      "plugins": ["http", "websocket"],
     *      "services": ["db"]
     *  }
     *
     * Then this new dependency configuration is returned and stored in
     *  the `dependencies` variable.
     */
    const newDependencies = useDependencies({
        plugins: ["websocket"],
    });

    /*
     * Here, `useDependencies` **gets** the dependencies and stores it in
     *  the `currentDependencies` variable.
     */
    const currentDependencies = useDependencies();

    useInstallEffect(() => {
        /*
         * Here, `useDependencies` **gets** the dependencies and stores it
         *  in the `finalDependencies` variable.
         */
        const finalDependencies = useDependencies();
    });
};

export const callback = () => {
    /*
     * Here, `useDependencies` **gets** the dependencies and stores it in the
     *  `dependencies` variable.
     */
    const dependencies = useDependencies();
};

useEffect

The useEffect hook can be used once a unit has been installed to run side effects when the effect's dependencies change. The useEffect hook also supports a cleanup function which can be returned from the effect callback.

import { useEffect } from "@leverage/core";

export const init = () => {
    /* ... */
};

export const callback = () => {
    /*
     * Here, `useEffect` will run on every call.
     */
    useEffect(() => {
        console.log("Hello, World!");
    });

    /*
     * Here, `useEffect` will run exactly once for the life of this unit.
     */
    useEffect(() => {
        console.log("Hello, World!");
    }, []);

    /*
     * Here, `useEffect` will run whenever `myValue` changes.
     */
    const myValue = Math.random();
    useEffect(() => {
        console.log("Hello, World!");
    }, [myValue]);

    /*
     * Here, `useEffect` will run once. When this unit is uninstalled or removed,
     *  the cleanup callback will be executed.
     */
    useEffect(() => {
        console.log("Hello, World!");

        return () => {
            console.log("Hello, Cleanup!");
        };
    }, []);
};

useHooks

The useHooks hook can be used to wrap functions in order to use hook calls inside of them. This is most useful for plugins in order to allow for asynchronous actions that may lack context otherwise.

import { useConfig, useHooks } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "plugin",
        type: "example",
    });

    /*
     * Calling `useHooks` gives us a `withHooks` helper that
     *  is bound to the current Unit. In this case, it is the
     *  example plugin.
     */
    const withHooks = useHooks();

    /*
     * Here, we wrap a function using `withHooks`.
     */
    const handleAsyncAction = withHooks(() => {
        /*
         * Hooks can safely be used within this function.
         */
        const config = useConfig();
        // => { is: "plugin", type: "example" }
    });

    doSomethingAsync().then(handleAsyncAction);
};

useInstallEffect

The useInstallEffect hook can be used during initialization to run side effects once the Unit is installed. The useInstallEffect hook also supports a cleanup function which can be returned from the effect callback.

This hook is particularly useful for one-time configuration, expensive setup processes, or interacting with other installed Units.

import { useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useInstallEffect` runs once this unit is installed.
     *  When this unit is uninstalled or removed, the cleanup callback
     *  will be executed.
     */
    useInstallEffect(() => {
        console.log("Hello, World!");

        return () => {
            console.log("Hello, Cleanup!");
        };
    });
};

useIs

The useIs hook can be used in two different ways. It can be used inside of an init function to set or get the configuration's is value. After a unit is initialized, useIs can be used to get the current configuration's is value.

import { useIs, useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useIs` **sets** the configuration to...
     *
     *  {
     *      "is": "component",
     *  }
     *
     * Then this new configuration's `is` value is returned and stored in the
     *  `is` variable.
     */
    const is = useIs("component");

    /*
     * Here, `useIs` **gets** the configuration's `is` value and stores it in the
     *  `currentIs` variable.
     */
    const currentIs = useIs();

    useInstallEffect(() => {
        /*
         * Here, `useIs` **gets** the configuration's `is` value and stores it in
         *  the `finalIs` variable.
         */
        const finalIs = useIs();
    });
};

export const callback = () => {
    /*
     * Here, `useIs` **gets** the configuration's `is` and stores it in the
     *  `is` variable.
     */
    const is = useIs();
};

useKeyRef

The useKeyRef hook can be used to create or fetch an existing Ref for storing persistent data. This hook is particularly useful because it can be used during and after initialization.

import { useKeyRef, useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useKeyRef` creates a new ref using the key "my-key" and an initial
     *  value of 42. The returned Ref is an object that looks like:
     *
     *  {
     *      current: 42
     *  }
     */
    const ref = useKeyRef("my-key", 42);

    /*
     * Here, `useKeyRef` **gets** the Ref for "my-key". This is the same object as
     *  the variable `ref` holds.
     *
     *  ref === sameRef
     *  //=> true
     */
    const sameRef = useKeyRef("my-key");

    useInstallEffect(() => {
        /*
         * Here, `useKeyRef` **gets** the Ref for "my-key".
         */
        const stillSameRef = useKeyRef("my-key");
    });
};

export const callback = () => {
    /*
     * Here, `useKeyRef` **gets** the Ref for "my-key".
     */
    const ref = useKeyRef("my-key");
};

usePlugin

The usePlugin hook can be used to get an installed Plugin. This hook can only be used after initialization. In order to get a Plugin, the Plugin's type should be added to the Unit's dependencies.

import { usePlugin, useInstallEffect } from "@leverage/core";

export const init = () => {
    useDependencies({
        plugins: ["http"],
    });

    useInstallEffect(() => {
        /*
         * Here, `usePlugin` gets the "http" plugin.
         */
        const plugin = usePlugin("http");
    });
};

export const callback = () => {
    /*
     * Here, `usePlugin` gets the "http" plugin.
     */
    const plugin = usePlugin("http");
};

useRef

The useRef hook can be used once a unit has been installed to create a Ref that can store persistent data.

import { useRef, useEffect } from "@leverage/core";

export const init = () => {
    /* ... */
};

export const callback = () => {
    /*
     * Here, `useRef` creates or gets an existing Ref, using an initial value of 42.*  *  The returned Ref is an object that looks like:
     *
     *  {
     *      current: 42
     *  }
     */
    const ref = useRef(42);
};

useService

The useService hook can be used to get an installed Service. This hook can only be used after initialization. In order to get a Service, the Service's type should be added to the Unit's dependencies.

import { useService, useInstallEffect } from "@leverage/core";

export const init = () => {
    useDependencies({
        services: ["db"],
    });

    useInstallEffect(() => {
        /*
         * Here, `useService` gets the "db" plugin.
         */
        const service = useService("db");
    });
};

export const callback = () => {
    /*
     * Here, `useService` gets the "http" plugin.
     */
    const service = useService("db");
};

useEmitter

The useEmitter hook can be used to get the event emitter object.

import { useEmitter } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "plugin",
        type: "example",
    });

    /*
     * Here, `useEmitter` will get the emitter object.
     */
    const emitter = useEmitter();

    /*
     * The emitter can then be used to handle events.
     */
    emitter.emit("my-event");

    const handler = () => {};

    /*
     * Listen to "my-event" events.
     */
    emitter.on("my-event", handler);

    /*
     * Stop listening to "my-event" events.
     */
    emitter.off("my-event", handler);

    /*
     * Listen to "my-event" only once.
     */
    emitter.once("my-event", () => {});
};

useEvent

The useEvent hook can be used to handle events.

import { useEvent } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "plugin",
        type: "example",
    });

    /*
     * Here, `useEvent` will handle the "example:message" event.
     *
     * For Example:
     *
     *  emit(
     *      "example:message",
     *      "Hello, World"
     *  )
     */
    useEvent("example:configure", (message) => {
        console.log(message);
        //=> logs "Hello, World"
    });
};

useState

The useState hook can be used once a unit has been installed to store persistent state between calls. This hook should only be used in the primary method for your Unit.

import { useState } from "@leverage/core";

export const init = () => {
    /* ... */
};

export const callback = () => {
    /*
     * Here, `useEffect` will run on every call.
     */
    const [value, setValue] = useState(42);

    /*
     * On the first call, the default value is used. On subsequent calls,
     *  the existing value is used.
     */
    value === 42;

    /*
     * Calling the state setter will update the value for the _next_ call.
     *  This does **not** update `value` in-place.
     */
    setValue(1337);
};

useType

The useType hook can be used in two different ways. It can be used inside of an init function to set or get the configuration's type value. After a unit is initialized, useType can be used to get the current configuration's is value.

import { useType, useInstallEffect } from "@leverage/core";

export const init = () => {
    /*
     * Here, `useType` **sets** the configuration to...
     *
     *  {
     *      "type": "http",
     *  }
     *
     * Then this new configuration's `type` value is returned and stored in the
     *  `type` variable.
     */
    const type = useType("http");

    /*
     * Here, `useType` **gets** the configuration's `type` value and stores it in the
     *  `currentType` variable.
     */
    const currentType = useType();

    useInstallEffect(() => {
        /*
         * Here, `useType` **gets** the configuration's `type` value and stores it in
         *  the `finalType` variable.
         */
        const finalType = useType();
    });
};

export const callback = () => {
    /*
     * Here, `useType` **gets** the configuration's `type` and stores it in the
     *  `type` variable.
     */
    const type = useType();
};

useUnit

The useUnit hook can be used to get the current unit. This is mostly useful when you want a reference to the current unit to pass between boundaries. useUnit can also be useful when you need to call the wrapped version of unit methods.

import { useConfig, useUnit } from "@leverage/core";

export const init = () => {
    useConfig({
        is: "plugin",
        type: "example",
    });

    /*
     * Here, `useType` gets the current unit.
     */
    const self = useUnit("http");

    /*
     * Any exported members can be accessed.
     */
    self.callback();
};

export const callback = () => {
    /* ... */
};

Readme

Keywords

Package Sidebar

Install

npm i @leverage/core

Weekly Downloads

1

Version

4.1.4

License

Apache-2.0

Unpacked Size

129 kB

Total Files

41

Last publish

Collaborators

  • jakehamilton