backend-plugin-api: allow modules to register extension points

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-08-10 18:01:45 +02:00
parent 999376ee01
commit c7aa4ff179
6 changed files with 32 additions and 3 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-plugin-api': patch
'@backstage/backend-app-api': patch
---
Allow modules to register extension points.
@@ -161,7 +161,7 @@ export class BackendInitializer {
for (const r of feature.getRegistrations()) {
const provides = new Set<ExtensionPoint<unknown>>();
if (r.type === 'plugin') {
if (r.type === 'plugin' || r.type === 'module') {
for (const [extRef, extImpl] of r.extensionPoints) {
if (this.#extensionPoints.has(extRef)) {
throw new Error(
@@ -31,6 +31,11 @@ export interface BackendModuleConfig {
// @public
export interface BackendModuleRegistrationPoints {
// (undocumented)
registerExtensionPoint<TExtensionPoint>(
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
// (undocumented)
registerInit<
Deps extends {
@@ -149,6 +149,7 @@ describe('createBackendModule', () => {
type: 'module',
pluginId: 'x',
moduleId: 'y',
extensionPoints: [],
init: {
deps: expect.any(Object),
func: expect.any(Function),
@@ -159,13 +159,14 @@ export function createBackendPlugin<TOptions extends [options?: object] = []>(
*/
export interface BackendModuleConfig {
/**
* The ID of this plugin.
* Should exactly match the `id` of the plugin that the module extends.
*
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
pluginId: string;
/**
* Should exactly match the `id` of the plugin that the module extends.
* The ID of this module, used to identify the module and ensure that it is not installed twice.
*/
moduleId: string;
register(reg: BackendModuleRegistrationPoints): void;
@@ -194,10 +195,20 @@ export function createBackendModule<TOptions extends [options?: object] = []>(
if (registrations) {
return registrations;
}
const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] =
[];
let init: InternalBackendModuleRegistration['init'] | undefined =
undefined;
c.register({
registerExtensionPoint(ext, impl) {
if (init) {
throw new Error(
'registerExtensionPoint called after registerInit',
);
}
extensionPoints.push([ext, impl]);
},
registerInit(regInit) {
if (init) {
throw new Error('registerInit must only be called once');
@@ -220,6 +231,7 @@ export function createBackendModule<TOptions extends [options?: object] = []>(
type: 'module',
pluginId: c.pluginId,
moduleId: c.moduleId,
extensionPoints,
init,
},
];
@@ -59,6 +59,10 @@ export interface BackendPluginRegistrationPoints {
* @public
*/
export interface BackendModuleRegistrationPoints {
registerExtensionPoint<TExtensionPoint>(
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerInit<Deps extends { [name in string]: unknown }>(options: {
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]> | ExtensionPoint<Deps[name]>;
@@ -105,6 +109,7 @@ export interface InternalBackendModuleRegistration {
pluginId: string;
moduleId: string;
type: 'module';
extensionPoints: Array<readonly [ExtensionPoint<unknown>, unknown]>;
init: {
deps: Record<string, ServiceRef<unknown> | ExtensionPoint<unknown>>;
func(deps: Record<string, unknown>): Promise<void>;