catalog-backend: attribute provider connection failures to modules

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-01-24 16:52:37 +01:00
parent 24eb7d7933
commit f1d29b4d4d
23 changed files with 649 additions and 137 deletions
@@ -35,7 +35,7 @@ describe('createBackendModule', () => {
expect(module.getRegistrations).toEqual(expect.any(Function));
expect(module.getRegistrations()).toEqual([
{
type: 'module',
type: 'module-v1.1',
pluginId: 'x',
moduleId: 'y',
extensionPoints: [],
@@ -17,8 +17,9 @@
import { BackendFeature } from '../types';
import {
BackendModuleRegistrationPoints,
InternalBackendModuleRegistration,
InternalBackendPluginRegistration,
ExtensionPoint,
ExtensionPointFactoryContext,
InternalBackendModuleRegistrationV1_1,
InternalBackendRegistrations,
} from './types';
@@ -55,16 +56,43 @@ export function createBackendModule(
options: CreateBackendModuleOptions,
): BackendFeature {
function getRegistrations() {
const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] =
const extensionPoints: InternalBackendModuleRegistrationV1_1['extensionPoints'] =
[];
let init: InternalBackendModuleRegistration['init'] | undefined = undefined;
let init: InternalBackendModuleRegistrationV1_1['init'] | undefined =
undefined;
options.register({
registerExtensionPoint(ext, impl) {
registerExtensionPoint<TExtensionPoint>(
extOrOpts:
| ExtensionPoint<TExtensionPoint>
| {
extensionPoint: ExtensionPoint<TExtensionPoint>;
factory: (
context: ExtensionPointFactoryContext,
) => TExtensionPoint;
},
impl?: TExtensionPoint,
) {
if (init) {
throw new Error('registerExtensionPoint called after registerInit');
}
extensionPoints.push([ext, impl]);
if (
typeof extOrOpts === 'object' &&
extOrOpts !== null &&
'extensionPoint' in extOrOpts
) {
extensionPoints.push({
extensionPoint: extOrOpts.extensionPoint,
factory: extOrOpts.factory as (
context: ExtensionPointFactoryContext,
) => unknown,
});
} else {
extensionPoints.push({
extensionPoint: extOrOpts,
factory: () => impl,
});
}
},
registerInit(regInit) {
if (init) {
@@ -85,7 +113,7 @@ export function createBackendModule(
return [
{
type: 'module',
type: 'module-v1.1',
pluginId: options.pluginId,
moduleId: options.moduleId,
extensionPoints,
@@ -34,7 +34,7 @@ describe('createBackendPlugin', () => {
expect(plugin.getRegistrations).toEqual(expect.any(Function));
expect(plugin.getRegistrations()).toEqual([
{
type: 'plugin',
type: 'plugin-v1.1',
pluginId: 'x',
extensionPoints: [],
init: {
@@ -17,7 +17,9 @@
import { BackendFeature } from '../types';
import {
BackendPluginRegistrationPoints,
InternalBackendPluginRegistration,
ExtensionPoint,
ExtensionPointFactoryContext,
InternalBackendPluginRegistrationV1_1,
InternalBackendRegistrations,
} from './types';
@@ -49,16 +51,43 @@ export function createBackendPlugin(
options: CreateBackendPluginOptions,
): BackendFeature {
function getRegistrations() {
const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] =
const extensionPoints: InternalBackendPluginRegistrationV1_1['extensionPoints'] =
[];
let init: InternalBackendPluginRegistration['init'] | undefined = undefined;
let init: InternalBackendPluginRegistrationV1_1['init'] | undefined =
undefined;
options.register({
registerExtensionPoint(ext, impl) {
registerExtensionPoint<TExtensionPoint>(
extOrOpts:
| ExtensionPoint<TExtensionPoint>
| {
extensionPoint: ExtensionPoint<TExtensionPoint>;
factory: (
context: ExtensionPointFactoryContext,
) => TExtensionPoint;
},
impl?: TExtensionPoint,
) {
if (init) {
throw new Error('registerExtensionPoint called after registerInit');
}
extensionPoints.push([ext, impl]);
if (
typeof extOrOpts === 'object' &&
extOrOpts !== null &&
'extensionPoint' in extOrOpts
) {
extensionPoints.push({
extensionPoint: extOrOpts.extensionPoint,
factory: extOrOpts.factory as (
context: ExtensionPointFactoryContext,
) => unknown,
});
} else {
extensionPoints.push({
extensionPoint: extOrOpts,
factory: () => impl,
});
}
},
registerInit(regInit) {
if (init) {
@@ -79,7 +108,7 @@ export function createBackendPlugin(
return [
{
type: 'plugin',
type: 'plugin-v1.1',
pluginId: options.pluginId,
extensionPoints,
init,
@@ -30,6 +30,7 @@ export type {
BackendModuleRegistrationPoints,
BackendPluginRegistrationPoints,
ExtensionPoint,
ExtensionPointFactoryContext,
} from './types';
export type {
@@ -36,6 +36,20 @@ export type ExtensionPoint<T> = {
$$type: '@backstage/ExtensionPoint';
};
/**
* Context provided to extension point factories.
*
* @public
*/
export interface ExtensionPointFactoryContext {
/**
* Report a startup failure that happened as part of using an extension that
* the module provided. This should be called before the plugin's `init`
* function returns.
*/
reportModuleStartupFailure(options: { error: Error }): void;
}
/** @ignore */
type DepsToInstances<
TDeps extends {
@@ -53,10 +67,20 @@ type DepsToInstances<
* @public
*/
export interface BackendPluginRegistrationPoints {
/**
* Registers an implementation for an extension point.
*/
registerExtensionPoint<TExtensionPoint>(
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
/**
* Registers a factory that produces a separate implementation for an extension point for each module.
*/
registerExtensionPoint<TExtensionPoint>(options: {
extensionPoint: ExtensionPoint<TExtensionPoint>;
factory: (context: ExtensionPointFactoryContext) => TExtensionPoint;
}): void;
registerInit<
TDeps extends {
[name in string]: ServiceRef<unknown>;
@@ -77,6 +101,10 @@ export interface BackendModuleRegistrationPoints {
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerExtensionPoint<TExtensionPoint>(options: {
extensionPoint: ExtensionPoint<TExtensionPoint>;
factory: (context: ExtensionPointFactoryContext) => TExtensionPoint;
}): void;
registerInit<
TDeps extends {
[name in string]: ServiceRef<unknown> | ExtensionPoint<unknown>;
@@ -92,7 +120,10 @@ export interface InternalBackendRegistrations extends BackendFeature {
version: 'v1';
featureType: 'registrations';
getRegistrations(): Array<
InternalBackendPluginRegistration | InternalBackendModuleRegistration
| InternalBackendPluginRegistration
| InternalBackendModuleRegistration
| InternalBackendPluginRegistrationV1_1
| InternalBackendModuleRegistrationV1_1
>;
}
@@ -119,6 +150,35 @@ export interface InternalBackendModuleRegistration {
};
}
/** @internal */
export type ExtensionPointRegistration = {
extensionPoint: ExtensionPoint<unknown>;
factory: (context: ExtensionPointFactoryContext) => unknown;
};
/** @internal */
export interface InternalBackendPluginRegistrationV1_1 {
pluginId: string;
type: 'plugin-v1.1';
extensionPoints: Array<ExtensionPointRegistration>;
init: {
deps: Record<string, ServiceRef<unknown>>;
func(deps: Record<string, unknown>): Promise<void>;
};
}
/** @internal */
export interface InternalBackendModuleRegistrationV1_1 {
pluginId: string;
moduleId: string;
type: 'module-v1.1';
extensionPoints: Array<ExtensionPointRegistration>;
init: {
deps: Record<string, ServiceRef<unknown> | ExtensionPoint<unknown>>;
func(deps: Record<string, unknown>): Promise<void>;
};
}
/**
* @public
*/