mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
Deleted moleculer-service-from-class
package
refs https://github.com/TryGhost/Toolbox/issues/354 - this is no longer used as the original project it was developed for was done in a different way
This commit is contained in:
parent
d5e38e6fc7
commit
2acdde2535
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ['ghost'],
|
|
||||||
extends: [
|
|
||||||
'plugin:ghost/node'
|
|
||||||
]
|
|
||||||
};
|
|
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2013-2022 Ghost Foundation
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
@ -1,115 +0,0 @@
|
|||||||
# Moleculer Service From Class
|
|
||||||
|
|
||||||
This module is used to wrap a standard JS Class as a moleculer service, exposing
|
|
||||||
the public methods as actions on the service. It will also wrap other moleculer
|
|
||||||
services in an object with methods, for each of the services actions and injects
|
|
||||||
them into the class constructor.
|
|
||||||
|
|
||||||
This allows us to write generic code, and then use moleculer as the transport
|
|
||||||
layer between services, taking advantage of its load balancing, transporters,
|
|
||||||
logging, tracing & other niceitys
|
|
||||||
|
|
||||||
Because moleculer is an asyncronous transport mechanism - all methods MUST be
|
|
||||||
async. Also all methods should accept params as an object with keys - this works
|
|
||||||
with moleculers concept of `ctx.params` much better.
|
|
||||||
|
|
||||||
Private methods are prefixed with an underscore ('_')
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
`npm install @tryghost/moleculer-service-from-class --save`
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
`yarn add @tryghost/moleculer-service-from-class`
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```js
|
|
||||||
const srvFromClass = require('@tryghost/moleculer-service-from-class');
|
|
||||||
|
|
||||||
class SomeService {
|
|
||||||
async capitalize({string}) {
|
|
||||||
return string.toUpperCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyAwesomeService {
|
|
||||||
constructor({someService, someConfig}) {
|
|
||||||
this._someService = someService;
|
|
||||||
this._someConfig = someConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
async myCoolMethod({name}) {
|
|
||||||
const NAME = await this._someService.capitalize({string: name});
|
|
||||||
|
|
||||||
return `${this._someConfig.greeting}, ${NAME}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moleculer way
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { ServiceBroker } = require('moleculer');
|
|
||||||
|
|
||||||
const broker = new ServiceBroker();
|
|
||||||
broker.addService(srvFromClass({
|
|
||||||
Service: SomeService,
|
|
||||||
name: 'some-service'
|
|
||||||
}));
|
|
||||||
broker.addService(srvFromClass({
|
|
||||||
Service: MyAwesomeService,
|
|
||||||
name: 'awesome',
|
|
||||||
serviceDeps: {
|
|
||||||
someService: 'some-service'
|
|
||||||
},
|
|
||||||
staticDeps: {
|
|
||||||
someConfig: { greeting: 'hello' }
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
broker.start().then(() => {
|
|
||||||
broker.call('awesome.myCoolMethod', {name: 'egg'}).then(console.log);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic way
|
|
||||||
*/
|
|
||||||
|
|
||||||
const awesome = new MyAwesomeService({
|
|
||||||
someConfig: { greeting: 'Hello' },
|
|
||||||
someService: new SomeService()
|
|
||||||
});
|
|
||||||
|
|
||||||
awesome.myCoolMethod({name: 'egg'}).then(console.log);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Develop
|
|
||||||
|
|
||||||
This is a mono repository, managed with [lerna](https://lernajs.io/).
|
|
||||||
|
|
||||||
Follow the instructions for the top-level repo.
|
|
||||||
1. `git clone` this repo & `cd` into it as usual
|
|
||||||
2. Run `yarn` to install top-level dependencies.
|
|
||||||
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
- `yarn dev`
|
|
||||||
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
- `yarn lint` run just eslint
|
|
||||||
- `yarn test` run lint and tests
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Copyright & License
|
|
||||||
|
|
||||||
Copyright (c) 2013-2022 Ghost Foundation - Released under the [MIT license](LICENSE).
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require('./lib/moleculer-service-from-class.js');
|
|
@ -1,176 +0,0 @@
|
|||||||
const moleculer = require('moleculer');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} Service
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template Type
|
|
||||||
* @typedef {function(new: Type, object)} Class<Type>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a naive "proxy" method which calls a moleculer service's method
|
|
||||||
* passing the first argument as the `params` to the moleculer service call
|
|
||||||
* It also binds the ctx correctly to allow for nested service calls
|
|
||||||
*
|
|
||||||
* @param {moleculer.Context} ctx
|
|
||||||
* @param {string} serviceName - The name of the service in the moleculer cluster
|
|
||||||
* @param {string} methodName - The name of the moleculer "action" on the service
|
|
||||||
* @param {string} serviceVersion - The version of the service in the moleculer cluster
|
|
||||||
*
|
|
||||||
* @returns {(params: object) => Promise<any>}
|
|
||||||
*/
|
|
||||||
function proxy(ctx, serviceName, methodName, serviceVersion) {
|
|
||||||
return async params => ctx.call(`${serviceVersion}.${serviceName}.${methodName}`, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the method names of the service
|
|
||||||
*
|
|
||||||
* @param {Class<any>} Class
|
|
||||||
*
|
|
||||||
* @returns {string[]} A list of the methods names for Class
|
|
||||||
*/
|
|
||||||
function getClassMethods(Class) {
|
|
||||||
return Reflect.ownKeys(Class.prototype).reduce((methods, key) => {
|
|
||||||
if (typeof Class.prototype[key] !== 'function') {
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
if (key === 'constructor' || key.toString().startsWith('_')) {
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
return methods.concat(key);
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* createServiceProxy
|
|
||||||
*
|
|
||||||
* @param {moleculer.Context} ctx
|
|
||||||
* @param {string} serviceName
|
|
||||||
* @param {string} serviceVersion
|
|
||||||
* @returns {Promise<object>}
|
|
||||||
*/
|
|
||||||
async function createServiceProxy(ctx, serviceName, serviceVersion) {
|
|
||||||
await ctx.broker.waitForServices([{
|
|
||||||
name: serviceName,
|
|
||||||
version: serviceVersion
|
|
||||||
}]);
|
|
||||||
/** @type {{action: {name: string, rawName: string}}[]} */
|
|
||||||
const actionsList = await ctx.call('$node.actions');
|
|
||||||
|
|
||||||
const serviceMethods = actionsList.filter((obj) => {
|
|
||||||
const isValidAction = obj && obj.action;
|
|
||||||
if (!isValidAction) {
|
|
||||||
ctx.broker.logger.debug(`Recieved invalid action ${JSON.stringify(obj)}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const belongsToService = obj.action.name.startsWith(`${serviceVersion}.${serviceName}.`);
|
|
||||||
|
|
||||||
return belongsToService;
|
|
||||||
}).map(obj => obj.action.rawName);
|
|
||||||
|
|
||||||
return serviceMethods.reduce((serviceProxy, methodName) => {
|
|
||||||
ctx.broker.logger.debug(`Creating proxy ${serviceName}.${methodName}`);
|
|
||||||
return Object.assign(serviceProxy, {
|
|
||||||
[methodName]: proxy(ctx, serviceName, methodName, serviceVersion)
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ServiceDefinition
|
|
||||||
* @prop {string} name
|
|
||||||
* @prop {string} version
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a ServiceSchema compatible with moleculer
|
|
||||||
*
|
|
||||||
* @template {{_init(): Promise<void>}} Type
|
|
||||||
*
|
|
||||||
* @param {object} params The Service to proxy via moleculer
|
|
||||||
* @param {Class<Type>} params.Service The Service to proxy via moleculer
|
|
||||||
* @param {string} params.name The name of the service in moleculer
|
|
||||||
* @param {Object.<string, ServiceDefinition>} [params.serviceDeps] A map of dependencies with a key of the param name and value of the moleculer service
|
|
||||||
* @param {Object.<string, any>} [params.staticDeps] Any static dependencies which do not need to be proxied by moleculer
|
|
||||||
* @param {boolean} [params.forceSingleton=false] Forces the wrapper to only ever create once instance of Service
|
|
||||||
* @param {string} [params.version='1'] Forces the wrapper to only ever create once instance of Service
|
|
||||||
*
|
|
||||||
* @returns {moleculer.ServiceSchema}
|
|
||||||
*/
|
|
||||||
function createMoleculerServiceSchema({Service, name, serviceDeps = null, staticDeps = null, forceSingleton = false, version = '1'}) {
|
|
||||||
const methods = getClassMethods(Service);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of the service - wiring and mapping any dependencies
|
|
||||||
*
|
|
||||||
* @param {moleculer.Context} ctx
|
|
||||||
* @returns {Promise<Type>}
|
|
||||||
*/
|
|
||||||
async function getDynamicServiceInstance(ctx) {
|
|
||||||
const instanceDeps = Object.create(serviceDeps);
|
|
||||||
if (serviceDeps) {
|
|
||||||
for (const dep in serviceDeps) {
|
|
||||||
const serviceDefinition = serviceDeps[dep];
|
|
||||||
const serviceName = serviceDefinition.name;
|
|
||||||
const serviceVersion = serviceDefinition.version;
|
|
||||||
const serviceProxy = await createServiceProxy(ctx, serviceName, serviceVersion);
|
|
||||||
instanceDeps[dep] = serviceProxy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instanceDeps.logging = ctx.broker.logger;
|
|
||||||
Object.assign(instanceDeps, staticDeps);
|
|
||||||
|
|
||||||
const service = new Service(instanceDeps);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
let singleton = null;
|
|
||||||
/**
|
|
||||||
* Ensures that the Service is only instantiated once
|
|
||||||
*
|
|
||||||
* @param {moleculer.Context} ctx
|
|
||||||
* @returns {Promise<Type>}
|
|
||||||
*/
|
|
||||||
async function getSingletonServiceInstance(ctx) {
|
|
||||||
if (singleton) {
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
singleton = await getDynamicServiceInstance(ctx);
|
|
||||||
if (singleton._init) {
|
|
||||||
await singleton._init();
|
|
||||||
}
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getServiceInstance = (!serviceDeps || forceSingleton) ? getSingletonServiceInstance : getDynamicServiceInstance;
|
|
||||||
|
|
||||||
/** @type moleculer.ServiceActionsSchema */
|
|
||||||
const actions = {
|
|
||||||
ping() {
|
|
||||||
return 'pong';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
/** @type {(ctx: moleculer.Context) => Promise<any>} */
|
|
||||||
actions[method] = async function (ctx) {
|
|
||||||
const service = await getServiceInstance(ctx);
|
|
||||||
return service[method](ctx.params);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
actions,
|
|
||||||
async started() {
|
|
||||||
const ctx = new moleculer.Context(this.broker, null);
|
|
||||||
await getServiceInstance(ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = createMoleculerServiceSchema;
|
|
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@tryghost/moleculer-service-from-class",
|
|
||||||
"version": "0.2.28",
|
|
||||||
"repository": "https://github.com/TryGhost/Utils/tree/main/packages/moleculer-service-from-class",
|
|
||||||
"author": "Ghost Foundation",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "echo \"Implement me!\"",
|
|
||||||
"test": "NODE_ENV=testing c8 --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
|
|
||||||
"lint": "eslint . --ext .js --cache",
|
|
||||||
"posttest": "yarn lint"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"index.js",
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"c8": "7.12.0",
|
|
||||||
"mocha": "10.0.0",
|
|
||||||
"should": "13.2.3",
|
|
||||||
"sinon": "14.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"moleculer": "^0.14.11"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ['ghost'],
|
|
||||||
extends: [
|
|
||||||
'plugin:ghost/test'
|
|
||||||
]
|
|
||||||
};
|
|
@ -1,91 +0,0 @@
|
|||||||
const should = require('should');
|
|
||||||
const sinon = require('sinon');
|
|
||||||
const moleculer = require('moleculer');
|
|
||||||
const createMoleculerServiceFromClass = require('../');
|
|
||||||
|
|
||||||
const hasOwnProperty = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
|
|
||||||
|
|
||||||
describe('MoleculerServiceFromClass', function () {
|
|
||||||
it('Exposes name & methods as actions (excluding private & constructor) as well as a default ping', function () {
|
|
||||||
class Dep {
|
|
||||||
async _init() {}
|
|
||||||
async _privateMethod() {}
|
|
||||||
async someMethod() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const service = createMoleculerServiceFromClass({Service: Dep, name: 'dep', version: '1'});
|
|
||||||
|
|
||||||
const name = service.name;
|
|
||||||
const version = service.version;
|
|
||||||
const actions = service.actions;
|
|
||||||
|
|
||||||
should.equal(name, 'dep');
|
|
||||||
should.equal(version, '1');
|
|
||||||
|
|
||||||
should.equal(hasOwnProperty(actions, '_privateMethod'), false);
|
|
||||||
should.equal(hasOwnProperty(actions, 'constructor'), false);
|
|
||||||
|
|
||||||
should.equal(hasOwnProperty(actions, 'someMethod'), true);
|
|
||||||
should.equal(hasOwnProperty(actions, 'ping'), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Wires up dynamic and static dependencies correctly', async function () {
|
|
||||||
const fakeStaticDep = 13;
|
|
||||||
class Dep {
|
|
||||||
async _init() {}
|
|
||||||
constructor({staticDep}) {
|
|
||||||
this._staticDep = staticDep;
|
|
||||||
should.equal(staticDep, fakeStaticDep);
|
|
||||||
}
|
|
||||||
async someMethod() {
|
|
||||||
return this._staticDep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Main {
|
|
||||||
async _init() {}
|
|
||||||
/**
|
|
||||||
* @param {object} deps
|
|
||||||
* @param {Dep} deps.dep
|
|
||||||
*/
|
|
||||||
constructor({dep}) {
|
|
||||||
this._dep = dep;
|
|
||||||
}
|
|
||||||
|
|
||||||
async someOtherMethod() {
|
|
||||||
const num = await this._dep.someMethod();
|
|
||||||
return num * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const depService = createMoleculerServiceFromClass({Service: Dep, name: 'dep', version: '1', staticDeps: {
|
|
||||||
staticDep: fakeStaticDep
|
|
||||||
}});
|
|
||||||
|
|
||||||
const mainService = createMoleculerServiceFromClass({Service: Main, name: 'main', version: '1', serviceDeps: {
|
|
||||||
dep: {
|
|
||||||
name: 'dep',
|
|
||||||
version: '1'
|
|
||||||
}
|
|
||||||
}});
|
|
||||||
|
|
||||||
const broker = new moleculer.ServiceBroker({logger: false});
|
|
||||||
broker.createService(depService);
|
|
||||||
broker.createService(mainService);
|
|
||||||
|
|
||||||
await broker.start();
|
|
||||||
|
|
||||||
const someMethod = sinon.spy(Dep.prototype, 'someMethod');
|
|
||||||
const someOtherMethod = sinon.spy(Main.prototype, 'someOtherMethod');
|
|
||||||
|
|
||||||
const result = await broker.call('1.main.someOtherMethod');
|
|
||||||
|
|
||||||
should.equal(someMethod.called, true);
|
|
||||||
should.equal(someOtherMethod.called, true);
|
|
||||||
should.equal(result, 26);
|
|
||||||
|
|
||||||
await broker.stop();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"declaration": true,
|
|
||||||
"emitDeclarationOnly": true,
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"target": "es6"
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
declare const _exports: typeof import("./lib/moleculer-service-from-class.js");
|
|
||||||
export = _exports;
|
|
@ -1,48 +0,0 @@
|
|||||||
export = createMoleculerServiceSchema;
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ServiceDefinition
|
|
||||||
* @prop {string} name
|
|
||||||
* @prop {string} version
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Create a ServiceSchema compatible with moleculer
|
|
||||||
*
|
|
||||||
* @template {{_init(): Promise<void>}} Type
|
|
||||||
*
|
|
||||||
* @param {object} params The Service to proxy via moleculer
|
|
||||||
* @param {Class<Type>} params.Service The Service to proxy via moleculer
|
|
||||||
* @param {string} params.name The name of the service in moleculer
|
|
||||||
* @param {Object.<string, ServiceDefinition>} [params.serviceDeps] A map of dependencies with a key of the param name and value of the moleculer service
|
|
||||||
* @param {Object.<string, any>} [params.staticDeps] Any static dependencies which do not need to be proxied by moleculer
|
|
||||||
* @param {boolean} [params.forceSingleton=false] Forces the wrapper to only ever create once instance of Service
|
|
||||||
* @param {string} [params.version='1'] Forces the wrapper to only ever create once instance of Service
|
|
||||||
*
|
|
||||||
* @returns {moleculer.ServiceSchema}
|
|
||||||
*/
|
|
||||||
declare function createMoleculerServiceSchema<Type extends {
|
|
||||||
_init(): Promise<void>;
|
|
||||||
}>({ Service, name, serviceDeps, staticDeps, forceSingleton, version }: {
|
|
||||||
Service: Class<Type>;
|
|
||||||
name: string;
|
|
||||||
serviceDeps?: {
|
|
||||||
[x: string]: ServiceDefinition;
|
|
||||||
};
|
|
||||||
staticDeps?: {
|
|
||||||
[x: string]: any;
|
|
||||||
};
|
|
||||||
forceSingleton?: boolean;
|
|
||||||
version?: string;
|
|
||||||
}): moleculer.ServiceSchema;
|
|
||||||
declare namespace createMoleculerServiceSchema {
|
|
||||||
export { Service, Class, ServiceDefinition };
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* <Type>
|
|
||||||
*/
|
|
||||||
type Class<Type> = new (arg1: object) => Type;
|
|
||||||
type ServiceDefinition = {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
import moleculer = require("moleculer");
|
|
||||||
type Service = object;
|
|
@ -1 +0,0 @@
|
|||||||
export {};
|
|
Loading…
Reference in New Issue
Block a user