backend-app-api: fixes, renames, cleanup

Co-authored-by: Johan Haals <johan.haals@gmail.com>
Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-07-08 15:33:26 +02:00
parent 825d769650
commit fcfea69d83
9 changed files with 88 additions and 51 deletions
+2
View File
@@ -1,5 +1,7 @@
# @backstage/backend-app-api
**This package is HIGHLY EXPERIMENTAL, do not use this for production**
This package provides the core API used by Backstage backend apps.
## Installation
+14 -4
View File
@@ -6,10 +6,20 @@
import { AnyServiceFactory } from '@backstage/backend-plugin-api';
import { BackendRegistrable } from '@backstage/backend-plugin-api';
// Warning: (ae-forgotten-export) The symbol "CreateBackendOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Backend" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "createBackend" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface Backend {
// (undocumented)
add(extension: BackendRegistrable): void;
// (undocumented)
start(): Promise<void>;
}
// @public (undocumented)
export function createBackend(options?: CreateBackendOptions): Backend;
// @public (undocumented)
export interface CreateBackendOptions {
// (undocumented)
apis: AnyServiceFactory[];
}
```
+1 -1
View File
@@ -20,4 +20,4 @@
* @packageDocumentation
*/
export { createBackend } from './wiring/types';
export * from './wiring';
@@ -14,31 +14,38 @@
* limitations under the License.
*/
import { BackendRegistrable, ServiceRef } from '@backstage/backend-plugin-api';
import { BackendRegisterInit, ApiHolder } from './types';
import {
BackendRegistrable,
ExtensionPoint,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { BackendRegisterInit, ServiceHolder } from './types';
type ServiceOrExtensionPoint = ExtensionPoint<unknown> | ServiceRef<unknown>;
export class BackendInitializer {
#started = false;
#extensions = new Map<BackendRegistrable, unknown>();
// #stops = [];
#registerInits = new Array<BackendRegisterInit>();
#apis = new Map<ServiceRef<unknown>, unknown>();
#apiHolder: ApiHolder;
#extensionPoints = new Map<ServiceOrExtensionPoint, unknown>();
#serviceHolder: ServiceHolder;
constructor(apiHolder: ApiHolder) {
this.#apiHolder = apiHolder;
constructor(serviceHolder: ServiceHolder) {
this.#serviceHolder = serviceHolder;
}
async #getInitDeps(
deps: { [name: string]: ServiceRef<unknown> },
deps: { [name: string]: ServiceOrExtensionPoint },
pluginId: string,
) {
return Object.fromEntries(
await Promise.all(
Object.entries(deps).map(async ([name, apiRef]) => [
Object.entries(deps).map(async ([name, ref]) => [
name,
this.#apis.get(apiRef) ||
(await this.#apiHolder.get(apiRef)!(pluginId)),
this.#extensionPoints.get(ref) ||
(await this.#serviceHolder.get(ref as ServiceRef<unknown>)!(
pluginId,
)),
]),
),
);
@@ -67,15 +74,15 @@ export class BackendInitializer {
console.log('Registering', extension.id);
extension.register({
registerExtensionPoint: (api, impl) => {
registerExtensionPoint: (extensionPointRef, impl) => {
if (registerInit) {
throw new Error('registerExtensionPoint called after registerInit');
}
if (this.#apis.has(api)) {
throw new Error(`API ${api.id} already registered`);
if (this.#extensionPoints.has(extensionPointRef)) {
throw new Error(`API ${extensionPointRef.id} already registered`);
}
this.#apis.set(api, impl);
provides.add(api);
this.#extensionPoints.set(extensionPointRef, impl);
provides.add(extensionPointRef);
},
registerInit: registerOptions => {
if (registerInit) {
@@ -105,20 +112,11 @@ export class BackendInitializer {
const orderedRegisterResults = this.#resolveInitOrder(this.#registerInits);
for (const registerInit of orderedRegisterResults) {
// TODO: DI
const deps = await this.#getInitDeps(registerInit.deps, registerInit.id);
await registerInit.init(deps);
// Maybe return stop? or lifecycle API
// this.#stops.push();
}
}
// async stop(): Promise<void> {
// for (const stop of this.#stops) {
// await stop.stop();
// }
// }
private validateSetup() {}
#resolveInitOrder(registerInits: Array<BackendRegisterInit>) {
@@ -133,13 +131,13 @@ export class BackendInitializer {
for (const registerInit of registerInitsToOrder) {
const unInitializedDependents = [];
for (const api of registerInit.provides) {
for (const serviceRef of registerInit.provides) {
if (
registerInitsToOrder.some(
init => init !== registerInit && init.consumes.has(api),
init => init !== registerInit && init.consumes.has(serviceRef),
)
) {
unInitializedDependents.push(api);
unInitializedDependents.push(serviceRef);
}
}
@@ -19,16 +19,16 @@ import {
BackendRegistrable,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { CoreApiRegistry } from './CoreApiRegistry';
import { ServiceRegistry } from './ServiceRegistry';
import { Backend } from './types';
export class BackstageBackend implements Backend {
#coreApis: CoreApiRegistry;
#services: ServiceRegistry;
#initializer: BackendInitializer;
constructor(apiFactories: AnyServiceFactory[]) {
this.#coreApis = new CoreApiRegistry(apiFactories);
this.#initializer = new BackendInitializer(this.#coreApis);
this.#services = new ServiceRegistry(apiFactories);
this.#initializer = new BackendInitializer(this.#services);
}
add(extension: BackendRegistrable): void {
@@ -19,7 +19,7 @@ import {
ServiceRef,
} from '@backstage/backend-plugin-api';
export class CoreApiRegistry {
export class ServiceRegistry {
readonly #implementations: Map<string, Map<string, unknown>>;
readonly #factories: Map<string, AnyServiceFactory>;
@@ -35,29 +35,27 @@ export class CoreApiRegistry {
}
return async (pluginId: string): Promise<T> => {
if (this.#implementations.has(ref.id)) {
if (this.#implementations.get(ref.id)!.has(pluginId)) {
return this.#implementations.get(ref.id)!.get(pluginId) as T;
let implementations = this.#implementations.get(ref.id);
if (implementations) {
if (implementations.has(pluginId)) {
return implementations.get(pluginId) as T;
}
this.#implementations.set(ref.id, new Map<string, unknown>());
} else {
this.#implementations.set(ref.id, new Map());
implementations = new Map();
this.#implementations.set(ref.id, implementations);
}
const factoryDeps = Object.fromEntries(
Object.entries(factory.deps).map(([name, apiRef]) => [
Object.entries(factory.deps).map(([name, serviceRef]) => [
name,
this.get(apiRef)!, // TODO: throw
this.get(serviceRef)!, // TODO: throw
]),
);
const factoryFunc = await factory.factory(factoryDeps);
const implementation = await factoryFunc(pluginId);
this.#implementations.set(
ref.id,
this.#implementations.get(ref.id)!.set(pluginId, implementation),
);
implementations.set(pluginId, implementation);
return implementation as T;
};
@@ -0,0 +1,18 @@
/*
* 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.
*/
export type { Backend, CreateBackendOptions } from './types';
export { createBackend } from './types';
+11 -2
View File
@@ -23,6 +23,9 @@ import {
import { defaultServiceFactories } from '../services/implementations';
import { BackstageBackend } from './BackstageBackend';
/**
* @public
*/
export interface Backend {
add(extension: BackendRegistrable): void;
start(): Promise<void>;
@@ -36,14 +39,20 @@ export interface BackendRegisterInit {
init: (deps: { [name: string]: unknown }) => Promise<void>;
}
interface CreateBackendOptions {
/**
* @public
*/
export interface CreateBackendOptions {
apis: AnyServiceFactory[];
}
export type ApiHolder = {
export type ServiceHolder = {
get<T>(api: ServiceRef<T>): FactoryFunc<T> | undefined;
};
/**
* @public
*/
export function createBackend(options?: CreateBackendOptions): Backend {
// TODO: merge with provided APIs
return new BackstageBackend([
+2
View File
@@ -1,5 +1,7 @@
# @backstage/backend-plugin-api
**This package is HIGHLY EXPERIMENTAL, do not use this for production**
This package provides the core API used by Backstage backend plugins.
## Installation