Merge pull request #12484 from backstage/mob/backend-system

Alpha Backend Framework 🚀
This commit is contained in:
Patrik Oldsberg
2022-07-08 16:20:09 +02:00
committed by GitHub
67 changed files with 2215 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+19
View File
@@ -0,0 +1,19 @@
# @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
Add the library to your backend app package:
```bash
# From your Backstage root directory
yarn add --cwd packages/backend @backstage/backend-app-api
```
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md)
+25
View File
@@ -0,0 +1,25 @@
## API Report File for "@backstage/backend-app-api"
> 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 { BackendRegistrable } from '@backstage/backend-plugin-api';
// @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[];
}
```
+52
View File
@@ -0,0 +1,52 @@
{
"name": "@backstage/backend-app-api",
"description": "Core API used by Backstage backend apps",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"alphaTypes": "dist/index.alpha.d.ts"
},
"backstage": {
"role": "node-library"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend-app-api"
},
"keywords": [
"backstage"
],
"license": "Apache-2.0",
"scripts": {
"build": "backstage-cli package build --experimental-type-build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean",
"start": "backstage-cli package start"
},
"dependencies": {
"@backstage/backend-plugin-api": "^0.0.0",
"@backstage/backend-common": "^0.14.1-next.2",
"@backstage/backend-tasks": "^0.3.3-next.2",
"@backstage/plugin-permission-node": "^0.6.3-next.1",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.18.0-next.2"
},
"files": [
"dist",
"alpha"
]
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2020 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.
*/
/**
* Core API used by Backstage backend apps.
*
* @packageDocumentation
*/
export * from './wiring';
@@ -0,0 +1,37 @@
/*
* 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 { CacheManager } from '@backstage/backend-common';
import {
configServiceRef,
createServiceFactory,
cacheServiceRef,
} from '@backstage/backend-plugin-api';
// TODO: Work out some naming and implementation patterns for these
export const cacheFactory = createServiceFactory({
service: cacheServiceRef,
deps: {
configFactory: configServiceRef,
},
factory: async ({ configFactory }) => {
const config = await configFactory('root');
const cacheManager = CacheManager.fromConfig(config);
return async (pluginId: string) => {
return cacheManager.forPlugin(pluginId);
};
},
});
@@ -0,0 +1,40 @@
/*
* 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 { loadBackendConfig } from '@backstage/backend-common';
import {
configServiceRef,
createServiceFactory,
loggerToWinstonLogger,
loggerServiceRef,
} from '@backstage/backend-plugin-api';
export const configFactory = createServiceFactory({
service: configServiceRef,
deps: {
loggerFactory: loggerServiceRef,
},
factory: async ({ loggerFactory }) => {
const logger = await loggerFactory('root');
const config = await loadBackendConfig({
argv: process.argv,
logger: loggerToWinstonLogger(logger),
});
return async () => {
return config;
};
},
});
@@ -0,0 +1,36 @@
/*
* 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 { DatabaseManager } from '@backstage/backend-common';
import {
configServiceRef,
createServiceFactory,
databaseServiceRef,
} from '@backstage/backend-plugin-api';
export const databaseFactory = createServiceFactory({
service: databaseServiceRef,
deps: {
configFactory: configServiceRef,
},
factory: async ({ configFactory }) => {
const config = await configFactory('root');
const databaseManager = DatabaseManager.fromConfig(config);
return async (pluginId: string) => {
return databaseManager.forPlugin(pluginId);
};
},
});
@@ -0,0 +1,36 @@
/*
* 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 { SingleHostDiscovery } from '@backstage/backend-common';
import {
configServiceRef,
createServiceFactory,
discoveryServiceRef,
} from '@backstage/backend-plugin-api';
export const discoveryFactory = createServiceFactory({
service: discoveryServiceRef,
deps: {
configFactory: configServiceRef,
},
factory: async ({ configFactory }) => {
const config = await configFactory('root');
const discovery = SingleHostDiscovery.fromConfig(config);
return async () => {
return discovery;
};
},
});
@@ -0,0 +1,49 @@
/*
* 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 {
createServiceFactory,
httpRouterServiceRef,
configServiceRef,
} from '@backstage/backend-plugin-api';
import Router from 'express-promise-router';
import { Handler } from 'express';
import { createServiceBuilder } from '@backstage/backend-common';
export const httpRouterFactory = createServiceFactory({
service: httpRouterServiceRef,
deps: {
configFactory: configServiceRef,
},
factory: async ({ configFactory }) => {
const rootRouter = Router();
const service = createServiceBuilder(module)
.loadConfig(await configFactory('root'))
.addRouter('', rootRouter);
await service.start();
return async (pluginId?: string) => {
const path = pluginId ? `/api/${pluginId}` : '';
return {
use(handler: Handler) {
rootRouter.use(path, handler);
},
};
};
},
});
@@ -0,0 +1,39 @@
/*
* 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 { cacheFactory } from './cacheService';
import { configFactory } from './configService';
import { databaseFactory } from './databaseService';
import { discoveryFactory } from './discoveryService';
import { loggerFactory } from './loggerService';
import { permissionsFactory } from './permissionsService';
import { schedulerFactory } from './schedulerService';
import { tokenManagerFactory } from './tokenManagerService';
import { urlReaderFactory } from './urlReaderService';
import { httpRouterFactory } from './httpRouterService';
export const defaultServiceFactories = [
cacheFactory,
configFactory,
databaseFactory,
discoveryFactory,
loggerFactory,
permissionsFactory,
schedulerFactory,
tokenManagerFactory,
urlReaderFactory,
httpRouterFactory,
];
@@ -0,0 +1,50 @@
/*
* 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 { createRootLogger } from '@backstage/backend-common';
import {
createServiceFactory,
Logger,
loggerServiceRef,
} from '@backstage/backend-plugin-api';
import { Logger as WinstonLogger } from 'winston';
class BackstageLogger implements Logger {
static fromWinston(logger: WinstonLogger): BackstageLogger {
return new BackstageLogger(logger);
}
private constructor(private readonly winston: WinstonLogger) {}
info(message: string, ...meta: any[]): void {
this.winston.info(message, ...meta);
}
child(fields: { [name: string]: string }): Logger {
return new BackstageLogger(this.winston.child(fields));
}
}
export const loggerFactory = createServiceFactory({
service: loggerServiceRef,
deps: {},
factory: async () => {
const root = BackstageLogger.fromWinston(createRootLogger());
return async (pluginId: string) => {
return root.child({ pluginId });
};
},
});
@@ -0,0 +1,45 @@
/*
* 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 {
configServiceRef,
createServiceFactory,
discoveryServiceRef,
permissionsServiceRef,
tokenManagerServiceRef,
} from '@backstage/backend-plugin-api';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
export const permissionsFactory = createServiceFactory({
service: permissionsServiceRef,
deps: {
configFactory: configServiceRef,
discoveryFactory: discoveryServiceRef,
tokenManagerFactory: tokenManagerServiceRef,
},
factory: async ({ configFactory, discoveryFactory, tokenManagerFactory }) => {
const config = await configFactory('root');
const discovery = await discoveryFactory('root');
const tokenManager = await tokenManagerFactory('root');
const permissions = ServerPermissionClient.fromConfig(config, {
discovery,
tokenManager,
});
return async (_pluginId: string) => {
return permissions;
};
},
});
@@ -0,0 +1,36 @@
/*
* 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 {
configServiceRef,
createServiceFactory,
schedulerServiceRef,
} from '@backstage/backend-plugin-api';
import { TaskScheduler } from '@backstage/backend-tasks';
export const schedulerFactory = createServiceFactory({
service: schedulerServiceRef,
deps: {
configFactory: configServiceRef,
},
factory: async ({ configFactory }) => {
const config = await configFactory('root');
const taskScheduler = TaskScheduler.fromConfig(config);
return async (pluginId: string) => {
return taskScheduler.forPlugin(pluginId);
};
},
});
@@ -0,0 +1,60 @@
/*
* 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 {
configServiceRef,
loggerServiceRef,
createServiceFactory,
tokenManagerServiceRef,
loggerToWinstonLogger,
} from '@backstage/backend-plugin-api';
import { ServerTokenManager } from '@backstage/backend-common';
export const tokenManagerFactory = createServiceFactory({
service: tokenManagerServiceRef,
deps: {
configFactory: configServiceRef,
loggerFactory: loggerServiceRef,
},
factory: async ({ configFactory, loggerFactory }) => {
const logger = await loggerFactory('root');
const config = await configFactory('root');
return async (_pluginId: string) => {
// doesn't the logger want to be inferred from the plugin tho here?
// maybe ... also why do we recreate it every time otherwise
// we should memoize on a per plugin right? so I think it's should be fine to re-use the plugin one
// we shouldn't recreate on a per plugin basis.
// hm - on the other hand, is this really ever called more than once?
// not this function right. should only be called when the plugin requests this serviceRef
// yeah so no need to worry about memo probably
// but we still want to scope the logger to the ServrTokenmanagfer>?
// mm sure maybe
// maybe in this case it doesn't provide so much value b
// oh hang on - isn't it up to THE MANAGER to make a child internally if it wants to do that
// so that it becomes a property intrinsic to that class, no matter how it's constructed
// or is that too much responsibility for it - making the constructor complex so to speak, making it harder to tweak that behavior
// this is not ultra efficient :)
// I think the naming here is wrong to be gonest
// this isn't like the cache manager or the database manager
// the manager name is confusuion i think
// aye perhaps
return ServerTokenManager.fromConfig(config, {
logger: loggerToWinstonLogger(logger),
});
};
},
});
@@ -0,0 +1,41 @@
/*
* 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 { UrlReaders } from '@backstage/backend-common';
import {
configServiceRef,
createServiceFactory,
loggerServiceRef,
loggerToWinstonLogger,
urlReaderServiceRef,
} from '@backstage/backend-plugin-api';
export const urlReaderFactory = createServiceFactory({
service: urlReaderServiceRef,
deps: {
configFactory: configServiceRef,
loggerFactory: loggerServiceRef,
},
factory: async ({ configFactory, loggerFactory }) => {
return async (pluginId: string) => {
const logger = await loggerFactory(pluginId);
return UrlReaders.default({
logger: loggerToWinstonLogger(logger),
config: await configFactory(pluginId),
});
};
},
});
@@ -0,0 +1,155 @@
/*
* 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 {
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>();
#registerInits = new Array<BackendRegisterInit>();
#extensionPoints = new Map<ServiceOrExtensionPoint, unknown>();
#serviceHolder: ServiceHolder;
constructor(serviceHolder: ServiceHolder) {
this.#serviceHolder = serviceHolder;
}
async #getInitDeps(
deps: { [name: string]: ServiceOrExtensionPoint },
pluginId: string,
) {
return Object.fromEntries(
await Promise.all(
Object.entries(deps).map(async ([name, ref]) => [
name,
this.#extensionPoints.get(ref) ||
(await this.#serviceHolder.get(ref as ServiceRef<unknown>)!(
pluginId,
)),
]),
),
);
}
add<TOptions>(extension: BackendRegistrable, options?: TOptions) {
if (this.#started) {
throw new Error(
'extension can not be added after the backend has started',
);
}
this.#extensions.set(extension, options);
}
async start(): Promise<void> {
console.log(`Starting backend`);
if (this.#started) {
throw new Error('Backend has already started');
}
this.#started = true;
for (const [extension] of this.#extensions) {
const provides = new Set<ServiceRef<unknown>>();
let registerInit: BackendRegisterInit | undefined = undefined;
console.log('Registering', extension.id);
extension.register({
registerExtensionPoint: (extensionPointRef, impl) => {
if (registerInit) {
throw new Error('registerExtensionPoint called after registerInit');
}
if (this.#extensionPoints.has(extensionPointRef)) {
throw new Error(`API ${extensionPointRef.id} already registered`);
}
this.#extensionPoints.set(extensionPointRef, impl);
provides.add(extensionPointRef);
},
registerInit: registerOptions => {
if (registerInit) {
throw new Error('registerInit must only be called once');
}
registerInit = {
id: extension.id,
provides,
consumes: new Set(Object.values(registerOptions.deps)),
deps: registerOptions.deps,
init: registerOptions.init as BackendRegisterInit['init'],
};
},
});
if (!registerInit) {
throw new Error(
`registerInit was not called by register in ${extension.id}`,
);
}
this.#registerInits.push(registerInit);
}
this.validateSetup();
const orderedRegisterResults = this.#resolveInitOrder(this.#registerInits);
for (const registerInit of orderedRegisterResults) {
const deps = await this.#getInitDeps(registerInit.deps, registerInit.id);
await registerInit.init(deps);
}
}
private validateSetup() {}
#resolveInitOrder(registerInits: Array<BackendRegisterInit>) {
let registerInitsToOrder = registerInits.slice();
const orderedRegisterInits = new Array<BackendRegisterInit>();
// TODO: Validate duplicates
while (registerInitsToOrder.length > 0) {
const toRemove = new Set<unknown>();
for (const registerInit of registerInitsToOrder) {
const unInitializedDependents = [];
for (const serviceRef of registerInit.provides) {
if (
registerInitsToOrder.some(
init => init !== registerInit && init.consumes.has(serviceRef),
)
) {
unInitializedDependents.push(serviceRef);
}
}
if (unInitializedDependents.length === 0) {
console.log(`DEBUG: pushed ${registerInit.id} to results`);
orderedRegisterInits.push(registerInit);
toRemove.add(registerInit);
}
}
registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r));
}
return orderedRegisterInits;
}
}
@@ -0,0 +1,45 @@
/*
* 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 {
AnyServiceFactory,
BackendRegistrable,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { ServiceRegistry } from './ServiceRegistry';
import { Backend } from './types';
export class BackstageBackend implements Backend {
#services: ServiceRegistry;
#initializer: BackendInitializer;
constructor(apiFactories: AnyServiceFactory[]) {
this.#services = new ServiceRegistry(apiFactories);
this.#initializer = new BackendInitializer(this.#services);
}
add(extension: BackendRegistrable): void {
this.#initializer.add(extension);
}
async start(): Promise<void> {
await this.#initializer.start();
}
// async stop(): Promise<void> {
// await this.#initializer.stop();
// }
}
@@ -0,0 +1,63 @@
/*
* 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 {
AnyServiceFactory,
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
export class ServiceRegistry {
readonly #implementations: Map<string, Map<string, unknown>>;
readonly #factories: Map<string, AnyServiceFactory>;
constructor(factories: AnyServiceFactory[]) {
this.#factories = new Map(factories.map(f => [f.service.id, f]));
this.#implementations = new Map();
}
get<T>(ref: ServiceRef<T>): FactoryFunc<T> | undefined {
const factory = this.#factories.get(ref.id);
if (!factory) {
return undefined;
}
return async (pluginId: string): Promise<T> => {
let implementations = this.#implementations.get(ref.id);
if (implementations) {
if (implementations.has(pluginId)) {
return implementations.get(pluginId) as T;
}
} else {
implementations = new Map();
this.#implementations.set(ref.id, implementations);
}
const factoryDeps = Object.fromEntries(
Object.entries(factory.deps).map(([name, serviceRef]) => [
name,
this.get(serviceRef)!, // TODO: throw
]),
);
const factoryFunc = await factory.factory(factoryDeps);
const implementation = await factoryFunc(pluginId);
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';
@@ -0,0 +1,62 @@
/*
* 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 {
AnyServiceFactory,
BackendRegistrable,
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { defaultServiceFactories } from '../services/implementations';
import { BackstageBackend } from './BackstageBackend';
/**
* @public
*/
export interface Backend {
add(extension: BackendRegistrable): void;
start(): Promise<void>;
}
export interface BackendRegisterInit {
id: string;
consumes: Set<ServiceRef<unknown>>;
provides: Set<ServiceRef<unknown>>;
deps: { [name: string]: ServiceRef<unknown> };
init: (deps: { [name: string]: unknown }) => Promise<void>;
}
/**
* @public
*/
export interface CreateBackendOptions {
apis: AnyServiceFactory[];
}
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([
...defaultServiceFactories,
...(options?.apis ?? []),
]);
}
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+5
View File
@@ -0,0 +1,5 @@
# example-backend-next
This is an example backend for the new **EXPERIMENTAL** Backstage backend system.
Do not use this in your own projects.
+38
View File
@@ -0,0 +1,38 @@
{
"name": "example-backend-next",
"version": "0.0.0",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"backstage": {
"role": "backend"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend-next"
},
"keywords": [
"backstage"
],
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/backend-app-api": "^0.0.0",
"@backstage/plugin-catalog-backend": "^1.2.1-next.2",
"@backstage/plugin-scaffolder-backend": "^1.4.0-next.2"
},
"devDependencies": {
"@backstage/cli": "^0.18.0-next.2"
},
"files": [
"dist"
]
}
+27
View File
@@ -0,0 +1,27 @@
/*
* 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 { createBackend } from '@backstage/backend-app-api';
import { catalogPlugin } from '@backstage/plugin-catalog-backend';
import { scaffolderCatalogModule } from '@backstage/plugin-scaffolder-backend';
const backend = createBackend({
apis: [],
});
backend.add(catalogPlugin({}));
backend.add(scaffolderCatalogModule({}));
backend.start();
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+19
View File
@@ -0,0 +1,19 @@
# @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
Add the library to your backend plugin package:
```bash
# From your Backstage root directory
yarn add --cwd plugin/<plugin>-backend @backstage/backend-plugin-api
```
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md)
+197
View File
@@ -0,0 +1,197 @@
## API Report File for "@backstage/backend-plugin-api"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Config } from '@backstage/config';
import { Handler } from 'express';
import { Logger as Logger_2 } from 'winston';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PluginCacheManager } from '@backstage/backend-common';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
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 BackendInitRegistry {
// (undocumented)
registerExtensionPoint<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
impl: TExtensionPoint,
): void;
// (undocumented)
registerInit<
Deps extends {
[name in string]: unknown;
},
>(options: {
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]>;
};
init: (deps: Deps) => Promise<void>;
}): void;
}
// @public (undocumented)
export interface BackendModuleConfig<TOptions> {
// (undocumented)
moduleId: string;
// (undocumented)
pluginId: string;
// (undocumented)
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
// @public (undocumented)
export interface BackendPluginConfig<TOptions> {
// (undocumented)
id: string;
// (undocumented)
register(reg: BackendInitRegistry, options: TOptions): void;
}
// @public (undocumented)
export interface BackendRegistrable {
// (undocumented)
id: string;
// (undocumented)
register(reg: BackendInitRegistry): void;
}
// @public (undocumented)
export const cacheServiceRef: ServiceRef<PluginCacheManager>;
// @public (undocumented)
export const configServiceRef: ServiceRef<Config>;
// @public (undocumented)
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): (option: TOptions) => BackendRegistrable;
// @public (undocumented)
export function createBackendPlugin<TOptions>(
config: BackendPluginConfig<TOptions>,
): (option: TOptions) => BackendRegistrable;
// @public (undocumented)
export function createExtensionPoint<T>(options: {
id: string;
}): ExtensionPoint<T>;
// @public (undocumented)
export function createServiceFactory<
Api,
Impl extends Api,
Deps extends {
[name in string]: unknown;
},
>(factory: ServiceFactory<Api, Impl, Deps>): ServiceFactory<Api, Api, {}>;
// @public (undocumented)
export function createServiceRef<T>(options: { id: string }): ServiceRef<T>;
// @public (undocumented)
export const databaseServiceRef: ServiceRef<PluginDatabaseManager>;
// @public (undocumented)
export type DepsToDepFactories<T> = {
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
};
// @public (undocumented)
export const discoveryServiceRef: ServiceRef<PluginEndpointDiscovery>;
// @public
export type ExtensionPoint<T> = {
id: string;
T: T;
toString(): string;
$$ref: 'extension-point';
};
// @public (undocumented)
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
// @public (undocumented)
export interface HttpRouterService {
// (undocumented)
use(handler: Handler): void;
}
// @public (undocumented)
export const httpRouterServiceRef: ServiceRef<HttpRouterService>;
// @public (undocumented)
export interface Logger {
// (undocumented)
child(fields: { [name: string]: string }): Logger;
// (undocumented)
info(message: string): void;
}
// @public (undocumented)
export const loggerServiceRef: ServiceRef<Logger>;
// @public (undocumented)
export function loggerToWinstonLogger(
logger: Logger,
opts?: TransportStreamOptions,
): Logger_2;
// @public (undocumented)
export const permissionsServiceRef: ServiceRef<
PermissionAuthorizer | PermissionEvaluator
>;
// @public (undocumented)
export const schedulerServiceRef: ServiceRef<PluginTaskScheduler>;
// @public (undocumented)
export type ServiceFactory<
TApi,
TImpl extends TApi,
TDeps extends {
[name in string]: unknown;
},
> = {
service: ServiceRef<TApi>;
deps: TypesToServiceRef<TDeps>;
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
};
// @public
export type ServiceRef<T> = {
id: string;
T: T;
toString(): string;
$$ref: 'service';
};
// @public (undocumented)
export const tokenManagerServiceRef: ServiceRef<TokenManager>;
// @public (undocumented)
export type TypesToServiceRef<T> = {
[key in keyof T]: ServiceRef<T[key]>;
};
// @public (undocumented)
export const urlReaderServiceRef: ServiceRef<UrlReader>;
```
+53
View File
@@ -0,0 +1,53 @@
{
"name": "@backstage/backend-plugin-api",
"description": "Core API used by Backstage backend plugins",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"alphaTypes": "dist/index.alpha.d.ts"
},
"backstage": {
"role": "node-library"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/backend-plugin-api"
},
"keywords": [
"backstage"
],
"license": "Apache-2.0",
"scripts": {
"build": "backstage-cli package build --experimental-type-build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean",
"start": "backstage-cli package start"
},
"dependencies": {
"@backstage/config": "^1.0.1",
"@backstage/backend-common": "^0.14.1-next.2",
"@backstage/plugin-permission-common": "^0.6.3-next.0",
"@backstage/backend-tasks": "^0.3.3-next.2",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"winston": "^3.2.1",
"winston-transport": "^4.5.0"
},
"devDependencies": {
"@backstage/cli": "^0.18.0-next.2"
},
"files": [
"dist",
"alpha"
]
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright 2020 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.
*/
/**
* Core API used by Backstage backend plugins.
*
* @packageDocumentation
*/
export * from './services';
export * from './wiring';
@@ -0,0 +1,25 @@
/*
* 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 } from '../system/types';
import { PluginCacheManager } from '@backstage/backend-common';
/**
* @public
*/
export const cacheServiceRef = createServiceRef<PluginCacheManager>({
id: 'core.cache',
});
@@ -0,0 +1,25 @@
/*
* 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 { Config } from '@backstage/config';
import { createServiceRef } from '../system/types';
/**
* @public
*/
export const configServiceRef = createServiceRef<Config>({
id: 'core.config',
});
@@ -0,0 +1,25 @@
/*
* 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 { PluginDatabaseManager } from '@backstage/backend-common';
import { createServiceRef } from '../system/types';
/**
* @public
*/
export const databaseServiceRef = createServiceRef<PluginDatabaseManager>({
id: 'core.database',
});
@@ -0,0 +1,25 @@
/*
* 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 } from '../system/types';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
/**
* @public
*/
export const discoveryServiceRef = createServiceRef<PluginEndpointDiscovery>({
id: 'core.discovery',
});
@@ -0,0 +1,32 @@
/*
* 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 } from '../system/types';
import { Handler } from 'express';
/**
* @public
*/
export interface HttpRouterService {
use(handler: Handler): void;
}
/**
* @public
*/
export const httpRouterServiceRef = createServiceRef<HttpRouterService>({
id: 'core.httpRouter',
});
@@ -0,0 +1,28 @@
/*
* 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 { configServiceRef } from './configServiceRef';
export { httpRouterServiceRef } from './httpRouterServiceRef';
export type { HttpRouterService } from './httpRouterServiceRef';
export { loggerServiceRef } from './loggerServiceRef';
export type { Logger } from './loggerServiceRef';
export { urlReaderServiceRef } from './urlReaderServiceRef';
export { cacheServiceRef } from './cacheServiceRef';
export { databaseServiceRef } from './databaseServiceRef';
export { discoveryServiceRef } from './discoveryServiceRef';
export { tokenManagerServiceRef } from './tokenManagerServiceRef';
export { permissionsServiceRef } from './permissionsServiceRef';
export { schedulerServiceRef } from './schedulerServiceRef';
@@ -0,0 +1,32 @@
/*
* 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 } from '../system/types';
/**
* @public
*/
export interface Logger {
info(message: string): void;
child(fields: { [name: string]: string }): Logger;
}
/**
* @public
*/
export const loggerServiceRef = createServiceRef<Logger>({
id: 'core.logger',
});
@@ -0,0 +1,30 @@
/*
* 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 } from '../system/types';
import {
PermissionAuthorizer,
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
/**
* @public
*/
export const permissionsServiceRef = createServiceRef<
PermissionEvaluator | PermissionAuthorizer
>({
id: 'core.permissions',
});
@@ -0,0 +1,25 @@
/*
* 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 } from '../system/types';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
/**
* @public
*/
export const schedulerServiceRef = createServiceRef<PluginTaskScheduler>({
id: 'core.scheduler',
});
@@ -0,0 +1,25 @@
/*
* 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 } from '../system/types';
import { TokenManager } from '@backstage/backend-common';
/**
* @public
*/
export const tokenManagerServiceRef = createServiceRef<TokenManager>({
id: 'core.tokenManager',
});
@@ -0,0 +1,25 @@
/*
* 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 } from '../system/types';
import { UrlReader } from '@backstage/backend-common';
/**
* @public
*/
export const urlReaderServiceRef = createServiceRef<UrlReader>({
id: 'core.urlReader',
});
@@ -0,0 +1,17 @@
/*
* 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 { loggerToWinstonLogger } from './loggerToWinstonLogger';
@@ -0,0 +1,44 @@
/*
* 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 { Logger as BackstageLogger } from '../definitions';
import { Logger as WinstonLogger, createLogger } from 'winston';
import Transport, { TransportStreamOptions } from 'winston-transport';
class BackstageLoggerTransport extends Transport {
constructor(
private readonly backstageLogger: BackstageLogger,
opts?: TransportStreamOptions,
) {
super(opts);
}
log(info: { message: string }, callback: VoidFunction) {
// TODO: add support for levels and fields
this.backstageLogger.info(info.message);
callback();
}
}
/** @public */
export function loggerToWinstonLogger(
logger: BackstageLogger,
opts?: TransportStreamOptions,
): WinstonLogger {
return createLogger({
transports: [new BackstageLoggerTransport(logger, opts)],
});
}
@@ -0,0 +1,19 @@
/*
* 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 * from './definitions';
export * from './helpers';
export * from './system';
@@ -0,0 +1,25 @@
/*
* 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 {
ServiceRef,
TypesToServiceRef,
DepsToDepFactories,
FactoryFunc,
ServiceFactory,
AnyServiceFactory,
} from './types';
export { createServiceRef, createServiceFactory } from './types';
@@ -0,0 +1,90 @@
/*
* 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.
*/
/**
* TODO
*
* @public
*/
export type ServiceRef<T> = {
id: string;
/**
* Utility for getting the type of the service, using `typeof serviceRef.T`.
* Attempting to actually read this value will result in an exception.
*/
T: T;
toString(): string;
$$ref: 'service';
};
/** @public */
export type TypesToServiceRef<T> = { [key in keyof T]: ServiceRef<T[key]> };
/** @public */
export type DepsToDepFactories<T> = {
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
};
/** @public */
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
/** @public */
export type ServiceFactory<
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
> = {
service: ServiceRef<TApi>;
deps: TypesToServiceRef<TDeps>;
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
};
/** @public */
export type AnyServiceFactory = ServiceFactory<
unknown,
unknown,
{ [key in string]: unknown }
>;
/**
* @public
*/
export function createServiceRef<T>(options: { id: string }): ServiceRef<T> {
return {
id: options.id,
get T(): T {
throw new Error(`tried to read ServiceRef.T of ${this}`);
},
toString() {
return `serviceRef{${options.id}}`;
},
$$ref: 'service', // TODO: declare
};
}
/**
* @public
*/
export function createServiceFactory<
Api,
Impl extends Api,
Deps extends { [name in string]: unknown },
>(factory: ServiceFactory<Api, Impl, Deps>): ServiceFactory<Api, Api, {}> {
return factory;
}
@@ -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 { BackendRegistrable } from './types';
export * from './types';
@@ -0,0 +1,113 @@
/*
* 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 { ServiceRef } from '../services/system/types';
/**
* TODO
*
* @public
*/
export type ExtensionPoint<T> = {
id: string;
/**
* Utility for getting the type of the extension point, using `typeof extensionPoint.T`.
* Attempting to actually read this value will result in an exception.
*/
T: T;
toString(): string;
$$ref: 'extension-point';
};
/** @public */
export function createExtensionPoint<T>(options: {
id: string;
}): ExtensionPoint<T> {
return {
id: options.id,
get T(): T {
throw new Error(`tried to read ExtensionPoint.T of ${this}`);
},
toString() {
return `extensionPoint{${options.id}}`;
},
$$ref: 'extension-point', // TODO: declare
};
}
/** @public */
export interface BackendInitRegistry {
registerExtensionPoint<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerInit<Deps extends { [name in string]: unknown }>(options: {
deps: { [name in keyof Deps]: ServiceRef<Deps[name]> };
init: (deps: Deps) => Promise<void>;
}): void;
}
/** @public */
export interface BackendRegistrable {
id: string;
register(reg: BackendInitRegistry): void;
}
/** @public */
export interface BackendPluginConfig<TOptions> {
id: string;
register(reg: BackendInitRegistry, options: TOptions): void;
}
// TODO: Make option optional in the returned factory if they are indeed optional
/** @public */
export function createBackendPlugin<TOptions>(
config: BackendPluginConfig<TOptions>,
): (option: TOptions) => BackendRegistrable {
return options => ({
id: config.id,
register(register) {
return config.register(register, options);
},
});
}
/** @public */
export interface BackendModuleConfig<TOptions> {
pluginId: string;
moduleId: string;
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
// TODO: Make option optional in the returned factory if they are indeed optional
/** @public */
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): (option: TOptions) => BackendRegistrable {
return options => ({
id: `${config.pluginId}.${config.moduleId}`,
register(register) {
// TODO: Hide registerExtensionPoint
return config.register(register, options);
},
});
}