@teambit/harmony
Version:
abstract extension system
127 lines (103 loc) • 3.86 kB
text/typescript
import { HookNotFound } from '../exceptions';
import { ExtensionManifest } from "./extension-manifest";
// :TODO refactor this file asap
export type ExtensionOptions = {
dependencies?: ExtensionManifest[],
name?: string
}
const map: any = {};
/**
* decorator for an Harmony extension.
*/
export function ExtensionDecorator({ name, dependencies }: ExtensionOptions = {}) {
function classDecorator<T extends {new(...args:any[]): {}}>(constructor:T) {
Reflect.defineMetadata('harmony:name', name || constructor.name, constructor);
Reflect.defineMetadata('harmony:dependencies', calculateDependnecies(constructor, dependencies), constructor)
}
return classDecorator;
}
export function provider() {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const keys = Reflect.getMetadata('design:paramtypes', descriptor);
}
}
// @hack todo: must be defined and assigned from a single location
function providerFn(classExtension: any) {
return classExtension.provide ? classExtension.provide : classExtension.provider;
}
function calculateDependnecies(classExtension: any, deps?: ExtensionManifest[]): ExtensionManifest[] {
function fromMetadata() {
const provider = providerFn(classExtension)
if (provider) {
// // TODO: check why Reflect.getMetadataKeys(provider) is empty and how to access method param types.
// console.log(Reflect.getMetadataKeys(classExtension.provide))
return [];
}
return Reflect.getMetadata('design:paramtypes', classExtension);
}
const dependnecies = deps ? deps : fromMetadata() || [];
const hookDeps = classExtension.__hookDeps ? classExtension.__hookDeps : [];
return dependnecies.concat(hookDeps) || [];
}
// :TODO refactor this asap to handle harmony objects properly
export function register(extension: ExtensionManifest, name?: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// if (!target.constructor.__hookDeps) Reflect.defineMetadata('harmony:subscriptions', [extension], target.constructor);
// else target.constructor.__hookDeps.push(extension);
const extensionName = Reflect.getMetadata('harmony:name', extension);
if (!map[extensionName]) {
map[extensionName] = {};
}
const hook = map[extensionName][name || propertyKey];
// if (!hook) throw new HookNotFound();
if (!hook) return;
hook.register(target[propertyKey]);
}
}
export function createHook() {
const randomId = Math.random().toString(36).substring(2);
map[randomId] = HookRegistry.create();
const decorator = function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const registry = map[randomId];
registry.register(descriptor.value);
}
decorator.hash = randomId;
return decorator;
}
export function hook(name?: string) {
return function(target: any, propertyKey: string) {
let instance = HookRegistry.create();
const extensionName = Reflect.getMetadata('harmony:name', target.constructor);
const hookName = name || propertyKey;
if (!map[extensionName]) map[extensionName] = {[hookName]: instance};
else map[extensionName][hookName] = instance;
Object.defineProperty(target, propertyKey, {
get: () => {
return instance;
},
set: (value) => {
instance = value;
}
});
}
}
export class HookRegistry<T> {
constructor(
private fillers: T[],
readonly hash?: string
) {}
register(filler: T) {
this.fillers.push(filler);
}
list() {
// return map[this.name][name] || [];
return this.fillers;
}
static of<T>(hook: any): HookRegistry<T> {
return map[hook.hash];
}
// hack due to https://github.com/microsoft/TypeScript/issues/4881
static create<T>() {
return new HookRegistry<T>([]);
}
}