All Projects → CodalReef → Halia

CodalReef / Halia

Licence: mit
Extensible TS / JS Dependency Injection Framework

Programming Languages

javascript
184084 projects - #8 most used programming language
typescript
32286 projects

Projects that are alternatives of or similar to Halia

zcomet
zcomet - Fast, Simple Zsh Plugin Manager
Stars: ✭ 144 (+323.53%)
Mutual labels:  plugin-manager
WpGet-Private-wordpress-plugin-repository
Wordpress private repository for plugins
Stars: ✭ 24 (-29.41%)
Mutual labels:  plugin-manager
Ctk
A set of common support code for medical imaging, surgical navigation, and related purposes.
Stars: ✭ 498 (+1364.71%)
Mutual labels:  plugin-manager
vis-plug
A minimal plugin-manager for vis
Stars: ✭ 17 (-50%)
Mutual labels:  plugin-manager
pluGET
📦 Powerful Package manager which updates plugins & server software for minecraft servers
Stars: ✭ 87 (+155.88%)
Mutual labels:  plugin-manager
cms
🛠️ Simple smart CMS for Nette and Vue.js
Stars: ✭ 12 (-64.71%)
Mutual labels:  plugin-manager
Harbol
Harbol is a collection of data structure and miscellaneous libraries, similar in nature to C++'s Boost, STL, and GNOME's GLib
Stars: ✭ 18 (-47.06%)
Mutual labels:  plugin-manager
Antigen
The plugin manager for zsh.
Stars: ✭ 6,843 (+20026.47%)
Mutual labels:  plugin-manager
endure
⚡ Fault-tolerant service container for Golang applications
Stars: ✭ 29 (-14.71%)
Mutual labels:  plugin-manager
Packer.nvim
A use-package inspired plugin manager for Neovim. Uses native packages, supports Luarocks dependencies, written in Lua, allows for expressive config
Stars: ✭ 418 (+1129.41%)
Mutual labels:  plugin-manager
.NetCorePluginManager
.Net Core Plugin Manager, extend web applications using plugin technology enabling true SOLID and DRY principles when developing applications
Stars: ✭ 17 (-50%)
Mutual labels:  plugin-manager
electron-plugin
an electron plugin extension package
Stars: ✭ 34 (+0%)
Mutual labels:  plugin-manager
vpm
ViM Plugin Manager (like apt, npm, pacman, etc ... )
Stars: ✭ 13 (-61.76%)
Mutual labels:  plugin-manager
hookman
A plugin management system in python to applications (in totally or partially) written in C++.
Stars: ✭ 29 (-14.71%)
Mutual labels:  plugin-manager
Fisher
A plugin manager for Fish.
Stars: ✭ 5,386 (+15741.18%)
Mutual labels:  plugin-manager
zgenom
A lightweight and fast plugin manager for ZSH
Stars: ✭ 240 (+605.88%)
Mutual labels:  plugin-manager
zinit
🌻 Flexible and fast ZSH plugin manager
Stars: ✭ 944 (+2676.47%)
Mutual labels:  plugin-manager
Peru
a generic package manager, for including other people's code in your projects
Stars: ✭ 913 (+2585.29%)
Mutual labels:  plugin-manager
Minpac
A minimal package manager for Vim 8 (and Neovim)
Stars: ✭ 693 (+1938.24%)
Mutual labels:  plugin-manager
Dein.vim
⚡ Dark powered Vim/Neovim plugin manager
Stars: ✭ 3,117 (+9067.65%)
Mutual labels:  plugin-manager

Halia Logo

Halia

Extensible TS / JS Dependency Injection Framework

Stop Spreading Features. Build Apps as a Tree of Plugins.

  • Extensible: Install extensions to customize the injector.
  • Tested: Test / Src Ratio (TSR): ~1/2.
  • Lightweight: ~ 400 lines of non-test code.
  • Independent: Not coupled with a particular back-end or front-end technology.
  • Philosophy: Extensibility is a first-class concern as discussed in Plugin Pattern.

Halia is a generic, extensible dependency injection (DI) framework. However, we use it to generate apps at runtime using the Plugin Pattern.

Build "Plugins" to encapsulate and inject features into your app (or other plugins). This keeps features simple, de-coupled, and open for extension.

📖 Table of Contents

Installation

Install with npm:

npm i --save git+ssh://[email protected]:CodalReef/Halia.git

Install with Yarn:

yarn add git+ssh://[email protected]:CodalReef/Halia.git

API

Plugins

Define a Plugin for each feature you wish to encapsulate. You determine what each Plugin does and what API is exported for dependencies to use.

To Halia, the functional API exported by a Plugin is the sole integration point. When manipulating a dependency, we recommend sticking to this functional interface and against direct manipulation when possible.

export const MyPlugin: HaliaPlugin = {
  id: "myPlugin",             //  Unique Identifier 
  name: "My Plugin",          //  Human Readable Name
  description: "My Plugin!",  //  Human Readable Description
  dependencies: [],           //  Array of Plugin Identifiers
  install: (imports) => {     //  Function to "Install" this Plugin
    return {};                //  Return a "Plugin API" for Downstream Dependencies
  }
}

Stacks

A "Stack" is a program built at run-time using several Halia Plugins. Because it's built at runtime, the set of plugins in the stack can be changed and the system re-built.

//  Initialize the Stack
const stack = new HaliaStack();

//  Register Plugins
stack.register(App);
stack.register(Feature1);
stack.register(Feature1_1);
stack.register(Feature2);
// etc...

//  Build the Stack
await stack.build();

//  Extract an Export
const f1Exports = stack.getExports(Feature1.id);

//  Extract a Plugin
const f1Plugin = stack.getPlugin(Feature1.id);

At this point, stack is an initialized instance of your Plugin Set. It may be a program, or perhaps a feature to be further nested in another Halia Stack.

To extract a Plugin or Exported API from a stack use the getExports and getPlugin Stack methods.

Extensions

Halia is itself, a Halia Plugin, open for extension.

For example, here we install the the "Optional Dependencies" Plugin to add a new optionalDependencies field to each Plugin.

//  Initialize the Stack
const coreStack = new HaliaStack();

//  Register Elements
coreStack.register(HaliaCore);
coreStack.register(OptionalDependencies);

//  Build the Stack
await coreStack.build();

Now, we can define Plugins with a new optionalDependencies field:

const MyPlugin: HaliaPlugin & OptionalDependenciesPatch = {
  id: "myPlugin",
  name: "My Plugin",
  install: () => {},  //  Do Something
  dependencies: [],

  //  New Feature!
  optionalDependencies: ["OtherPlugin"]
}

The "Core Stack" is currently a Global which may only be built once. The functionality added by Plugins is integrated as it's built. We plan to address these concerns by clearing the global and turning off modifications prior to core extension.

Note that some DI Frameworks don't support chaining (modules which depend upon other modules) or nesting (modules defined within other modules). Halia supports both of these use-cases out of the box.

Concepts

Halia is a "Dependency Injection Framework". Before using Halia, it's good to have an understanding of "Dependency Injection", the "Plugin Pattern", and related concepts.

Dependency Injection

Doug

If you're new to Dependency Injection, we recommend reading our article: Learn Dependency Injection with Doug the Goldfish 🐠

Pluggable Systems

Lenny

If you're new to building "Pluggable" systems, we recommend reading our article: Build Pluggable Apps with Lenny the Duck 🦆

Pluggable Systems with DI

In a DI Framework, the state of each dependency is typically set on construction and (in many cases) it doesn't change much after that. A function typically depends upon a module and it uses that module to accomplish a goal.

However, when using DI to manage "Plugins", the intention is slightly different. The dependencies will still be injected into each module, but instead of simply using these dependencies to accomplish a goal, we can also modify them. Because modules in this pattern are expected to "plug" functionality into their dependencies, we call them "Plugins".

Each dependency is still responsible for defining the initial API passed to its consumers. With the Plugin Pattern, its not unexpected for the API methods to change the dependency's state, and / or the exported API itself.

With the introduction of API Modification, we make a distinction between the "API Contract" and "API". The "API Contract" is a set of rules / conditions set by a Plugin, and it should remain unchanged. The "API", which includes the exported member functions and their associated functionality, is expected to change (within the confines of the API Contract).

We call a Plugin "stable" if it's contract is well-defined and unbreakable. Conversely, if usage of a API can lead to a breach of contract (or the contract is ambiguous), we call the Plugin "unstable". As long as a Plugin is stable, we can still predictably use and extend its API.

It's possible to be explicit about which types of APIs a Plugin exports. For example, one for usage and another for modification. However, Halia doesn't make this distinction, and a Plugin can export an API which does any combination of these things.

With this pattern, it becomes easy to mix, match, and build new features. It's even possible to open your app for extension by external developers (like Wordpress does).

Example

You have a duck that everyone loves:

//  duck-app.ts
export const getDuck = () => {
  return "Quack";
}

Everyone except Paul. Paul wants a special 🦄 Disco Duck 🦄, so you make an update:

//  duck-app.ts
import { Paul } from 'client-list';
import { config } from 'config';
export const getDuck = () => {
  if (params.client === Paul) {
    return "Michael Quackson";
  }
  return "Quack";
}

While the code works for Paul, it's become more complex, harder to read, and coupled with the "client" concept.

Instead, we can use a Halia Plugin to encapsulate and inject this feature:

