Merge pull request #25593 from backstage/mob/nbs10-service-modules
[NBS 1.0] Enabling multiple service implementations
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': minor
|
||||
---
|
||||
|
||||
The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref.
|
||||
|
||||
We're looking for ways to make it possible to augment services without the need to replace the entire service.
|
||||
|
||||
Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets:
|
||||
|
||||
```diff
|
||||
// @backstage/backend-defaults
|
||||
|
||||
+ export const urlReaderFactoriesServiceRef = createServiceRef<ReaderFactory>({
|
||||
+ id: 'core.urlReader.factories',
|
||||
+ scope: 'plugin',
|
||||
+ multiton: true,
|
||||
+ });
|
||||
|
||||
...
|
||||
|
||||
export const urlReaderServiceFactory = createServiceFactory({
|
||||
service: coreServices.urlReader,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
+ factories: urlReaderFactoriesServiceRef,
|
||||
},
|
||||
- async factory({ config, logger }) {
|
||||
+ async factory({ config, logger, factories }) {
|
||||
return UrlReaders.default({
|
||||
config,
|
||||
logger,
|
||||
+ factories,
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like:
|
||||
|
||||
```ts
|
||||
// packages/backend/index.ts
|
||||
import { createServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults';
|
||||
...
|
||||
|
||||
backend.add(createServiceFactory({
|
||||
service: urlReaderFactoriesServiceRef,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return CustomUrlReader.factory;
|
||||
},
|
||||
}));
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-node': patch
|
||||
'@backstage/plugin-catalog-node': patch
|
||||
---
|
||||
|
||||
Explicit declare if the service ref accepts `single` or `multiple` implementations.
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const featureDiscoveryServiceFactory: ServiceFactoryCompat<
|
||||
FeatureDiscoveryService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import { UserInfoService } from '@backstage/backend-plugin-api';
|
||||
export const authServiceFactory: ServiceFactoryCompat<
|
||||
AuthService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -80,6 +81,7 @@ export interface Backend {
|
||||
export const cacheServiceFactory: ServiceFactoryCompat<
|
||||
CacheService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -113,6 +115,7 @@ export interface CreateSpecializedBackendOptions {
|
||||
export const databaseServiceFactory: ServiceFactoryCompat<
|
||||
DatabaseService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -135,6 +138,7 @@ export type DefaultRootHttpRouterOptions = DefaultRootHttpRouterOptions_2;
|
||||
export const discoveryServiceFactory: ServiceFactoryCompat<
|
||||
DiscoveryService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -161,6 +165,7 @@ export class HostDiscovery implements DiscoveryService {
|
||||
export const httpAuthServiceFactory: ServiceFactoryCompat<
|
||||
HttpAuthService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -168,6 +173,7 @@ export const httpAuthServiceFactory: ServiceFactoryCompat<
|
||||
export const httpRouterServiceFactory: ServiceFactoryCompat<
|
||||
HttpRouterService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -191,6 +197,7 @@ export type IdentityFactoryOptions = {
|
||||
export const identityServiceFactory: ServiceFactoryCompat<
|
||||
IdentityService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
IdentityFactoryOptions
|
||||
>;
|
||||
|
||||
@@ -203,6 +210,7 @@ export type LifecycleMiddlewareOptions = LifecycleMiddlewareOptions_2;
|
||||
export const lifecycleServiceFactory: ServiceFactoryCompat<
|
||||
LifecycleService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -220,6 +228,7 @@ export function loadBackendConfig(options: {
|
||||
export const loggerServiceFactory: ServiceFactoryCompat<
|
||||
LoggerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -248,6 +257,7 @@ export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2;
|
||||
export const permissionsServiceFactory: ServiceFactoryCompat<
|
||||
PermissionsService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -278,6 +288,7 @@ export interface RootConfigFactoryOptions {
|
||||
export const rootConfigServiceFactory: ServiceFactoryCompat<
|
||||
RootConfigService,
|
||||
'root',
|
||||
'singleton',
|
||||
RootConfigFactoryOptions
|
||||
>;
|
||||
|
||||
@@ -294,13 +305,14 @@ export type RootHttpRouterFactoryOptions = RootHttpRouterFactoryOptions_2;
|
||||
// @public @deprecated (undocumented)
|
||||
export const rootHttpRouterServiceFactory: ((
|
||||
options?: RootHttpRouterFactoryOptions_2 | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
) => ServiceFactory<RootHttpRouterService, 'root', 'singleton'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root', 'singleton'>;
|
||||
|
||||
// @public @deprecated
|
||||
export const rootLifecycleServiceFactory: ServiceFactoryCompat<
|
||||
RootLifecycleService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -308,6 +320,7 @@ export const rootLifecycleServiceFactory: ServiceFactoryCompat<
|
||||
export const rootLoggerServiceFactory: ServiceFactoryCompat<
|
||||
RootLoggerService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -315,6 +328,7 @@ export const rootLoggerServiceFactory: ServiceFactoryCompat<
|
||||
export const schedulerServiceFactory: ServiceFactoryCompat<
|
||||
SchedulerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -322,6 +336,7 @@ export const schedulerServiceFactory: ServiceFactoryCompat<
|
||||
export const tokenManagerServiceFactory: ServiceFactoryCompat<
|
||||
TokenManagerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -329,6 +344,7 @@ export const tokenManagerServiceFactory: ServiceFactoryCompat<
|
||||
export const urlReaderServiceFactory: ServiceFactoryCompat<
|
||||
UrlReaderService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -336,6 +352,7 @@ export const urlReaderServiceFactory: ServiceFactoryCompat<
|
||||
export const userInfoServiceFactory: ServiceFactoryCompat<
|
||||
UserInfoService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -58,12 +58,24 @@ function createPluginMetadataServiceFactory(pluginId: string) {
|
||||
|
||||
export class ServiceRegistry {
|
||||
static create(factories: Array<ServiceFactory>): ServiceRegistry {
|
||||
const registry = new ServiceRegistry(factories);
|
||||
const factoryMap = new Map<string, InternalServiceFactory[]>();
|
||||
for (const factory of factories) {
|
||||
if (factory.service.multiton) {
|
||||
const existing = factoryMap.get(factory.service.id) ?? [];
|
||||
factoryMap.set(
|
||||
factory.service.id,
|
||||
existing.concat(toInternalServiceFactory(factory)),
|
||||
);
|
||||
} else {
|
||||
factoryMap.set(factory.service.id, [toInternalServiceFactory(factory)]);
|
||||
}
|
||||
}
|
||||
const registry = new ServiceRegistry(factoryMap);
|
||||
registry.checkForCircularDeps();
|
||||
return registry;
|
||||
}
|
||||
|
||||
readonly #providedFactories: Map<string, InternalServiceFactory>;
|
||||
readonly #providedFactories: Map<string, InternalServiceFactory[]>;
|
||||
readonly #loadedDefaultFactories: Map<
|
||||
Function,
|
||||
Promise<InternalServiceFactory>
|
||||
@@ -82,10 +94,8 @@ export class ServiceRegistry {
|
||||
readonly #addedFactoryIds = new Set<string>();
|
||||
readonly #instantiatedFactories = new Set<string>();
|
||||
|
||||
private constructor(factories: Array<ServiceFactory>) {
|
||||
this.#providedFactories = new Map(
|
||||
factories.map(sf => [sf.service.id, toInternalServiceFactory(sf)]),
|
||||
);
|
||||
private constructor(factories: Map<string, InternalServiceFactory[]>) {
|
||||
this.#providedFactories = factories;
|
||||
this.#loadedDefaultFactories = new Map();
|
||||
this.#implementations = new Map();
|
||||
}
|
||||
@@ -93,17 +103,17 @@ export class ServiceRegistry {
|
||||
#resolveFactory(
|
||||
ref: ServiceRef<unknown>,
|
||||
pluginId: string,
|
||||
): Promise<InternalServiceFactory> | undefined {
|
||||
): Promise<InternalServiceFactory[]> | undefined {
|
||||
// Special case handling of the plugin metadata service, generating a custom factory for it each time
|
||||
if (ref.id === coreServices.pluginMetadata.id) {
|
||||
return Promise.resolve(
|
||||
return Promise.resolve([
|
||||
toInternalServiceFactory(createPluginMetadataServiceFactory(pluginId)),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
let resolvedFactory:
|
||||
| Promise<InternalServiceFactory>
|
||||
| InternalServiceFactory
|
||||
| Promise<InternalServiceFactory[]>
|
||||
| InternalServiceFactory[]
|
||||
| undefined = this.#providedFactories.get(ref.id);
|
||||
const { __defaultFactory: defaultFactory } = ref as InternalServiceRef;
|
||||
if (!resolvedFactory && !defaultFactory) {
|
||||
@@ -120,15 +130,18 @@ export class ServiceRegistry {
|
||||
);
|
||||
this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory);
|
||||
}
|
||||
resolvedFactory = loadedFactory.catch(error => {
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${
|
||||
ref.id
|
||||
}' because the default factory loader threw an error, ${stringifyError(
|
||||
error,
|
||||
)}`,
|
||||
);
|
||||
});
|
||||
resolvedFactory = loadedFactory.then(
|
||||
factory => [factory],
|
||||
error => {
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${
|
||||
ref.id
|
||||
}' because the default factory loader threw an error, ${stringifyError(
|
||||
error,
|
||||
)}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve(resolvedFactory);
|
||||
@@ -142,6 +155,9 @@ export class ServiceRegistry {
|
||||
if (this.#providedFactories.get(ref.id)) {
|
||||
return false;
|
||||
}
|
||||
if (ref.multiton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(ref as InternalServiceRef).__defaultFactory;
|
||||
});
|
||||
@@ -156,13 +172,13 @@ export class ServiceRegistry {
|
||||
|
||||
checkForCircularDeps(): void {
|
||||
const graph = DependencyGraph.fromIterable(
|
||||
Array.from(this.#providedFactories).map(
|
||||
([serviceId, serviceFactory]) => ({
|
||||
value: serviceId,
|
||||
provides: [serviceId],
|
||||
consumes: Object.values(serviceFactory.deps).map(d => d.id),
|
||||
}),
|
||||
),
|
||||
Array.from(this.#providedFactories).map(([serviceId, factories]) => ({
|
||||
value: serviceId,
|
||||
provides: [serviceId],
|
||||
consumes: factories.flatMap(factory =>
|
||||
Object.values(factory.deps).map(d => d.id),
|
||||
),
|
||||
})),
|
||||
);
|
||||
const circularDependencies = Array.from(graph.detectCircularDependencies());
|
||||
|
||||
@@ -183,27 +199,36 @@ export class ServiceRegistry {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.#addedFactoryIds.has(factoryId)) {
|
||||
throw new Error(
|
||||
`Duplicate service implementations provided for ${factoryId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.#instantiatedFactories.has(factoryId)) {
|
||||
throw new Error(
|
||||
`Unable to set service factory with id ${factoryId}, service has already been instantiated`,
|
||||
);
|
||||
}
|
||||
|
||||
this.#addedFactoryIds.add(factoryId);
|
||||
this.#providedFactories.set(factoryId, toInternalServiceFactory(factory));
|
||||
if (factory.service.multiton) {
|
||||
const newFactories = (
|
||||
this.#providedFactories.get(factoryId) ?? []
|
||||
).concat(toInternalServiceFactory(factory));
|
||||
this.#providedFactories.set(factoryId, newFactories);
|
||||
} else {
|
||||
if (this.#addedFactoryIds.has(factoryId)) {
|
||||
throw new Error(
|
||||
`Duplicate service implementations provided for ${factoryId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.#addedFactoryIds.add(factoryId);
|
||||
this.#providedFactories.set(factoryId, [
|
||||
toInternalServiceFactory(factory),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
async initializeEagerServicesWithScope(
|
||||
scope: 'root' | 'plugin',
|
||||
pluginId: string = 'root',
|
||||
) {
|
||||
for (const factory of this.#providedFactories.values()) {
|
||||
for (const [factory] of this.#providedFactories.values()) {
|
||||
if (factory.service.scope === scope) {
|
||||
// Root-scoped services are eager by default, plugin-scoped are lazy by default
|
||||
if (scope === 'root' && factory.initialization !== 'lazy') {
|
||||
@@ -215,88 +240,112 @@ export class ServiceRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
get<T>(ref: ServiceRef<T>, pluginId: string): Promise<T> | undefined {
|
||||
get<T, TInstances extends 'singleton' | 'multiton'>(
|
||||
ref: ServiceRef<T, 'plugin' | 'root', TInstances>,
|
||||
pluginId: string,
|
||||
): Promise<TInstances extends 'multiton' ? T[] : T> | undefined {
|
||||
this.#instantiatedFactories.add(ref.id);
|
||||
|
||||
return this.#resolveFactory(ref, pluginId)?.then(factory => {
|
||||
if (factory.service.scope === 'root') {
|
||||
let existing = this.#rootServiceImplementations.get(factory);
|
||||
if (!existing) {
|
||||
this.#checkForMissingDeps(factory, pluginId);
|
||||
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
|
||||
const resolvedFactory = this.#resolveFactory(ref, pluginId);
|
||||
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
if (serviceRef.scope !== 'root') {
|
||||
throw new Error(
|
||||
`Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`,
|
||||
);
|
||||
if (!resolvedFactory) {
|
||||
return ref.multiton
|
||||
? (Promise.resolve([]) as
|
||||
| Promise<TInstances extends 'multiton' ? T[] : T>
|
||||
| undefined)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
return resolvedFactory
|
||||
.then(factories => {
|
||||
return Promise.all(
|
||||
factories.map(factory => {
|
||||
if (factory.service.scope === 'root') {
|
||||
let existing = this.#rootServiceImplementations.get(factory);
|
||||
if (!existing) {
|
||||
this.#checkForMissingDeps(factory, pluginId);
|
||||
const rootDeps = new Array<
|
||||
Promise<[name: string, impl: unknown]>
|
||||
>();
|
||||
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
if (serviceRef.scope !== 'root') {
|
||||
throw new Error(
|
||||
`Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`,
|
||||
);
|
||||
}
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
rootDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
|
||||
existing = Promise.all(rootDeps).then(entries =>
|
||||
factory.factory(Object.fromEntries(entries), undefined),
|
||||
);
|
||||
this.#rootServiceImplementations.set(factory, existing);
|
||||
}
|
||||
return existing as Promise<T>;
|
||||
}
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
rootDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
|
||||
existing = Promise.all(rootDeps).then(entries =>
|
||||
factory.factory(Object.fromEntries(entries), undefined),
|
||||
);
|
||||
this.#rootServiceImplementations.set(factory, existing);
|
||||
}
|
||||
return existing as Promise<T>;
|
||||
}
|
||||
let implementation = this.#implementations.get(factory);
|
||||
if (!implementation) {
|
||||
this.#checkForMissingDeps(factory, pluginId);
|
||||
const rootDeps = new Array<
|
||||
Promise<[name: string, impl: unknown]>
|
||||
>();
|
||||
|
||||
let implementation = this.#implementations.get(factory);
|
||||
if (!implementation) {
|
||||
this.#checkForMissingDeps(factory, pluginId);
|
||||
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
if (serviceRef.scope === 'root') {
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
rootDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
if (serviceRef.scope === 'root') {
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
rootDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
}
|
||||
implementation = {
|
||||
context: Promise.all(rootDeps)
|
||||
.then(entries =>
|
||||
factory.createRootContext?.(Object.fromEntries(entries)),
|
||||
)
|
||||
.catch(error => {
|
||||
const cause = stringifyError(error);
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`,
|
||||
);
|
||||
}),
|
||||
byPlugin: new Map(),
|
||||
};
|
||||
|
||||
implementation = {
|
||||
context: Promise.all(rootDeps)
|
||||
.then(entries =>
|
||||
factory.createRootContext?.(Object.fromEntries(entries)),
|
||||
)
|
||||
.catch(error => {
|
||||
const cause = stringifyError(error);
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`,
|
||||
);
|
||||
}),
|
||||
byPlugin: new Map(),
|
||||
};
|
||||
this.#implementations.set(factory, implementation);
|
||||
}
|
||||
|
||||
this.#implementations.set(factory, implementation);
|
||||
}
|
||||
let result = implementation.byPlugin.get(pluginId) as Promise<any>;
|
||||
if (!result) {
|
||||
const allDeps = new Array<
|
||||
Promise<[name: string, impl: unknown]>
|
||||
>();
|
||||
|
||||
let result = implementation.byPlugin.get(pluginId) as Promise<any>;
|
||||
if (!result) {
|
||||
const allDeps = new Array<Promise<[name: string, impl: unknown]>>();
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
allDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
|
||||
for (const [name, serviceRef] of Object.entries(factory.deps)) {
|
||||
const target = this.get(serviceRef, pluginId)!;
|
||||
allDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
|
||||
result = implementation.context
|
||||
.then(context =>
|
||||
Promise.all(allDeps).then(entries =>
|
||||
factory.factory(Object.fromEntries(entries), context),
|
||||
),
|
||||
)
|
||||
.catch(error => {
|
||||
const cause = stringifyError(error);
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`,
|
||||
);
|
||||
});
|
||||
implementation.byPlugin.set(pluginId, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
result = implementation.context
|
||||
.then(context =>
|
||||
Promise.all(allDeps).then(entries =>
|
||||
factory.factory(Object.fromEntries(entries), context),
|
||||
),
|
||||
)
|
||||
.catch(error => {
|
||||
const cause = stringifyError(error);
|
||||
throw new Error(
|
||||
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`,
|
||||
);
|
||||
});
|
||||
implementation.byPlugin.set(pluginId, result);
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
})
|
||||
.then(results => (ref.multiton ? results : results[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const authServiceFactory: ServiceFactoryCompat<
|
||||
AuthService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export type CacheManagerOptions = {
|
||||
export const cacheServiceFactory: ServiceFactoryCompat<
|
||||
CacheService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export type DatabaseManagerOptions = {
|
||||
export const databaseServiceFactory: ServiceFactoryCompat<
|
||||
DatabaseService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const discoveryServiceFactory: ServiceFactoryCompat<
|
||||
DiscoveryService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const httpAuthServiceFactory: ServiceFactoryCompat<
|
||||
HttpAuthService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export function createLifecycleMiddleware(
|
||||
export const httpRouterServiceFactory: ServiceFactoryCompat<
|
||||
HttpRouterService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const lifecycleServiceFactory: ServiceFactoryCompat<
|
||||
LifecycleService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const loggerServiceFactory: ServiceFactoryCompat<
|
||||
LoggerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const permissionsServiceFactory: ServiceFactoryCompat<
|
||||
PermissionsService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ export interface RootConfigFactoryOptions {
|
||||
// @public (undocumented)
|
||||
export const rootConfigServiceFactory: ((
|
||||
options?: RootConfigFactoryOptions,
|
||||
) => ServiceFactory<RootConfigService, 'root'>) &
|
||||
ServiceFactory<RootConfigService, 'root'>;
|
||||
) => ServiceFactory<RootConfigService, 'root', 'singleton'>) &
|
||||
ServiceFactory<RootConfigService, 'root', 'singleton'>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const rootHealthServiceFactory: ServiceFactoryCompat<
|
||||
RootHealthService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@ export type RootHttpRouterFactoryOptions = {
|
||||
// @public (undocumented)
|
||||
export const rootHttpRouterServiceFactory: ((
|
||||
options?: RootHttpRouterFactoryOptions,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
) => ServiceFactory<RootHttpRouterService, 'root', 'singleton'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root', 'singleton'>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
export const rootLifecycleServiceFactory: ServiceFactoryCompat<
|
||||
RootLifecycleService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { transport } from 'winston';
|
||||
export const rootLoggerServiceFactory: ServiceFactoryCompat<
|
||||
RootLoggerService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export class DefaultSchedulerService {
|
||||
export const schedulerServiceFactory: ServiceFactoryCompat<
|
||||
SchedulerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { HarnessIntegration } from '@backstage/integration';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Readable } from 'stream';
|
||||
import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
import { UrlReaderServiceReadTreeOptions } from '@backstage/backend-plugin-api';
|
||||
import { UrlReaderServiceReadTreeResponse } from '@backstage/backend-plugin-api';
|
||||
@@ -413,6 +414,13 @@ export type ReadUrlResponseFactoryFromStreamOptions = {
|
||||
lastModifiedAt?: Date;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const urlReaderFactoriesServiceRef: ServiceRef<
|
||||
ReaderFactory,
|
||||
'plugin',
|
||||
'multiton'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type UrlReaderPredicateTuple = {
|
||||
predicate: (url: URL) => boolean;
|
||||
@@ -429,6 +437,7 @@ export class UrlReaders {
|
||||
export const urlReaderServiceFactory: ServiceFactoryCompat<
|
||||
UrlReaderService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserInfoService } from '@backstage/backend-plugin-api';
|
||||
export const userInfoServiceFactory: ServiceFactoryCompat<
|
||||
UserInfoService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
|
||||
@@ -15,4 +15,7 @@
|
||||
*/
|
||||
|
||||
export * from './lib';
|
||||
export { urlReaderServiceFactory } from './urlReaderServiceFactory';
|
||||
export {
|
||||
urlReaderServiceFactory,
|
||||
urlReaderFactoriesServiceRef,
|
||||
} from './urlReaderServiceFactory';
|
||||
|
||||
@@ -14,12 +14,36 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReaderFactory } from './lib';
|
||||
import { UrlReaders } from './lib/UrlReaders';
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* A non-singleton reference to URL Reader factory services.
|
||||
*
|
||||
* @example
|
||||
* Creating a service factory implementation for a Custom URL Reader.
|
||||
* ```ts
|
||||
* createServiceFactory({
|
||||
* service: urlReaderFactoriesServiceRef,
|
||||
* deps: {},
|
||||
* async factory() {
|
||||
* return CustomUrlReader.factory;
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const urlReaderFactoriesServiceRef = createServiceRef<ReaderFactory>({
|
||||
id: 'core.urlReader.factories',
|
||||
scope: 'plugin',
|
||||
multiton: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Reading content from external systems.
|
||||
*
|
||||
@@ -34,11 +58,13 @@ export const urlReaderServiceFactory = createServiceFactory({
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
factories: urlReaderFactoriesServiceRef,
|
||||
},
|
||||
async factory({ config, logger }) {
|
||||
async factory({ config, logger, factories }) {
|
||||
return UrlReaders.default({
|
||||
config,
|
||||
logger,
|
||||
factories,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface DynamicPluginsFactoryOptions {
|
||||
export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactoryCompat<
|
||||
FeatureDiscoveryService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -127,6 +128,7 @@ export const dynamicPluginsFrontendSchemas: BackendFeatureCompat;
|
||||
export const dynamicPluginsRootLoggerServiceFactory: ServiceFactoryCompat<
|
||||
RootLoggerService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -147,6 +149,7 @@ export interface DynamicPluginsSchemasService {
|
||||
export const dynamicPluginsSchemasServiceFactory: ServiceFactoryCompat<
|
||||
DynamicPluginsSchemasService,
|
||||
'root',
|
||||
'singleton',
|
||||
DynamicPluginsSchemasOptions
|
||||
>;
|
||||
|
||||
@@ -154,13 +157,15 @@ export const dynamicPluginsSchemasServiceFactory: ServiceFactoryCompat<
|
||||
export const dynamicPluginsServiceFactory: ServiceFactoryCompat<
|
||||
DynamicPluginProvider,
|
||||
'root',
|
||||
'singleton',
|
||||
DynamicPluginsFactoryOptions
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const dynamicPluginsServiceRef: ServiceRef<
|
||||
DynamicPluginProvider,
|
||||
'root'
|
||||
'root',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -17,7 +17,8 @@ export interface FeatureDiscoveryService {
|
||||
// @alpha
|
||||
export const featureDiscoveryServiceRef: ServiceRef<
|
||||
FeatureDiscoveryService,
|
||||
'root'
|
||||
'root',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -189,28 +189,32 @@ export type CacheServiceSetOptions = {
|
||||
|
||||
// @public
|
||||
export namespace coreServices {
|
||||
const auth: ServiceRef<AuthService, 'plugin'>;
|
||||
const userInfo: ServiceRef<UserInfoService, 'plugin'>;
|
||||
const cache: ServiceRef<CacheService, 'plugin'>;
|
||||
const rootConfig: ServiceRef<RootConfigService, 'root'>;
|
||||
const database: ServiceRef<DatabaseService, 'plugin'>;
|
||||
const discovery: ServiceRef<DiscoveryService, 'plugin'>;
|
||||
const rootHealth: ServiceRef<RootHealthService, 'root'>;
|
||||
const httpAuth: ServiceRef<HttpAuthService, 'plugin'>;
|
||||
const httpRouter: ServiceRef<HttpRouterService, 'plugin'>;
|
||||
const lifecycle: ServiceRef<LifecycleService, 'plugin'>;
|
||||
const logger: ServiceRef<LoggerService, 'plugin'>;
|
||||
const permissions: ServiceRef<PermissionsService, 'plugin'>;
|
||||
const pluginMetadata: ServiceRef<PluginMetadataService, 'plugin'>;
|
||||
const rootHttpRouter: ServiceRef<RootHttpRouterService, 'root'>;
|
||||
const rootLifecycle: ServiceRef<RootLifecycleService, 'root'>;
|
||||
const rootLogger: ServiceRef<RootLoggerService, 'root'>;
|
||||
const scheduler: ServiceRef<SchedulerService, 'plugin'>;
|
||||
const auth: ServiceRef<AuthService, 'plugin', 'singleton'>;
|
||||
const userInfo: ServiceRef<UserInfoService, 'plugin', 'singleton'>;
|
||||
const cache: ServiceRef<CacheService, 'plugin', 'singleton'>;
|
||||
const rootConfig: ServiceRef<RootConfigService, 'root', 'singleton'>;
|
||||
const database: ServiceRef<DatabaseService, 'plugin', 'singleton'>;
|
||||
const discovery: ServiceRef<DiscoveryService, 'plugin', 'singleton'>;
|
||||
const rootHealth: ServiceRef<RootHealthService, 'root', 'singleton'>;
|
||||
const httpAuth: ServiceRef<HttpAuthService, 'plugin', 'singleton'>;
|
||||
const httpRouter: ServiceRef<HttpRouterService, 'plugin', 'singleton'>;
|
||||
const lifecycle: ServiceRef<LifecycleService, 'plugin', 'singleton'>;
|
||||
const logger: ServiceRef<LoggerService, 'plugin', 'singleton'>;
|
||||
const permissions: ServiceRef<PermissionsService, 'plugin', 'singleton'>;
|
||||
const pluginMetadata: ServiceRef<
|
||||
PluginMetadataService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
const rootHttpRouter: ServiceRef<RootHttpRouterService, 'root', 'singleton'>;
|
||||
const rootLifecycle: ServiceRef<RootLifecycleService, 'root', 'singleton'>;
|
||||
const rootLogger: ServiceRef<RootLoggerService, 'root', 'singleton'>;
|
||||
const scheduler: ServiceRef<SchedulerService, 'plugin', 'singleton'>;
|
||||
const // @deprecated
|
||||
tokenManager: ServiceRef<TokenManagerService, 'plugin'>;
|
||||
const urlReader: ServiceRef<UrlReaderService, 'plugin'>;
|
||||
tokenManager: ServiceRef<TokenManagerService, 'plugin', 'singleton'>;
|
||||
const urlReader: ServiceRef<UrlReaderService, 'plugin', 'singleton'>;
|
||||
const // @deprecated
|
||||
identity: ServiceRef<IdentityService, 'plugin'>;
|
||||
identity: ServiceRef<IdentityService, 'plugin', 'singleton'>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -292,18 +296,20 @@ export interface CreateExtensionPointOptions {
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown, 'root'>;
|
||||
},
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options: RootServiceFactoryOptions<TService, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root'>;
|
||||
options: RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TInstances>;
|
||||
|
||||
// @public @deprecated
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown, 'root'>;
|
||||
@@ -312,12 +318,13 @@ export function createServiceFactory<
|
||||
>(
|
||||
options: (
|
||||
options?: TOpts,
|
||||
) => RootServiceFactoryOptions<TService, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TOpts>;
|
||||
) => RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TInstances, TOpts>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
@@ -325,12 +332,19 @@ export function createServiceFactory<
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options: PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'plugin'>;
|
||||
options: PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TInstances>;
|
||||
|
||||
// @public @deprecated
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
@@ -340,18 +354,34 @@ export function createServiceFactory<
|
||||
>(
|
||||
options: (
|
||||
options?: TOpts,
|
||||
) => PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TOpts>;
|
||||
) => PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TInstances, TOpts>;
|
||||
|
||||
// @public
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'plugin'>,
|
||||
): ServiceRef<TService, 'plugin'>;
|
||||
options: ServiceRefOptions<TService, 'plugin', 'singleton'>,
|
||||
): ServiceRef<TService, 'plugin', 'singleton'>;
|
||||
|
||||
// @public
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'root'>,
|
||||
): ServiceRef<TService, 'root'>;
|
||||
options: ServiceRefOptions<TService, 'root', 'singleton'>,
|
||||
): ServiceRef<TService, 'root', 'singleton'>;
|
||||
|
||||
// @public
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'plugin', 'multiton'>,
|
||||
): ServiceRef<TService, 'plugin', 'multiton'>;
|
||||
|
||||
// @public
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'root', 'multiton'>,
|
||||
): ServiceRef<TService, 'root', 'multiton'>;
|
||||
|
||||
// @public
|
||||
export interface DatabaseService {
|
||||
@@ -488,16 +518,18 @@ export interface PluginMetadataService {
|
||||
// @public @deprecated (undocumented)
|
||||
export type PluginServiceFactoryConfig<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
> = PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>;
|
||||
> = PluginServiceFactoryOptions<TService, TInstances, TContext, TImpl, TDeps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
@@ -517,7 +549,7 @@ export interface PluginServiceFactoryOptions<
|
||||
): TImpl | Promise<TImpl>;
|
||||
initialization?: 'always' | 'lazy';
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, 'plugin'>;
|
||||
service: ServiceRef<TService, 'plugin', TInstances>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -579,15 +611,17 @@ export interface RootLoggerService extends LoggerService {}
|
||||
// @public @deprecated (undocumented)
|
||||
export type RootServiceFactoryConfig<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
> = RootServiceFactoryOptions<TService, TImpl, TDeps>;
|
||||
> = RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootServiceFactoryOptions<
|
||||
TService,
|
||||
TService, // TODO(Rugvip): Can we forward the entire service ref type here instead of forwarding each type arg once the callback form is gone?
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
@@ -599,7 +633,7 @@ export interface RootServiceFactoryOptions<
|
||||
factory(deps: ServiceRefsToInstances<TDeps, 'root'>): TImpl | Promise<TImpl>;
|
||||
initialization?: 'always' | 'lazy';
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
service: ServiceRef<TService, 'root', TInstances>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -690,21 +724,23 @@ export type SearchResponseFile = UrlReaderServiceSearchResponseFile;
|
||||
export interface ServiceFactory<
|
||||
TService = unknown,
|
||||
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
> extends BackendFeature {
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, TScope>;
|
||||
service: ServiceRef<TService, TScope, TInstances>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export interface ServiceFactoryCompat<
|
||||
TService = unknown,
|
||||
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
TOpts extends object | undefined = undefined,
|
||||
> extends ServiceFactory<TService, TScope> {
|
||||
> extends ServiceFactory<TService, TScope, TInstances> {
|
||||
// @deprecated (undocumented)
|
||||
(
|
||||
...options: undefined extends TOpts ? [] : [options?: TOpts]
|
||||
): ServiceFactory<TService, TScope>;
|
||||
): ServiceFactory<TService, TScope, TInstances>;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
@@ -714,9 +750,11 @@ export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory);
|
||||
export type ServiceRef<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
> = {
|
||||
id: string;
|
||||
scope: TScope;
|
||||
multiton?: TInstances extends 'multiton' ? true : false;
|
||||
T: TService;
|
||||
$$type: '@backstage/ServiceRef';
|
||||
};
|
||||
@@ -725,10 +763,15 @@ export type ServiceRef<
|
||||
export type ServiceRefConfig<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
> = ServiceRefOptions<TService, TScope>;
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
> = ServiceRefOptions<TService, TScope, TInstances>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceRefOptions<TService, TScope extends 'root' | 'plugin'> {
|
||||
export interface ServiceRefOptions<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
> {
|
||||
// (undocumented)
|
||||
defaultFactory?(
|
||||
service: ServiceRef<TService, TScope>,
|
||||
@@ -740,6 +783,8 @@ export interface ServiceRefOptions<TService, TScope extends 'root' | 'plugin'> {
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
multiton?: TInstances extends 'multiton' ? true : false;
|
||||
// (undocumented)
|
||||
scope?: TScope;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
export type ServiceRefConfig<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
> = ServiceRefOptions<TService, TScope>;
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
> = ServiceRefOptions<TService, TScope, TInstances>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -36,9 +37,10 @@ export type ServiceRefConfig<
|
||||
*/
|
||||
export type RootServiceFactoryConfig<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
> = RootServiceFactoryOptions<TService, TImpl, TDeps>;
|
||||
> = RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -46,7 +48,8 @@ export type RootServiceFactoryConfig<
|
||||
*/
|
||||
export type PluginServiceFactoryConfig<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
> = PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>;
|
||||
> = PluginServiceFactoryOptions<TService, TInstances, TContext, TImpl, TDeps>;
|
||||
|
||||
@@ -332,6 +332,24 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should support old service refs without a multiton field', () => {
|
||||
const oldPluginDep = pluginDep as Omit<typeof pluginDep, 'multiton'>; // Old refs don't have a multiton field
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {
|
||||
plugin: oldPluginDep,
|
||||
},
|
||||
async factory({ plugin }) {
|
||||
const plugin1: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin2: number = plugin;
|
||||
unused(plugin1, plugin2);
|
||||
return 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
});
|
||||
|
||||
it('should only allow objects as options', () => {
|
||||
// @ts-expect-error
|
||||
const metaFactory = createServiceFactory((_opts: string) => ({
|
||||
|
||||
@@ -24,6 +24,7 @@ import { BackendFeature } from '../../types';
|
||||
export type ServiceRef<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
> = {
|
||||
id: string;
|
||||
|
||||
@@ -38,6 +39,8 @@ export type ServiceRef<
|
||||
*/
|
||||
scope: TScope;
|
||||
|
||||
multiton?: TInstances extends 'multiton' ? true : false;
|
||||
|
||||
/**
|
||||
* Utility for getting the type of the service, using `typeof serviceRef.T`.
|
||||
* Attempting to actually read this value will result in an exception.
|
||||
@@ -51,8 +54,9 @@ export type ServiceRef<
|
||||
export interface ServiceFactory<
|
||||
TService = unknown,
|
||||
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
> extends BackendFeature {
|
||||
service: ServiceRef<TService, TScope>;
|
||||
service: ServiceRef<TService, TScope, TInstances>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,21 +66,23 @@ export interface ServiceFactory<
|
||||
export interface ServiceFactoryCompat<
|
||||
TService = unknown,
|
||||
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
TOpts extends object | undefined = undefined,
|
||||
> extends ServiceFactory<TService, TScope> {
|
||||
> extends ServiceFactory<TService, TScope, TInstances> {
|
||||
/**
|
||||
* @deprecated Callable service factories will be removed in a future release, please re-implement the service factory using the available APIs instead. If no options are being passed, you can simply remove the trailing `()`.
|
||||
*/
|
||||
(
|
||||
...options: undefined extends TOpts ? [] : [options?: TOpts]
|
||||
): ServiceFactory<TService, TScope>;
|
||||
): ServiceFactory<TService, TScope, TInstances>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalServiceFactory<
|
||||
TService = unknown,
|
||||
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
|
||||
> extends ServiceFactory<TService, TScope> {
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton',
|
||||
> extends ServiceFactory<TService, TScope, TInstances> {
|
||||
version: 'v1';
|
||||
featureType: 'service';
|
||||
initialization?: 'always' | 'lazy';
|
||||
@@ -97,9 +103,14 @@ export interface InternalServiceFactory<
|
||||
export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory);
|
||||
|
||||
/** @public */
|
||||
export interface ServiceRefOptions<TService, TScope extends 'root' | 'plugin'> {
|
||||
export interface ServiceRefOptions<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
> {
|
||||
id: string;
|
||||
scope?: TScope;
|
||||
multiton?: TInstances extends 'multiton' ? true : false;
|
||||
defaultFactory?(
|
||||
service: ServiceRef<TService, TScope>,
|
||||
): Promise<ServiceFactory>;
|
||||
@@ -117,8 +128,8 @@ export interface ServiceRefOptions<TService, TScope extends 'root' | 'plugin'> {
|
||||
* @public
|
||||
*/
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'plugin'>,
|
||||
): ServiceRef<TService, 'plugin'>;
|
||||
options: ServiceRefOptions<TService, 'plugin', 'singleton'>,
|
||||
): ServiceRef<TService, 'plugin', 'singleton'>;
|
||||
|
||||
/**
|
||||
* Creates a new service definition. This overload is used to create root scoped services.
|
||||
@@ -126,16 +137,37 @@ export function createServiceRef<TService>(
|
||||
* @public
|
||||
*/
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'root'>,
|
||||
): ServiceRef<TService, 'root'>;
|
||||
options: ServiceRefOptions<TService, 'root', 'singleton'>,
|
||||
): ServiceRef<TService, 'root', 'singleton'>;
|
||||
|
||||
/**
|
||||
* Creates a new service definition. This overload is used to create plugin scoped services.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, any>,
|
||||
): ServiceRef<TService, any> {
|
||||
const { id, scope = 'plugin', defaultFactory } = options;
|
||||
options: ServiceRefOptions<TService, 'plugin', 'multiton'>,
|
||||
): ServiceRef<TService, 'plugin', 'multiton'>;
|
||||
|
||||
/**
|
||||
* Creates a new service definition. This overload is used to create root scoped services.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createServiceRef<TService>(
|
||||
options: ServiceRefOptions<TService, 'root', 'multiton'>,
|
||||
): ServiceRef<TService, 'root', 'multiton'>;
|
||||
export function createServiceRef<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
>(
|
||||
options: ServiceRefOptions<TService, any, TInstances>,
|
||||
): ServiceRef<TService, any, TInstances> {
|
||||
const { id, scope = 'plugin', multiton = false, defaultFactory } = options;
|
||||
return {
|
||||
id,
|
||||
scope,
|
||||
multiton,
|
||||
get T(): TService {
|
||||
throw new Error(`tried to read ServiceRef.T of ${this}`);
|
||||
},
|
||||
@@ -144,7 +176,7 @@ export function createServiceRef<TService>(
|
||||
},
|
||||
$$type: '@backstage/ServiceRef',
|
||||
__defaultFactory: defaultFactory,
|
||||
} as ServiceRef<TService, typeof scope> & {
|
||||
} as ServiceRef<TService, typeof scope, TInstances> & {
|
||||
__defaultFactory?: (
|
||||
service: ServiceRef<TService>,
|
||||
) => Promise<ServiceFactory<TService> | (() => ServiceFactory<TService>)>;
|
||||
@@ -156,12 +188,17 @@ type ServiceRefsToInstances<
|
||||
T extends { [key in string]: ServiceRef<unknown> },
|
||||
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
|
||||
> = {
|
||||
[key in keyof T as T[key]['scope'] extends TScope ? key : never]: T[key]['T'];
|
||||
[key in keyof T as T[key]['scope'] extends TScope
|
||||
? key
|
||||
: never]: T[key]['multiton'] extends true | undefined
|
||||
? Array<T[key]['T']>
|
||||
: T[key]['T'];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface RootServiceFactoryOptions<
|
||||
TService,
|
||||
TService, // TODO(Rugvip): Can we forward the entire service ref type here instead of forwarding each type arg once the callback form is gone?
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
> {
|
||||
@@ -176,7 +213,7 @@ export interface RootServiceFactoryOptions<
|
||||
* Service factories for root scoped services use `always` as the default, while plugin scoped services use `lazy`.
|
||||
*/
|
||||
initialization?: 'always' | 'lazy';
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
service: ServiceRef<TService, 'root', TInstances>;
|
||||
deps: TDeps;
|
||||
factory(deps: ServiceRefsToInstances<TDeps, 'root'>): TImpl | Promise<TImpl>;
|
||||
}
|
||||
@@ -184,6 +221,7 @@ export interface RootServiceFactoryOptions<
|
||||
/** @public */
|
||||
export interface PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
@@ -199,7 +237,7 @@ export interface PluginServiceFactoryOptions<
|
||||
* Service factories for root scoped services use `always` as the default, while plugin scoped services use `lazy`.
|
||||
*/
|
||||
initialization?: 'always' | 'lazy';
|
||||
service: ServiceRef<TService, 'plugin'>;
|
||||
service: ServiceRef<TService, 'plugin', TInstances>;
|
||||
deps: TDeps;
|
||||
createRootContext?(
|
||||
deps: ServiceRefsToInstances<TDeps, 'root'>,
|
||||
@@ -218,12 +256,13 @@ export interface PluginServiceFactoryOptions<
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown, 'root'> },
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options: RootServiceFactoryOptions<TService, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root'>;
|
||||
options: RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TInstances>;
|
||||
/**
|
||||
* Creates a root scoped service factory with optional options.
|
||||
*
|
||||
@@ -236,14 +275,15 @@ export function createServiceFactory<
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown, 'root'> },
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options: (
|
||||
options?: TOpts,
|
||||
) => RootServiceFactoryOptions<TService, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TOpts>;
|
||||
) => RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'root', TInstances, TOpts>;
|
||||
/**
|
||||
* Creates a plugin scoped service factory without options.
|
||||
*
|
||||
@@ -252,13 +292,20 @@ export function createServiceFactory<
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options: PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'plugin'>;
|
||||
options: PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TInstances>;
|
||||
/**
|
||||
* Creates a plugin scoped service factory with optional options.
|
||||
*
|
||||
@@ -271,6 +318,7 @@ export function createServiceFactory<
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext = undefined,
|
||||
@@ -278,25 +326,51 @@ export function createServiceFactory<
|
||||
>(
|
||||
options: (
|
||||
options?: TOpts,
|
||||
) => PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TOpts>;
|
||||
) => PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>,
|
||||
): ServiceFactoryCompat<TService, 'plugin', TInstances, TOpts>;
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TInstances extends 'singleton' | 'multiton',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
options:
|
||||
| RootServiceFactoryOptions<TService, TImpl, TDeps>
|
||||
| PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>
|
||||
| ((options: TOpts) => RootServiceFactoryOptions<TService, TImpl, TDeps>)
|
||||
| RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>
|
||||
| PluginServiceFactoryOptions<TService, TInstances, TContext, TImpl, TDeps>
|
||||
| ((
|
||||
options: TOpts,
|
||||
) => PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>)
|
||||
| (() => RootServiceFactoryOptions<TService, TImpl, TDeps>)
|
||||
| (() => PluginServiceFactoryOptions<TService, TContext, TImpl, TDeps>),
|
||||
): ServiceFactoryCompat<TService, 'root' | 'plugin', TOpts> {
|
||||
) => RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>)
|
||||
| ((
|
||||
options: TOpts,
|
||||
) => PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>)
|
||||
| (() => RootServiceFactoryOptions<TService, TInstances, TImpl, TDeps>)
|
||||
| (() => PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
>),
|
||||
): ServiceFactoryCompat<
|
||||
TService,
|
||||
'root' | 'plugin',
|
||||
'singleton' | 'multiton',
|
||||
TOpts
|
||||
> {
|
||||
const configCallback =
|
||||
typeof options === 'function' ? options : () => options;
|
||||
const factory = (
|
||||
@@ -304,7 +378,12 @@ export function createServiceFactory<
|
||||
): InternalServiceFactory<TService, 'plugin' | 'root'> => {
|
||||
const anyConf = configCallback(o!);
|
||||
if (anyConf.service.scope === 'root') {
|
||||
const c = anyConf as RootServiceFactoryOptions<TService, TImpl, TDeps>;
|
||||
const c = anyConf as RootServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TImpl,
|
||||
TDeps
|
||||
>;
|
||||
return {
|
||||
$$type: '@backstage/BackendFeature',
|
||||
version: 'v1',
|
||||
@@ -312,11 +391,13 @@ export function createServiceFactory<
|
||||
service: c.service,
|
||||
initialization: c.initialization,
|
||||
deps: c.deps,
|
||||
factory: async (deps: TDeps) => c.factory(deps),
|
||||
factory: async (deps: ServiceRefsToInstances<TDeps, 'root'>) =>
|
||||
c.factory(deps),
|
||||
};
|
||||
}
|
||||
const c = anyConf as PluginServiceFactoryOptions<
|
||||
TService,
|
||||
TInstances,
|
||||
TContext,
|
||||
TImpl,
|
||||
TDeps
|
||||
@@ -329,12 +410,14 @@ export function createServiceFactory<
|
||||
initialization: c.initialization,
|
||||
...('createRootContext' in c
|
||||
? {
|
||||
createRootContext: async (deps: TDeps) =>
|
||||
c?.createRootContext?.(deps),
|
||||
createRootContext: async (
|
||||
deps: ServiceRefsToInstances<TDeps, 'root'>,
|
||||
) => c?.createRootContext?.(deps),
|
||||
}
|
||||
: {}),
|
||||
deps: c.deps,
|
||||
factory: async (deps: TDeps, ctx: TContext) => c.factory(deps, ctx),
|
||||
factory: async (deps: ServiceRefsToInstances<TDeps>, ctx: TContext) =>
|
||||
c.factory(deps, ctx),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -156,7 +156,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace auth {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<AuthService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
AuthService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<AuthService> | undefined,
|
||||
@@ -165,7 +170,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace cache {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<CacheService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
CacheService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<CacheService> | undefined,
|
||||
@@ -174,7 +184,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace database {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<DatabaseService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
DatabaseService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<DatabaseService> | undefined,
|
||||
@@ -185,7 +200,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace discovery {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<DiscoveryService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
DiscoveryService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<DiscoveryService> | undefined,
|
||||
@@ -194,7 +214,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace events {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<EventsService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
EventsService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<EventsService> | undefined,
|
||||
@@ -208,8 +233,8 @@ export namespace mockServices {
|
||||
export namespace httpAuth {
|
||||
const factory: ((options?: {
|
||||
defaultCredentials?: BackstageCredentials;
|
||||
}) => ServiceFactory<HttpAuthService, 'plugin'>) &
|
||||
ServiceFactory<HttpAuthService, 'plugin'>;
|
||||
}) => ServiceFactory<HttpAuthService, 'plugin', 'singleton'>) &
|
||||
ServiceFactory<HttpAuthService, 'plugin', 'singleton'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<HttpAuthService> | undefined,
|
||||
@@ -218,7 +243,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace httpRouter {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<HttpRouterService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
HttpRouterService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<HttpRouterService> | undefined,
|
||||
@@ -229,7 +259,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace identity {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<IdentityService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
IdentityService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<IdentityService> | undefined,
|
||||
@@ -238,7 +273,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace lifecycle {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<LifecycleService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
LifecycleService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<LifecycleService> | undefined,
|
||||
@@ -247,7 +287,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace logger {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<LoggerService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
LoggerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<LoggerService> | undefined,
|
||||
@@ -256,7 +301,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace permissions {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<PermissionsService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
PermissionsService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<PermissionsService> | undefined,
|
||||
@@ -271,15 +321,28 @@ export namespace mockServices {
|
||||
data?: JsonObject;
|
||||
};
|
||||
const // (undocumented)
|
||||
factory: ServiceFactory<RootConfigService, 'root'> &
|
||||
factory: ServiceFactory<
|
||||
RootConfigService,
|
||||
'root',
|
||||
'singleton' | 'multiton'
|
||||
> &
|
||||
((
|
||||
options?: Options | undefined,
|
||||
) => ServiceFactory<RootConfigService, 'root'>);
|
||||
) => ServiceFactory<
|
||||
RootConfigService,
|
||||
'root',
|
||||
'singleton' | 'multiton'
|
||||
>);
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootHealth {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<RootHealthService, 'root', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
RootHealthService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootHealthService> | undefined,
|
||||
@@ -290,8 +353,8 @@ export namespace mockServices {
|
||||
const // (undocumented)
|
||||
factory: ((
|
||||
options?: RootHttpRouterFactoryOptions | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
) => ServiceFactory<RootHttpRouterService, 'root', 'singleton'>) &
|
||||
ServiceFactory<RootHttpRouterService, 'root', 'singleton'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootHttpRouterService> | undefined,
|
||||
@@ -300,7 +363,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace rootLifecycle {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<RootLifecycleService, 'root', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
RootLifecycleService,
|
||||
'root',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootLifecycleService> | undefined,
|
||||
@@ -315,10 +383,10 @@ export namespace mockServices {
|
||||
level?: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
||||
};
|
||||
const // (undocumented)
|
||||
factory: ServiceFactory<LoggerService, 'root'> &
|
||||
factory: ServiceFactory<LoggerService, 'root', 'singleton' | 'multiton'> &
|
||||
((
|
||||
options?: Options | undefined,
|
||||
) => ServiceFactory<LoggerService, 'root'>);
|
||||
) => ServiceFactory<LoggerService, 'root', 'singleton' | 'multiton'>);
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootLoggerService> | undefined,
|
||||
@@ -327,7 +395,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace scheduler {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<SchedulerService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
SchedulerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<SchedulerService> | undefined,
|
||||
@@ -338,7 +411,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace tokenManager {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<TokenManagerService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
TokenManagerService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<TokenManagerService> | undefined,
|
||||
@@ -347,7 +425,12 @@ export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace urlReader {
|
||||
const // (undocumented)
|
||||
factory: ServiceFactoryCompat<UrlReaderService, 'plugin', undefined>;
|
||||
factory: ServiceFactoryCompat<
|
||||
UrlReaderService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<UrlReaderService> | undefined,
|
||||
@@ -358,7 +441,12 @@ export namespace mockServices {
|
||||
): UserInfoService;
|
||||
// (undocumented)
|
||||
export namespace userInfo {
|
||||
const factory: ServiceFactoryCompat<UserInfoService, 'plugin', undefined>;
|
||||
const factory: ServiceFactoryCompat<
|
||||
UserInfoService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<UserInfoService> | undefined,
|
||||
@@ -374,22 +462,34 @@ export function registerMswTestHooks(worker: {
|
||||
}): void;
|
||||
|
||||
// @public
|
||||
export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
static from<TService, TScope extends 'root' | 'plugin'>(
|
||||
subject: ServiceFactory<TService, TScope>,
|
||||
export class ServiceFactoryTester<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
> {
|
||||
static from<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
>(
|
||||
subject: ServiceFactory<TService, TScope, TInstances>,
|
||||
options?: ServiceFactoryTesterOptions,
|
||||
): ServiceFactoryTester<TService, TScope>;
|
||||
): ServiceFactoryTester<TService, TScope, TInstances>;
|
||||
// @deprecated
|
||||
get(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService>;
|
||||
getService<TGetService, TGetScope extends 'root' | 'plugin'>(
|
||||
service: ServiceRef<TGetService, TGetScope>,
|
||||
): Promise<TInstances extends 'multiton' ? TService[] : TService>;
|
||||
getService<
|
||||
TGetService,
|
||||
TGetScope extends 'root' | 'plugin',
|
||||
TGetInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
>(
|
||||
service: ServiceRef<TGetService, TGetScope, TGetInstances>,
|
||||
...args: 'root' extends TGetScope ? [] : [pluginId?: string]
|
||||
): Promise<TGetService>;
|
||||
): Promise<TGetInstances extends 'multiton' ? TGetService[] : TGetService>;
|
||||
getSubject(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService>;
|
||||
): Promise<TInstances extends 'multiton' ? TService[] : TService>;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -43,8 +43,12 @@ export interface ServiceFactoryTesterOptions {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
readonly #subject: ServiceRef<TService, TScope>;
|
||||
export class ServiceFactoryTester<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
> {
|
||||
readonly #subject: ServiceRef<TService, TScope, TInstances>;
|
||||
readonly #registry: ServiceRegistry;
|
||||
|
||||
/**
|
||||
@@ -54,10 +58,14 @@ export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
* @param options - Additional options
|
||||
* @returns A new tester instance for the provided subject.
|
||||
*/
|
||||
static from<TService, TScope extends 'root' | 'plugin'>(
|
||||
subject: ServiceFactory<TService, TScope>,
|
||||
static from<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
>(
|
||||
subject: ServiceFactory<TService, TScope, TInstances>,
|
||||
options?: ServiceFactoryTesterOptions,
|
||||
) {
|
||||
): ServiceFactoryTester<TService, TScope, TInstances> {
|
||||
const registry = ServiceRegistry.create([
|
||||
...defaultServiceFactories,
|
||||
...(options?.dependencies ?? []),
|
||||
@@ -67,7 +75,7 @@ export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
}
|
||||
|
||||
private constructor(
|
||||
subject: ServiceRef<TService, TScope>,
|
||||
subject: ServiceRef<TService, TScope, TInstances>,
|
||||
registry: ServiceRegistry,
|
||||
) {
|
||||
this.#subject = subject;
|
||||
@@ -81,7 +89,7 @@ export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
*/
|
||||
async get(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService> {
|
||||
): Promise<TInstances extends 'multiton' ? TService[] : TService> {
|
||||
return this.getSubject(...args);
|
||||
}
|
||||
|
||||
@@ -97,9 +105,10 @@ export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
*/
|
||||
async getSubject(
|
||||
...args: 'root' extends TScope ? [] : [pluginId?: string]
|
||||
): Promise<TService> {
|
||||
): Promise<TInstances extends 'multiton' ? TService[] : TService> {
|
||||
const [pluginId] = args;
|
||||
return this.#registry.get(this.#subject, pluginId ?? 'test')!;
|
||||
const instance = this.#registry.get(this.#subject, pluginId ?? 'test')!;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,10 +118,14 @@ export class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin'> {
|
||||
*
|
||||
* A plugin ID can optionally be provided for plugin scoped services, otherwise the plugin ID 'test' is used.
|
||||
*/
|
||||
async getService<TGetService, TGetScope extends 'root' | 'plugin'>(
|
||||
service: ServiceRef<TGetService, TGetScope>,
|
||||
async getService<
|
||||
TGetService,
|
||||
TGetScope extends 'root' | 'plugin',
|
||||
TGetInstances extends 'singleton' | 'multiton' = 'singleton',
|
||||
>(
|
||||
service: ServiceRef<TGetService, TGetScope, TGetInstances>,
|
||||
...args: 'root' extends TGetScope ? [] : [pluginId?: string]
|
||||
): Promise<TGetService> {
|
||||
): Promise<TGetInstances extends 'multiton' ? TGetService[] : TGetService> {
|
||||
const [pluginId] = args;
|
||||
const instance = await this.#registry.get(service, pluginId ?? 'test');
|
||||
if (instance === undefined) {
|
||||
|
||||
@@ -96,7 +96,7 @@ export interface CatalogProcessingExtensionPoint {
|
||||
export const catalogProcessingExtensionPoint: ExtensionPoint<CatalogProcessingExtensionPoint>;
|
||||
|
||||
// @alpha
|
||||
export const catalogServiceRef: ServiceRef<CatalogApi, 'plugin'>;
|
||||
export const catalogServiceRef: ServiceRef<CatalogApi, 'plugin', 'singleton'>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -66,11 +66,12 @@ export type EventsServiceEventHandler = (params: EventParams) => Promise<void>;
|
||||
export const eventsServiceFactory: ServiceFactoryCompat<
|
||||
EventsService,
|
||||
'plugin',
|
||||
'singleton',
|
||||
undefined
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const eventsServiceRef: ServiceRef<EventsService, 'plugin'>;
|
||||
export const eventsServiceRef: ServiceRef<EventsService, 'plugin', 'singleton'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type EventsServiceSubscribeOptions = {
|
||||
|
||||
@@ -65,7 +65,11 @@ export interface NotificationService {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const notificationService: ServiceRef<NotificationService, 'plugin'>;
|
||||
export const notificationService: ServiceRef<
|
||||
NotificationService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type NotificationServiceOptions = {
|
||||
|
||||
@@ -46,7 +46,11 @@ export type SearchIndexServiceInitOptions = {
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export const searchIndexServiceRef: ServiceRef<SearchIndexService, 'plugin'>;
|
||||
export const searchIndexServiceRef: ServiceRef<
|
||||
SearchIndexService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -37,7 +37,7 @@ export type SignalPayload<TMessage extends JsonObject = JsonObject> = {
|
||||
export interface SignalService extends SignalsService {}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const signalService: ServiceRef<SignalsService, 'plugin'>;
|
||||
export const signalService: ServiceRef<SignalsService, 'plugin', 'singleton'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SignalsService {
|
||||
@@ -52,7 +52,11 @@ export type SignalsServiceOptions = {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const signalsServiceRef: ServiceRef<SignalsService, 'plugin'>;
|
||||
export const signalsServiceRef: ServiceRef<
|
||||
SignalsService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user