diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 8495f961db..a857eff27d 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -35,16 +35,18 @@ import { ApiHolder, AppComponents, AppContext, + attachComponentData, +} from '@backstage/core-plugin-api'; +import { appThemeApiRef, ConfigApi, configApiRef, IconComponent, BackstagePlugin as LegacyBackstagePlugin, featureFlagsApiRef, - attachComponentData, identityApiRef, AppTheme, -} from '@backstage/core-plugin-api'; +} from '@backstage/frontend-plugin-api'; import { getAvailableFeatures } from './discovery'; import { ApiFactoryRegistry, diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 2a1d741d3d..d7d19e3d74 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -39,6 +39,7 @@ "react-router-dom": "6.0.0-beta.0 || ^6.3.0" }, "dependencies": { + "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/types": "workspace:^", diff --git a/packages/frontend-plugin-api/src/definitions/AlertApi.ts b/packages/frontend-plugin-api/src/definitions/AlertApi.ts new file mode 100644 index 0000000000..afdcb6a617 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/AlertApi.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef, ApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Message handled by the {@link AlertApi}. + * + * @public + */ +export type AlertMessage = { + message: string; + // Severity will default to success since that is what material ui defaults the value to. + severity?: 'success' | 'info' | 'warning' | 'error'; + display?: 'permanent' | 'transient'; +}; + +/** + * The alert API is used to report alerts to the app, and display them to the user. + * + * @public + */ +export type AlertApi = { + /** + * Post an alert for handling by the application. + */ + post(alert: AlertMessage): void; + + /** + * Observe alerts posted by other parts of the application. + */ + alert$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link AlertApi}. + * + * @public + */ +export const alertApiRef: ApiRef = createApiRef({ + id: 'core.alert', +}); diff --git a/packages/frontend-plugin-api/src/definitions/AppLanguageApi.ts b/packages/frontend-plugin-api/src/definitions/AppLanguageApi.ts new file mode 100644 index 0000000000..1207e84755 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/AppLanguageApi.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { Observable } from '@backstage/types'; + +/** @alpha */ +export type AppLanguageApi = { + getAvailableLanguages(): { languages: string[] }; + + setLanguage(language?: string): void; + + getLanguage(): { language: string }; + + language$(): Observable<{ language: string }>; +}; + +/** + * @alpha + */ +export const appLanguageApiRef: ApiRef = createApiRef({ + id: 'core.applanguage', +}); diff --git a/packages/frontend-plugin-api/src/definitions/AppThemeApi.ts b/packages/frontend-plugin-api/src/definitions/AppThemeApi.ts new file mode 100644 index 0000000000..e771fad597 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/AppThemeApi.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Describes a theme provided by the app. + * + * @public + */ +export type AppTheme = { + /** + * ID used to remember theme selections. + */ + id: string; + + /** + * Title of the theme + */ + title: string; + + /** + * Theme variant + */ + variant: 'light' | 'dark'; + + /** + * An Icon for the theme mode setting. + */ + icon?: React.ReactElement; + + Provider(props: { children: ReactNode }): JSX.Element | null; +}; + +/** + * The AppThemeApi gives access to the current app theme, and allows switching + * to other options that have been registered as a part of the App. + * + * @public + */ +export type AppThemeApi = { + /** + * Get a list of available themes. + */ + getInstalledThemes(): AppTheme[]; + + /** + * Observe the currently selected theme. A value of undefined means no specific theme has been selected. + */ + activeThemeId$(): Observable; + + /** + * Get the current theme ID. Returns undefined if no specific theme is selected. + */ + getActiveThemeId(): string | undefined; + + /** + * Set a specific theme to use in the app, overriding the default theme selection. + * + * Clear the selection by passing in undefined. + */ + setActiveThemeId(themeId?: string): void; +}; + +/** + * The {@link ApiRef} of {@link AppThemeApi}. + * + * @public + */ +export const appThemeApiRef: ApiRef = createApiRef({ + id: 'core.apptheme', +}); diff --git a/packages/frontend-plugin-api/src/definitions/ConfigApi.ts b/packages/frontend-plugin-api/src/definitions/ConfigApi.ts new file mode 100644 index 0000000000..d3bada9e46 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/ConfigApi.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ApiRef, createApiRef } from '../system'; +import { Config } from '@backstage/config'; + +/** + * The Config API is used to provide a mechanism to access the + * runtime configuration of the system. + * + * @public + */ +export type ConfigApi = Config; + +/** + * The {@link ApiRef} of {@link ConfigApi}. + * + * @public + */ +export const configApiRef: ApiRef = createApiRef({ + id: 'core.config', +}); diff --git a/packages/frontend-plugin-api/src/definitions/DiscoveryApi.ts b/packages/frontend-plugin-api/src/definitions/DiscoveryApi.ts new file mode 100644 index 0000000000..d23fe3db6c --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/DiscoveryApi.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ApiRef, createApiRef } from '../system'; + +/** + * The discovery API is used to provide a mechanism for plugins to + * discover the endpoint to use to talk to their backend counterpart. + * + * @remarks + * + * The purpose of the discovery API is to allow for many different deployment + * setups and routing methods through a central configuration, instead + * of letting each individual plugin manage that configuration. + * + * Implementations of the discovery API can be a simple as a URL pattern + * using the pluginId, but could also have overrides for individual plugins, + * or query a separate discovery service. + * + * @public + */ +export type DiscoveryApi = { + /** + * Returns the HTTP base backend URL for a given plugin, without a trailing slash. + * + * This method must always be called just before making a request, as opposed to + * fetching the URL when constructing an API client. That is to ensure that more + * flexible routing patterns can be supported. + * + * For example, asking for the URL for `auth` may return something + * like `https://backstage.example.com/api/auth` + */ + getBaseUrl(pluginId: string): Promise; +}; + +/** + * The {@link ApiRef} of {@link DiscoveryApi}. + * + * @public + */ +export const discoveryApiRef: ApiRef = createApiRef({ + id: 'core.discovery', +}); diff --git a/packages/frontend-plugin-api/src/definitions/ErrorApi.ts b/packages/frontend-plugin-api/src/definitions/ErrorApi.ts new file mode 100644 index 0000000000..9c73d94cac --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/ErrorApi.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '../system'; +import { Observable } from '@backstage/types'; + +/** + * Mirrors the JavaScript Error class, for the purpose of + * providing documentation and optional fields. + * + * @public + */ +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; + +/** + * Provides additional information about an error that was posted to the application. + * + * @public + */ +export type ErrorApiErrorContext = { + /** + * If set to true, this error should not be displayed to the user. + * + * Hidden errors are typically not displayed in the UI, but the ErrorApi + * implementation may still report them to error tracking services + * or other utilities that care about all errors. + * + * @defaultValue false + */ + hidden?: boolean; +}; + +/** + * The error API is used to report errors to the app, and display them to the user. + * + * @remarks + * + * Plugins can use this API as a method of displaying errors to the user, but also + * to report errors for collection by error reporting services. + * + * If an error can be displayed inline, e.g. as feedback in a form, that should be + * preferred over relying on this API to display the error. The main use of this API + * for displaying errors should be for asynchronous errors, such as a failing background process. + * + * Even if an error is displayed inline, it should still be reported through this API + * if it would be useful to collect or log it for debugging purposes, but with + * the hidden flag set. For example, an error arising from form field validation + * should probably not be reported, while a failed REST call would be useful to report. + * + * @public + */ +export type ErrorApi = { + /** + * Post an error for handling by the application. + */ + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; + + /** + * Observe errors posted by other parts of the application. + */ + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; +}; + +/** + * The {@link ApiRef} of {@link ErrorApi}. + * + * @public + */ +export const errorApiRef: ApiRef = createApiRef({ + id: 'core.error', +}); diff --git a/packages/frontend-plugin-api/src/definitions/FeatureFlagsApi.ts b/packages/frontend-plugin-api/src/definitions/FeatureFlagsApi.ts new file mode 100644 index 0000000000..66260afa32 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/FeatureFlagsApi.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '../system'; + +/** + * Feature flag descriptor. + * + * @public + */ +export type FeatureFlag = { + name: string; + pluginId: string; + description?: string; +}; + +/** + * Enum representing the state of a feature flag (inactive/active). + * + * @public + */ +export enum FeatureFlagState { + /** + * Feature flag inactive (disabled). + */ + None = 0, + /** + * Feature flag active (enabled). + */ + Active = 1, +} + +/** + * Options to use when saving feature flags. + * + * @public + */ +export type FeatureFlagsSaveOptions = { + /** + * The new feature flag states to save. + */ + states: Record; + + /** + * Whether the saves states should be merged into the existing ones, or replace them. + * + * Defaults to false. + */ + merge?: boolean; +}; + +/** + * The feature flags API is used to toggle functionality to users across plugins and Backstage. + * + * @remarks + * + * Plugins can use this API to register feature flags that they have available + * for users to enable/disable, and this API will centralize the current user's + * state of which feature flags they would like to enable. + * + * This is ideal for Backstage plugins, as well as your own App, to trial incomplete + * or unstable upcoming features. Although there will be a common interface for users + * to enable and disable feature flags, this API acts as another way to enable/disable. + * + * @public + */ +export interface FeatureFlagsApi { + /** + * Registers a new feature flag. Once a feature flag has been registered it + * can be toggled by users, and read back to enable or disable features. + */ + registerFlag(flag: FeatureFlag): void; + + /** + * Get a list of all registered flags. + */ + getRegisteredFlags(): FeatureFlag[]; + + /** + * Whether the feature flag with the given name is currently activated for the user. + */ + isActive(name: string): boolean; + + /** + * Save the user's choice of feature flag states. + */ + save(options: FeatureFlagsSaveOptions): void; +} + +/** + * The {@link ApiRef} of {@link FeatureFlagsApi}. + * + * @public + */ +export const featureFlagsApiRef: ApiRef = createApiRef({ + id: 'core.featureflags', +}); diff --git a/packages/frontend-plugin-api/src/definitions/FetchApi.ts b/packages/frontend-plugin-api/src/definitions/FetchApi.ts new file mode 100644 index 0000000000..aba0e53bb7 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/FetchApi.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '../system'; + +/** + * A wrapper for the fetch API, that has additional behaviors such as the + * ability to automatically inject auth information where necessary. + * + * @public + */ +export type FetchApi = { + /** + * The `fetch` implementation. + */ + fetch: typeof fetch; +}; + +/** + * The {@link ApiRef} of {@link FetchApi}. + * + * @remarks + * + * This is a wrapper for the fetch API, that has additional behaviors such as + * the ability to automatically inject auth information where necessary. + * + * Note that the default behavior of this API (unless overridden by your org), + * is to require that the user is already signed in so that it has auth + * information to inject. Therefore, using the default implementation of this + * utility API e.g. on the `SignInPage` or similar, would cause issues. In + * special circumstances like those, you can use the regular system `fetch` + * instead. + * + * @public + */ +export const fetchApiRef: ApiRef = createApiRef({ + id: 'core.fetch', +}); diff --git a/packages/frontend-plugin-api/src/definitions/IdentityApi.ts b/packages/frontend-plugin-api/src/definitions/IdentityApi.ts new file mode 100644 index 0000000000..1b127a1971 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/IdentityApi.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ApiRef, createApiRef } from '../system'; +import { BackstageUserIdentity, ProfileInfo } from './auth'; + +/** + * The Identity API used to identify and get information about the signed in user. + * + * @public + */ +export type IdentityApi = { + /** + * The profile of the signed in user. + */ + getProfileInfo(): Promise; + + /** + * User identity information within Backstage. + */ + getBackstageIdentity(): Promise; + + /** + * Provides credentials in the form of a token which proves the identity of the signed in user. + * + * The token will be undefined if the signed in user does not have a verified + * identity, such as a demo user or mocked user for e2e tests. + */ + getCredentials(): Promise<{ token?: string }>; + + /** + * Sign out the current user + */ + signOut(): Promise; +}; + +/** + * The {@link ApiRef} of {@link IdentityApi}. + * + * @public + */ +export const identityApiRef: ApiRef = createApiRef({ + id: 'core.identity', +}); diff --git a/packages/frontend-plugin-api/src/definitions/OAuthRequestApi.ts b/packages/frontend-plugin-api/src/definitions/OAuthRequestApi.ts new file mode 100644 index 0000000000..75bf3a3864 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/OAuthRequestApi.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Observable } from '@backstage/types'; +import { ApiRef, createApiRef } from '../system'; +import { AuthProviderInfo } from './auth'; + +/** + * Describes how to handle auth requests. Both how to show them to the user, and what to do when + * the user accesses the auth request. + * + * @public + */ +export type OAuthRequesterOptions = { + /** + * Information about the auth provider, which will be forwarded to auth requests. + */ + provider: AuthProviderInfo; + + /** + * Implementation of the auth flow, which will be called synchronously when + * trigger() is called on an auth requests. + */ + onAuthRequest(scopes: Set): Promise; +}; + +/** + * Function used to trigger new auth requests for a set of scopes. + * + * @remarks + * + * The returned promise will resolve to the same value returned by the onAuthRequest in the + * {@link OAuthRequesterOptions}. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the + * union of all requested scopes. + * + * @public + */ +export type OAuthRequester = ( + scopes: Set, +) => Promise; + +/** + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * @remarks + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. + * + * @public + */ +export type PendingOAuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + provider: AuthProviderInfo; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject(): void; + + /** + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. + */ + trigger(): Promise; +}; + +/** + * Provides helpers for implemented OAuth login flows within Backstage. + * + * @public + */ +export type OAuthRequestApi = { + /** + * A utility for showing login popups or similar things, and merging together multiple requests for + * different scopes into one request that includes all scopes. + * + * The passed in options provide information about the login provider, and how to handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester( + options: OAuthRequesterOptions, + ): OAuthRequester; + + /** + * Observers pending auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. + */ + authRequest$(): Observable; +}; + +/** + * The {@link ApiRef} of {@link OAuthRequestApi}. + * + * @public + */ +export const oauthRequestApiRef: ApiRef = createApiRef({ + id: 'core.oauthrequest', +}); diff --git a/packages/frontend-plugin-api/src/definitions/StorageApi.ts b/packages/frontend-plugin-api/src/definitions/StorageApi.ts new file mode 100644 index 0000000000..1506e5f143 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/StorageApi.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '../system'; +import { JsonValue, Observable } from '@backstage/types'; + +/** + * A snapshot in time of the current known value of a storage key. + * + * @public + */ +export type StorageValueSnapshot = + | { + key: string; + presence: 'unknown' | 'absent'; + value?: undefined; + } + | { + key: string; + presence: 'present'; + value: TValue; + }; + +/** + * Provides a key-value persistence API. + * + * @public + */ +export interface StorageApi { + /** + * Create a bucket to store data in. + * + * @param name - Namespace for the storage to be stored under, + * will inherit previous namespaces too + */ + forBucket(name: string): StorageApi; + + /** + * Remove persistent data. + * + * @param key - Unique key associated with the data. + */ + remove(key: string): Promise; + + /** + * Save persistent data, and emit messages to anyone that is using + * {@link StorageApi.observe$} for this key. + * + * @param key - Unique key associated with the data. + * @param data - The data to be stored under the key. + */ + set(key: string, data: T): Promise; + + /** + * Observe the value over time for a particular key in the current bucket. + * + * @remarks + * + * The observable will only emit values when the value changes in the underlying + * storage, although multiple values with the same shape may be emitted in a row. + * + * If a {@link StorageApi.snapshot} of a key is retrieved and the presence is + * `'unknown'`, then you are guaranteed to receive a snapshot with a known + * presence, as long as you observe the key within the same tick. + * + * Since the emitted values are shared across all subscribers, it is important + * not to mutate the returned values. The values may be frozen as a precaution. + * + * @param key - Unique key associated with the data + */ + observe$( + key: string, + ): Observable>; + + /** + * Returns an immediate snapshot value for the given key, if possible. + * + * @remarks + * + * Combine with {@link StorageApi.observe$} to get notified of value changes. + * + * Note that this method is synchronous, and some underlying storages may be + * unable to retrieve a value using this method - the result may or may not + * consistently have a presence of 'unknown'. Use {@link StorageApi.observe$} + * to be sure to receive an actual value eventually. + */ + snapshot(key: string): StorageValueSnapshot; +} + +/** + * The {@link ApiRef} of {@link StorageApi}. + * + * @public + */ +export const storageApiRef: ApiRef = createApiRef({ + id: 'core.storage', +}); diff --git a/packages/frontend-plugin-api/src/definitions/alpha.ts b/packages/frontend-plugin-api/src/definitions/alpha.ts new file mode 100644 index 0000000000..a4ccde1170 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/alpha.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; diff --git a/packages/frontend-plugin-api/src/definitions/auth.ts b/packages/frontend-plugin-api/src/definitions/auth.ts new file mode 100644 index 0000000000..5ed873a566 --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/auth.ts @@ -0,0 +1,452 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '../system'; +import { IconComponent } from '../icons/types'; +import { Observable } from '@backstage/types'; + +/** + * This file contains declarations for common interfaces of auth-related APIs. + * The declarations should be used to signal which type of authentication and + * authorization methods each separate auth provider supports. + * + * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, + * would be declared as follows: + * + * const googleAuthApiRef = createApiRef({ ... }) + */ + +/** + * Information about the auth provider. + * + * @remarks + * + * This information is used both to connect the correct auth provider in the backend, as + * well as displaying the provider to the user. + * + * @public + */ +export type AuthProviderInfo = { + /** + * The ID of the auth provider. This should match with ID of the provider in the `@backstage/auth-backend`. + */ + id: string; + + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + */ + icon: IconComponent; +}; + +/** + * An array of scopes, or a scope string formatted according to the + * auth provider, which is typically a space separated list. + * + * @remarks + * + * See the documentation for each auth provider for the list of scopes + * supported by each provider. + * + * @public + */ +export type OAuthScope = string | string[]; + +/** + * Configuration of an authentication request. + * + * @public + */ +export type AuthRequestOptions = { + /** + * If this is set to true, the user will not be prompted to log in, + * and an empty response will be returned if there is no existing session. + * + * This can be used to perform a check whether the user is logged in, or if you don't + * want to force a user to be logged in, but provide functionality if they already are. + * + * @defaultValue false + */ + optional?: boolean; + + /** + * If this is set to true, the request will bypass the regular oauth login modal + * and open the login popup directly. + * + * The method must be called synchronously from a user action for this to work in all browsers. + * + * @defaultValue false + */ + instantPopup?: boolean; +}; + +/** + * This API provides access to OAuth 2 credentials. It lets you request access tokens, + * which can be used to act on behalf of the user when talking to APIs. + * + * @public + */ +export type OAuthApi = { + /** + * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows + * you to make requests on behalf of the user, and the copes may grant you broader access, depending + * on the auth provider. + * + * Each auth provider has separate handling of scope, so you need to look at the documentation + * for each one to know what scope you need to request. + * + * This method is cheap and should be called each time an access token is used. Do not for example + * store the access token in React component state, as that could cause the token to expire. Instead + * fetch a new access token for each request. + * + * Be sure to include all required scopes when requesting an access token. When testing your implementation + * it is best to log out the Backstage session and then visit your plugin page directly, as + * you might already have some required scopes in your existing session. Not requesting the correct + * scopes can lead to 403 or other authorization errors, which can be tricky to debug. + * + * If the user has not yet granted access to the provider and the set of requested scopes, the user + * will be prompted to log in. The returned promise will not resolve until the user has + * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. + */ + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * This API provides access to OpenID Connect credentials. It lets you request ID tokens, + * which can be passed to backend services to prove the user's identity. + * + * @public + */ +export type OpenIdConnectApi = { + /** + * Requests an OpenID Connect ID Token. + * + * This method is cheap and should be called each time an ID token is used. Do not for example + * store the id token in React component state, as that could cause the token to expire. Instead + * fetch a new id token for each request. + * + * If the user has not yet logged in to Google inside Backstage, the user will be prompted + * to log in. The returned promise will not resolve until the user has successfully logged in. + * The returned promise can be rejected, but only if the user rejects the login request. + */ + getIdToken(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to profile information of the user from an auth provider. + * + * @public + */ +export type ProfileInfoApi = { + /** + * Get profile information for the user as supplied by this auth provider. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getProfile(options?: AuthRequestOptions): Promise; +}; + +/** + * This API provides access to the user's identity within Backstage. + * + * @remarks + * + * An auth provider that implements this interface can be used to sign-in to backstage. It is + * not intended to be used directly from a plugin, but instead serves as a connection between + * this authentication method and the app's {@link IdentityApi} + * + * @public + */ +export type BackstageIdentityApi = { + /** + * Get the user's identity within Backstage. This should normally not be called directly, + * use the {@link IdentityApi} instead. + * + * If the optional flag is not set, a session is guaranteed to be returned, while if + * the optional flag is set, the session may be undefined. See {@link AuthRequestOptions} for more details. + */ + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; +}; + +/** + * User identity information within Backstage. + * + * @public + */ +export type BackstageUserIdentity = { + /** + * The type of identity that this structure represents. In the frontend app + * this will currently always be 'user'. + */ + type: 'user'; + + /** + * The entityRef of the user in the catalog. + * For example User:default/sandra + */ + userEntityRef: string; + + /** + * The user and group entities that the user claims ownership through + */ + ownershipEntityRefs: string[]; +}; + +/** + * Token and Identity response, with the users claims in the Identity. + * + * @public + */ +export type BackstageIdentityResponse = { + /** + * The token used to authenticate the user within Backstage. + */ + token: string; + + /** + * The time at which the token expires. If not set, it can be assumed that the token does not expire. + */ + expiresAt?: Date; + + /** + * Identity information derived from the token. + */ + identity: BackstageUserIdentity; +}; + +/** + * Profile information of the user. + * + * @public + */ +export type ProfileInfo = { + /** + * Email ID. + */ + email?: string; + + /** + * Display name that can be presented to the user. + */ + displayName?: string; + + /** + * URL to an avatar image of the user. + */ + picture?: string; +}; + +/** + * Session state values passed to subscribers of the SessionApi. + * + * @public + */ +export enum SessionState { + /** + * User signed in. + */ + SignedIn = 'SignedIn', + /** + * User not signed in. + */ + SignedOut = 'SignedOut', +} + +/** + * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. + * + * @public + */ +export type SessionApi = { + /** + * Sign in with a minimum set of permissions. + */ + signIn(): Promise; + + /** + * Sign out from the current session. This will reload the page. + */ + signOut(): Promise; + + /** + * Observe the current state of the auth session. Emits the current state on subscription. + */ + sessionState$(): Observable; +}; + +/** + * Provides authentication towards Google APIs and identities. + * + * @public + * @remarks + * + * See {@link https://developers.google.com/identity/protocols/googlescopes} for a full list of supported scopes. + * + * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, + * email and expiration information. Do not rely on any other fields, as they might not be present. + */ +export const googleAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.google', +}); + +/** + * Provides authentication towards GitHub APIs. + * + * @public + * @remarks + * + * See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/} + * for a full list of supported scopes. + */ +export const githubAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.github', +}); + +/** + * Provides authentication towards Okta APIs. + * + * @public + * @remarks + * + * See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/} + * for a full list of supported scopes. + */ +export const oktaAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.okta', +}); + +/** + * Provides authentication towards GitLab APIs. + * + * @public + * @remarks + * + * See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token} + * for a full list of supported scopes. + */ +export const gitlabAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.gitlab', +}); + +/** + * Provides authentication towards Microsoft APIs and identities. + * + * @public + * @remarks + * + * For more info and a full list of supported scopes, see: + * - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent} + * - {@link https://docs.microsoft.com/en-us/graph/permissions-reference} + */ +export const microsoftAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.microsoft', +}); + +/** + * Provides authentication towards OneLogin APIs. + * + * @public + */ +export const oneloginAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +> = createApiRef({ + id: 'core.auth.onelogin', +}); + +/** + * Provides authentication towards Bitbucket APIs. + * + * @public + * @remarks + * + * See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/} + * for a full list of supported scopes. + */ +export const bitbucketAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket', +}); + +/** + * Provides authentication towards Bitbucket Server APIs. + * + * @public + * @remarks + * + * See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes} + * for a full list of supported scopes. + */ +export const bitbucketServerAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.bitbucket-server', +}); + +/** + * Provides authentication towards Atlassian APIs. + * + * @public + * @remarks + * + * See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/} + * for a full list of supported scopes. + */ +export const atlassianAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'core.auth.atlassian', +}); diff --git a/packages/frontend-plugin-api/src/definitions/index.ts b/packages/frontend-plugin-api/src/definitions/index.ts new file mode 100644 index 0000000000..cc02442f3d --- /dev/null +++ b/packages/frontend-plugin-api/src/definitions/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This folder contains definitions for all core APIs. +// +// Plugins should rely on these APIs for functionality as much as possible. +// +// If you think some API definition is missing, please open an Issue or send a PR! + +export * from './auth'; + +export * from './AlertApi'; +export * from './AppThemeApi'; +export * from './ConfigApi'; +export * from './DiscoveryApi'; +export * from './ErrorApi'; +export * from './FeatureFlagsApi'; +export * from './FetchApi'; +export * from './IdentityApi'; +export * from './OAuthRequestApi'; +export * from './StorageApi'; diff --git a/packages/frontend-plugin-api/src/icons/index.ts b/packages/frontend-plugin-api/src/icons/index.ts new file mode 100644 index 0000000000..9c9e45e54c --- /dev/null +++ b/packages/frontend-plugin-api/src/icons/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { IconComponent } from './types'; diff --git a/packages/frontend-plugin-api/src/icons/types.ts b/packages/frontend-plugin-api/src/icons/types.ts new file mode 100644 index 0000000000..4d54629de2 --- /dev/null +++ b/packages/frontend-plugin-api/src/icons/types.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType } from 'react'; + +/** + * IconComponent is the common icon type used throughout Backstage when + * working with and rendering generic icons, including the app system icons. + * + * @remarks + * + * The type is based on SvgIcon from Material UI, but both do not what the plugin-api + * package to have a dependency on Material UI, nor do we want the props to be as broad + * as the SvgIconProps interface. + * + * If you have the need to forward additional props from SvgIconProps, you can + * open an issue or submit a PR to the main Backstage repo. When doing so please + * also describe your use-case and reasoning of the addition. + * + * @public + */ + +export type IconComponent = ComponentType< + /* Material UI v4 */ + | { + fontSize?: 'large' | 'small' | 'default' | 'inherit'; + } + /* Material UI v5: https://mui.com/material-ui/migration/v5-component-changes/#icon */ + | { + fontSize?: 'medium' | 'large' | 'small' | 'inherit'; + } +>; diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index 498bc96a77..f3656a0dc3 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -22,7 +22,10 @@ export * from './apis'; export * from './components'; +export * from './definitions'; export * from './extensions'; +export * from './icons'; export * from './routing'; export * from './schema'; +export * from './system'; export * from './wiring'; diff --git a/packages/frontend-plugin-api/src/system/ApiRef.test.ts b/packages/frontend-plugin-api/src/system/ApiRef.test.ts new file mode 100644 index 0000000000..dab872236f --- /dev/null +++ b/packages/frontend-plugin-api/src/system/ApiRef.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from './ApiRef'; + +describe('ApiRef', () => { + it('should be created', () => { + const ref = createApiRef({ id: 'abc' }); + expect(ref.id).toBe('abc'); + expect(String(ref)).toBe('apiRef{abc}'); + expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); + }); + + it('should reject invalid ids', () => { + for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { + expect(createApiRef({ id }).id).toBe(id); + } + + for (const id of [ + '123', + 'ab-3', + 'ab_c', + '.', + '2ac', + 'ab.3a', + '.abc', + 'abc.', + 'ab..s', + '', + '_', + ]) { + expect(() => createApiRef({ id }).id).toThrow( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, + ); + } + }); +}); diff --git a/packages/frontend-plugin-api/src/system/ApiRef.ts b/packages/frontend-plugin-api/src/system/ApiRef.ts new file mode 100644 index 0000000000..adedff9f73 --- /dev/null +++ b/packages/frontend-plugin-api/src/system/ApiRef.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ApiRef } from './types'; + +/** + * API reference configuration - holds an ID of the referenced API. + * + * @public + */ +export type ApiRefConfig = { + id: string; +}; + +class ApiRefImpl implements ApiRef { + constructor(private readonly config: ApiRefConfig) { + const valid = config.id + .split('.') + .flatMap(part => part.split('-')) + .every(part => part.match(/^[a-z][a-z0-9]*$/)); + if (!valid) { + throw new Error( + `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, + ); + } + } + + get id(): string { + return this.config.id; + } + + // Utility for getting type of an api, using `typeof apiRef.T` + get T(): T { + throw new Error(`tried to read ApiRef.T of ${this}`); + } + + toString() { + return `apiRef{${this.config.id}}`; + } +} + +/** + * Creates a reference to an API. + * + * @param config - The descriptor of the API to reference. + * @returns An API reference. + * @public + */ +export function createApiRef(config: ApiRefConfig): ApiRef { + return new ApiRefImpl(config); +} diff --git a/packages/frontend-plugin-api/src/system/index.ts b/packages/frontend-plugin-api/src/system/index.ts new file mode 100644 index 0000000000..6aad24daa9 --- /dev/null +++ b/packages/frontend-plugin-api/src/system/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { createApiRef } from './ApiRef'; +export type { ApiRefConfig } from './ApiRef'; +export * from './types'; diff --git a/packages/frontend-plugin-api/src/system/types.ts b/packages/frontend-plugin-api/src/system/types.ts new file mode 100644 index 0000000000..96614c320c --- /dev/null +++ b/packages/frontend-plugin-api/src/system/types.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * API reference. + * + * @public + */ +export type ApiRef = { + id: string; + T: T; +}; + +/** + * Catch-all {@link ApiRef} type. + * + * @public + */ +export type AnyApiRef = ApiRef; + +/** + * Wraps a type with API properties into a type holding their respective {@link ApiRef}s. + * + * @public + */ +export type TypesToApiRefs = { [key in keyof T]: ApiRef }; + +/** + * Provides lookup of APIs through their {@link ApiRef}s. + * + * @public + */ +export type ApiHolder = { + get(api: ApiRef): T | undefined; +}; + +/** + * Describes type returning API implementations. + * + * @public + */ +export type ApiFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; +}; + +/** + * Catch-all {@link ApiFactory} type. + * + * @public + */ +export type AnyApiFactory = ApiFactory< + unknown, + unknown, + { [key in string]: unknown } +>;