moleculer-decorators
Version:
decorators for moleculer
203 lines (176 loc) • 6.18 kB
text/typescript
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);
}
};
};
}