New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

alar

Package Overview
Dependencies
Maintainers
1
Versions
122
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

alar

Auto-Load And Remote.

  • 5.4.2
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
39
decreased by-11.36%
Maintainers
1
Weekly downloads
 
Created
Source

Alar

Alar is a light-weight framework that provides applications the ability to auto-load and hot-reload modules, as well as the ability to serve instances remotely as RPC services.

Prerequisites

  • Node.js v8.3.0+

Auto-loading and Hot-reloading

In NodeJS (with CommonJS module solution), require and import will immediately load the corresponding module and make a reference "copy" in the current scope. Which means, if the module doesn't finish initiation, e.g. circular import, the application may not work as expected, and if the module file is modified, the application won't be able to reload that module without restart the program.

Alar, on the other hand, based on namespace and ES6 proxy, it creates a weak-reference of the module, and only import the module when needed. And since it's weak-referenced, it will not make any copy to the module, and when the module file is changed, it can wipe out the memory cache and reload the module with very few side-effects.

How to use?

In order to use Alar, one must create a root ModuleProxy instance, and assign it to the global namespace, so other files can directly use it as a root namespace without import and share the benefits of declaration merging (in TypeScript vernacular, if not using is, just ignore any tips and code of declaration merging that may be discussed).

Example

// src/app.ts
import { ModuleProxy } from "alar";

// Expose and merge the app as a namespace under the global namespace.
declare global {
    namespace app { }
}

// Create the instance.
export const App = global["app"] = new ModuleProxy("app", __dirname);

// Watch file changes and hot-reload modules.
App.watch();

In other files, just define and export a default class, and merge the type to the namespace app, so that another file can access it directly as namespace.

