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:
Daniel Lockyer 2022-07-26 12:37:57 +02:00
parent d5e38e6fc7
commit 2acdde2535
12 changed files with 0 additions and 511 deletions

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/node'
]
};

View File

@ -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.

View File

@ -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).

View File

@ -1 +0,0 @@
module.exports = require('./lib/moleculer-service-from-class.js');

View File

@ -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;

View File

@ -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"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/test'
]
};

View File

@ -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();
});
});

View File

@ -1,14 +0,0 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"allowJs": true,
"checkJs": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es6"
},
"exclude": [
"node_modules"
]
}

View File

@ -1,2 +0,0 @@
declare const _exports: typeof import("./lib/moleculer-service-from-class.js");
export = _exports;

View File

@ -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;