UNPKG

moleculer-decorators

Version:
203 lines (176 loc) 6.18 kB
import { ServiceSchema, ActionSchema, ActionHandler, ServiceBroker, ServiceEventHandler, EventSchema, } from "moleculer"; import * as _ from "./util"; const blacklist = [ "created", "started", "stopped", "actions", "methods", "events", "broker", "logger", ]; const blacklist2 = ["metadata", "settings", "mixins", "name", "version"].concat( blacklist ); const defaultServiceOptions: Options = { constructOverride: true, skipHandler: false, // not needed, just for clarity }; export interface Options extends Partial<ServiceSchema> { name?: string; constructOverride?: boolean; } export interface ActionOptions extends Partial<ActionSchema> { name?: string; handler?: ActionHandler<any>; // Not really used skipHandler?: boolean; } export interface EventOptions extends Partial<EventSchema> { name?: string; group?: string; handler?: ServiceEventHandler; // not really used } export function Method(target, key: string, descriptor: PropertyDescriptor) { (target.methods || (target.methods = {}))[key] = descriptor.value; } export function Event(options?: EventOptions) { return function (target, key: string, descriptor: PropertyDescriptor) { (target.events || (target.events = {}))[key] = options ? { ...options, handler: descriptor.value, } : descriptor.value; }; } export function Action(options: ActionOptions = {}) { return function (target, key: string, descriptor: PropertyDescriptor) { if (!options.skipHandler) { options.handler = descriptor.value; } else { delete options.skipHandler; } (target.actions || (target.actions = {}))[key] = options ? { ...options, } : options.skipHandler ? "" : descriptor.value; }; } // Instead of using moleculer's ServiceBroker, we will fake the broker class to pass it to service constructor const mockServiceBroker = new Object({ Promise }); export function Service<T extends Options>(opts: T = {} as T): Function { const options = opts || ({} as Options); return function (constructor: Function) { let base: ServiceSchema = { name: "", // will be overridden }; const _options = Object.assign({}, defaultServiceOptions, options); Object.defineProperty(base, "name", { value: options.name || constructor.name, writable: false, enumerable: true, }); if (options.name) { delete options.name; // not needed } Object.assign(base, _.omit(options, Object.keys(defaultServiceOptions))); // Apply const parentService = constructor.prototype; const vars = []; Object.getOwnPropertyNames(parentService).forEach(function (key) { if (key === "constructor") { if (_options.constructOverride) { // Override properties defined in @Service const ServiceClass = new parentService.constructor(mockServiceBroker); Object.getOwnPropertyNames(ServiceClass).forEach(function (key) { if ( blacklist.indexOf(key) === -1 && !_.isFunction(ServiceClass[key]) ) { base[key] = Object.getOwnPropertyDescriptor( ServiceClass, key )!.value; if (blacklist2.indexOf(key) === -1) { // Needed, otherwize if the service is used as a mixin, these variables will overwrite the toplevel's vars[key] = Object.getOwnPropertyDescriptor( ServiceClass, key )!.value; } } }); /* Insane hack below :D * It's needed since moleculer don't transfer all defined props in the schema to the actual service, so we have to do it. * Side note: This is quite hacky and would be a performance loss if the created function would be called over and over, since it's called once, it's more than fine :) */ const bypass: any = Object.defineProperty; // typescript fix const obj: any = {}; // placeholder // Defining our 'own' created function bypass(obj, "created", { value: function created(broker: ServiceBroker) { for (let key in vars) { this[key] = vars[key]; } // Check if user defined a created function, if so, we need to call it after ours. if ( Object.getOwnPropertyDescriptor(parentService, "created") != null ) { Object.getOwnPropertyDescriptor( parentService, "created" ).value.call(this, broker); } }, writable: true, enumerable: true, configurable: true, }); base["created"] = obj.created; } return; } const descriptor = Object.getOwnPropertyDescriptor(parentService, key)!; if (key === "created" && !_options.constructOverride) { base[key] = descriptor.value; } if (key === "started" || key === "stopped") { base[key] = descriptor.value; return; } if (key === "events" || key === "methods" || key === "actions") { base[key] ? Object.assign(base[key], descriptor.value) : (base[key] = descriptor.value); return; } // moleculer-db lifecycle methods (https://github.com/ColonelBundy/moleculer-decorators/issues/2) if ( key === "afterConnected" || key === "entityCreated" || key === "entityUpdated" || key === "entityRemoved" ) { base[key] = descriptor.value; return; } }); return class extends parentService.constructor { constructor(broker, schema) { super(broker, schema); this.parseServiceSchema(base); } }; }; }