All files / lib/metadata types.ts

30% Statements 6/20
33.33% Branches 4/12
33.33% Functions 3/9
26.66% Lines 4/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                                                                                                          150x 209x   209x                     150x                                                                      
import { ArkErrors } from 'arktype';
 
import type * as DB from '$lib/database';
import { MetadataRuntimeValue, type RuntimeValue } from '$lib/schemas/metadata';
 
export type TypedMetadataValue<Type extends DB.MetadataType = DB.MetadataType> = Omit<
	DB.MetadataValue,
	'value'
> & { value: RuntimeValue<Type> };
 
export type RuntimeValuesPerType = { [K in DB.MetadataType]: RuntimeValue<K> };
 
type TypeswitchReturnTypes<R> = { [T in DB.MetadataType]: R };
 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TypeswitchCases<ReturnTypes extends TypeswitchReturnTypes<any>> = {
	[T in DB.MetadataType]: (...values: RuntimeValue<T>[]) => ReturnTypes[T];
};
 
type TypeswitchValues = RuntimeValue | [RuntimeValue, RuntimeValue] | RuntimeValue[];
 
/**
 * Run different code depending on metadata value's type.
 * Return types for the branches can be specified in two ways:
 * - by specifying R: a common return type for all branches
 * - by specifying RT: a mapping of return types per metadata type. For example, RuntimeValuesPerType if you want each branch to preserve its own runtime type. When specifying RT, you can set R to `any` to avoid TS errors.
 */
export function switchOnMetadataType<
	R,
	RT extends TypeswitchReturnTypes<R> = TypeswitchReturnTypes<R>
>(type: DB.MetadataType, value: TypeswitchValues, cases: TypeswitchCases<RT>): RT[DB.MetadataType];
export function switchOnMetadataType<
	R,
	RT extends TypeswitchReturnTypes<R> = TypeswitchReturnTypes<R>
>(
	type: DB.MetadataType,
	value: TypeswitchValues,
	cases: TypeswitchCases<RT>,
	fallback: undefined,
	error?: (...values: ArkErrors[]) => RT[DB.MetadataType]
): RT[DB.MetadataType];
export function switchOnMetadataType<
	R,
	RT extends TypeswitchReturnTypes<R> = TypeswitchReturnTypes<R>
>(
	type: DB.MetadataType,
	value: TypeswitchValues,
	cases: Partial<TypeswitchCases<RT>>,
	fallback: (...values: RuntimeValue[]) => RT[DB.MetadataType],
	error?: (...values: ArkErrors[]) => RT[DB.MetadataType]
): RT[DB.MetadataType];
export function switchOnMetadataType<
	R,
	RT extends TypeswitchReturnTypes<R> = TypeswitchReturnTypes<R>
>(
	type: DB.MetadataType,
	value: TypeswitchValues,
	cases: Partial<TypeswitchCases<RT>>,
	fallback?: (...values: RuntimeValue[]) => RT[DB.MetadataType],
	error?: (...values: ArkErrors[]) => RT[DB.MetadataType]
): RT[DB.MetadataType] {
	const values = Array.isArray(value) ? value : [value];
	const typeds = values.map((v) => MetadataRuntimeValue[type](v));
 
	Iif (typeds.some((v) => v instanceof ArkErrors)) {
		if (error) return error(...(typeds.filter((v) => v instanceof ArkErrors) as ArkErrors[]));
		throw new Error(
			`Invalid metadata value for type ${type}: ${typeds
				.filter((v) => v instanceof ArkErrors)
				.map((v) => v.toString())
				.join('; ')}`
		);
	}
 
	// @ts-expect-error typescript can't link the type of value and type parameter
	return (cases[type] ?? fallback)(...typeds);
}
 
export function hasRuntimeType<Type extends DB.MetadataType>(
	type: Type,
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	value: any
): value is RuntimeValue<Type> {
	return MetadataRuntimeValue[type].allows(value);
}
 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function assertIs<Type extends DB.MetadataType>(type: Type, value: any): RuntimeValue<Type> {
	if (!hasRuntimeType(type, value))
		throw new Error(`La valeur n'est pas de type ${type}: ${JSON.stringify(value)}`);
 
	return value;
}
 
/**
 * Get a strongly-typed metadata value from an image (Image ONLY, not Observation).
 */
export function getMetadataValue<Type extends DB.MetadataType>(
	image: DB.Image,
	type: Type,
	metadataId: string
): TypedMetadataValue<Type> | undefined {
	const value = image.metadata[metadataId];
	if (value === undefined) return undefined;
 
	return {
		...value,
		value: assertIs(type, value.value)
	};
}