(NOTE: Alar offers first priority of the default export, if a module doesn't have default export, Alar will try to load the entire exports object instead.)

// src/bootstrap.ts
declare global {
    namespace app {
        const bootstrap: ModuleProxy<Bootstrap>
    }
}

export default class Bootstrap {
    init() {
        // ...
    }
}
// src/service/user.ts
// The namespace must correspond to the filename.
declare global {
    namespace app {
        namespace service {
            // Since v5.0, a module class with parameters must use the signature
            // `typeof T`.
            const user: ModuleProxy<typeof User>
        }
    }
}

export default class User {
    constructor(private name: string) { }

    getName() {
        return this.name;
    }
}

And other files can access to the modules via the namespace:

// src/index.ts
import "./app";

// The instance() method will link to the singleton instance of the module.
app.bootstrap.instance().init();

// Since v5.4, calling instance() is optional, you can call the module proxy
// as function directly. So this is equivalent to the above one:
app.bootstrap().init();

// The create() method will create a new instance.
var user = app.service.user.create("Mr. Handsome");

console.log(user.getName()); // Mr. Handsome

Prototype Module

Any module that exports an object as default will be considered as a prototype module, when calling create() of that module, the object will be used as a prototype (since v4.0.4, a deep clone will be used instead, if an argument is passed, it will be merged to the new object). However when calling instance() of that module, the original object itself will be used as the singleton.

// src/config.ts
declare global {
    namespace app {
        const config: ModuleProxy<Config>;
    }
}

export interface Config {
    // ...
}

export default <Config>{
    // ...
}

Remote Service

Alar allows user to easily serve the module remotely, whether in another process or in another machine.

Example

Say I want to serve the user service in a different process and communicate via IPC channel, I just have to do this:

// src/service/user.ts
declare global {
    namespace app {
        namespace service {
            const user: ModuleProxy<typeof User>
        }
    }
}

export default class User {
    constructor(private name?: string) {}

    // Any method that will potentially be called remotely should be async.
    async getName() {
        return this.name;
    }

    // Static method getInstance() is used to create the singleton instance.
    static getInstance() {
        return new this("Mr. Handsome");
    }
}
// src/remote-service.ts
import { App } from "./app";

(async () => {
    let service = await App.serve("/tmp/my-app/remote-service.sock");

    service.register(app.service.user);

    console.log("Service started!");
})();

Just try ts-node --files src/remote-service (or node dist/remote-service), and the service will be started immediately.

And in index.ts, connect to the service before using remote functions:

// index.ts
import { App } from "./app";

(async () => {
    let service = await App.connect("/tmp/my-app/remote-service.sock");

    service.register(app.service.user);

    // Access the instance in local style but actually remote.
    console.log(await app.service.user.instance().getName()); // Mr. Handsome
})();

Hot-reloading in Remote Service

The local watcher may notice the local file has changed and try to reload the local module (and the local singleton), however, it will not affect any remote instances, that said, the instance served remotely can still be watched and reloaded on the remote server individually.

In the above example, since the remote-service module imports app module as well, which starts the watcher, when the user module is changed, the remote-service will reload the module as expected, and the index calls it remotely will get the new result as expected.

Generator Support

Since version 3.3, Alar supports generators (and async generators) in both local call and remote call contexts.

// src/service/user.ts
declare global {
    namespace app {
        namespace service {
            const user: ModuleProxy<User>
        }
    }
}

export default class User {
    // ...
    async *getFriends() {
        yield "Jane";
        yield "Ben";
        yield "Albert";
        return "We are buddies";
    }
}

// index.ts
(async () => {
    // Whther calling the local instance or a remote instance, the following 
    // program produce the same result.

    let generator = app.service.user.instance().getFriends();

    for await (let name of generator) {
        console.log(name);
        // Jane
        // Ben
        // Albert
    }

    // If want to get the returning value, just call await on the generator.
    // NOTE: this syntax only works with Alar framework, don't use it with 
    // general generators.
    console.log(await generator); // We are buddies

    // The following usage gets the same result.

    let generator2 = app.service.user.instance().getFriends();

    while (true) {
        let { value, done } = await generator2.next();

        console.log(value);
        // NOTE: calling next() will return the returning value of the generator
        // as well, so the output would be:
        //
        // Jane
        // Ben
        // Albert
        // We are buddies

        if (done) {
            break;
        }
    }
})();

Dependency Injection

Since 3.5.0, Alar add a new method inject(route?: any) to allow you setting up dependency for a specific class in a handy way, check this example:

class Article {
    @app.service.user.inject()
    protected user: User;

    getAuthorName(): Promise<string> {
        return this.user.getName();
    }
}

(async () => {
    var article = new Article;
    console.log(await article.getAuthorName());
})();

Be noticed that this method differs from the usage of assigning the instance to an variable, if you use the syntax below to add the instance to a class property, it will break the hot-reloading feature that Alar provided.

class Article {
    protected user = app.services.user.instance(); // DON't do this
}

This syntax will make a strong reference to the user module, which will not allow the program to refer to the new instance after reloading the user module. But, when using the inject() method, which ships with instance() under the hood, the hot-reloading model will still work fine.

However, Alar doesn't provide a way to inject new instances dynamically, since every new instance will create a strong reference itself, that makes that kind of injection less useful.

Life Cycle Support

Since 5.0, Alar now supports life cycle functions, if a service class contains an init() method, it will be used to perform asynchronous initiation, for example, connecting to a database. And if it contains a destroy() method, it will be used to perform asynchronous destruction, to release resources.

To enable this feature, after all needed modules are registered (and any other preparations are done), call the RpcServer.init() method to perform initiation process for every registered module.

This feature will still work after hot-reloaded the module. However, there would be a slight downtime during hot-reloading, and any call would fail until the service is re-available again.

// src/service/user.ts
declare global {
    namespace app {
        namespace service {
            const user: ModuleProxy<User>
        }
    }
}

export default class User {
    async init() {
        // ...
    }

    async destroy() {
        // ...
    }
}


(async () => {
    server.register(app.services.user);

    await server.init();
})();

For more details, please check the API documentation.

Keywords

FAQs

Package last updated on 01 Jan 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc