api-ref: keep plugin ownership metadata internal

Hide plugin ownership metadata from the public ApiRef type while preserving internal ownership resolution for the builder-based API ref flow.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-17 09:34:48 +01:00
parent 76b89c7437
commit ccc6b25f87
6 changed files with 16 additions and 7 deletions
+1 -1
View File
@@ -4,4 +4,4 @@
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` 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.
The `createApiRef().with({ ... })` form can also use an explicit `pluginId` to declare API ownership without encoding the plugin ID into the API ref ID, while keeping that metadata internal to runtime handling.
@@ -168,6 +168,7 @@ describe('createSpecializedApp', () => {
"factory": {
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.featureflags",
"pluginId": "app",
"toString": [Function],
@@ -182,6 +183,7 @@ describe('createSpecializedApp', () => {
"factory": {
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.app-tree",
"pluginId": "app",
"toString": [Function],
@@ -196,6 +198,7 @@ describe('createSpecializedApp', () => {
"factory": {
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.config",
"pluginId": "app",
"toString": [Function],
@@ -210,6 +213,7 @@ describe('createSpecializedApp', () => {
"factory": {
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.route-resolution",
"pluginId": "app",
"toString": [Function],
@@ -224,6 +228,7 @@ describe('createSpecializedApp', () => {
"factory": {
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.identity",
"pluginId": "app",
"toString": [Function],
@@ -455,9 +455,10 @@ function createApiFactories(options: {
// TODO(Rugvip): It would be good if this was more explicit, but I think that
// might need to wait for some future update for API factories.
function getApiOwnerId(apiRef: { id: string; pluginId?: string }): string {
if (apiRef.pluginId) {
return apiRef.pluginId;
function getApiOwnerId(apiRef: { id: string }): string {
const pluginId = (apiRef as { pluginId?: string }).pluginId;
if (pluginId) {
return pluginId;
}
const apiRefId = apiRef.id;
@@ -194,7 +194,6 @@ export type ApiHolder = {
export type ApiRef<T, TId extends string = string> = {
readonly $$type?: '@backstage/ApiRef';
readonly id: TId;
readonly pluginId?: string;
readonly T: T;
};
@@ -27,6 +27,8 @@ describe('ApiRef', () => {
});
it('should not accept pluginId with deprecated config form', () => {
expect(createApiRef<string>({ id: 'abc' }).id).toBe('abc');
// @ts-expect-error pluginId is only supported through .with(...)
createApiRef<string>({ id: 'abc', pluginId: 'test' });
});
@@ -45,9 +47,12 @@ describe('ApiRef', () => {
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).toBeNull();
expect((ref as { pluginId?: string }).pluginId).toBe('test');
// @ts-expect-error pluginId is internal runtime metadata
expect(ref.pluginId).toBe('test');
});
it('should infer literal ids with builder pattern', () => {
@@ -22,7 +22,6 @@
export type ApiRef<T, TId extends string = string> = {
readonly $$type?: '@backstage/ApiRef';
readonly id: TId;
readonly pluginId?: string;
readonly T: T;
};