//  duck-app-plugin.ts
import * as DuckApp from './duck-app';
export const DuckAppPlugin: HaliaPlugin = {
  id: "duckApp",
  name: "Duck App Plugin",
  install: () => ({
    setGetDuck: (getDuck) => DuckApp.getDuck = getDuck
  })
}
//  disco-duck-plugin.ts
import { Paul } from 'client-list';
import * as config from 'config';
export const DiscoDuckPlugin: HaliaPlugin = {
  id: "discoDuck",
  name: "Disco Duck Plugin",
  dependencies: [DuckAppPlugin.id],
  install: ({ duckApp }) => {
    if (config.client === Paul) {
      duckApp.setGetDuck (() => "Michael Quackson")
    }
  }
}

Then we can build the stack and invoke the code:

//  main.ts
import { HaliaStack } from Halia;
import { DuckApp } from './DuckApp';
import { DiscoFeature } from './DiscoFeature';

const buildApp = async () => {

  //  Initialize the Stack
  const appStack = new HaliaStack();

  //  Register Plugins
  appStack.register(DuckApp);
  appStack.register(DiscoFeature);

  //  Build the Stack
  await appStack.build();

  //  Call the Method
  const duckApp = appStack.getExports(DuckApp.id);
  duckApp.logNoise();
}

buildApp();

With this, the original code is left in-tact and de-coupled.

If Paul longer wants the 🦄 Disco Duck 🦄 we just don't register the Plugin. If he needs an additional change, we have a namespace dedicated to his unique needs.

This is a simple example that can be solved in other ways. However, it demonstrates the general idea, and as features become more complex, we've found this pattern helps to keep things organized.

]

Road Map

Features

  • Plugin Configuration
  • Plugin Factories / Plugin "Instances" (we can currently do this just building a Plugin before we build the stack)
  • Consider exporting an Object OR specifically a Plugin from each Halia "Stack".

Extensions

  • Incompatibility Management
  • Plugin Versions
  • Global Injections
  • Cyclic Dependencies
  • Plugin Inheritance
  • Plugin Overloads
  • API Transformers
  • Additional Passes / Channels
  • External Integration
  • Multiple Exports: It should be possible to export multiple APIs, for example, one for usage and another for modification and route these to modules tagged for that purpose.
  • Generic Tagging

More Info

Package Managers (like npm ) vs. Halia

NPM is a "Package Manager" used to manages static, package-level dependencies which typically stay constant between runs.

Halia also manages dependencies, but the dependency tree can be built on-demand at runtime, and typically between "Features" in the application domain, not static libraries used to build features.

Halia also has an "install" step, which enables Plugins to change the functionality of existing Plugins.

Halia "Plugins" are similar to "Packages", and depending on your definition, Halia may be classified as a "Package Manager". However, with the addition of runtime construction and first-class support for mutation, we feel the label "Plugin Manager" is more fitting.

Module Systems (like JS Modules) vs. Halia

JS Modules is a "Module System" used to bundle code across the filesystem and map it to variables in the local scope (using "import" and "export" statements).

In contrast, Halia automatically resolves dependencies between modules without the need to manually manage import order. In addition, each Halia Plugin has a unique identifier which ensures it's registered as a singleton instance.

You don't need Halia to manage dependencies or implement the "Plugin Pattern". However, as the number of dependencies grows and use-cases evolve, several problems emerge:

  • Import Order: You need to manage import order, which can become complex and difficult to refactor.
  • Singleton Guarantee: There's no guarantee the loaded module is a Singleton.
  • Dependency Verification: It's your responsibility to ensure dependencies are met prior to installation.
  • Delayed Installation: You'll need to manually manage installation if it occurs after initial load.
  • Dynamic Modification: If the set of “Features” changes at runtime, you'll need to manage that change. For example, verifying module compatibility.

In Halia, the complete dependency graph is built at runtime, so there's no need to manually order them. Each Plugin has a unique ID associated with a singleton instance. If the same ID is used twice, an error is thrown. Halia Stacks can be built on-demand (and re-built) as needed. This means, if the feature set changes at runtime, we can re-build the stack and run again.

Plugin Incompatibility

While using Halia (or any DI Framework with the Plugin Pattern) you might encounter incompatibility between Plugins.

An "Incompatibility" exists between two or more Plugins which, when installed together, result in a dysfunctional state. To solve this problem, try the following suggestions:

  • Remove one of the Plugins.
  • Update one of the Plugins to fix the incompatibility.
  • Build a new "Coupling Plugin" to fix the incompatibility.
  • Update the dependency's exported APIs to support the Plugins.

A "Coupling Plugin" is used to correct invalid or missing functionality that results from an incompatibility.

For example, imagine we add two new Plugins, "Video" and "Messaging". Assuming they both inject themselves with the same API, it's possible they'd overwrite one another. To solve this, we can build a new "Coupling Plugin", perhaps called "VideoMessaging", to inject the necessary patch.

This approach can be useful, but it's good practice to keep Plugins independent and compatible. This helps control feature "fan-out" and keeps combinatorial explosion in check.

Attribution

Halia is inspired by similar DI tools like Angular and Nest. Both resources helped shape our understanding of DI, IoC Container, and DI Frameworks.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].