diff --git a/src/plugins/presentation_util/README.mdx b/src/plugins/presentation_util/README.mdx index 8bb13a1aaedce..4a98d90b2de28 100755 --- a/src/plugins/presentation_util/README.mdx +++ b/src/plugins/presentation_util/README.mdx @@ -12,208 +12,12 @@ related: [] The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). -## Plugin Services Toolkit - -While Kibana provides a `useKibana` hook for use in a plugin, the number of services it provides is very large. This presents a set of difficulties: - -- a direct dependency upon the Kibana environment; -- a requirement to mock the full Kibana environment when testing or using Storybook; -- a lack of knowledge as to what services are being consumed at any given time. - -To mitigate these difficulties, the Presentation Team creates services within the plugin that then consume Kibana-provided (or other) services. This is a toolkit for creating simple services within a plugin. - -### Overview - -- A `PluginServiceFactory` is a function that will return a set of functions-- which comprise a `Service`-- given a set of parameters. -- A `PluginServiceProvider` is an object that use a factory to start, stop or provide a `Service`. -- A `PluginServiceRegistry` is a collection of providers for a given environment, (e.g. Kibana, Jest, Storybook, stub, etc). -- A `PluginServices` object uses a registry to provide services throughout the plugin. - -### Defining Services - -To start, a plugin should define a set of services it wants to provide to itself or other plugins. - - -```ts -export interface PresentationDashboardsService { - findDashboards: ( - query: string, - fields: string[] - ) => Promise>>; - findDashboardsByTitle: (title: string) => Promise>>; -} - -export interface PresentationFooService { - getFoo: () => string; - setFoo: (bar: string) => void; -} - -export interface PresentationUtilServices { - dashboards: PresentationDashboardsService; - foo: PresentationFooService; -} -``` - - -This definition will be used in the toolkit to ensure services are complete and as expected. - -### Plugin Services - -The `PluginServices` class hosts a registry of service providers from which a plugin can access its services. It uses the service definition as a generic. - -```ts -export const pluginServices = new PluginServices(); -``` - -This can be placed in the `index.ts` file of a `services` directory within your plugin. - -Once created, it simply requires a `PluginServiceRegistry` to be started and set. - -### Service Provider Registry - -Each environment in which components are used requires a `PluginServiceRegistry` to specify how the providers are started. For example, simple stubs of services require no parameters to start, (so the `StartParameters` generic remains unspecified) - - -```ts -export const providers: PluginServiceProviders = { - dashboards: new PluginServiceProvider(dashboardsServiceFactory), - foo: new PluginServiceProvider(fooServiceFactory), -}; - -export const serviceRegistry = new PluginServiceRegistry(providers); -``` - - -By contrast, a registry that uses Kibana can provide `KibanaPluginServiceParams` to determine how to start its providers, so the `StartParameters` generic is given: - - -```ts -export const providers: PluginServiceProviders< - PresentationUtilServices, - KibanaPluginServiceParams -> = { - dashboards: new PluginServiceProvider(dashboardsServiceFactory), - foo: new PluginServiceProvider(fooServiceFactory), -}; - -export const serviceRegistry = new PluginServiceRegistry< - PresentationUtilServices, - KibanaPluginServiceParams ->(providers); -``` - - -### Service Provider - -A `PluginServiceProvider` is a container for a Service Factory that is responsible for starting, stopping and providing a service implementation. A Service Provider doesn't change, rather the factory and the relevant `StartParameters` change. - -### Service Factories - -A Service Factory is nothing more than a function that uses `StartParameters` to return a set of functions that conforms to a portion of the `Services` specification. For each service, a factory is provided for each environment. - -Given a service definition: - -```ts -export interface PresentationFooService { - getFoo: () => string; - setFoo: (bar: string) => void; -} -``` - -a factory for a stubbed version might look like this: - -```ts -type FooServiceFactory = PluginServiceFactory; - -export const fooServiceFactory: FooServiceFactory = () => ({ - getFoo: () => 'bar', - setFoo: (bar) => { console.log(`${bar} set!`)}, -}); -``` - -and a factory for a Kibana version might look like this: - -```ts -export type FooServiceFactory = KibanaPluginServiceFactory< - PresentationFooService, - PresentationUtilPluginStart ->; - -export const fooServiceFactory: FooServiceFactory = ({ - coreStart, - startPlugins, -}) => { - // ...do something with Kibana services... - - return { - getFoo: //... - setFoo: //... - } -} -``` - -### Using Services - -Once your services and providers are defined, and you have at least one set of factories, you can use `PluginServices` to provide the services to your React components: - - -```ts -// plugin.ts -import { pluginServices } from './services'; -import { registry } from './services/kibana'; - - public start( - coreStart: CoreStart, - startPlugins: StartDeps - ): Promise { - pluginServices.setRegistry(registry.start({ coreStart, startPlugins })); - return {}; - } -``` - - -and wrap your root React component with the `PluginServices` context: - - -```ts -import { pluginServices } from './services'; - -const ContextProvider = pluginServices.getContextProvider(), - -return( - - - {application} - - -) -``` - - -and then, consume your services using provided hooks in a component: - - -```ts -// component.ts - -import { pluginServices } from '../services'; - -export function MyComponent() { - // Retrieve all context hooks from `PluginServices`, destructuring for the one we're using - const { foo } = pluginServices.getHooks(); - - // Use the `useContext` hook to access the API. - const { getFoo } = foo.useService(); - - // ... -} -``` - - ## Redux Embeddables + The Redux Embeddables system allows embeddable authors to interact with their embeddables in a standardized way using Redux toolkit. This wrapper abstracts away store and slice creation, and embeddable input sync. To use this system, a developer can use CreateReduxEmbeddableTools in the constructor of their embeddable, supplying a collection of reducers. ### Reducers + The reducer object expected by the ReduxEmbeddableWrapper is the same type as the reducers expected by [Redux Toolkit's CreateSlice](https://redux-toolkit.js.org/api/createslice). @@ -247,6 +51,8 @@ From components under the embeddable, actions, containerActions, and the current // change specialBoolean after 5 seconds setTimeout(() => embeddableInstance.dispatch.setSpecialBoolean(false), 5000); - } - ``` +} + +``` +``` diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index 16f33d237364a..568a691482a72 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -12,14 +12,6 @@ import { PresentationUtilPlugin } from './plugin'; export type { PresentationLabsService } from './services/presentation_labs_service'; -export type { - KibanaPluginServiceFactory, - PluginServiceFactory, - PluginServiceProviders, - KibanaPluginServiceParams, -} from './services/create'; -export { PluginServices, PluginServiceProvider, PluginServiceRegistry } from './services/create'; - export type { PresentationUtilPluginSetup, PresentationUtilPluginStart } from './types'; export type { SaveModalDashboardProps } from './components/types'; diff --git a/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts b/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts deleted file mode 100644 index eb1a4413056fa..0000000000000 --- a/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { DependencyManager } from './dependency_manager'; - -describe('DependencyManager', () => { - it('orderDependencies. Should sort topology by dependencies', () => { - const graph = { - N: [], - R: [], - A: ['B', 'C'], - B: ['D'], - C: ['F', 'B'], - F: ['E'], - E: ['D'], - D: ['L'], - }; - const sortedTopology = ['N', 'R', 'L', 'D', 'B', 'E', 'F', 'C', 'A']; - expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology); - }); - - it('should include final vertex if it has dependencies', () => { - const graph = { - A: [], - B: [], - C: ['A', 'B'], - }; - const sortedTopology = ['A', 'B', 'C']; - expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology); - }); - - it('orderDependencies. Should return base topology if no depended vertices', () => { - const graph = { - N: [], - R: [], - D: undefined, - }; - const sortedTopology = ['N', 'R', 'D']; - expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology); - }); - - describe('circular dependencies', () => { - it('should detect circular dependencies and throw error with path', () => { - const graph = { - N: ['R'], - R: ['A'], - A: ['B'], - B: ['C'], - C: ['D'], - D: ['E'], - E: ['F'], - F: ['L'], - L: ['G'], - G: ['N'], - }; - const circularPath = ['G', 'L', 'F', 'E', 'D', 'C', 'B', 'A', 'R', 'N'].join(' -> '); - const errorMessage = `Circular dependency detected while setting up services: ${circularPath}`; - - expect(() => DependencyManager.orderDependencies(graph)).toThrowError(errorMessage); - }); - - it('should detect circular dependency if circular reference is the first dependency for a vertex', () => { - const graph = { - A: ['B'], - B: ['A', 'C'], - C: [], - }; - - expect(() => DependencyManager.orderDependencies(graph)).toThrow(); - }); - }); -}); diff --git a/src/plugins/presentation_util/public/services/create/dependency_manager.ts b/src/plugins/presentation_util/public/services/create/dependency_manager.ts deleted file mode 100644 index aa05544648807..0000000000000 --- a/src/plugins/presentation_util/public/services/create/dependency_manager.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -type GraphVertex = string | number | symbol; -type Graph = Record; -type BreadCrumbs = Record; - -interface CycleDetectionResult { - hasCycle: boolean; - path: T[]; -} - -export class DependencyManager { - static orderDependencies(graph: Graph) { - const cycleInfo = DependencyManager.getSortedDependencies(graph); - if (cycleInfo.hasCycle) { - const error = DependencyManager.getCyclePathError(cycleInfo.path); - DependencyManager.throwCyclicPathError(error); - } - - return cycleInfo.path; - } - - /** - * DFS algorithm for checking if graph is a DAG (Directed Acyclic Graph) - * and sorting topogy (dependencies) if graph is DAG. - * @param {Graph} graph - graph of dependencies. - */ - private static getSortedDependencies( - graph: Graph = {} as Graph - ): CycleDetectionResult { - const sortedVertices: Set = new Set(); - const vertices = Object.keys(graph) as T[]; - return vertices.reduce>((cycleInfo, srcVertex) => { - if (cycleInfo.hasCycle) { - return cycleInfo; - } - - return DependencyManager.sortVerticesFrom( - srcVertex, - graph, - sortedVertices, - {}, - {}, - cycleInfo - ); - }, DependencyManager.createCycleInfo()); - } - - /** - * Modified DFS algorithm for topological sort. - * @param {T extends GraphVertex} srcVertex - a source vertex - the start point of dependencies ordering. - * @param {Graph} graph - graph of dependencies, represented in the adjacency list form. - * @param {Set} sortedVertices - ordered dependencies path from the free to the dependent vertex. - * @param {BreadCrumbs} visited - record of visited vertices. - * @param {BreadCrumbs} inpath - record of vertices, which was met in the path. Is used for detecting cycles. - */ - private static sortVerticesFrom( - srcVertex: T, - graph: Graph, - sortedVertices: Set, - visited: BreadCrumbs = {}, - inpath: BreadCrumbs = {}, - cycle: CycleDetectionResult - ): CycleDetectionResult { - visited[srcVertex] = true; - inpath[srcVertex] = true; - - const vertexEdges = - graph[srcVertex] === undefined || graph[srcVertex] === null ? [] : graph[srcVertex]; - - cycle = vertexEdges!.reduce>((info, vertex) => { - if (inpath[vertex]) { - return { ...info, hasCycle: true }; - } else if (!visited[vertex]) { - return DependencyManager.sortVerticesFrom( - vertex, - graph, - sortedVertices, - visited, - inpath, - info - ); - } - return info; - }, cycle); - - inpath[srcVertex] = false; - - if (!sortedVertices.has(srcVertex)) { - sortedVertices.add(srcVertex); - } - - return { - ...cycle, - path: [...sortedVertices], - }; - } - - private static createCycleInfo( - path: T[] = [], - hasCycle: boolean = false - ): CycleDetectionResult { - return { hasCycle, path }; - } - - private static getCyclePathError( - cyclePath: CycleDetectionResult['path'] - ) { - const cycleString = cyclePath.join(' -> '); - return `Circular dependency detected while setting up services: ${cycleString}`; - } - - private static throwCyclicPathError(error: string) { - throw new Error(error); - } -} diff --git a/src/plugins/presentation_util/public/services/create/factory.ts b/src/plugins/presentation_util/public/services/create/factory.ts deleted file mode 100644 index dad0d23929e45..0000000000000 --- a/src/plugins/presentation_util/public/services/create/factory.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { BehaviorSubject } from 'rxjs'; -import { CoreStart, AppUpdater, PluginInitializerContext } from '@kbn/core/public'; - -/** - * A factory function for creating a service. - * - * The `Service` generic determines the shape of the API being produced. - * The `StartParameters` generic determines what parameters are expected to - * create the service. - */ -export type PluginServiceFactory = ( - params: Parameters, - requiredServices: RequiredServices -) => Service; - -/** - * Parameters necessary to create a Kibana-based service, (e.g. during Plugin - * startup or setup). - * - * The `Start` generic refers to the specific Plugin `TPluginsStart`. - */ -export interface KibanaPluginServiceParams { - coreStart: CoreStart; - startPlugins: Start; - appUpdater?: BehaviorSubject; - initContext?: PluginInitializerContext; -} - -/** - * A factory function for creating a Kibana-based service. - * - * The `Service` generic determines the shape of the API being produced. - * The `Setup` generic refers to the specific Plugin `TPluginsSetup`. - * The `Start` generic refers to the specific Plugin `TPluginsStart`. - */ -export type KibanaPluginServiceFactory = ( - params: KibanaPluginServiceParams, - requiredServices: RequiredServices -) => Service; diff --git a/src/plugins/presentation_util/public/services/create/index.ts b/src/plugins/presentation_util/public/services/create/index.ts deleted file mode 100644 index 87afe0ed4c7fa..0000000000000 --- a/src/plugins/presentation_util/public/services/create/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceRegistry } from './registry'; - -export { PluginServiceRegistry } from './registry'; -export type { PluginServiceProviders } from './provider'; -export { PluginServiceProvider } from './provider'; -export type { - PluginServiceFactory, - KibanaPluginServiceFactory, - KibanaPluginServiceParams, -} from './factory'; - -type ServiceHooks = { [K in keyof Services]: { useService: () => Services[K] } }; - -/** - * `PluginServices` is a top-level class for specifying and accessing services within a plugin. - * - * A `PluginServices` object can be provided with a `PluginServiceRegistry` at any time, which will - * then be used to provide services to any component that accesses it. - * - * The `Services` generic determines the shape of all service APIs being produced. - */ -export class PluginServices> { - private registry: PluginServiceRegistry | null = null; - - /** - * Supply a `PluginServiceRegistry` for the class to use to provide services and context. - * - * @param registry A setup and started `PluginServiceRegistry`. - */ - setRegistry(registry: PluginServiceRegistry | null) { - if (registry && !registry.isStarted()) { - throw new Error('Registry has not been started.'); - } - - this.registry = registry; - } - - /** - * Returns true if a registry has been provided, false otherwise. - */ - hasRegistry() { - return !!this.registry; - } - - /** - * Private getter that will enforce proper setup throughout the class. - */ - private getRegistry() { - if (!this.registry) { - throw new Error('No registry has been provided.'); - } - - return this.registry; - } - - /** - * Return the React Context Provider that will supply services. - */ - getContextProvider() { - return this.getRegistry().getContextProvider(); - } - - /** - * Return a map of React Hooks that can be used in React components. - */ - getHooks(): ServiceHooks { - const registry = this.getRegistry(); - const providers = registry.getServiceProviders(); - - const providerNames = Object.keys(providers) as Array; - - return providerNames.reduce((acc, providerName) => { - acc[providerName] = { useService: providers[providerName].getServiceReactHook() }; - return acc; - }, {} as ServiceHooks); - } - - getServices(): Services { - const registry = this.getRegistry(); - const providers = registry.getServiceProviders(); - - const providerNames = Object.keys(providers) as Array; - - return providerNames.reduce((acc, providerName) => { - acc[providerName] = providers[providerName].getService(); - return acc; - }, {} as Services); - } -} diff --git a/src/plugins/presentation_util/public/services/create/provider.tsx b/src/plugins/presentation_util/public/services/create/provider.tsx deleted file mode 100644 index 2418673262061..0000000000000 --- a/src/plugins/presentation_util/public/services/create/provider.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { createContext, useContext, FC, PropsWithChildren } from 'react'; -import { PluginServiceFactory } from './factory'; - -/** - * A collection of `PluginServiceProvider` objects, keyed by the `Services` API generic. - * - * The `Services` generic determines the shape of all service APIs being produced. - * The `StartParameters` generic determines what parameters are expected to - * start the service. - */ -export type PluginServiceProviders< - Services extends Record, - StartParameters = {} -> = { - [K in keyof Services]: PluginServiceProvider< - Services[K], - StartParameters, - Services, - Array - >; -}; - -type ElementOfArray = ArrayType extends Array< - infer ElementType -> - ? ElementType - : never; - -export type PluginServiceRequiredServices< - RequiredServices extends Array, - AvailableServices -> = { - [K in ElementOfArray]: AvailableServices[K]; -}; - -/** - * An object which uses a given factory to start, stop or provide a service. - * - * The `Service` generic determines the shape of the API being produced. - * The `StartParameters` generic determines what parameters are expected to - * start the service. - */ -export class PluginServiceProvider< - Service extends {}, - StartParameters = {}, - Services = {}, - RequiredServices extends Array = [] -> { - private factory: PluginServiceFactory< - Service, - StartParameters, - PluginServiceRequiredServices - >; - private _requiredServices?: RequiredServices; - private context = createContext(null); - private pluginService: Service | null = null; - public readonly Provider: FC> = ({ children }) => { - return {children}; - }; - - constructor( - factory: PluginServiceFactory< - Service, - StartParameters, - PluginServiceRequiredServices - >, - requiredServices?: RequiredServices - ) { - this.factory = factory; - this._requiredServices = requiredServices; - this.context.displayName = 'PluginServiceContext'; - } - - /** - * Getter that will enforce proper setup throughout the class. - */ - public getService() { - if (!this.pluginService) { - throw new Error('Service not started'); - } - return this.pluginService; - } - - /** - * Start the service. - * - * @param params Parameters used to start the service. - */ - start( - params: StartParameters, - requiredServices: PluginServiceRequiredServices - ) { - this.pluginService = this.factory(params, requiredServices); - } - - /** - * Returns a function for providing a Context hook for the service. - */ - getServiceReactHook() { - return () => { - const service = useContext(this.context); - - if (!service) { - throw new Error('Provider is not set up correctly'); - } - - return service; - }; - } - - /** - * Stop the service. - */ - stop() { - this.pluginService = null; - } - - public get requiredServices() { - return this._requiredServices ?? []; - } -} diff --git a/src/plugins/presentation_util/public/services/create/providers_mediator.ts b/src/plugins/presentation_util/public/services/create/providers_mediator.ts deleted file mode 100644 index 45410906ec731..0000000000000 --- a/src/plugins/presentation_util/public/services/create/providers_mediator.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { DependencyManager } from './dependency_manager'; -import { PluginServiceProviders, PluginServiceRequiredServices } from './provider'; - -export class PluginServiceProvidersMediator< - Services extends Record, - StartParameters -> { - constructor(private readonly providers: PluginServiceProviders) {} - - start(params: StartParameters) { - this.getOrderedDependencies().forEach((service) => { - this.providers[service].start(params, this.getServiceDependencies(service)); - }); - } - - stop() { - this.getOrderedDependencies().forEach((service) => this.providers[service].stop()); - } - - private getOrderedDependencies() { - const dependenciesGraph = this.getGraphOfDependencies(); - return DependencyManager.orderDependencies(dependenciesGraph); - } - - private getGraphOfDependencies() { - return this.getProvidersNames().reduce>>( - (graph, vertex) => ({ ...graph, [vertex]: this.providers[vertex].requiredServices ?? [] }), - {} as Record> - ); - } - - private getProvidersNames() { - return Object.keys(this.providers) as Array; - } - - private getServiceDependencies(service: keyof Services) { - const requiredServices = this.providers[service].requiredServices ?? []; - return this.getServicesByDeps(requiredServices); - } - - private getServicesByDeps(deps: Array) { - return deps.reduce, Services>>( - (services, dependency) => ({ - ...services, - [dependency]: this.providers[dependency].getService(), - }), - {} as PluginServiceRequiredServices, Services> - ); - } -} diff --git a/src/plugins/presentation_util/public/services/create/registry.tsx b/src/plugins/presentation_util/public/services/create/registry.tsx deleted file mode 100644 index 0cff51c6f6604..0000000000000 --- a/src/plugins/presentation_util/public/services/create/registry.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { FC, PropsWithChildren } from 'react'; -import { PluginServiceProvider, PluginServiceProviders } from './provider'; -import { PluginServiceProvidersMediator } from './providers_mediator'; - -/** - * A `PluginServiceRegistry` maintains a set of service providers which can be collectively - * started, stopped or retreived. - * - * The `Services` generic determines the shape of all service APIs being produced. - * The `StartParameters` generic determines what parameters are expected to - * start the service. - */ -export class PluginServiceRegistry< - Services extends Record, - StartParameters = {} -> { - private providers: PluginServiceProviders; - private providersMediator: PluginServiceProvidersMediator; - private _isStarted = false; - - constructor(providers: PluginServiceProviders) { - this.providers = providers; - this.providersMediator = new PluginServiceProvidersMediator(providers); - } - - /** - * Returns true if the registry has been started, false otherwise. - */ - isStarted() { - return this._isStarted; - } - - /** - * Returns a map of `PluginServiceProvider` objects. - */ - getServiceProviders() { - if (!this._isStarted) { - throw new Error('Registry not started'); - } - return this.providers; - } - - /** - * Returns a React Context Provider for use in consuming applications. - */ - getContextProvider() { - const values = Object.values(this.getServiceProviders()) as Array< - PluginServiceProvider - >; - - // Collect and combine Context.Provider elements from each Service Provider into a single - // Functional Component. - const provider: FC> = ({ children }) => ( - <> - {values.reduceRight((acc, serviceProvider) => { - return {acc}; - }, children)} - - ); - - return provider; - } - - /** - * Start the registry. - * - * @param params Parameters used to start the registry. - */ - start(params: StartParameters) { - this.providersMediator.start(params); - this._isStarted = true; - return this; - } - - /** - * Stop the registry. - */ - stop() { - this.providersMediator.stop(); - this._isStarted = false; - return this; - } -}