diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 92bd623744..9d71d3ba14 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -43,13 +43,13 @@ import { createApiExtension, createApiFactory, createPlugin, - configApiRef, - ConfigApi, + storageApiRef, + StorageApi, } from '@backstage/frontend-plugin-api'; import { WorkApi, workApiRef } from '@internal/plugin-example-react'; class WorkImpl implements WorkApi { - constructor(options: { configApi: ConfigApi }) { + constructor(options: { storageApiRef: StorageApi }) { /* TODO */ } async doWork() { @@ -62,8 +62,8 @@ const workApi = createApiExtension({ factory: () => createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), }); @@ -79,7 +79,7 @@ export default createPlugin({ For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. Users who install the plugin will now get the utility API automatically as well. -The code also illustrates how the API factory declares a dependency on another utility API - the core config API in this case. An instance of that utility API is then provided to the factory function. +The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. You can now use this ID to refer to the API in app-config and elsewhere. diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index b24489a21d..006f3b8fab 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -64,4 +64,4 @@ const myApi = createApiExtension({ }); ``` -Note how the `deps` section essentially assigns free-form names that you choose, to API refs. In this example we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. +Note how the `deps` section essentially assigns free-form names that you choose, to API refs. Here we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index b56cd10c5c..d6b6167ca2 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -6,9 +6,64 @@ sidebar_label: Configuring description: Configuring, extending, and overriding utility APIs --- +Utility APIs are extensions and can therefore optionally be amended with configurability, as well as inputs that other extensions attach themselves to. This section describes how to add those capabilities to your own utility APIs, and how to make use of them as a consumer of such utility APIs. + ## Adding configurability -> TODO +Here we will describe how to amend a utility API with the capability of being configured in app-config. You do this by giving a config schema to your API extension factory function. + +Let's make the required additions to [our original work example](./02-creating.md) API. + +```tsx title="in @internal/plugin-example" +import { + createApiExtension, + createApiFactory, + createPlugin, + /* highlight-add-next-line */ + createSchemaFromZod, + storageApiRef, + StorageApi, +} from '@backstage/frontend-plugin-api'; +import { WorkApi, workApiRef } from '@internal/plugin-example-react'; + +/* highlight-add-start */ +interface WorkApiConfig { + goSlow: boolean; +} +/* highlight-add-end */ + +const workApi = createApiExtension({ + api: workApiRef, + /* highlight-add-start */ + configSchema: createSchemaFromZod(z => + z.object({ + goSlow: z.boolean().default(false), + }), + ), + /* highlight-add-end */ + /* highlight-remove-next-line */ + factory: () => + /* highlight-add-next-line */ + factory: ({ config }) => + createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + /* highlight-add-start */ + if (config.goSlow) { + /* ... */ + } + /* highlight-add-end */ + }, + }), +}); +``` + +We wanted users to be able to configure a `goSlow` parameter for our API instances. So we added another interface type for holding our various options, and passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual config values will then be passed in to the `factory` callback, where we can do what we wish with them. + +Note that while we use the word "config" here, it's _not_ the same thing as the `configApi` which gives you access to the full app-config. The config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the section below](#configuring). + +Note also that the config schema contained a default value fo the `goSlow` field. This is an important consideration. You want users of your API to be able to make maximum use of it, without having to dive deep into how to configure it. For that reason you generally want to provide as many sane defaults as possible, while letting users override them rarely but with purpose, only when called for. If you have a config schema without defaults, the framework will refuse to instantiate the utility API on startup unless the user had configured those values explicitly. Since it had a default value, the TypeScript code and interfaces also don't have to defensively allow `undefined` - we know that it'll have either the default value or an overridden value when we start consuming the config data. ## Configuring diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md index f3e85ceaf4..b4b456e65f 100644 --- a/docs/frontend-system/utility-apis/05-migrating.md +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -78,7 +78,7 @@ Now let's turn to the main plugin package where the plugin itself is exported. Y ```tsx title="in @internal/plugin-example, NOTE THIS IS LEGACY CODE" import { - configApiRef, + storageApiRef, createPlugin, createApiFactory, } from '@backstage/core-plugin-api'; @@ -87,8 +87,8 @@ import { WorkImpl } from './WorkImpl'; const workApi = createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }); /** @public */ @@ -107,7 +107,7 @@ The major changes we'll make are ```tsx title="in @internal/plugin-example" import { - configApiRef, + storageApiRef, createPlugin, createApiFactory, createApiExtension, @@ -121,8 +121,8 @@ const workApi = createApiExtension({ // The factory itself is unchanged createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), });