From ccc6b25f879b8b640a51b2a151061302a7a43a17 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 17 Mar 2026 09:34:48 +0100 Subject: [PATCH] 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 Made-with: Cursor --- .changeset/opaque-api-ref-type.md | 2 +- .../src/wiring/createSpecializedApp.test.tsx | 5 +++++ .../frontend-app-api/src/wiring/createSpecializedApp.tsx | 7 ++++--- packages/frontend-plugin-api/report.api.md | 1 - .../frontend-plugin-api/src/apis/system/ApiRef.test.ts | 7 ++++++- packages/frontend-plugin-api/src/apis/system/types.ts | 1 - 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.changeset/opaque-api-ref-type.md b/.changeset/opaque-api-ref-type.md index a2862e30bc..6ae300f556 100644 --- a/.changeset/opaque-api-ref-type.md +++ b/.changeset/opaque-api-ref-type.md @@ -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. diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 36f6ff250d..f954ba5750 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -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], diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index da77e31103..fd009db479 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -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; diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 2534d83eed..ae36baf2a6 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -194,7 +194,6 @@ export type ApiHolder = { export type ApiRef = { readonly $$type?: '@backstage/ApiRef'; readonly id: TId; - readonly pluginId?: string; readonly T: T; }; diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts index ff4b83b978..37bf0205be 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.test.ts @@ -27,6 +27,8 @@ describe('ApiRef', () => { }); it('should not accept pluginId with deprecated config form', () => { + expect(createApiRef({ id: 'abc' }).id).toBe('abc'); + // @ts-expect-error pluginId is only supported through .with(...) createApiRef({ id: 'abc', pluginId: 'test' }); }); @@ -45,9 +47,12 @@ describe('ApiRef', () => { const ref = createApiRef().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', () => { diff --git a/packages/frontend-plugin-api/src/apis/system/types.ts b/packages/frontend-plugin-api/src/apis/system/types.ts index 50911ded1e..5bc6136bdf 100644 --- a/packages/frontend-plugin-api/src/apis/system/types.ts +++ b/packages/frontend-plugin-api/src/apis/system/types.ts @@ -22,7 +22,6 @@ export type ApiRef = { readonly $$type?: '@backstage/ApiRef'; readonly id: TId; - readonly pluginId?: string; readonly T: T; };