frontend-plugin-api: refactor createDataContainer -> createExtensionDataContainer

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-12 14:22:23 +02:00
parent d71c7469c7
commit 4e1ff3407e
5 changed files with 104 additions and 81 deletions
@@ -19,12 +19,14 @@ import { PortableSchema, createSchemaFromZod } from '../schema';
import { Expand } from '../types';
import {
ResolveInputValueOverrides,
createDataContainer,
resolveInputOverrides,
} from './createExtensionBlueprint';
import {
ExtensionDataContainer,
createExtensionDataContainer,
} from './createExtensionDataContainer';
import {
AnyExtensionDataRef,
ExtensionDataRef,
ExtensionDataValue,
} from './createExtensionDataRef';
import { ExtensionInput, LegacyExtensionInput } from './createExtensionInput';
@@ -68,28 +70,6 @@ export type ExtensionDataValues<TExtensionData extends AnyExtensionDataMap> = {
: never]?: TExtensionData[DataName]['T'];
};
/** @public */
export type ExtensionDataContainer<UExtensionData extends AnyExtensionDataRef> =
Iterable<
UExtensionData extends ExtensionDataRef<
infer IData,
infer IId,
infer IConfig
>
? IConfig['optional'] extends true
? never
: ExtensionDataValue<IData, IId>
: never
> & {
get<TId extends UExtensionData['id']>(
ref: ExtensionDataRef<any, TId, any>,
): UExtensionData extends ExtensionDataRef<infer IData, TId, infer IConfig>
? IConfig['optional'] extends true
? IData | undefined
: IData
: never;
};
/**
* Convert a single extension input into a matching resolved input.
* @public
@@ -669,7 +649,7 @@ export function createExtension<
};
inputs?: ResolveInputValueOverrides<TInputs>;
}): ExtensionDataContainer<UOutput> => {
return createDataContainer<UOutput>(
return createExtensionDataContainer<UOutput>(
newOptions.factory({
node,
config: innerContext?.config ?? config,
@@ -16,10 +16,7 @@
import React from 'react';
import { coreExtensionData } from './coreExtensionData';
import {
createDataContainer,
createExtensionBlueprint,
} from './createExtensionBlueprint';
import { createExtensionBlueprint } from './createExtensionBlueprint';
import { createExtensionTester } from '@backstage/frontend-test-utils';
import {
ExtensionDataValue,
@@ -31,6 +28,7 @@ import {
ExtensionDefinition,
toInternalExtensionDefinition,
} from './createExtension';
import { createExtensionDataContainer } from './createExtensionDataContainer';
function unused(..._any: any[]) {}
@@ -376,7 +374,7 @@ describe('createExtensionBlueprint', () => {
});
const mockInput = (node: string, ...data: ExtensionDataValue<any, any>[]) =>
Object.assign(createDataContainer(data), {
Object.assign(createExtensionDataContainer(data), {
node,
});
const mockParentInputs = {
@@ -18,7 +18,6 @@ import { AppNode } from '../apis';
import { Expand } from '../types';
import {
CreateExtensionOptions,
ExtensionDataContainer,
ExtensionDefinition,
ResolvedExtensionInput,
ResolvedExtensionInputs,
@@ -29,10 +28,13 @@ import { z } from 'zod';
import { ExtensionInput } from './createExtensionInput';
import {
AnyExtensionDataRef,
ExtensionDataRef,
ExtensionDataRefToValue,
ExtensionDataValue,
} from './createExtensionDataRef';
import {
ExtensionDataContainer,
createExtensionDataContainer,
} from './createExtensionDataContainer';
/**
* @public
@@ -252,51 +254,6 @@ export interface ExtensionBlueprint<
>;
}
/** @internal */
export function createDataContainer<UData extends AnyExtensionDataRef>(
values: Iterable<
UData extends ExtensionDataRef<infer IData, infer IId>
? ExtensionDataValue<IData, IId>
: never
>,
declaredRefs?: ExtensionDataRef<any, any, any>[],
): ExtensionDataContainer<UData> {
const container = new Map<string, ExtensionDataValue<any, any>>();
const verifyRefs =
declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));
for (const output of values) {
if (verifyRefs) {
if (!verifyRefs.delete(output.id)) {
throw new Error(
`extension data '${output.id}' was provided but not declared`,
);
}
}
container.set(output.id, output);
}
const remainingRefs =
verifyRefs &&
Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);
if (remainingRefs && remainingRefs.length > 0) {
throw new Error(
`missing required extension data value(s) '${remainingRefs
.map(ref => ref.id)
.join(', ')}'`,
);
}
return {
get(ref) {
return container.get(ref.id)?.value;
},
[Symbol.iterator]() {
return container.values();
},
} as ExtensionDataContainer<UData>;
}
function expectArray<T>(value: T | T[]): T[] {
return value as T[];
}
@@ -333,7 +290,7 @@ export function resolveInputOverrides(
if (declaredInput.config.singleton) {
const originalInput = expectItem(inputs[name]);
if (providedData) {
const providedContainer = createDataContainer(
const providedContainer = createExtensionDataContainer(
providedData as Iterable<ExtensionDataValue<any, any>>,
declaredInput.extensionData,
);
@@ -362,7 +319,7 @@ export function resolveInputOverrides(
);
}
newInputs[name] = providedData.map((data, i) => {
const providedContainer = createDataContainer(
const providedContainer = createExtensionDataContainer(
data as Iterable<ExtensionDataValue<any, any>>,
declaredInput.extensionData,
);
@@ -506,7 +463,7 @@ class ExtensionBlueprintImpl<
inputs?: ResolveInputValueOverrides;
},
): ExtensionDataContainer<UOutput> => {
return createDataContainer<UOutput>(
return createExtensionDataContainer<UOutput>(
this.options.factory(innerParams, {
node,
config: innerContext?.config ?? config,
@@ -0,0 +1,88 @@
/*
* Copyright 2024 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 {
AnyExtensionDataRef,
ExtensionDataRef,
ExtensionDataValue,
} from './createExtensionDataRef';
/** @public */
export type ExtensionDataContainer<UExtensionData extends AnyExtensionDataRef> =
Iterable<
UExtensionData extends ExtensionDataRef<
infer IData,
infer IId,
infer IConfig
>
? IConfig['optional'] extends true
? never
: ExtensionDataValue<IData, IId>
: never
> & {
get<TId extends UExtensionData['id']>(
ref: ExtensionDataRef<any, TId, any>,
): UExtensionData extends ExtensionDataRef<infer IData, TId, infer IConfig>
? IConfig['optional'] extends true
? IData | undefined
: IData
: never;
};
/** @internal */
export function createExtensionDataContainer<UData extends AnyExtensionDataRef>(
values: Iterable<
UData extends ExtensionDataRef<infer IData, infer IId>
? ExtensionDataValue<IData, IId>
: never
>,
declaredRefs?: ExtensionDataRef<any, any, any>[],
): ExtensionDataContainer<UData> {
const container = new Map<string, ExtensionDataValue<any, any>>();
const verifyRefs =
declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));
for (const output of values) {
if (verifyRefs) {
if (!verifyRefs.delete(output.id)) {
throw new Error(
`extension data '${output.id}' was provided but not declared`,
);
}
}
container.set(output.id, output);
}
const remainingRefs =
verifyRefs &&
Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);
if (remainingRefs && remainingRefs.length > 0) {
throw new Error(
`missing required extension data value(s) '${remainingRefs
.map(ref => ref.id)
.join(', ')}'`,
);
}
return {
get(ref) {
return container.get(ref.id)?.value;
},
[Symbol.iterator]() {
return container.values();
},
} as ExtensionDataContainer<UData>;
}
@@ -17,7 +17,6 @@
export { coreExtensionData } from './coreExtensionData';
export {
createExtension,
type ExtensionDataContainer,
type ExtensionDefinition,
type CreateExtensionOptions,
type ExtensionDataValues,
@@ -32,6 +31,7 @@ export {
type ExtensionInput,
type LegacyExtensionInput,
} from './createExtensionInput';
export { type ExtensionDataContainer } from './createExtensionDataContainer';
export {
createExtensionDataRef,
type AnyExtensionDataRef,