backend-plugin-api: refactor to flatten internal backend feature loader

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-05 01:43:06 +02:00
parent 6061061a81
commit 8f70547ea5
5 changed files with 53 additions and 66 deletions
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { createServiceFactory, createServiceRef } from './types';
import {
InternalServiceFactory,
createServiceFactory,
createServiceRef,
} from './types';
const ref = createServiceRef<string>({ id: 'x' });
const rootDep = createServiceRef<number>({ id: 'y', scope: 'root' });
@@ -36,6 +40,10 @@ describe('createServiceFactory', () => {
},
});
expect(metaFactory).toEqual(expect.any(Function));
expect(metaFactory().$$type).toBe('@backstage/BackendFeature');
expect((metaFactory() as InternalServiceFactory).featureType).toBe(
'service',
);
expect(metaFactory().service).toBe(ref);
// @ts-expect-error
@@ -78,6 +78,7 @@ export interface InternalServiceFactory<
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
> extends ServiceFactory<TService, TScope> {
version: 'v1';
featureType: 'service';
initialization?: 'always' | 'lazy';
deps: { [key in string]: ServiceRef<unknown> };
createRootContext?(deps: { [key in string]: unknown }): Promise<unknown>;
@@ -307,6 +308,7 @@ export function createServiceFactory<
return {
$$type: '@backstage/BackendFeature',
version: 'v1',
featureType: 'service',
service: c.service,
initialization: c.initialization,
deps: c.deps,
@@ -322,6 +324,7 @@ export function createServiceFactory<
return {
$$type: '@backstage/BackendFeature',
version: 'v1',
featureType: 'service',
service: c.service,
initialization: c.initialization,
...('createRootContext' in c
@@ -15,33 +15,25 @@
*/
import { coreServices, createServiceFactory } from '../services';
import { InternalServiceFactory } from '../services/system/types';
import { BackendFeature } from '../types';
import { createBackendFeatureLoader } from './createBackendFeatureLoader';
import { createBackendPlugin } from './createBackendPlugin';
import {
InternalBackendFeature,
InternalBackendFeatureLoaderRegistration,
} from './types';
import { InternalBackendFeatureLoader } from './types';
describe('createBackendFeatureLoader', () => {
it('should create an empty feature loader', () => {
const result = createBackendFeatureLoader({
deps: {},
loader: () => [],
});
}) as InternalBackendFeatureLoader;
const plugin = result as unknown as InternalBackendFeature;
expect(plugin.$$type).toEqual('@backstage/BackendFeature');
expect(plugin.version).toEqual('v1');
expect(plugin.getRegistrations).toEqual(expect.any(Function));
expect(plugin.getRegistrations()).toEqual([
{
type: 'loader',
description: expect.stringMatching(/^created at '.*'$/),
deps: expect.any(Object),
loader: expect.any(Function),
},
]);
expect(result.$$type).toEqual('@backstage/BackendFeature');
expect(result.version).toEqual('v1');
expect(result.featureType).toEqual('loader');
expect(result.deps).toEqual({});
expect(result.loader).toEqual(expect.any(Function));
expect(result.description).toMatch(/^created at '.*'$/);
});
it('should create a feature loader that loads a few features', async () => {
@@ -69,29 +61,21 @@ describe('createBackendFeatureLoader', () => {
}),
];
},
}) as InternalBackendFeature;
}) as InternalBackendFeatureLoader;
expect(result.$$type).toEqual('@backstage/BackendFeature');
expect(result.version).toEqual('v1');
expect(result.getRegistrations).toEqual(expect.any(Function));
expect(result.featureType).toEqual('loader');
const registrations = result.getRegistrations();
expect(registrations).toEqual([
{
type: 'loader',
description: expect.stringMatching(/^created at '.*'$/),
deps: expect.any(Object),
loader: expect.any(Function),
},
]);
const results = await (registrations[0] as any).loader({ config: {} });
const results = await result.loader({ config: {} });
expect(results.length).toBe(3);
const [pluginX, serviceFactory, pluginY] = results;
expect(pluginX.$$type).toBe('@backstage/BackendFeature');
expect(serviceFactory.$$type).toBe('@backstage/BackendFeature');
expect(pluginY.$$type).toBe('@backstage/BackendFeature');
expect(serviceFactory.service.id).toBe(coreServices.pluginMetadata.id);
expect((serviceFactory as InternalServiceFactory).service.id).toBe(
coreServices.pluginMetadata.id,
);
});
it('should support multiple output formats', async () => {
@@ -99,10 +83,8 @@ describe('createBackendFeatureLoader', () => {
const dynamicFeature = Promise.resolve({ default: feature });
async function extractResult(f: BackendFeature) {
const internal = f as InternalBackendFeature;
const reg =
internal.getRegistrations()[0] as InternalBackendFeatureLoaderRegistration;
return reg.loader({});
const internal = f as InternalBackendFeatureLoader;
return internal.loader({});
}
await expect(
@@ -16,7 +16,7 @@
import { ServiceRef } from '../services';
import { BackendFeature } from '../types';
import { InternalBackendFeature } from './types';
import { InternalBackendFeatureLoader } from './types';
/**
* @public
@@ -43,31 +43,25 @@ export interface CreateBackendFeatureLoaderOptions<
export function createBackendFeatureLoader<
TDeps extends { [name in string]: unknown },
>(options: CreateBackendFeatureLoaderOptions<TDeps>): BackendFeature {
const registrations = [
{
type: 'loader',
description: `created at '${new Date().toISOString()}'`,
deps: options.deps,
async loader(deps: TDeps) {
const it = await options.loader(deps);
const result = new Array<BackendFeature>();
for await (const item of it) {
if ('$$type' in item && item.$$type === '@backstage/BackendFeature') {
result.push(item);
} else if ('default' in item) {
result.push(item.default);
} else {
throw new Error(`Invalid item "${item}"`);
}
}
return result;
},
},
];
return {
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: () => registrations,
} as InternalBackendFeature;
featureType: 'loader',
description: `created at '${new Date().toISOString()}'`,
deps: options.deps,
async loader(deps: TDeps) {
const it = await options.loader(deps);
const result = new Array<BackendFeature>();
for await (const item of it) {
if ('$$type' in item && item.$$type === '@backstage/BackendFeature') {
result.push(item);
} else if ('default' in item) {
result.push(item.default);
} else {
throw new Error(`Invalid item "${item}"`);
}
}
return result;
},
} as InternalBackendFeatureLoader;
}
@@ -75,10 +75,9 @@ export interface BackendModuleRegistrationPoints {
/** @internal */
export interface InternalBackendFeature extends BackendFeature {
version: 'v1';
featureType: 'registrations';
getRegistrations(): Array<
| InternalBackendPluginRegistration
| InternalBackendModuleRegistration
| InternalBackendFeatureLoaderRegistration
InternalBackendPluginRegistration | InternalBackendModuleRegistration
>;
}
@@ -108,8 +107,9 @@ export interface InternalBackendModuleRegistration {
/**
* @public
*/
export interface InternalBackendFeatureLoaderRegistration {
type: 'loader';
export interface InternalBackendFeatureLoader extends BackendFeature {
version: 'v1';
featureType: 'loader';
description: string;
deps: Record<string, ServiceRef<unknown>>;
loader(deps: Record<string, unknown>): Promise<BackendFeature[]>;