frontend-internal: switch opaqueType.createInstance to decorate + select version
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -33,42 +33,20 @@ describe('OpaqueType', () => {
|
||||
versions: ['v1'],
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
// @ts-expect-error - wrong type
|
||||
$$type: 'wrong-type',
|
||||
version: 'v1',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
version: 'v2',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing version
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
OpaqueMyType.createInstance('v2', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing internal field
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
});
|
||||
OpaqueMyType.createInstance('v1', {});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
// @ts-expect-error - invalid internal field
|
||||
foo: 3,
|
||||
});
|
||||
|
||||
const myInstance = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
const myInstance = OpaqueMyType.createInstance('v1', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
@@ -170,62 +148,34 @@ describe('OpaqueType', () => {
|
||||
versions: ['v1', 'v2', 'v3'],
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
// @ts-expect-error - wrong type
|
||||
$$type: 'wrong-type',
|
||||
version: 'v1',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
version: 'v0',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing version
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
OpaqueMyType.createInstance('v0', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing internal field
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
});
|
||||
OpaqueMyType.createInstance('v1', {});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
// @ts-expect-error - invalid internal field
|
||||
foo: 3,
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v2',
|
||||
OpaqueMyType.createInstance('v2', {
|
||||
// @ts-expect-error - version mismatch
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
// @ts-expect-error - version mismatch
|
||||
bar: 'foo',
|
||||
});
|
||||
|
||||
const myInstanceV1 = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
const myInstanceV1 = OpaqueMyType.createInstance('v1', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
const myInstanceV2 = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v2',
|
||||
const myInstanceV2 = OpaqueMyType.createInstance('v2', {
|
||||
bar: 'foo',
|
||||
});
|
||||
|
||||
@@ -293,35 +243,20 @@ describe('OpaqueType', () => {
|
||||
versions: [undefined],
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
// @ts-expect-error - wrong type
|
||||
$$type: 'wrong-type',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
version: 'v1',
|
||||
// @ts-expect-error - unsupported version
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing internal field
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: undefined,
|
||||
});
|
||||
OpaqueMyType.createInstance(undefined, {});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: undefined,
|
||||
OpaqueMyType.createInstance(undefined, {
|
||||
// @ts-expect-error - invalid internal field
|
||||
foo: 3,
|
||||
});
|
||||
|
||||
const myInstance = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: undefined,
|
||||
const myInstance = OpaqueMyType.createInstance(undefined, {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
@@ -370,62 +305,34 @@ describe('OpaqueType', () => {
|
||||
versions: [undefined, 'v1'],
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
// @ts-expect-error - wrong type
|
||||
$$type: 'wrong-type',
|
||||
version: 'v1',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
version: 'v3',
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing version
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
// @ts-expect-error - unsupported version
|
||||
OpaqueMyType.createInstance('v0', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
// @ts-expect-error - missing internal field
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
});
|
||||
OpaqueMyType.createInstance('v1', {});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
// @ts-expect-error - invalid internal field
|
||||
foo: 3,
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: undefined,
|
||||
OpaqueMyType.createInstance(undefined, {
|
||||
// @ts-expect-error - version mismatch
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
OpaqueMyType.createInstance('v1', {
|
||||
// @ts-expect-error - version mismatch
|
||||
bar: 'foo',
|
||||
});
|
||||
|
||||
const myInstanceV1 = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: 'v1',
|
||||
const myInstanceV1 = OpaqueMyType.createInstance('v1', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
const myInstanceV2 = OpaqueMyType.createInstance({
|
||||
$$type: 'my-type',
|
||||
version: undefined,
|
||||
const myInstanceV2 = OpaqueMyType.createInstance(undefined, {
|
||||
bar: 'foo',
|
||||
});
|
||||
|
||||
@@ -472,4 +379,38 @@ describe('OpaqueType', () => {
|
||||
`"Invalid opaque type instance, got version 'v3', expected undefined or 'v1'"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should create an empty opaque type with no versions', () => {
|
||||
type MyType = {
|
||||
$$type: 'my-type';
|
||||
};
|
||||
|
||||
const OpaqueMyType = OpaqueType.create<{
|
||||
public: MyType;
|
||||
versions: {
|
||||
version: undefined;
|
||||
};
|
||||
}>({
|
||||
type: 'my-type',
|
||||
versions: [undefined],
|
||||
});
|
||||
|
||||
// @ts-expect-error - unsupported version
|
||||
OpaqueMyType.createInstance('v0', {
|
||||
foo: 'bar',
|
||||
});
|
||||
|
||||
const myInstance = OpaqueMyType.createInstance(undefined, {});
|
||||
|
||||
expect(myInstance.$$type).toBe('my-type');
|
||||
|
||||
expect(OpaqueMyType.isType(myInstance)).toBe(true);
|
||||
expect(OpaqueMyType.isType('hello')).toBe(false);
|
||||
|
||||
const myInternal = OpaqueMyType.toInternal(myInstance);
|
||||
expect(myInternal).toBe(myInstance);
|
||||
// All fields accessible
|
||||
expect(myInternal.$$type).toBe('my-type');
|
||||
expect(myInternal.version).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,14 +116,27 @@ export class OpaqueType<
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the opaque type, returning the public public type.
|
||||
* Creates an instance of the opaque type, returning the public type.
|
||||
*
|
||||
* By providing a type argument you can narrow the return to specific type parameters.
|
||||
* @param version The version of the instance to create
|
||||
* @param value The remaining public and internal properties of the instance
|
||||
* @returns An instance of the opaque type
|
||||
*/
|
||||
createInstance<TBase extends T['public'] = T['public']>(
|
||||
value: T['public'] & T['versions'] & Object, // & Object to allow for object properties too, e.g. toString()
|
||||
): TBase {
|
||||
return value as unknown as TBase;
|
||||
createInstance<TVersion extends T['versions']['version']>(
|
||||
version: TVersion,
|
||||
props: Omit<T['public'], '$$type'> &
|
||||
(T['versions'] extends infer UVersion
|
||||
? UVersion extends { version: TVersion }
|
||||
? Omit<UVersion, 'version'>
|
||||
: never
|
||||
: never) &
|
||||
Object, // & Object to allow for object properties too, e.g. toString()
|
||||
): T['public'] {
|
||||
return {
|
||||
...(props as object),
|
||||
$$type: this.#type,
|
||||
...(version && { version }),
|
||||
} as T['public'];
|
||||
}
|
||||
|
||||
#isThisInternalType(value: unknown): value is T['public'] & T['versions'] {
|
||||
|
||||
@@ -366,6 +366,26 @@ export function createExtension<
|
||||
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
|
||||
name: string | undefined extends TName ? undefined : TName;
|
||||
}> {
|
||||
type T = {
|
||||
config: string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
};
|
||||
configInput: string extends keyof TConfigSchema
|
||||
? {}
|
||||
: z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>;
|
||||
output: UOutput;
|
||||
inputs: TInputs;
|
||||
kind: string | undefined extends TKind ? undefined : TKind;
|
||||
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
|
||||
name: string | undefined extends TName ? undefined : TName;
|
||||
};
|
||||
|
||||
const schemaDeclaration = options.config?.schema;
|
||||
const configSchema =
|
||||
schemaDeclaration &&
|
||||
@@ -377,30 +397,8 @@ export function createExtension<
|
||||
),
|
||||
);
|
||||
|
||||
return OpaqueExtensionDefinition.createInstance({
|
||||
$$type: '@backstage/ExtensionDefinition',
|
||||
version: 'v2',
|
||||
T: undefined as unknown as {
|
||||
config: string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
configInput: string extends keyof TConfigSchema
|
||||
? {}
|
||||
: z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>;
|
||||
output: UOutput;
|
||||
inputs: TInputs;
|
||||
kind: string | undefined extends TKind ? undefined : TKind;
|
||||
namespace: string | undefined extends TNamespace ? undefined : TNamespace;
|
||||
name: string | undefined extends TName ? undefined : TName;
|
||||
},
|
||||
return OpaqueExtensionDefinition.createInstance('v2', {
|
||||
T: undefined as unknown as T,
|
||||
kind: options.kind,
|
||||
namespace: options.namespace,
|
||||
name: options.name,
|
||||
@@ -512,5 +510,5 @@ export function createExtension<
|
||||
},
|
||||
}) as ExtensionDefinition<any>;
|
||||
},
|
||||
});
|
||||
}) as ExtensionDefinition<T>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user