diff --git a/.gitignore b/.gitignore index 5b38ef15..7e4b2bda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +!packages/workit-core/tests/units/node_modules lerna-debug.log packages/*/lib .idea diff --git a/packages/workit-bpm-client/src/camundaBpmClient.ts b/packages/workit-bpm-client/src/camundaBpmClient.ts index fc1cd249..76af27ac 100644 --- a/packages/workit-bpm-client/src/camundaBpmClient.ts +++ b/packages/workit-bpm-client/src/camundaBpmClient.ts @@ -15,6 +15,7 @@ import { ICreateWorkflowInstanceResponse, IDeployWorkflowResponse, IHttpResponse, + ILogger, IMessage, IPagination, IPaginationOptions, @@ -30,6 +31,7 @@ import { IWorkflowOptions, IWorkflowProcessIdDefinition, } from 'workit-types'; +import { IoC, PluginLoader, SERVICE_IDENTIFIER, NOOP_LOGGER } from 'workit-core'; import { PaginationUtils } from './utils/paginationUtils'; import { CamundaMessage } from './camundaMessage'; @@ -56,6 +58,10 @@ export class CamundaBpmClient implements IClient, IWorkflowClie this._client = client; this._config = config; this._repo = new CamundaRepository(config); + const pluginLoader = new PluginLoader(IoC, this._getLogger()); + if (config.plugins) { + pluginLoader.load(config.plugins); + } } public subscribe(onMessageReceived: (message: IMessage, service: ICamundaService) => Promise): Promise { @@ -196,4 +202,12 @@ export class CamundaBpmClient implements IClient, IWorkflowClie private _hasBpmnProcessId(request: IWorkflowDefinitionRequest): request is IWorkflowProcessIdDefinition { return (request as IWorkflowProcessIdDefinition).bpmnProcessId !== undefined; } + + private _getLogger(): ILogger { + try { + return IoC.get(SERVICE_IDENTIFIER.logger); + } catch (error) { + return NOOP_LOGGER; + } + } } diff --git a/packages/workit-core/src/config/container.ts b/packages/workit-core/src/config/container.ts index 76ef887f..846f9ae1 100644 --- a/packages/workit-core/src/config/container.ts +++ b/packages/workit-core/src/config/container.ts @@ -12,6 +12,7 @@ import { SuccessStrategySimple } from '../strategies/SuccessStrategySimple'; import { NoopTracerPropagator } from '../tracer/noopTracerPropagator'; import { SERVICE_IDENTIFIER } from './constants/identifiers'; import { IOC } from '../IoC'; +import { NOOP_LOGGER } from '../common/noopLogger'; try { decorate(injectable(), EventEmitter); @@ -24,6 +25,7 @@ try { const kernel = new Container(); const container = new Container(); +kernel.bind(SERVICE_IDENTIFIER.logger).toConstantValue(NOOP_LOGGER); kernel.bind(SERVICE_IDENTIFIER.tracer_propagator).toConstantValue(new NoopTracerPropagator()); kernel.bind(SERVICE_IDENTIFIER.tracer).toConstantValue(NOOP_TRACER); kernel.bind(SERVICE_IDENTIFIER.success_strategy).toConstantValue(new SuccessStrategySimple()); diff --git a/packages/workit-core/src/index.ts b/packages/workit-core/src/index.ts index 5e1f1581..6a47f5c8 100644 --- a/packages/workit-core/src/index.ts +++ b/packages/workit-core/src/index.ts @@ -7,6 +7,7 @@ export * from './config/container'; export * from './config/constants/identifiers'; export * from './config/constants'; +export * from './common/noopLogger'; export * from './processHandler/simpleCamundaProcessHandler'; export * from './interceptors'; @@ -24,4 +25,6 @@ export * from './proxyObserver'; export * from './worker'; +export * from './plugin'; + export * from './utils/utils'; diff --git a/packages/workit-core/src/plugin/basePlugin.ts b/packages/workit-core/src/plugin/basePlugin.ts new file mode 100644 index 00000000..47baf4a9 --- /dev/null +++ b/packages/workit-core/src/plugin/basePlugin.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ + +import { IPluginConfig, IPlugin, ILogger, IIoC } from 'workit-types'; + +// TODO: add bpmn files and failures/success strategies as well + +export abstract class BasePlugin implements IPlugin { + public supportedVersions?: string[]; + + public abstract readonly moduleName: string; + + public readonly version?: string; + + protected _ioc!: IIoC; + + protected _logger!: ILogger; + + protected _config!: IPluginConfig; + + constructor(protected readonly packageName: string) {} + + public enable(ioc: IIoC, logger: ILogger, config?: IPluginConfig): void { + this._ioc = ioc; + this._logger = logger; + if (config) this._config = config; + this.bind(); + } + + public disable(): void { + this.unbind(); + } + + protected abstract bind(): void; + + protected abstract unbind(): void; +} diff --git a/packages/workit-core/src/plugin/index.ts b/packages/workit-core/src/plugin/index.ts new file mode 100644 index 00000000..ab0e3c0a --- /dev/null +++ b/packages/workit-core/src/plugin/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +export * from './basePlugin'; +export * from './pluginLoader'; diff --git a/packages/workit-core/src/plugin/pluginLoader.ts b/packages/workit-core/src/plugin/pluginLoader.ts new file mode 100644 index 00000000..8e5dc060 --- /dev/null +++ b/packages/workit-core/src/plugin/pluginLoader.ts @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ + +/* eslint @typescript-eslint/no-unsafe-assignment: 0 */ +/* eslint @typescript-eslint/no-unsafe-call: 0 */ +/* eslint @typescript-eslint/no-unsafe-member-access: 0 */ +/* eslint @typescript-eslint/no-var-requires: 0 */ +/* eslint @typescript-eslint/no-unsafe-return: 0 */ +/* eslint @typescript-eslint/explicit-member-accessibility: 0 */ +/* eslint @typescript-eslint/restrict-template-expressions: 0 */ +/* eslint import/no-dynamic-require: 0 */ +/* eslint global-require: 0 */ +/* eslint no-restricted-syntax: 0 */ + +import { IPlugin, IPlugins, HookState, ILogger, IIoC } from 'workit-types'; + +/** + * Returns the Plugins object that meet the below conditions. + * Valid criteria: 1. It should be enabled. 2. Should have non-empty path. + */ +function filterPlugins(plugins: IPlugins): IPlugins { + const keys = Object.keys(plugins); + return keys.reduce((acc: IPlugins, key: string) => { + if (plugins[key].enabled && plugins[key].path) acc[key] = plugins[key]; + return acc; + }, {}); +} + +/** + * The PluginLoader class can load instrumentation plugins that use a patch + * mechanism to enable automatic tracing for specific target modules. + */ +export class PluginLoader { + /** A list of loaded plugins. */ + private _plugins: IPlugin[] = []; + + /** + * A field that tracks whether the plugin has been loaded + * for the first time, as well as whether the plugin is activated or not. + */ + private _hookState = HookState.UNINITIALIZED; + + /** Constructs a new PluginLoader instance. */ + constructor(readonly ioc: IIoC, readonly logger: ILogger) {} + + /** + * Loads a list of plugins. Each plugin module should implement the core + * {@link Plugin} interface and export an instance named as 'plugin'. + * @param Plugins an object whose keys are plugin names and whose + * {@link PluginConfig} values indicate several configuration options. + */ + load(plugins: IPlugins): void { + if (this._hookState === HookState.UNINITIALIZED) { + const pluginsToLoad = filterPlugins(plugins); + const modulesToHook = Object.keys(pluginsToLoad); + + if (modulesToHook.length === 0) { + this._hookState = HookState.UNLOADED; + return; + } + + const alreadyRequiredModules = Object.keys(require.cache); + const requiredModulesToHook = modulesToHook.filter( + (name) => + alreadyRequiredModules.find((cached) => { + try { + return require.resolve(name) === cached; + } catch (err) { + return false; + } + }) !== undefined + ); + + if (requiredModulesToHook.length > 0) { + this.logger.info( + `Some modules (${requiredModulesToHook.join( + ', ' + )}) were already required when their respective plugin was loaded, some plugins might not work. Make sure Workit is setup before you require in other modules.` + ); + } + + modulesToHook.forEach((name) => { + const config = pluginsToLoad[name]; + const modulePath = config.path!; + const version = null; + + this.logger.info(`PluginLoader#load: trying loading ${name}@${version}`); + this.logger.debug(`PluginLoader#load: applying binding to ${name}@${version} using ${modulePath} module`); + + // Expecting a plugin from module; + try { + const { plugin } = require(modulePath); + + if (plugin.moduleName !== name) { + this.logger.error(`PluginLoader#load: Entry ${name} use a plugin that instruments ${plugin.moduleName}`); + return exports; + } + + this._plugins.push(plugin); + // Enable each supported plugin. + return plugin.enable(this.ioc, this.logger, config); + } catch (e) { + this.logger.error( + `PluginLoader#load: could not load plugin ${modulePath} of module ${name}. Error: ${e.message}` + ); + return exports; + } + }); + this._hookState = HookState.LOADED; + } else if (this._hookState === HookState.UNLOADED) { + this.logger.error('PluginLoader#load: Currently cannot re-enable plugin loader.'); + } else { + this.logger.error('PluginLoader#load: Plugin loader already enabled.'); + } + } + + /** Unloads plugins. */ + unload(): void { + if (this._hookState === HookState.LOADED) { + for (const plugin of this._plugins) { + plugin.disable(); + } + this._plugins = []; + this._hookState = HookState.UNLOADED; + } + } +} + +/** + * Adds a search path for plugin modules. Intended for testing purposes only. + * @param searchPath The path to add. + */ +export function searchPathForTest(searchPath: string) { + module.paths.push(searchPath); +} diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/index.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/index.js new file mode 100644 index 00000000..1b22b5ce --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/index.js @@ -0,0 +1,5 @@ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./simple-module")); diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/package.json b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/package.json new file mode 100644 index 00000000..5e695d92 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "@villemontreal/plugin-notsupported-module", + "version": "1.0.0" +} diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/simple-module.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/simple-module.js new file mode 100644 index 00000000..f9950d01 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-notsupported-module/simple-module.js @@ -0,0 +1,21 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require('../../../../../lib/plugin/basePlugin'); + +class SimpleModulePlugin extends core_1.BasePlugin { + constructor(name) { + super(`@villemontreal/plugin-${name}`); + this.moduleName = name; + } + + bind() { + + } + + unbind() { + + } +} +exports.SimpleModulePlugin = SimpleModulePlugin; +const plugin = new SimpleModulePlugin('notsupported-module'); +plugin.supportedVersions = ['1.0.0']; +exports.plugin = plugin; diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/index.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/index.js new file mode 100644 index 00000000..1b22b5ce --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/index.js @@ -0,0 +1,5 @@ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./simple-module")); diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/package.json b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/package.json new file mode 100644 index 00000000..e149699a --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "@villemontreal/plugin-simple-module", + "version": "0.0.1" +} diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/simple-module.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/simple-module.js new file mode 100644 index 00000000..45c8a8f3 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-simple-module/simple-module.js @@ -0,0 +1,19 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require('../../../../../lib/plugin/basePlugin'); +class SimpleModulePlugin extends core_1.BasePlugin { + constructor(name) { + super(`@villemontreal/plugin-${name}`); + this.moduleName = name; + } + + bind() { + this._ioc.bindTo(core_1.BasePlugin, 'test', { Bpmn: 'MON_BPMN' }); + } + + unbind() { + this._ioc.unbind('test'); + } +} +exports.SimpleModulePlugin = SimpleModulePlugin; +const plugin = new SimpleModulePlugin('simple-module'); +exports.plugin = plugin; diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/index.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/index.js new file mode 100644 index 00000000..1b22b5ce --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/index.js @@ -0,0 +1,5 @@ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./simple-module")); diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/package.json b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/package.json new file mode 100644 index 00000000..a2e533f4 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "@villemontreal/plugin-supported-module", + "version": "0.0.1" +} diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/simple-module.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/simple-module.js new file mode 100644 index 00000000..1595bd98 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-supported-module/simple-module.js @@ -0,0 +1,20 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require('../../../../../lib/plugin/basePlugin'); + +class SimpleModulePlugin extends core_1.BasePlugin { + constructor(name) { + super(`@villemontreal/plugin-${name}`); + this.moduleName = name; + } + + bind() { + + } + + unbind() { + } +} +exports.SimpleModulePlugin = SimpleModulePlugin; +const plugin = new SimpleModulePlugin(); +plugin.supportedVersions = ['^0.0.1']; +exports.plugin = plugin; diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/index.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/index.js new file mode 100644 index 00000000..3af0ce08 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/index.js @@ -0,0 +1,5 @@ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./task-module")); diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/package.json b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/package.json new file mode 100644 index 00000000..e2fbad5f --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "@villemontreal/plugin-task-module", + "version": "0.0.1" +} diff --git a/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/task-module.js b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/task-module.js new file mode 100644 index 00000000..507350d3 --- /dev/null +++ b/packages/workit-core/tests/units/node_modules/@villemontreal/plugin-task-module/task-module.js @@ -0,0 +1,20 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require('../../../../../lib/plugin/basePlugin'); + +class TaskModulePlugin extends core_1.BasePlugin { + constructor(name) { + super(`@villemontreal/plugin-${name}`); + this.moduleName = name; + } + + bind() { + + } + + unbind() { + + } +} +exports.TaskModulePlugin = TaskModulePlugin; +const plugin = new TaskModulePlugin('task'); +exports.plugin = plugin; diff --git a/packages/workit-core/tests/units/pluginLoader.test.ts b/packages/workit-core/tests/units/pluginLoader.test.ts new file mode 100644 index 00000000..8d82c212 --- /dev/null +++ b/packages/workit-core/tests/units/pluginLoader.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +import * as path from 'path'; +import { PluginLoader, searchPathForTest } from '../../src/plugin/pluginLoader'; +import { IPlugins, HookState } from 'workit-types'; +import { IoC } from '../../src/config/container'; + +const INSTALLED_PLUGINS_PATH = path.join(__dirname, 'node_modules'); + +const simplePlugins: IPlugins = { + 'simple-module': { + enabled: true, + path: `${INSTALLED_PLUGINS_PATH}/@villemontreal/plugin-simple-module`, + }, +}; + +const disablePlugins: IPlugins = { + 'simple-module': { + enabled: false, + path: `${INSTALLED_PLUGINS_PATH}/@villemontreal/plugin-simple-module`, + }, + nonexistent: { + enabled: false, + path: `${INSTALLED_PLUGINS_PATH}/@villemontreal/plugin-nonexistent-module`, + }, +}; + +const nonexistentPlugins: IPlugins = { + nonexistent: { + enabled: true, + path: `${INSTALLED_PLUGINS_PATH}/@villemontreal/plugin-nonexistent-module`, + }, +}; + +const missingPathPlugins: IPlugins = { + 'simple-module': { + enabled: true, + }, + nonexistent: { + enabled: true, + }, +}; + +describe('PluginLoader', () => { + const logger = { + log: () => {}, + warn: () => {}, + error: () => {}, + info: () => {}, + debug: () => {}, + }; + + beforeAll(() => { + module.paths.push(INSTALLED_PLUGINS_PATH); + searchPathForTest(INSTALLED_PLUGINS_PATH); + }); + + afterEach(() => { + // clear require cache + Object.keys(require.cache).forEach((key) => delete require.cache[key]); + }); + + describe('.state()', () => { + it('returns UNINITIALIZED when first called', () => { + const pluginLoader = new PluginLoader(IoC, logger); + expect(pluginLoader['_hookState']).toBe(HookState.UNINITIALIZED); + }); + + it('transitions from UNINITIALIZED to LOADED', () => { + const pluginLoader = new PluginLoader(IoC, logger); + pluginLoader.load(simplePlugins); + expect(pluginLoader['_hookState']).toBe(HookState.LOADED); + pluginLoader.unload(); + }); + + it('transitions from LOADED to UNLOADED', () => { + const pluginLoader = new PluginLoader(IoC, logger); + pluginLoader.load(simplePlugins); + pluginLoader.unload(); + expect(pluginLoader['_hookState']).toBe(HookState.UNLOADED); + }); + }); + describe('.load()', () => { + it('sanity check', () => { + // Ensure that module fixtures contain values that we expect. + const { moduleName, packageName } = require('@villemontreal/plugin-simple-module').plugin; + + expect(moduleName).toBe('simple-module'); + expect(packageName).toBe('@villemontreal/plugin-simple-module'); + expect(IoC.isServiceBound('test')).toBeFalsy(); + expect(() => require('nonexistent-module')).toThrow(); + }); + + it('should load a plugin and bind the target', () => { + const pluginLoader = new PluginLoader(IoC, logger); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.load(simplePlugins); + expect(pluginLoader['_plugins'].length).toBe(1); + expect(IoC.isServiceBound('test')).toBeTruthy(); + pluginLoader.unload(); + }); + + it('should not load a plugin when value is true but path is missing', () => { + const pluginLoader = new PluginLoader(IoC, logger); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.load(missingPathPlugins); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.unload(); + }); + + it('should not load a non existing plugin', () => { + const pluginLoader = new PluginLoader(IoC, logger); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.load(nonexistentPlugins); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.unload(); + }); + + it('should not load a plugin when value is false', () => { + const pluginLoader = new PluginLoader(IoC, logger); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.load(disablePlugins); + expect(pluginLoader['_plugins'].length).toBe(0); + expect(IoC.isServiceBound('test')).toBeFalsy(); + pluginLoader.unload(); + }); + + it(`doesn't patch modules for which plugins aren't specified`, () => { + const pluginLoader = new PluginLoader(IoC, logger); + pluginLoader.load({}); + expect(pluginLoader['_plugins'].length).toBe(0); + pluginLoader.unload(); + }); + }); + + describe('.unload()', () => { + it('should unload a plugin and unbind the target', () => { + const pluginLoader = new PluginLoader(IoC, logger); + + pluginLoader.load(simplePlugins); + pluginLoader.unload(); + + expect(IoC.isServiceBound('test')).toBeFalsy(); + }); + }); +}); diff --git a/packages/workit-types/src/camundaBpm/camundaConfig.ts b/packages/workit-types/src/camundaBpm/camundaConfig.ts index b29c19e1..d5745c0e 100644 --- a/packages/workit-types/src/camundaBpm/camundaConfig.ts +++ b/packages/workit-types/src/camundaBpm/camundaConfig.ts @@ -4,6 +4,7 @@ * See LICENSE file in the project root for full license information. */ +import { IPlugins } from '../plugin'; import { ISubscriptionOptions } from './subscriptionOptions'; export interface ICamundaConfig { @@ -51,4 +52,6 @@ export interface ICamundaConfig { asyncResponseTimeout?: number; // eslint-disable-next-line @typescript-eslint/ban-types interceptors?: Function | Function[]; + /** load librairies containing workflow tasks */ + plugins?: IPlugins; } diff --git a/packages/workit-types/src/core/ioc.ts b/packages/workit-types/src/core/ioc.ts new file mode 100644 index 00000000..c3d0ef00 --- /dev/null +++ b/packages/workit-types/src/core/ioc.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +export interface IIoC { + bindToObject(obj: any, serviceIdentifier: symbol | string, named?: string): void; + bind(serviceIdentifier: string, ctor: any, targetNamed: string, singletonMode?: boolean): void; + get(serviceIdentifier: symbol | string, named?: string | symbol): T; + /** + * Useful for getting task instance for a specific workflow. + * It can check if there is a task for a specific workflow version or it will rollback to the serviceIdentifier if nothing is boundNamed. + * Otherwise it will throw an error + */ + getTask(serviceIdentifier: symbol | string, workflow?: { bpmnProcessId: string; version: number }): T; + + bindTask( + ctor: any, + serviceIdentifier: string | symbol, + workflow: { bpmnProcessId: string; version?: number }, + dependencies?: (symbol | string)[], + singletonMode?: boolean + ): void; +} diff --git a/packages/workit-types/src/index.ts b/packages/workit-types/src/index.ts index 896be6c2..49a6c473 100644 --- a/packages/workit-types/src/index.ts +++ b/packages/workit-types/src/index.ts @@ -26,10 +26,14 @@ export * from './core/client'; export * from './core/successStrategy'; export * from './core/failureStrategy'; export * from './core/camunda'; +export * from './core/ioc'; // Task export * from './tasks/task'; +// Plugin +export * from './plugin'; + // Tracer export * from './tracer/tracerPropagator'; diff --git a/packages/workit-types/src/plugin/hookState.ts b/packages/workit-types/src/plugin/hookState.ts new file mode 100644 index 00000000..9db5cefe --- /dev/null +++ b/packages/workit-types/src/plugin/hookState.ts @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ + +export enum HookState { + UNINITIALIZED, + LOADED, + UNLOADED, +} diff --git a/packages/workit-types/src/plugin/index.ts b/packages/workit-types/src/plugin/index.ts new file mode 100644 index 00000000..441e2b47 --- /dev/null +++ b/packages/workit-types/src/plugin/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +export * from './hookState'; +export * from './plugin'; +export * from './pluginConfig'; +export * from './plugins'; diff --git a/packages/workit-types/src/plugin/plugin.ts b/packages/workit-types/src/plugin/plugin.ts new file mode 100644 index 00000000..e3a06a89 --- /dev/null +++ b/packages/workit-types/src/plugin/plugin.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +import { IIoC } from '../core/ioc'; +import { ILogger } from '../commons/logger'; +import { IPluginConfig } from './pluginConfig'; + +export interface IPlugin { + /** + * Contains all supported versions. + * All versions must be compatible with [semver](https://semver.org/spec/v2.0.0.html) format. + * If the version is not supported, we won't apply tasks (see `enable` method). + * If omitted, all versions of the module will be used. + * NOT IMPLEMENTED YET + */ + supportedVersions?: string[]; + + /** + * Name of the module. + */ + moduleName: string; + + /** + * Method that enables the tasks. + * @param ioc ioc that let you bind tasks among other objects + * @param logger a logger instance. + * @param [config] an object to configure the plugin. + */ + enable(ioc: IIoC, logger: ILogger, config?: IPluginConfig): void; + + /** Method to disable the tasks */ + disable(): void; +} diff --git a/packages/workit-types/src/plugin/pluginConfig.ts b/packages/workit-types/src/plugin/pluginConfig.ts new file mode 100644 index 00000000..201e1aae --- /dev/null +++ b/packages/workit-types/src/plugin/pluginConfig.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +export interface IPluginConfig { + /** + * Whether to enable the plugin. + * @default true + */ + enabled?: boolean; + + /** + * Path of the trace plugin to load. + */ + path?: string; +} diff --git a/packages/workit-types/src/plugin/plugins.ts b/packages/workit-types/src/plugin/plugins.ts new file mode 100644 index 00000000..6c98a93a --- /dev/null +++ b/packages/workit-types/src/plugin/plugins.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 Ville de Montreal. All rights reserved. + * Licensed under the MIT license. + * See LICENSE file in the project root for full license information. + */ +import { IPluginConfig } from './pluginConfig'; + +export interface IPlugins { + [pluginName: string]: IPluginConfig; +} diff --git a/packages/workit-types/src/zeebe/zeebeOptions.ts b/packages/workit-types/src/zeebe/zeebeOptions.ts index a16e487a..bc43871a 100644 --- a/packages/workit-types/src/zeebe/zeebeOptions.ts +++ b/packages/workit-types/src/zeebe/zeebeOptions.ts @@ -4,6 +4,7 @@ * See LICENSE file in the project root for full license information. */ +import { IPlugins } from '../plugin'; import { IZeebeClientOptions } from './zeebeClientOptions'; import { IZeebeWorkerOptions } from './zeebeWorkerOptions'; @@ -20,4 +21,7 @@ export interface IZeebeOptions extends Partial, Partial ) { this._config = config; + const pluginLoader = new PluginLoader(IoC, this._getLogger()); + if (config.plugins) { + pluginLoader.load(config.plugins); + } if (client && (client as Partial).url) { this._exporterConfig = client as Partial; @@ -235,4 +241,12 @@ export class ZeebeClient