frontend-plugin-api: add explicit ApiRef plugin ownership
Add the new frontend ApiRef builder form while preserving compatibility with existing refs, and let frontend apps resolve API ownership through an explicit pluginId when provided. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
@@ -24,7 +24,9 @@ export type PluginWrapperApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const pluginWrapperApiRef: ApiRef<PluginWrapperApi>;
|
||||
export const pluginWrapperApiRef: ApiRef<PluginWrapperApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export const PluginWrapperBlueprint: ExtensionBlueprint<{
|
||||
|
||||
@@ -188,14 +188,16 @@ export type ApiHolder = {
|
||||
|
||||
// @public
|
||||
export type ApiRef<T> = {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
readonly $$type?: '@backstage/ApiRef';
|
||||
readonly id: string;
|
||||
readonly pluginId?: string;
|
||||
readonly T: T;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ApiRefConfig = {
|
||||
id: string;
|
||||
pluginId?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -306,7 +308,9 @@ export interface AppTreeApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const appTreeApiRef: ApiRef_2<AppTreeApi>;
|
||||
export const appTreeApiRef: ApiRef_2<AppTreeApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export const atlassianAuthApiRef: ApiRef<
|
||||
@@ -416,12 +420,16 @@ export function createApiFactory<Api, Impl extends Api>(
|
||||
instance: Impl,
|
||||
): ApiFactory<Api, Impl, {}>;
|
||||
|
||||
// @public
|
||||
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T>;
|
||||
// @public @deprecated
|
||||
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export function createApiRef<T>(): {
|
||||
with(config: ApiRefConfig): ApiRef<T>;
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -875,7 +883,9 @@ export interface DialogApiDialog<TResult = void> {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const dialogApiRef: ApiRef_2<DialogApi>;
|
||||
export const dialogApiRef: ApiRef_2<DialogApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export type DiscoveryApi = {
|
||||
@@ -1436,7 +1446,9 @@ export interface IconsApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const iconsApiRef: ApiRef_2<IconsApi>;
|
||||
export const iconsApiRef: ApiRef_2<IconsApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export type IdentityApi = {
|
||||
@@ -1851,7 +1863,9 @@ export type PluginHeaderActionsApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const pluginHeaderActionsApiRef: ApiRef_2<PluginHeaderActionsApi>;
|
||||
export const pluginHeaderActionsApiRef: ApiRef_2<PluginHeaderActionsApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PluginOptions<
|
||||
@@ -1999,7 +2013,9 @@ export interface RouteResolutionApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const routeResolutionApiRef: ApiRef_2<RouteResolutionApi>;
|
||||
export const routeResolutionApiRef: ApiRef_2<RouteResolutionApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SessionApi = {
|
||||
@@ -2127,7 +2143,9 @@ export interface SwappableComponentsApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const swappableComponentsApiRef: ApiRef_2<SwappableComponentsApi>;
|
||||
export const swappableComponentsApiRef: ApiRef_2<SwappableComponentsApi> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TranslationApi = {
|
||||
|
||||
@@ -51,6 +51,7 @@ export type AlertApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const alertApiRef: ApiRef<AlertApi> = createApiRef({
|
||||
export const alertApiRef: ApiRef<AlertApi> = createApiRef<AlertApi>().with({
|
||||
id: 'core.alert',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -151,6 +151,8 @@ export type AnalyticsApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const analyticsApiRef: ApiRef<AnalyticsApi> = createApiRef({
|
||||
id: 'core.analytics',
|
||||
});
|
||||
export const analyticsApiRef: ApiRef<AnalyticsApi> =
|
||||
createApiRef<AnalyticsApi>().with({
|
||||
id: 'core.analytics',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -31,6 +31,8 @@ export type AppLanguageApi = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const appLanguageApiRef: ApiRef<AppLanguageApi> = createApiRef({
|
||||
id: 'core.applanguage',
|
||||
});
|
||||
export const appLanguageApiRef: ApiRef<AppLanguageApi> =
|
||||
createApiRef<AppLanguageApi>().with({
|
||||
id: 'core.applanguage',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -82,6 +82,8 @@ export type AppThemeApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const appThemeApiRef: ApiRef<AppThemeApi> = createApiRef({
|
||||
id: 'core.apptheme',
|
||||
});
|
||||
export const appThemeApiRef: ApiRef<AppThemeApi> =
|
||||
createApiRef<AppThemeApi>().with({
|
||||
id: 'core.apptheme',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -117,4 +117,7 @@ export interface AppTreeApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });
|
||||
export const appTreeApiRef = createApiRef<AppTreeApi>().with({
|
||||
id: 'core.app-tree',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export type ConfigApi = Config;
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const configApiRef: ApiRef<ConfigApi> = createApiRef({
|
||||
export const configApiRef: ApiRef<ConfigApi> = createApiRef<ConfigApi>().with({
|
||||
id: 'core.config',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -173,6 +173,7 @@ export interface DialogApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const dialogApiRef = createApiRef<DialogApi>({
|
||||
export const dialogApiRef = createApiRef<DialogApi>().with({
|
||||
id: 'core.dialog',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -50,6 +50,8 @@ export type DiscoveryApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const discoveryApiRef: ApiRef<DiscoveryApi> = createApiRef({
|
||||
id: 'core.discovery',
|
||||
});
|
||||
export const discoveryApiRef: ApiRef<DiscoveryApi> =
|
||||
createApiRef<DiscoveryApi>().with({
|
||||
id: 'core.discovery',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -86,6 +86,7 @@ export type ErrorApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const errorApiRef: ApiRef<ErrorApi> = createApiRef({
|
||||
export const errorApiRef: ApiRef<ErrorApi> = createApiRef<ErrorApi>().with({
|
||||
id: 'core.error',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -121,6 +121,8 @@ export interface FeatureFlagsApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const featureFlagsApiRef: ApiRef<FeatureFlagsApi> = createApiRef({
|
||||
id: 'core.featureflags',
|
||||
});
|
||||
export const featureFlagsApiRef: ApiRef<FeatureFlagsApi> =
|
||||
createApiRef<FeatureFlagsApi>().with({
|
||||
id: 'core.featureflags',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ export type FetchApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const fetchApiRef: ApiRef<FetchApi> = createApiRef({
|
||||
export const fetchApiRef: ApiRef<FetchApi> = createApiRef<FetchApi>().with({
|
||||
id: 'core.fetch',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface IconsApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const iconsApiRef = createApiRef<IconsApi>({
|
||||
export const iconsApiRef = createApiRef<IconsApi>().with({
|
||||
id: 'core.icons',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -51,6 +51,8 @@ export type IdentityApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const identityApiRef: ApiRef<IdentityApi> = createApiRef({
|
||||
id: 'core.identity',
|
||||
});
|
||||
export const identityApiRef: ApiRef<IdentityApi> =
|
||||
createApiRef<IdentityApi>().with({
|
||||
id: 'core.identity',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -126,6 +126,8 @@ export type OAuthRequestApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const oauthRequestApiRef: ApiRef<OAuthRequestApi> = createApiRef({
|
||||
id: 'core.oauthrequest',
|
||||
});
|
||||
export const oauthRequestApiRef: ApiRef<OAuthRequestApi> =
|
||||
createApiRef<OAuthRequestApi>().with({
|
||||
id: 'core.oauthrequest',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -40,6 +40,8 @@ export type PluginHeaderActionsApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const pluginHeaderActionsApiRef = createApiRef<PluginHeaderActionsApi>({
|
||||
id: 'core.plugin-header-actions',
|
||||
});
|
||||
export const pluginHeaderActionsApiRef =
|
||||
createApiRef<PluginHeaderActionsApi>().with({
|
||||
id: 'core.plugin-header-actions',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ export type PluginWrapperApi = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const pluginWrapperApiRef = createApiRef<PluginWrapperApi>({
|
||||
export const pluginWrapperApiRef = createApiRef<PluginWrapperApi>().with({
|
||||
id: 'core.plugin-wrapper',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -65,6 +65,7 @@ export interface RouteResolutionApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const routeResolutionApiRef = createApiRef<RouteResolutionApi>({
|
||||
export const routeResolutionApiRef = createApiRef<RouteResolutionApi>().with({
|
||||
id: 'core.route-resolution',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -105,6 +105,8 @@ export interface StorageApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const storageApiRef: ApiRef<StorageApi> = createApiRef({
|
||||
id: 'core.storage',
|
||||
});
|
||||
export const storageApiRef: ApiRef<StorageApi> =
|
||||
createApiRef<StorageApi>().with({
|
||||
id: 'core.storage',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface SwappableComponentsApi {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({
|
||||
id: 'core.swappable-components',
|
||||
});
|
||||
export const swappableComponentsApiRef =
|
||||
createApiRef<SwappableComponentsApi>().with({
|
||||
id: 'core.swappable-components',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -358,6 +358,8 @@ export type TranslationApi = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const translationApiRef: ApiRef<TranslationApi> = createApiRef({
|
||||
id: 'core.translation',
|
||||
});
|
||||
export const translationApiRef: ApiRef<TranslationApi> =
|
||||
createApiRef<TranslationApi>().with({
|
||||
id: 'core.translation',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -28,7 +28,10 @@ import { Observable } from '@backstage/types';
|
||||
* For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect,
|
||||
* would be declared as follows:
|
||||
*
|
||||
* const googleAuthApiRef = createApiRef<OAuthApi & OpenIDConnectApi>({ ... })
|
||||
* const googleAuthApiRef = createApiRef<OAuthApi & OpenIDConnectApi>().with({
|
||||
* id: 'core.auth.google',
|
||||
* pluginId: 'app',
|
||||
* })
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -339,8 +342,15 @@ export const googleAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.google',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -354,8 +364,11 @@ export const googleAuthApiRef: ApiRef<
|
||||
*/
|
||||
export const githubAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.github',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -373,8 +386,15 @@ export const oktaAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.okta',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -392,8 +412,15 @@ export const gitlabAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.gitlab',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -412,8 +439,15 @@ export const microsoftAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.microsoft',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -427,8 +461,15 @@ export const oneloginAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.onelogin',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -442,8 +483,11 @@ export const oneloginAuthApiRef: ApiRef<
|
||||
*/
|
||||
export const bitbucketAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.bitbucket',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -457,8 +501,11 @@ export const bitbucketAuthApiRef: ApiRef<
|
||||
*/
|
||||
export const bitbucketServerAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.bitbucket-server',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -472,8 +519,11 @@ export const bitbucketServerAuthApiRef: ApiRef<
|
||||
*/
|
||||
export const atlassianAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.atlassian',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -491,8 +541,15 @@ export const vmwareCloudAuthApiRef: ApiRef<
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.vmware-cloud',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -508,6 +565,9 @@ export const vmwareCloudAuthApiRef: ApiRef<
|
||||
*/
|
||||
export const openshiftAuthApiRef: ApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
> = createApiRef({
|
||||
> = createApiRef<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
>().with({
|
||||
id: 'core.auth.openshift',
|
||||
pluginId: 'app',
|
||||
});
|
||||
|
||||
@@ -26,9 +26,10 @@ describe('ApiRef', () => {
|
||||
});
|
||||
|
||||
it('should be created with builder pattern', () => {
|
||||
const ref = createApiRef<string>().with({ id: 'abc' });
|
||||
const ref = createApiRef<string>().with({ id: 'abc', pluginId: 'test' });
|
||||
expect(ref.$$type).toBe('@backstage/ApiRef');
|
||||
expect(ref.id).toBe('abc');
|
||||
expect(ref.pluginId).toBe('test');
|
||||
expect(String(ref)).toBe('apiRef{abc}');
|
||||
expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}');
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OpaqueType } from '@internal/opaque';
|
||||
import type { ApiRef } from './types';
|
||||
|
||||
/**
|
||||
@@ -23,8 +24,21 @@ import type { ApiRef } from './types';
|
||||
*/
|
||||
export type ApiRefConfig = {
|
||||
id: string;
|
||||
pluginId?: string;
|
||||
};
|
||||
|
||||
const OpaqueApiRef = OpaqueType.create<{
|
||||
public: ApiRef<unknown> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
versions: {
|
||||
readonly version: 'v1';
|
||||
};
|
||||
}>({
|
||||
type: '@backstage/ApiRef',
|
||||
versions: ['v1'],
|
||||
});
|
||||
|
||||
function validateId(id: string): void {
|
||||
const valid = id
|
||||
.split('.')
|
||||
@@ -37,22 +51,24 @@ function validateId(id: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function makeApiRef<T>(id: string): ApiRef<T> {
|
||||
const ref = {
|
||||
$$type: '@backstage/ApiRef' as const,
|
||||
version: 'v1',
|
||||
id,
|
||||
function makeApiRef<T>(
|
||||
config: ApiRefConfig,
|
||||
): ApiRef<T> & { readonly $$type: '@backstage/ApiRef' } {
|
||||
const ref = OpaqueApiRef.createInstance('v1', {
|
||||
id: config.id,
|
||||
...(config.pluginId ? { pluginId: config.pluginId } : {}),
|
||||
T: undefined as T,
|
||||
toString() {
|
||||
return `apiRef{${id}}`;
|
||||
return `apiRef{${config.id}}`;
|
||||
},
|
||||
};
|
||||
}) as ApiRef<T> & { readonly $$type: '@backstage/ApiRef' };
|
||||
Object.defineProperty(ref, 'T', {
|
||||
get(): T {
|
||||
throw new Error(`tried to read ApiRef.T of ${this}`);
|
||||
},
|
||||
enumerable: false,
|
||||
});
|
||||
return ref as unknown as ApiRef<T>;
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,18 +77,22 @@ function makeApiRef<T>(id: string): ApiRef<T> {
|
||||
* @remarks
|
||||
*
|
||||
* The `id` is a stable identifier for the API implementation. The frontend
|
||||
* system infers the owning plugin for an API from the `id`. The recommended
|
||||
* pattern is `plugin.<plugin-id>.*` (for example,
|
||||
* system infers the owning plugin for an API from the `id`, unless you provide
|
||||
* a `pluginId` explicitly. The recommended pattern is `plugin.<plugin-id>.*`
|
||||
* (for example,
|
||||
* `plugin.catalog.entity-presentation`). This ensures that other plugins can't
|
||||
* mistakenly override your API implementation.
|
||||
*
|
||||
* The recommended way to create an API reference is:
|
||||
*
|
||||
* ```ts
|
||||
* const myApiRef = createApiRef<MyApi>().with({ id: 'plugin.my.api' });
|
||||
* const myApiRef = createApiRef<MyApi>().with({
|
||||
* id: 'my-api',
|
||||
* pluginId: 'my-plugin',
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* For backwards compatibility, you can also pass the config directly:
|
||||
* The legacy way to create an API reference is:
|
||||
*
|
||||
* ```ts
|
||||
* const myApiRef = createApiRef<MyApi>({ id: 'plugin.my.api' });
|
||||
@@ -80,30 +100,47 @@ function makeApiRef<T>(id: string): ApiRef<T> {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T>;
|
||||
/**
|
||||
* Creates a reference to an API.
|
||||
*
|
||||
* @deprecated Use `createApiRef<T>().with(...)` instead.
|
||||
* @public
|
||||
*/
|
||||
export function createApiRef<T>(
|
||||
config: ApiRefConfig,
|
||||
): ApiRef<T> & { readonly $$type: '@backstage/ApiRef' };
|
||||
/**
|
||||
* Creates a reference to an API.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Returns a builder with a `.with()` method for providing the `id`.
|
||||
* Returns a builder with a `.with()` method for providing the API reference
|
||||
* configuration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createApiRef<T>(): {
|
||||
with(config: ApiRefConfig): ApiRef<T>;
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
};
|
||||
export function createApiRef<T>(
|
||||
config?: ApiRefConfig,
|
||||
): ApiRef<T> | { with(config: ApiRefConfig): ApiRef<T> } {
|
||||
export function createApiRef<T>(config?: ApiRefConfig):
|
||||
| (ApiRef<T> & { readonly $$type: '@backstage/ApiRef' })
|
||||
| {
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
} {
|
||||
if (config) {
|
||||
validateId(config.id);
|
||||
return makeApiRef<T>(config.id);
|
||||
return makeApiRef<T>(config);
|
||||
}
|
||||
return {
|
||||
with(withConfig: ApiRefConfig): ApiRef<T> {
|
||||
with(withConfig: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
} {
|
||||
validateId(withConfig.id);
|
||||
return makeApiRef<T>(withConfig.id);
|
||||
return makeApiRef<T>(withConfig);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
* @public
|
||||
*/
|
||||
export type ApiRef<T> = {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
readonly $$type?: '@backstage/ApiRef';
|
||||
readonly id: string;
|
||||
readonly pluginId?: string;
|
||||
readonly T: T;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ describe('useApiHolder', () => {
|
||||
const renderedHook = renderHook(() => useApiHolder());
|
||||
|
||||
const holder = renderedHook.result.current;
|
||||
expect(holder.get(createApiRef<string>({ id: 'x' }))).toBeUndefined();
|
||||
expect(
|
||||
holder.get(createApiRef<string>().with({ id: 'x' })),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +55,7 @@ describe('useApi', () => {
|
||||
const get = jest.fn(() => 'my-api-impl');
|
||||
context.set({ 1: { get } });
|
||||
|
||||
const apiRef = createApiRef<string>({ id: 'x' });
|
||||
const apiRef = createApiRef<string>().with({ id: 'x' });
|
||||
const renderedHook = renderHook(() => useApi(apiRef));
|
||||
|
||||
const value = renderedHook.result.current;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { createApiRef } from '../apis/system';
|
||||
|
||||
describe('ApiBlueprint', () => {
|
||||
it('should create an extension with sensible defaults', () => {
|
||||
const api = createApiRef<{ foo: string }>({ id: 'test' });
|
||||
const api = createApiRef<{ foo: string }>().with({ id: 'test' });
|
||||
|
||||
const extension = ApiBlueprint.make({
|
||||
params: defineParams =>
|
||||
@@ -57,8 +57,8 @@ describe('ApiBlueprint', () => {
|
||||
});
|
||||
|
||||
it('should properly type the API factory', () => {
|
||||
const fooApi = createApiRef<{ foo: string }>({ id: 'foo' });
|
||||
const barApi = createApiRef<{ bar: string }>({ id: 'bar' });
|
||||
const fooApi = createApiRef<{ foo: string }>().with({ id: 'foo' });
|
||||
const barApi = createApiRef<{ bar: string }>().with({ id: 'bar' });
|
||||
|
||||
expect('test').not.toBe('failing without assertions');
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('ApiBlueprint', () => {
|
||||
});
|
||||
|
||||
it('should create an extension with custom factory', () => {
|
||||
const api = createApiRef<{ foo: string }>({ id: 'test' });
|
||||
const api = createApiRef<{ foo: string }>().with({ id: 'test' });
|
||||
const factory = jest.fn(() => ({ foo: 'bar' }));
|
||||
|
||||
const extension = ApiBlueprint.makeWithOverrides({
|
||||
|
||||
Reference in New Issue
Block a user