) {
- return function withApisWrapper(
- WrappedComponent: React.ComponentType
,
- ) {
- const Hoc = (props: PropsWithChildren>) => {
- const apiHolder = useApiHolder();
-
- const impls = {} as T;
-
- for (const key in apis) {
- if (apis.hasOwnProperty(key)) {
- const ref = apis[key];
-
- const api = apiHolder.get(ref);
- if (!api) {
- throw new Error(`No implementation available for ${ref}`);
- }
- impls[key] = api;
- }
- }
-
- return ;
- };
- const displayName =
- WrappedComponent.displayName || WrappedComponent.name || 'Component';
-
- Hoc.displayName = `withApis(${displayName})`;
-
- return Hoc;
- };
-}
diff --git a/packages/core-api/src/apis/system/ApiRef.test.ts b/packages/core-api/src/apis/system/ApiRef.test.ts
deleted file mode 100644
index 7cd778634c..0000000000
--- a/packages/core-api/src/apis/system/ApiRef.test.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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', description: '123' });
- expect(ref.id).toBe('abc');
- expect(ref.description).toBe('123');
- 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, description: '123' }).id).toBe(id);
- }
-
- for (const id of [
- '123',
- 'ab-3',
- 'ab_c',
- '.',
- '2ac',
- 'ab.3a',
- '.abc',
- 'abc.',
- 'ab..s',
- '',
- '_',
- ]) {
- expect(() => createApiRef({ id, description: '123' }).id).toThrow(
- `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`,
- );
- }
- });
-});
diff --git a/packages/core-api/src/apis/system/ApiRef.ts b/packages/core-api/src/apis/system/ApiRef.ts
deleted file mode 100644
index 5a774474d8..0000000000
--- a/packages/core-api/src/apis/system/ApiRef.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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';
-
-export type ApiRefConfig = {
- id: string;
- description?: 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;
- }
-
- get description() {
- return this.config.description;
- }
-
- // 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}}`;
- }
-}
-
-export function createApiRef(config: ApiRefConfig): ApiRef {
- return new ApiRefImpl(config);
-}
diff --git a/packages/core-api/src/apis/system/ApiRegistry.test.ts b/packages/core-api/src/apis/system/ApiRegistry.test.ts
deleted file mode 100644
index 66fcb70262..0000000000
--- a/packages/core-api/src/apis/system/ApiRegistry.test.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 { ApiRegistry } from './ApiRegistry';
-import { createApiRef } from './ApiRef';
-
-describe('ApiRegistry', () => {
- const x1Ref = createApiRef({ id: 'x1', description: '' });
- const x1DuplicateRef = createApiRef({ id: 'x1', description: '' });
- const x2Ref = createApiRef({ id: 'x2', description: '' });
-
- it('should be created', () => {
- const registry = ApiRegistry.from([]);
- expect(registry.get(x1Ref)).toBe(undefined);
- });
-
- it('should be created with APIs', () => {
- const registry = ApiRegistry.from([
- [x1Ref, 3],
- [x2Ref, 'y'],
- ]);
- expect(registry.get(x1Ref)).toBe(3);
- expect(registry.get(x1DuplicateRef)).toBe(3);
- expect(registry.get(x2Ref)).toBe('y');
- });
-
- it('should be built', () => {
- const registry = ApiRegistry.builder().build();
- expect(registry.get(x1Ref)).toBe(undefined);
- expect(registry.get(x1DuplicateRef)).toBe(undefined);
- });
-
- it('should be built with APIs', () => {
- const builder = ApiRegistry.builder();
- builder.add(x1Ref, 3);
- builder.add(x2Ref, 'y');
-
- const registry = builder.build();
- expect(registry.get(x1Ref)).toBe(3);
- expect(registry.get(x1DuplicateRef)).toBe(3);
- expect(registry.get(x2Ref)).toBe('y');
- });
-
- it('should be created with API', () => {
- const reg1 = ApiRegistry.with(x1Ref, 3);
- const reg2 = reg1.with(x2Ref, 'y');
- const reg3 = reg2.with(x2Ref, 'z');
- const reg4 = reg3.with(x1Ref, 2);
- const reg5 = reg3.with(x1DuplicateRef, 4);
-
- expect(reg1.get(x1Ref)).toBe(3);
- expect(reg1.get(x2Ref)).toBe(undefined);
- expect(reg2.get(x1Ref)).toBe(3);
- expect(reg2.get(x2Ref)).toBe('y');
- expect(reg3.get(x1Ref)).toBe(3);
- expect(reg3.get(x2Ref)).toBe('z');
- expect(reg4.get(x1Ref)).toBe(2);
- expect(reg4.get(x2Ref)).toBe('z');
- expect(reg5.get(x1Ref)).toBe(4);
- expect(reg5.get(x2Ref)).toBe('z');
- });
-});
diff --git a/packages/core-api/src/apis/system/ApiRegistry.ts b/packages/core-api/src/apis/system/ApiRegistry.ts
deleted file mode 100644
index 6fcfe03f62..0000000000
--- a/packages/core-api/src/apis/system/ApiRegistry.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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, ApiHolder } from './types';
-
-type ApiImpl = readonly [ApiRef, T];
-
-class ApiRegistryBuilder {
- private apis: [string, unknown][] = [];
-
- add(api: ApiRef, impl: I): I {
- this.apis.push([api.id, impl]);
- return impl;
- }
-
- build(): ApiRegistry {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- return new ApiRegistry(new Map(this.apis));
- }
-}
-
-export class ApiRegistry implements ApiHolder {
- static builder() {
- return new ApiRegistryBuilder();
- }
-
- static from(apis: ApiImpl[]) {
- return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl])));
- }
-
- /**
- * Creates a new ApiRegistry with a single API implementation.
- *
- * @param api ApiRef for the API to add
- * @param impl Implementation of the API to add
- */
- static with(api: ApiRef, impl: T): ApiRegistry {
- return new ApiRegistry(new Map([[api.id, impl]]));
- }
-
- constructor(private readonly apis: Map) {}
-
- /**
- * Returns a new ApiRegistry with the provided API added to the existing ones.
- *
- * @param api ApiRef for the API to add
- * @param impl Implementation of the API to add
- */
- with(api: ApiRef, impl: T): ApiRegistry {
- return new ApiRegistry(new Map([...this.apis, [api.id, impl]]));
- }
-
- get(api: ApiRef): T | undefined {
- return this.apis.get(api.id) as T | undefined;
- }
-}
diff --git a/packages/core-api/src/apis/system/ApiResolver.test.ts b/packages/core-api/src/apis/system/ApiResolver.test.ts
deleted file mode 100644
index 064a4f8d77..0000000000
--- a/packages/core-api/src/apis/system/ApiResolver.test.ts
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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 { ApiResolver } from './ApiResolver';
-import { createApiRef } from './ApiRef';
-import { ApiFactoryRegistry } from './ApiFactoryRegistry';
-
-const aRef = createApiRef({ id: 'a', description: '' });
-const otherARef = createApiRef({ id: 'a', description: 'other' });
-const bRef = createApiRef({ id: 'b', description: '' });
-const otherBRef = createApiRef({ id: 'b', description: 'other' });
-const cRef = createApiRef<{ x: string }>({ id: 'c', description: '' });
-const otherCRef = createApiRef<{ x: string }>({
- id: 'c',
- description: 'other',
-});
-
-function createRegistry() {
- const registry = new ApiFactoryRegistry();
- registry.register('default', {
- api: aRef,
- deps: {},
- factory: () => 1,
- });
- registry.register('default', {
- api: bRef,
- deps: {},
- factory: () => 'b',
- });
- registry.register('default', {
- api: cRef,
- deps: { b: otherBRef },
- factory: ({ b }) => ({ x: 'x', b }),
- });
- return registry;
-}
-
-function createSelfCyclicRegistry() {
- const registry = new ApiFactoryRegistry();
- registry.register('default', {
- api: aRef,
- deps: { a: aRef },
- factory: () => 1,
- });
- return registry;
-}
-
-function createShortCyclicRegistry() {
- const registry = new ApiFactoryRegistry();
- registry.register('default', {
- api: aRef,
- deps: { b: bRef },
- factory: () => 1,
- });
- registry.register('default', {
- api: bRef,
- deps: { a: aRef },
- factory: () => 'x',
- });
- return registry;
-}
-
-function createShortCyclicRegistryWithOther() {
- const registry = new ApiFactoryRegistry();
- registry.register('default', {
- api: aRef,
- deps: { b: bRef },
- factory: () => 1,
- });
- registry.register('default', {
- api: otherBRef,
- deps: { a: otherARef },
- factory: () => 'x',
- });
- return registry;
-}
-
-function createLongCyclicRegistry() {
- const registry = new ApiFactoryRegistry();
- registry.register('default', {
- api: aRef,
- deps: { b: otherBRef },
- factory: () => 1,
- });
- registry.register('default', {
- api: bRef,
- deps: { c: cRef },
- factory: () => 'b',
- });
- registry.register('default', {
- api: cRef,
- deps: { a: aRef },
- factory: () => ({ x: 'x' }),
- });
- return registry;
-}
-
-describe('ApiResolver', () => {
- it('should be created empty', () => {
- const resolver = new ApiResolver(new ApiFactoryRegistry());
- expect(resolver.get(aRef)).toBe(undefined);
- expect(resolver.get(bRef)).toBe(undefined);
- expect(resolver.get(otherBRef)).toBe(undefined);
- expect(resolver.get(cRef)).toBe(undefined);
- });
-
- it('should instantiate APIs', () => {
- const resolver = new ApiResolver(createRegistry());
- expect(resolver.get(aRef)).toBe(1);
- expect(resolver.get(otherARef)).toBe(1);
- expect(resolver.get(bRef)).toBe('b');
- expect(resolver.get(otherBRef)).toBe('b');
- expect(resolver.get(cRef)).toEqual({ x: 'x', b: 'b' });
- expect(resolver.get(cRef)).toBe(resolver.get(otherCRef));
- });
-
- it('should detect self dependency cycles', () => {
- const resolver = new ApiResolver(createSelfCyclicRegistry());
- expect(() => resolver.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- });
-
- it('should detect short dependency cycles', () => {
- const resolver = new ApiResolver(createShortCyclicRegistry());
- expect(() => resolver.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => resolver.get(bRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- });
-
- it('should detect short dependency cycles with other refs', () => {
- const resolver = new ApiResolver(createShortCyclicRegistryWithOther());
- expect(() => resolver.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => resolver.get(bRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => resolver.get(otherARef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => resolver.get(otherBRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- });
-
- it('should detect long dependency cycles', () => {
- const resolver = new ApiResolver(createLongCyclicRegistry());
- expect(() => resolver.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- // Second call for same ref should still throw
- expect(() => resolver.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => resolver.get(bRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => resolver.get(otherBRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => resolver.get(cRef)).toThrow(
- 'Circular dependency of api factory for apiRef{c}',
- );
- });
-
- it('should validate a factory holder', () => {
- expect(() => {
- ApiResolver.validateFactories(createRegistry(), [
- aRef,
- bRef,
- otherBRef,
- cRef,
- ]);
- }).not.toThrow();
- });
-
- it('should find self cycles with validation', () => {
- const self = createSelfCyclicRegistry();
- expect(() => ApiResolver.validateFactories(self, [aRef])).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => ApiResolver.validateFactories(self, [otherARef])).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- });
-
- it('should find dependency cycles with validation', () => {
- const short = createShortCyclicRegistry();
- expect(() => ApiResolver.validateFactories(short, [aRef])).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => ApiResolver.validateFactories(short, [otherARef])).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => ApiResolver.validateFactories(short, [bRef])).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => ApiResolver.validateFactories(short, [otherBRef])).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
-
- const shortOther = createShortCyclicRegistryWithOther();
- expect(() => ApiResolver.validateFactories(shortOther, [aRef])).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() =>
- ApiResolver.validateFactories(shortOther, [otherARef]),
- ).toThrow('Circular dependency of api factory for apiRef{a}');
- expect(() => ApiResolver.validateFactories(shortOther, [bRef])).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() =>
- ApiResolver.validateFactories(shortOther, [otherBRef]),
- ).toThrow('Circular dependency of api factory for apiRef{b}');
-
- const long = createLongCyclicRegistry();
- expect(() =>
- ApiResolver.validateFactories(long, long.getAllApis()),
- ).toThrow('Circular dependency of api factory for apiRef{a}');
- expect(() => ApiResolver.validateFactories(long, [bRef])).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => ApiResolver.validateFactories(long, [otherBRef])).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => ApiResolver.validateFactories(long, [cRef])).toThrow(
- 'Circular dependency of api factory for apiRef{c}',
- );
- });
-
- it('should only call factory func once', () => {
- const registry = new ApiFactoryRegistry();
- const factory = jest.fn().mockReturnValue(2);
- registry.register('default', {
- api: aRef,
- deps: {},
- factory,
- });
-
- const resolver = new ApiResolver(registry);
- expect(factory).toHaveBeenCalledTimes(0);
- expect(resolver.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- expect(resolver.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- expect(resolver.get(otherARef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/packages/core-api/src/apis/system/ApiResolver.ts b/packages/core-api/src/apis/system/ApiResolver.ts
deleted file mode 100644
index 4d69067b43..0000000000
--- a/packages/core-api/src/apis/system/ApiResolver.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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,
- ApiHolder,
- ApiFactoryHolder,
- AnyApiRef,
- TypesToApiRefs,
-} from './types';
-
-export class ApiResolver implements ApiHolder {
- /**
- * Validate factories by making sure that each of the apis can be created
- * without hitting any circular dependencies.
- */
- static validateFactories(
- factories: ApiFactoryHolder,
- apis: Iterable,
- ) {
- for (const api of apis) {
- const heap = [api];
- const allDeps = new Set();
-
- while (heap.length) {
- const apiRef = heap.shift()!;
- const factory = factories.get(apiRef);
- if (!factory) {
- continue;
- }
-
- for (const dep of Object.values(factory.deps)) {
- if (dep.id === api.id) {
- throw new Error(`Circular dependency of api factory for ${api}`);
- }
- if (!allDeps.has(dep)) {
- allDeps.add(dep);
- heap.push(dep);
- }
- }
- }
- }
- }
-
- private readonly apis = new Map();
-
- constructor(private readonly factories: ApiFactoryHolder) {}
-
- get(ref: ApiRef): T | undefined {
- return this.load(ref);
- }
-
- private load(ref: ApiRef, loading: AnyApiRef[] = []): T | undefined {
- const impl = this.apis.get(ref.id);
- if (impl) {
- return impl as T;
- }
-
- const factory = this.factories.get(ref);
- if (!factory) {
- return undefined;
- }
-
- if (loading.includes(factory.api)) {
- throw new Error(`Circular dependency of api factory for ${factory.api}`);
- }
-
- const deps = this.loadDeps(ref, factory.deps, [...loading, factory.api]);
- const api = factory.factory(deps);
- this.apis.set(ref.id, api);
- return api as T;
- }
-
- private loadDeps(
- dependent: ApiRef,
- apis: TypesToApiRefs,
- loading: AnyApiRef[],
- ): T {
- const impls = {} as T;
-
- for (const key in apis) {
- if (apis.hasOwnProperty(key)) {
- const ref = apis[key];
-
- const api = this.load(ref, loading);
- if (!api) {
- throw new Error(
- `No API factory available for dependency ${ref} of dependent ${dependent}`,
- );
- }
- impls[key] = api;
- }
- }
-
- return impls;
- }
-}
diff --git a/packages/core-api/src/apis/system/helpers.ts b/packages/core-api/src/apis/system/helpers.ts
deleted file mode 100644
index 8e84dd6c09..0000000000
--- a/packages/core-api/src/apis/system/helpers.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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, ApiFactory, TypesToApiRefs } from './types';
-
-/**
- * Used to infer types for a standalone ApiFactory that isn't immediately passed
- * to another function.
- * This function doesn't actually do anything, it's only used to infer types.
- */
-export function createApiFactory<
- Api,
- Impl extends Api,
- Deps extends { [name in string]: unknown }
->(factory: ApiFactory): ApiFactory;
-export function createApiFactory(
- api: ApiRef,
- instance: Impl,
-): ApiFactory