api-ref: infer builder ids and plugin ownership
Preserve literal API ref ids in the builder form while keeping the deprecated constructor compatible, and rely on explicit ownership metadata instead of the old core id fallback. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`.
|
||||
Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`. The builder form now also preserves literal API ref IDs in the resulting `ApiRef` type.
|
||||
|
||||
`ApiRef` and `ApiRefConfig` now also support an explicit `pluginId`, making it possible to declare API ownership without encoding the plugin ID into the API ref ID.
|
||||
`ApiRef` now also supports an explicit `pluginId`, and the `createApiRef().with({ ... })` form can use it to declare API ownership without encoding the plugin ID into the API ref ID.
|
||||
|
||||
@@ -22,7 +22,12 @@ describe('ApiRef', () => {
|
||||
expect(ref.$$type).toBe('@backstage/ApiRef');
|
||||
expect(ref.id).toBe('abc');
|
||||
expect(String(ref)).toBe('apiRef{abc}');
|
||||
expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}');
|
||||
expect(ref.T).toBeNull();
|
||||
});
|
||||
|
||||
it('should not accept pluginId in the core createApiRef config', () => {
|
||||
// @ts-expect-error pluginId is not supported in core-plugin-api
|
||||
createApiRef<string>({ id: 'abc', pluginId: 'test' });
|
||||
});
|
||||
|
||||
it('should reject invalid ids', () => {
|
||||
|
||||
@@ -417,6 +417,51 @@ describe('createSpecializedApp', () => {
|
||||
expect(app.apis.get(testApiRef)).toEqual({ value: 'owner' });
|
||||
});
|
||||
|
||||
it('should not infer app ownership from core-prefixed API ids', () => {
|
||||
const testApiRef = createApiRef<{ value: string }>({ id: 'core.shared' });
|
||||
|
||||
const app = createSpecializedApp({
|
||||
features: [
|
||||
makeAppPlugin(),
|
||||
createFrontendPlugin({
|
||||
pluginId: 'other-before',
|
||||
extensions: [
|
||||
ApiBlueprint.make({
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: testApiRef,
|
||||
deps: {},
|
||||
factory: () => ({ value: 'other' }),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
createFrontendModule({
|
||||
pluginId: 'app',
|
||||
extensions: [
|
||||
ApiBlueprint.make({
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: testApiRef,
|
||||
deps: {},
|
||||
factory: () => ({ value: 'app' }),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(app.errors).toEqual([
|
||||
expect.objectContaining({
|
||||
code: 'API_FACTORY_CONFLICT',
|
||||
message: expect.stringContaining("API 'core.shared'"),
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(app.apis.get(testApiRef)).toEqual({ value: 'other' });
|
||||
});
|
||||
|
||||
it('should allow API overrides within the same plugin', () => {
|
||||
const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' });
|
||||
|
||||
|
||||
@@ -407,8 +407,8 @@ function createApiFactories(options: {
|
||||
|
||||
// This allows modules to override factories provided by the plugin, but
|
||||
// it rejects API overrides from other plugins. In the event of a
|
||||
// conflict, the owning plugin is attempted to be inferred from the API
|
||||
// reference ID.
|
||||
// conflict, the owning plugin is inferred from the explicit pluginId or
|
||||
// legacy plugin-prefixed API reference ID.
|
||||
if (existingFactory && existingFactory.pluginId !== pluginId) {
|
||||
const shouldReplace =
|
||||
ownerId === pluginId && existingFactory.pluginId !== ownerId;
|
||||
@@ -465,9 +465,6 @@ function getApiOwnerId(apiRef: { id: string; pluginId?: string }): string {
|
||||
if (!prefix) {
|
||||
return apiRefId;
|
||||
}
|
||||
if (prefix === 'core') {
|
||||
return 'app';
|
||||
}
|
||||
if (prefix === 'plugin' && rest[0]) {
|
||||
return rest[0];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ export type PluginWrapperApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const pluginWrapperApiRef: ApiRef<PluginWrapperApi> & {
|
||||
export const pluginWrapperApiRef: ApiRef<
|
||||
PluginWrapperApi,
|
||||
'core.plugin-wrapper'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export type AlertApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const alertApiRef: ApiRef_2<AlertApi> & {
|
||||
export const alertApiRef: ApiRef_2<AlertApi, 'core.alert'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ export type AnalyticsApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const analyticsApiRef: ApiRef_2<AnalyticsApi> & {
|
||||
export const analyticsApiRef: ApiRef_2<AnalyticsApi, 'core.analytics'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -191,9 +191,9 @@ export type ApiHolder = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ApiRef<T> = {
|
||||
export type ApiRef<T, TId extends string = string> = {
|
||||
readonly $$type?: '@backstage/ApiRef';
|
||||
readonly id: string;
|
||||
readonly id: TId;
|
||||
readonly pluginId?: string;
|
||||
readonly T: T;
|
||||
};
|
||||
@@ -201,7 +201,6 @@ export type ApiRef<T> = {
|
||||
// @public
|
||||
export type ApiRefConfig = {
|
||||
id: string;
|
||||
pluginId?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -219,7 +218,7 @@ export type AppLanguageApi = {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const appLanguageApiRef: ApiRef_2<AppLanguageApi> & {
|
||||
export const appLanguageApiRef: ApiRef_2<AppLanguageApi, 'core.applanguage'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -294,7 +293,7 @@ export type AppThemeApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const appThemeApiRef: ApiRef_2<AppThemeApi> & {
|
||||
export const appThemeApiRef: ApiRef_2<AppThemeApi, 'core.apptheme'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -316,13 +315,14 @@ export interface AppTreeApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const appTreeApiRef: ApiRef_2<AppTreeApi> & {
|
||||
export const appTreeApiRef: ApiRef_2<AppTreeApi, 'core.app-tree'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export const atlassianAuthApiRef: ApiRef_2<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
|
||||
'core.auth.atlassian'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -364,14 +364,16 @@ export type BackstageUserIdentity = {
|
||||
|
||||
// @public
|
||||
export const bitbucketAuthApiRef: ApiRef_2<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
|
||||
'core.auth.bitbucket'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
// @public
|
||||
export const bitbucketServerAuthApiRef: ApiRef_2<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
|
||||
'core.auth.bitbucket-server'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -380,7 +382,7 @@ export const bitbucketServerAuthApiRef: ApiRef_2<
|
||||
export type ConfigApi = Config;
|
||||
|
||||
// @public
|
||||
export const configApiRef: ApiRef_2<Config> & {
|
||||
export const configApiRef: ApiRef_2<Config, 'core.config'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -443,7 +445,12 @@ export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> & {
|
||||
|
||||
// @public
|
||||
export function createApiRef<T>(): {
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
with<TId extends string>(
|
||||
config: ApiRefConfig & {
|
||||
id: TId;
|
||||
pluginId?: string;
|
||||
},
|
||||
): ApiRef<T, TId> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
};
|
||||
@@ -899,7 +906,7 @@ export interface DialogApiDialog<TResult = void> {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const dialogApiRef: ApiRef_2<DialogApi> & {
|
||||
export const dialogApiRef: ApiRef_2<DialogApi, 'core.dialog'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -909,7 +916,7 @@ export type DiscoveryApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const discoveryApiRef: ApiRef_2<DiscoveryApi> & {
|
||||
export const discoveryApiRef: ApiRef_2<DiscoveryApi, 'core.discovery'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -935,7 +942,7 @@ export type ErrorApiErrorContext = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const errorApiRef: ApiRef_2<ErrorApi> & {
|
||||
export const errorApiRef: ApiRef_2<ErrorApi, 'core.error'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1321,7 +1328,10 @@ export interface FeatureFlagsApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const featureFlagsApiRef: ApiRef_2<FeatureFlagsApi> & {
|
||||
export const featureFlagsApiRef: ApiRef_2<
|
||||
FeatureFlagsApi,
|
||||
'core.featureflags'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1355,7 +1365,7 @@ export type FetchApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const fetchApiRef: ApiRef_2<FetchApi> & {
|
||||
export const fetchApiRef: ApiRef_2<FetchApi, 'core.fetch'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1431,7 +1441,8 @@ export type FrontendPluginInfoOptions = {
|
||||
|
||||
// @public
|
||||
export const githubAuthApiRef: ApiRef_2<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
|
||||
'core.auth.github'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1442,7 +1453,8 @@ export const gitlabAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.gitlab'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1453,7 +1465,8 @@ export const googleAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.google'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1476,7 +1489,7 @@ export interface IconsApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const iconsApiRef: ApiRef_2<IconsApi> & {
|
||||
export const iconsApiRef: ApiRef_2<IconsApi, 'core.icons'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1491,7 +1504,7 @@ export type IdentityApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const identityApiRef: ApiRef_2<IdentityApi> & {
|
||||
export const identityApiRef: ApiRef_2<IdentityApi, 'core.identity'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1501,7 +1514,8 @@ export const microsoftAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.microsoft'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1567,7 +1581,10 @@ export type OAuthRequestApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const oauthRequestApiRef: ApiRef_2<OAuthRequestApi> & {
|
||||
export const oauthRequestApiRef: ApiRef_2<
|
||||
OAuthRequestApi,
|
||||
'core.oauthrequest'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1591,7 +1608,8 @@ export const oktaAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.okta'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1602,7 +1620,8 @@ export const oneloginAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.onelogin'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1614,7 +1633,8 @@ export type OpenIdConnectApi = {
|
||||
|
||||
// @public
|
||||
export const openshiftAuthApiRef: ApiRef_2<
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
|
||||
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
|
||||
'core.auth.openshift'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
@@ -1905,7 +1925,10 @@ export type PluginHeaderActionsApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const pluginHeaderActionsApiRef: ApiRef_2<PluginHeaderActionsApi> & {
|
||||
export const pluginHeaderActionsApiRef: ApiRef_2<
|
||||
PluginHeaderActionsApi,
|
||||
'core.plugin-header-actions'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -1949,7 +1972,10 @@ export type PluginWrapperApi = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const pluginWrapperApiRef: ApiRef_2<PluginWrapperApi> & {
|
||||
export const pluginWrapperApiRef: ApiRef_2<
|
||||
PluginWrapperApi,
|
||||
'core.plugin-wrapper'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -2057,7 +2083,10 @@ export interface RouteResolutionApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const routeResolutionApiRef: ApiRef_2<RouteResolutionApi> & {
|
||||
export const routeResolutionApiRef: ApiRef_2<
|
||||
RouteResolutionApi,
|
||||
'core.route-resolution'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -2097,7 +2126,7 @@ export interface StorageApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const storageApiRef: ApiRef_2<StorageApi> & {
|
||||
export const storageApiRef: ApiRef_2<StorageApi, 'core.storage'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -2189,7 +2218,10 @@ export interface SwappableComponentsApi {
|
||||
}
|
||||
|
||||
// @public
|
||||
export const swappableComponentsApiRef: ApiRef_2<SwappableComponentsApi> & {
|
||||
export const swappableComponentsApiRef: ApiRef_2<
|
||||
SwappableComponentsApi,
|
||||
'core.swappable-components'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -2212,7 +2244,7 @@ export type TranslationApi = {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const translationApiRef: ApiRef_2<TranslationApi> & {
|
||||
export const translationApiRef: ApiRef_2<TranslationApi, 'core.translation'> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -2409,7 +2441,8 @@ export const vmwareCloudAuthApiRef: ApiRef_2<
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
SessionApi,
|
||||
'core.auth.vmware-cloud'
|
||||
> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from './ApiRef';
|
||||
import type { ApiRef as ApiRefType } from './types';
|
||||
|
||||
describe('ApiRef', () => {
|
||||
it('should be created with config', () => {
|
||||
@@ -22,7 +23,22 @@ describe('ApiRef', () => {
|
||||
expect(ref.$$type).toBe('@backstage/ApiRef');
|
||||
expect(ref.id).toBe('abc');
|
||||
expect(String(ref)).toBe('apiRef{abc}');
|
||||
expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}');
|
||||
expect(ref.T).toBeNull();
|
||||
});
|
||||
|
||||
it('should not accept pluginId with deprecated config form', () => {
|
||||
// @ts-expect-error pluginId is only supported through .with(...)
|
||||
createApiRef<string>({ id: 'abc', pluginId: 'test' });
|
||||
});
|
||||
|
||||
it('should keep the deprecated config form id wide', () => {
|
||||
const ref = createApiRef<string>({ id: 'abc' });
|
||||
const wideRef: ApiRefType<string> = ref;
|
||||
expect(wideRef.id).toBe('abc');
|
||||
|
||||
// @ts-expect-error deprecated config form should not infer literal ids
|
||||
const literalRef: ApiRefType<string, 'abc'> = ref;
|
||||
expect(literalRef.id).toBe('abc');
|
||||
});
|
||||
|
||||
it('should be created with builder pattern', () => {
|
||||
@@ -31,7 +47,17 @@ describe('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}');
|
||||
expect(ref.T).toBeNull();
|
||||
});
|
||||
|
||||
it('should infer literal ids with builder pattern', () => {
|
||||
const ref = createApiRef<string>().with({ id: 'abc', pluginId: 'test' });
|
||||
const literalRef: ApiRefType<string, 'abc'> = ref;
|
||||
expect(literalRef.id).toBe('abc');
|
||||
|
||||
// @ts-expect-error builder pattern should preserve literal ids
|
||||
const wrongLiteralRef: ApiRefType<string, 'def'> = ref;
|
||||
expect(wrongLiteralRef.id).toBe('abc');
|
||||
});
|
||||
|
||||
it('should reject invalid ids', () => {
|
||||
|
||||
@@ -24,6 +24,10 @@ import type { ApiRef } from './types';
|
||||
*/
|
||||
export type ApiRefConfig = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type ApiRefBuilderConfig<TId extends string> = {
|
||||
id: TId;
|
||||
pluginId?: string;
|
||||
};
|
||||
|
||||
@@ -51,24 +55,17 @@ function validateId(id: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function makeApiRef<T>(
|
||||
config: ApiRefConfig,
|
||||
): ApiRef<T> & { readonly $$type: '@backstage/ApiRef' } {
|
||||
const ref = OpaqueApiRef.createInstance('v1', {
|
||||
function makeApiRef<T, TId extends string>(
|
||||
config: ApiRefBuilderConfig<TId>,
|
||||
): ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' } {
|
||||
return OpaqueApiRef.createInstance('v1', {
|
||||
id: config.id,
|
||||
...(config.pluginId ? { pluginId: config.pluginId } : {}),
|
||||
T: undefined as T,
|
||||
T: null as unknown as T,
|
||||
toString() {
|
||||
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 ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,9 +74,9 @@ function makeApiRef<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`, unless you provide
|
||||
* a `pluginId` explicitly. The recommended pattern is `plugin.<plugin-id>.*`
|
||||
* (for example,
|
||||
* system infers the owning plugin for an API from the `id`. When using the
|
||||
* builder form, you can instead 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.
|
||||
*
|
||||
@@ -120,27 +117,31 @@ export function createApiRef<T>(
|
||||
* @public
|
||||
*/
|
||||
export function createApiRef<T>(): {
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
with<TId extends string>(
|
||||
config: ApiRefConfig & { id: TId; pluginId?: string },
|
||||
): ApiRef<T, TId> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
};
|
||||
export function createApiRef<T>(config?: ApiRefConfig):
|
||||
| (ApiRef<T> & { readonly $$type: '@backstage/ApiRef' })
|
||||
| {
|
||||
with(config: ApiRefConfig): ApiRef<T> & {
|
||||
with<TId extends string>(
|
||||
config: ApiRefConfig & { id: TId; pluginId?: string },
|
||||
): ApiRef<T, TId> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
};
|
||||
} {
|
||||
if (config) {
|
||||
validateId(config.id);
|
||||
return makeApiRef<T>(config);
|
||||
return makeApiRef<T, string>(config);
|
||||
}
|
||||
return {
|
||||
with(withConfig: ApiRefConfig): ApiRef<T> & {
|
||||
readonly $$type: '@backstage/ApiRef';
|
||||
} {
|
||||
with<TId extends string>(
|
||||
withConfig: ApiRefConfig & { id: TId; pluginId?: string },
|
||||
): ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' } {
|
||||
validateId(withConfig.id);
|
||||
return makeApiRef<T>(withConfig);
|
||||
return makeApiRef<T, TId>(withConfig);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApiRef<T> = {
|
||||
export type ApiRef<T, TId extends string = string> = {
|
||||
readonly $$type?: '@backstage/ApiRef';
|
||||
readonly id: string;
|
||||
readonly id: TId;
|
||||
readonly pluginId?: string;
|
||||
readonly T: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user