From f3bdd15ab38df03fa2437ef4eb608cbd64d6ff32 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Aug 2022 13:50:45 +0200 Subject: [PATCH 01/11] catalog-node: allow multiple providers to be passed to extension point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- plugins/catalog-backend/src/service/CatalogPlugin.ts | 12 ++++++++---- plugins/catalog-node/api-report.md | 8 ++++++-- plugins/catalog-node/src/extensions.ts | 8 ++++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index ed356df5a2..64e1e52494 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -35,12 +35,16 @@ class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint { #processors = new Array(); #entityProviders = new Array(); - addProcessor(processor: CatalogProcessor): void { - this.#processors.push(processor); + addProcessor( + ...processors: Array> + ): void { + this.#processors.push(...processors.flat()); } - addEntityProvider(provider: EntityProvider): void { - this.#entityProviders.push(provider); + addEntityProvider( + ...providers: Array> + ): void { + this.#entityProviders.push(...providers.flat()); } get processors() { diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index 46e3efef99..26617910b9 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -13,9 +13,13 @@ import { JsonValue } from '@backstage/types'; // @alpha (undocumented) export interface CatalogProcessingExtensionPoint { // (undocumented) - addEntityProvider(provider: EntityProvider): void; + addEntityProvider( + ...providers: Array> + ): void; // (undocumented) - addProcessor(processor: CatalogProcessor): void; + addProcessor( + ...processors: Array> + ): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 80fbd7630f..8dd9d8579a 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -21,8 +21,12 @@ import { CatalogProcessor } from './api/processor'; * @alpha */ export interface CatalogProcessingExtensionPoint { - addProcessor(processor: CatalogProcessor): void; - addEntityProvider(provider: EntityProvider): void; + addProcessor( + ...processors: Array> + ): void; + addEntityProvider( + ...providers: Array> + ): void; } /** From 7ac3306e271df385ca11621e113518c57141d6a1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Aug 2022 13:53:11 +0200 Subject: [PATCH 02/11] catalog-backend-module-github: add initial module export for provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .../api-report.md | 12 ++++ .../package.json | 2 + .../src/index.ts | 2 + .../src/module.ts | 69 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 plugins/catalog-backend-module-github/src/module.ts diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 3e4c2cb06b..8e8055bafc 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -14,6 +15,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -58,6 +60,16 @@ export class GitHubEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha +export const githubEntityProviderCatalogModule: ( + options?: GithubEntityProviderCatalogModuleOptions | undefined, +) => BackendFeature; + +// @alpha +export type GithubEntityProviderCatalogModuleOptions = { + schedule?: TaskScheduleDefinition; +}; + // @public export type GithubMultiOrgConfig = Array<{ name: string; diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index a0b7f10833..88a540077e 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -35,11 +35,13 @@ "dependencies": { "@backstage/backend-common": "^0.15.0", "@backstage/backend-tasks": "^0.3.4", + "@backstage/backend-plugin-api": "^0.1.1", "@backstage/catalog-model": "^1.1.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.1.0", "@backstage/integration": "^1.3.0", "@backstage/plugin-catalog-backend": "^1.3.1", + "@backstage/plugin-catalog-node": "^1.0.1", "@backstage/types": "^1.0.0", "@octokit/graphql": "^5.0.0", "lodash": "^4.17.21", diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index 22a1167d48..d793d48f1e 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -27,3 +27,5 @@ export { GitHubEntityProvider } from './providers/GitHubEntityProvider'; export { GitHubOrgEntityProvider } from './providers/GitHubOrgEntityProvider'; export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntityProvider'; export type { GithubMultiOrgConfig } from './lib'; +export { githubEntityProviderCatalogModule } from './module'; +export type { GithubEntityProviderCatalogModuleOptions } from './module'; diff --git a/plugins/catalog-backend-module-github/src/module.ts b/plugins/catalog-backend-module-github/src/module.ts new file mode 100644 index 0000000000..4eccef0611 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/module.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { GitHubEntityProvider } from './providers/GitHubEntityProvider'; + +/** + * Options for {@link githubEntityProviderCatalogModule}. + * + * @alpha + */ +export type GithubEntityProviderCatalogModuleOptions = { + schedule?: TaskScheduleDefinition; +}; + +/** + * Registers the GitHubEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const githubEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'github-entity-provider', + register(env, options?: GithubEntityProviderCatalogModuleOptions) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + const scheduleDef = options?.schedule ?? { + frequency: { seconds: 600 }, + timeout: { seconds: 900 }, + initialDelay: { seconds: 3 }, + }; + + catalog.addEntityProvider( + GitHubEntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + schedule: scheduler.createScheduledTaskRunner(scheduleDef), + }), + ); + }, + }); + }, +}); From 7220eb700ebd2756f62e6df930ce95296447a4cd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 14:34:32 +0200 Subject: [PATCH 03/11] backend-app-api: add tests for ServiceRegistry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .../src/wiring/ServiceRegistry.test.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 packages/backend-app-api/src/wiring/ServiceRegistry.test.ts diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts new file mode 100644 index 0000000000..438a11130f --- /dev/null +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createServiceRef, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { ServiceRegistry } from './ServiceRegistry'; + +const ref1 = createServiceRef<{ x: number; pluginId: string }>({ + id: '1', +}); +const sf1 = createServiceFactory({ + service: ref1, + deps: {}, + factory: async ({}) => { + return async pluginId => { + return { x: 1, pluginId }; + }; + }, +}); + +const ref2 = createServiceRef<{ x: number; pluginId: string }>({ + id: '2', +}); +const sf2 = createServiceFactory({ + service: ref2, + deps: {}, + factory: async ({}) => { + return async pluginId => { + return { x: 2, pluginId }; + }; + }, +}); +const sf2b = createServiceFactory({ + service: ref2, + deps: {}, + factory: async ({}) => { + return async pluginId => { + return { x: 22, pluginId }; + }; + }, +}); + +describe('ServiceRegistry', () => { + it('should return undefined if there is no factory defined', async () => { + const registry = new ServiceRegistry([]); + expect(registry.get(ref1)).toBe(undefined); + }); + + it('should return a factory for a registered ref', async () => { + const registry = new ServiceRegistry([sf1]); + const factory = registry.get(ref1)!; + expect(factory).toEqual(expect.any(Function)); + await expect(factory('catalog')).resolves.toEqual({ + x: 1, + pluginId: 'catalog', + }); + await expect(factory('scaffolder')).resolves.toEqual({ + x: 1, + pluginId: 'scaffolder', + }); + expect(await factory('catalog')).toBe(await factory('catalog')); + }); + + it('should handle multiple factories with different serviceRefs', async () => { + const registry = new ServiceRegistry([sf1, sf2]); + const factory1 = registry.get(ref1)!; + const factory2 = registry.get(ref2)!; + expect(factory1).toEqual(expect.any(Function)); + expect(factory2).toEqual(expect.any(Function)); + await expect(factory1('catalog')).resolves.toEqual({ + x: 1, + pluginId: 'catalog', + }); + await expect(factory2('catalog')).resolves.toEqual({ + x: 2, + pluginId: 'catalog', + }); + expect(await factory1('catalog')).not.toBe(await factory2('catalog')); + }); + + it('should use the last factory for each ref', async () => { + const registry = new ServiceRegistry([sf2, sf2b]); + const factory2 = registry.get(ref2)!; + await expect(factory2('catalog')).resolves.toEqual({ + x: 22, + pluginId: 'catalog', + }); + }); +}); From 70343d6813f75c6f918ac29c92dd10a589a0675b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 14:16:04 +0200 Subject: [PATCH 04/11] backend-plugin-api: add defaultFactory to ServiceRef MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .../src/services/system/types.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index d578434170..1c6426ecec 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -28,6 +28,14 @@ export type ServiceRef = { */ T: T; + /** + * The default factory that will be used to create service + * instances if not other factory is provided. + */ + defaultFactory?: ( + service: ServiceRef, + ) => Promise>; + toString(): string; $$ref: 'service'; @@ -65,12 +73,19 @@ export type AnyServiceFactory = ServiceFactory< /** * @public */ -export function createServiceRef(options: { id: string }): ServiceRef { +export function createServiceRef(options: { + id: string; + defaultFactory?: ( + service: ServiceRef, + ) => Promise>; +}): ServiceRef { + const { id, defaultFactory } = options; return { - id: options.id, + id, get T(): T { throw new Error(`tried to read ServiceRef.T of ${this}`); }, + defaultFactory, toString() { return `serviceRef{${options.id}}`; }, From eef91a2558e8ea66b973e5330da94aab7be4d7a3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 18:11:41 +0200 Subject: [PATCH 05/11] backend-plugin-api: simplified ServiceFactory type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .changeset/old-tables-joke.md | 5 ++ packages/backend-app-api/api-report.md | 49 +++++-------------- .../src/wiring/BackendInitializer.ts | 4 +- .../src/wiring/BackstageBackend.ts | 7 +-- packages/backend-app-api/src/wiring/types.ts | 4 +- packages/backend-defaults/api-report.md | 4 +- .../backend-defaults/src/CreateBackend.ts | 8 +-- packages/backend-plugin-api/api-report.md | 45 ++++++++--------- .../src/services/system/index.ts | 1 - .../src/services/system/types.ts | 41 ++++++---------- packages/backend-test-utils/api-report.md | 4 +- .../src/next/wiring/TestBackend.ts | 6 +-- 12 files changed, 70 insertions(+), 108 deletions(-) create mode 100644 .changeset/old-tables-joke.md diff --git a/.changeset/old-tables-joke.md b/.changeset/old-tables-joke.md new file mode 100644 index 0000000000..d458244b3a --- /dev/null +++ b/.changeset/old-tables-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Simplified the `ServiceFactory` type and removed `AnyServiceFactory`. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 26c8179771..0f8806870d 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyServiceFactory } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; @@ -29,14 +28,10 @@ export interface Backend { } // @public (undocumented) -export const cacheFactory: ServiceFactory< - PluginCacheManager, - PluginCacheManager, - {} ->; +export const cacheFactory: ServiceFactory; // @public (undocumented) -export const configFactory: ServiceFactory; +export const configFactory: ServiceFactory; // @public (undocumented) export function createSpecializedBackend( @@ -46,46 +41,28 @@ export function createSpecializedBackend( // @public (undocumented) export interface CreateSpecializedBackendOptions { // (undocumented) - services: AnyServiceFactory[]; + services: ServiceFactory[]; } // @public (undocumented) -export const databaseFactory: ServiceFactory< - PluginDatabaseManager, - PluginDatabaseManager, - {} ->; +export const databaseFactory: ServiceFactory; // @public (undocumented) -export const discoveryFactory: ServiceFactory< - PluginEndpointDiscovery, - PluginEndpointDiscovery, - {} ->; +export const discoveryFactory: ServiceFactory; // @public (undocumented) -export const httpRouterFactory: ServiceFactory< - HttpRouterService, - HttpRouterService, - {} ->; +export const httpRouterFactory: ServiceFactory; // @public (undocumented) -export const loggerFactory: ServiceFactory; +export const loggerFactory: ServiceFactory; // @public (undocumented) export const permissionsFactory: ServiceFactory< - PermissionAuthorizer | PermissionEvaluator, - PermissionAuthorizer | PermissionEvaluator, - {} + PermissionAuthorizer | PermissionEvaluator >; // @public (undocumented) -export const schedulerFactory: ServiceFactory< - PluginTaskScheduler, - PluginTaskScheduler, - {} ->; +export const schedulerFactory: ServiceFactory; // @public (undocumented) export type ServiceOrExtensionPoint = @@ -93,12 +70,8 @@ export type ServiceOrExtensionPoint = | ServiceRef; // @public (undocumented) -export const tokenManagerFactory: ServiceFactory< - TokenManager, - TokenManager, - {} ->; +export const tokenManagerFactory: ServiceFactory; // @public (undocumented) -export const urlReaderFactory: ServiceFactory; +export const urlReaderFactory: ServiceFactory; ``` diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index afb5250504..355fa6001d 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -108,7 +108,9 @@ export class BackendInitializer { id: feature.id, provides, consumes: new Set(Object.values(registerOptions.deps)), - deps: registerOptions.deps, + deps: registerOptions.deps as { + [name: string]: ServiceOrExtensionPoint; + }, init: registerOptions.init as BackendRegisterInit['init'], }; }, diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index 1e09ed77ba..1d76161bfe 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - AnyServiceFactory, - BackendFeature, -} from '@backstage/backend-plugin-api'; +import { ServiceFactory, BackendFeature } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; import { ServiceRegistry } from './ServiceRegistry'; import { Backend } from './types'; @@ -26,7 +23,7 @@ export class BackstageBackend implements Backend { #services: ServiceRegistry; #initializer: BackendInitializer; - constructor(apiFactories: AnyServiceFactory[]) { + constructor(apiFactories: ServiceFactory[]) { this.#services = new ServiceRegistry(apiFactories); this.#initializer = new BackendInitializer(this.#services); } diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 0ca40b5660..55009edbdf 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -15,7 +15,7 @@ */ import { - AnyServiceFactory, + ServiceFactory, BackendFeature, ExtensionPoint, FactoryFunc, @@ -43,7 +43,7 @@ export interface BackendRegisterInit { * @public */ export interface CreateSpecializedBackendOptions { - services: AnyServiceFactory[]; + services: ServiceFactory[]; } export type ServiceHolder = { diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md index 4c0fb80714..b5f1e154bb 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -3,8 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyServiceFactory } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) export function createBackend(options?: CreateBackendOptions): Backend; @@ -12,6 +12,6 @@ export function createBackend(options?: CreateBackendOptions): Backend; // @public (undocumented) export interface CreateBackendOptions { // (undocumented) - services?: AnyServiceFactory[]; + services?: ServiceFactory[]; } ``` diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 7cc116a052..819a77ea11 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -28,7 +28,7 @@ import { tokenManagerFactory, urlReaderFactory, } from '@backstage/backend-app-api'; -import { AnyServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; export const defaultServiceFactories = [ cacheFactory, @@ -47,15 +47,15 @@ export const defaultServiceFactories = [ * @public */ export interface CreateBackendOptions { - services?: AnyServiceFactory[]; + services?: ServiceFactory[]; } /** * @public */ export function createBackend(options?: CreateBackendOptions): Backend { - const services = new Map( - defaultServiceFactories.map(sf => [sf.service.id, sf]), + const services = new Map( + defaultServiceFactories.map(sf => [sf.service.id, sf as ServiceFactory]), ); if (options?.services) { diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index fa5097be64..01423390e6 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -16,15 +16,6 @@ import { TokenManager } from '@backstage/backend-common'; import { TransportStreamOptions } from 'winston-transport'; import { UrlReader } from '@backstage/backend-common'; -// @public (undocumented) -export type AnyServiceFactory = ServiceFactory< - unknown, - unknown, - { - [key in string]: unknown; - } ->; - // @public (undocumented) export interface BackendFeature { // (undocumented) @@ -101,15 +92,22 @@ export function createExtensionPoint(options: { // @public (undocumented) export function createServiceFactory< - Api, - Impl extends Api, - Deps extends { + TService, + TImpl extends TService, + TDeps extends { [name in string]: unknown; }, ->(factory: ServiceFactory): ServiceFactory; +>(factory: { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}): ServiceFactory; // @public (undocumented) -export function createServiceRef(options: { id: string }): ServiceRef; +export function createServiceRef(options: { + id: string; + defaultFactory?: (service: ServiceRef) => Promise>; +}): ServiceRef; // @public (undocumented) export const databaseServiceRef: ServiceRef; @@ -168,22 +166,21 @@ export const permissionsServiceRef: ServiceRef< export const schedulerServiceRef: ServiceRef; // @public (undocumented) -export type ServiceFactory< - TApi, - TImpl extends TApi, - TDeps extends { - [name in string]: unknown; - }, -> = { - service: ServiceRef; - deps: TypesToServiceRef; - factory(deps: DepsToDepFactories): Promise>; +export type ServiceFactory = { + service: ServiceRef; + deps: { + [key in string]: ServiceRef; + }; + factory(deps: { + [key in string]: unknown; + }): Promise>; }; // @public export type ServiceRef = { id: string; T: T; + defaultFactory?: (service: ServiceRef) => Promise>; toString(): string; $$ref: 'service'; }; diff --git a/packages/backend-plugin-api/src/services/system/index.ts b/packages/backend-plugin-api/src/services/system/index.ts index eead2297a5..817b0a590f 100644 --- a/packages/backend-plugin-api/src/services/system/index.ts +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -20,6 +20,5 @@ export type { DepsToDepFactories, FactoryFunc, ServiceFactory, - AnyServiceFactory, } from './types'; export { createServiceRef, createServiceFactory } from './types'; diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index 1c6426ecec..dcd047c63d 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -32,9 +32,7 @@ export type ServiceRef = { * The default factory that will be used to create service * instances if not other factory is provided. */ - defaultFactory?: ( - service: ServiceRef, - ) => Promise>; + defaultFactory?: (service: ServiceRef) => Promise>; toString(): string; @@ -53,31 +51,18 @@ export type DepsToDepFactories = { export type FactoryFunc = (pluginId: string) => Promise; /** @public */ -export type ServiceFactory< - TApi, - TImpl extends TApi, - TDeps extends { [name in string]: unknown }, -> = { - service: ServiceRef; - deps: TypesToServiceRef; - factory(deps: DepsToDepFactories): Promise>; +export type ServiceFactory = { + service: ServiceRef; + deps: { [key in string]: ServiceRef }; + factory(deps: { [key in string]: unknown }): Promise>; }; -/** @public */ -export type AnyServiceFactory = ServiceFactory< - unknown, - unknown, - { [key in string]: unknown } ->; - /** * @public */ export function createServiceRef(options: { id: string; - defaultFactory?: ( - service: ServiceRef, - ) => Promise>; + defaultFactory?: (service: ServiceRef) => Promise>; }): ServiceRef { const { id, defaultFactory } = options; return { @@ -97,9 +82,13 @@ export function createServiceRef(options: { * @public */ export function createServiceFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown }, ->(factory: ServiceFactory): ServiceFactory { - return factory; + TService, + TImpl extends TService, + TDeps extends { [name in string]: unknown }, +>(factory: { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}): ServiceFactory { + return factory as ServiceFactory; } diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index b8881e659d..c0f6a43321 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyServiceFactory } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -45,7 +45,7 @@ export interface TestBackendOptions< services?: readonly [ ...{ [index in keyof TServices]: - | AnyServiceFactory + | ServiceFactory | [ServiceRef, Partial]; }, ]; diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index b9276fa9b9..98e979793e 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -16,7 +16,7 @@ import { createSpecializedBackend } from '@backstage/backend-app-api'; import { - AnyServiceFactory, + ServiceFactory, ServiceRef, createServiceFactory, BackendFeature, @@ -31,7 +31,7 @@ export interface TestBackendOptions< services?: readonly [ ...{ [index in keyof TServices]: - | AnyServiceFactory + | ServiceFactory | [ServiceRef, Partial]; }, ]; @@ -68,7 +68,7 @@ export async function startTestBackend< factory: async () => async () => serviceDef[1], }); } - return serviceDef as AnyServiceFactory; + return serviceDef as ServiceFactory; }); const backend = createSpecializedBackend({ From 5ef764830708afc0d52ed448478caad45165798f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 18:12:26 +0200 Subject: [PATCH 06/11] backend-app-api: implement handling of default factories is ServiceRegistry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .../src/wiring/ServiceRegistry.test.ts | 75 ++++++++++++++++++- .../src/wiring/ServiceRegistry.ts | 30 +++++--- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts index 438a11130f..acdd2a4c41 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts @@ -26,7 +26,7 @@ const ref1 = createServiceRef<{ x: number; pluginId: string }>({ const sf1 = createServiceFactory({ service: ref1, deps: {}, - factory: async ({}) => { + factory: async () => { return async pluginId => { return { x: 1, pluginId }; }; @@ -39,7 +39,7 @@ const ref2 = createServiceRef<{ x: number; pluginId: string }>({ const sf2 = createServiceFactory({ service: ref2, deps: {}, - factory: async ({}) => { + factory: async () => { return async pluginId => { return { x: 2, pluginId }; }; @@ -48,13 +48,43 @@ const sf2 = createServiceFactory({ const sf2b = createServiceFactory({ service: ref2, deps: {}, - factory: async ({}) => { + factory: async () => { return async pluginId => { return { x: 22, pluginId }; }; }, }); +const refDefault1 = createServiceRef<{ x: number; pluginId: string }>({ + id: '1', + defaultFactory: async service => + createServiceFactory({ + service, + deps: {}, + factory: async () => async pluginId => ({ x: 10, pluginId }), + }), +}); + +const refDefault2a = createServiceRef<{ x: number; pluginId: string }>({ + id: '2a', + defaultFactory: async service => + createServiceFactory({ + service, + deps: {}, + factory: async () => async pluginId => ({ x: 20, pluginId }), + }), +}); + +const refDefault2b = createServiceRef<{ x: number; pluginId: string }>({ + id: '2b', + defaultFactory: async service => + createServiceFactory({ + service, + deps: {}, + factory: async () => async pluginId => ({ x: 220, pluginId }), + }), +}); + describe('ServiceRegistry', () => { it('should return undefined if there is no factory defined', async () => { const registry = new ServiceRegistry([]); @@ -101,4 +131,43 @@ describe('ServiceRegistry', () => { pluginId: 'catalog', }); }); + + it('should return the defaultFactory from the ref if not provided to the registry', async () => { + const registry = new ServiceRegistry([]); + const factory = registry.get(refDefault1)!; + expect(factory).toEqual(expect.any(Function)); + await expect(factory('catalog')).resolves.toEqual({ + x: 10, + pluginId: 'catalog', + }); + }); + + it('should not return the defaultFactory from the ref if provided to the registry', async () => { + const registry = new ServiceRegistry([sf1]); + const factory = registry.get(refDefault1)!; + expect(factory).toEqual(expect.any(Function)); + await expect(factory('catalog')).resolves.toEqual({ + x: 1, + pluginId: 'catalog', + }); + }); + + it('should handle duplicate defaultFactories by duplicating the implementations', async () => { + const registry = new ServiceRegistry([]); + const factoryA = registry.get(refDefault2a)!; + const factoryB = registry.get(refDefault2b)!; + expect(factoryA).toEqual(expect.any(Function)); + expect(factoryB).toEqual(expect.any(Function)); + await expect(factoryA('catalog')).resolves.toEqual({ + x: 20, + pluginId: 'catalog', + }); + await expect(factoryB('catalog')).resolves.toEqual({ + x: 220, + pluginId: 'catalog', + }); + expect(await factoryA('catalog')).toBe(await factoryA('catalog')); + expect(await factoryB('catalog')).toBe(await factoryB('catalog')); + expect(await factoryA('catalog')).not.toBe(await factoryB('catalog')); + }); }); diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index bdaecf30c6..b471cd67ca 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -14,35 +14,47 @@ * limitations under the License. */ import { - AnyServiceFactory, + ServiceFactory, FactoryFunc, ServiceRef, } from '@backstage/backend-plugin-api'; export class ServiceRegistry { - readonly #implementations: Map>; - readonly #factories: Map; + readonly #providedFactories: Map; + readonly #loadedDefaultFactories: Map; + readonly #implementations: Map>; - constructor(factories: AnyServiceFactory[]) { - this.#factories = new Map(factories.map(f => [f.service.id, f])); + constructor(factories: ServiceFactory[]) { + this.#providedFactories = new Map(factories.map(f => [f.service.id, f])); + this.#loadedDefaultFactories = new Map(); this.#implementations = new Map(); } get(ref: ServiceRef): FactoryFunc | undefined { - const factory = this.#factories.get(ref.id); - if (!factory) { + let factory = this.#providedFactories.get(ref.id); + const { defaultFactory } = ref; + if (!factory && !defaultFactory) { return undefined; } return async (pluginId: string): Promise => { - let implementations = this.#implementations.get(ref.id); + if (!factory) { + let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory!); + if (!loadedFactory) { + loadedFactory = (await defaultFactory!(ref)) as ServiceFactory; + this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory); + } + factory = loadedFactory; + } + + let implementations = this.#implementations.get(factory); if (implementations) { if (implementations.has(pluginId)) { return implementations.get(pluginId) as T; } } else { implementations = new Map(); - this.#implementations.set(ref.id, implementations); + this.#implementations.set(factory, implementations); } const factoryDeps = Object.fromEntries( From 68513f169a2cf5893bcb7caf7dcaf1914536677b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 18:13:32 +0200 Subject: [PATCH 07/11] changesets: added changeset for default service factories Signed-off-by: Patrik Oldsberg --- .changeset/tame-papayas-protect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tame-papayas-protect.md diff --git a/.changeset/tame-papayas-protect.md b/.changeset/tame-papayas-protect.md new file mode 100644 index 0000000000..9a0266748a --- /dev/null +++ b/.changeset/tame-papayas-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +When defining a new `ServiceRef` you can now also include a `defaultFactory`, which will be used to construct instances of the service in case there is no explicit factory defined. From 3c4a3885376e09ae85004f312dc1a034b4f54a03 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 18:18:53 +0200 Subject: [PATCH 08/11] catalog-backend-module-github: enable alpha types build + changeset Signed-off-by: Patrik Oldsberg --- .changeset/gorgeous-swans-kiss.md | 5 +++++ plugins/catalog-backend-module-github/package.json | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/gorgeous-swans-kiss.md diff --git a/.changeset/gorgeous-swans-kiss.md b/.changeset/gorgeous-swans-kiss.md new file mode 100644 index 0000000000..c33eb49d73 --- /dev/null +++ b/.changeset/gorgeous-swans-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +New experimental alpha exports for use with the upcoming backend system. diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 88a540077e..b9a566fb93 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -9,7 +9,8 @@ "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "backend-plugin-module" @@ -24,7 +25,7 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", @@ -57,6 +58,7 @@ }, "files": [ "dist", + "alpha", "config.d.ts" ], "configSchema": "config.d.ts" From 62788b2ee8dfe61e990674b87795e57ca8110a19 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Aug 2022 18:20:53 +0200 Subject: [PATCH 09/11] added changeset for CatalogProcessingExtensionPoint update Signed-off-by: Patrik Oldsberg --- .changeset/strong-games-kiss.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/strong-games-kiss.md diff --git a/.changeset/strong-games-kiss.md b/.changeset/strong-games-kiss.md new file mode 100644 index 0000000000..a5f6d5f864 --- /dev/null +++ b/.changeset/strong-games-kiss.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-node': patch +--- + +The experimental `CatalogProcessingExtensionPoint` now accepts multiple providers and processors at once. From de3347ca74f341881910f9c8fb306629d6f4219c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 18 Aug 2022 10:24:51 +0200 Subject: [PATCH 10/11] added changeset for updates usages of ServiceFactory Signed-off-by: Patrik Oldsberg --- .changeset/spicy-cherries-remember.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/spicy-cherries-remember.md diff --git a/.changeset/spicy-cherries-remember.md b/.changeset/spicy-cherries-remember.md new file mode 100644 index 0000000000..c742c7a1e8 --- /dev/null +++ b/.changeset/spicy-cherries-remember.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/backend-defaults': patch +'@backstage/backend-test-utils': patch +--- + +Updated usages of `ServiceFactory`. From d3ddd3ef44159d319d98f7064361091462b53302 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 18 Aug 2022 12:08:33 +0200 Subject: [PATCH 11/11] Update packages/backend-plugin-api/src/services/system/types.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- packages/backend-plugin-api/src/services/system/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index dcd047c63d..3d6ebfc0dd 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -30,7 +30,7 @@ export type ServiceRef = { /** * The default factory that will be used to create service - * instances if not other factory is provided. + * instances if no other factory is provided. */ defaultFactory?: (service: ServiceRef) => Promise>;