import { mergeArrays, firstDefined, resourceBaseName, getResourceKeyFor, resource, isFunction, isString, } from '@aurelia/kernel'; import { aliasRegistration, singletonRegistration } from '../utilities-di'; import { objectFreeze } from '../utilities'; import { defineMetadata, getAnnotationKeyFor, getMetadata, hasMetadata } from '../utilities-metadata'; import type { Constructable, IContainer, ResourceDefinition, ResourceType, PartialResourceDefinition, IServiceLocator, StaticResourceType, } from '@aurelia/kernel'; import { ErrorNames, createMappedError } from '../errors'; import { getDefinitionFromStaticAu, type IResourceKind } from './resources-shared'; export type PartialValueConverterDefinition = PartialResourceDefinition; export type ValueConverterStaticAuDefinition = PartialValueConverterDefinition & { type: 'value-converter'; }; export type ValueConverterType = ResourceType; export type ValueConverterInstance = { signals?: string[]; withContext?: boolean; toView(input: unknown, ...args: unknown[]): unknown; fromView?(input: unknown, ...args: unknown[]): unknown; } & T; export interface ICallerContext { source?: unknown; binding: unknown; } export type ValueConverterKind = IResourceKind & { isType(value: T): value is (T extends Constructable ? ValueConverterType : never); define(name: string, Type: T, decoratorContext?: DecoratorContext): ValueConverterType; define(def: PartialValueConverterDefinition, Type: T, decoratorContext?: DecoratorContext): ValueConverterType; define(nameOrDef: string | PartialValueConverterDefinition, Type: T, decoratorContext?: DecoratorContext): ValueConverterType; getDefinition(Type: T): ValueConverterDefinition; annotate(Type: Constructable, prop: K, value: PartialValueConverterDefinition[K]): void; getAnnotation(Type: Constructable, prop: K, context: DecoratorContext | null): PartialValueConverterDefinition[K] | undefined; find(container: IContainer, name: string): ValueConverterDefinition | null; get(container: IServiceLocator, name: string): ValueConverterInstance; }; export type ValueConverterDecorator = (Type: T, context: ClassDecoratorContext) => ValueConverterType; export function valueConverter(definition: PartialValueConverterDefinition): ValueConverterDecorator; export function valueConverter(name: string): ValueConverterDecorator; export function valueConverter(nameOrDef: string | PartialValueConverterDefinition): ValueConverterDecorator; export function valueConverter(nameOrDef: string | PartialValueConverterDefinition): ValueConverterDecorator { return function (target: T, context: ClassDecoratorContext): ValueConverterType { context.addInitializer(function (this) { ValueConverter.define(nameOrDef, this as Constructable); }); return target as ValueConverterType; }; } export class ValueConverterDefinition implements ResourceDefinition { private constructor( public readonly Type: ValueConverterType, public readonly name: string, public readonly aliases: readonly string[], public readonly key: string, ) { } public static create( nameOrDef: string | PartialValueConverterDefinition, Type: ValueConverterType, ): ValueConverterDefinition { let name: string; let def: PartialValueConverterDefinition; if (isString(nameOrDef)) { name = nameOrDef; def = { name }; } else { name = nameOrDef.name; def = nameOrDef; } return new ValueConverterDefinition( Type, firstDefined(getConverterAnnotation(Type, 'name'), name), mergeArrays(getConverterAnnotation(Type, 'aliases'), def.aliases, Type.aliases), ValueConverter.keyFrom(name), ); } public register(container: IContainer, aliasName?: string): void { const $Type = this.Type; const key = typeof aliasName === 'string' ? getValueConverterKeyFrom(aliasName) : this.key; const aliases = this.aliases; if (!container.has(key, false)) { container.register( container.has($Type, false) ? null : singletonRegistration($Type, $Type), aliasRegistration($Type, key), ...aliases.map(alias => aliasRegistration($Type, getValueConverterKeyFrom(alias))) ); } /* istanbul ignore next */ else if(__DEV__) { // eslint-disable-next-line no-console console.warn(`[DEV:aurelia] ${createMappedError(ErrorNames.value_converter_existed, this.name)}`); } } } /** @internal */ export const converterTypeName = 'value-converter'; const vcBaseName = /*@__PURE__*/getResourceKeyFor(converterTypeName); const getConverterAnnotation = ( Type: Constructable, prop: K, ): PartialValueConverterDefinition[K] | undefined => getMetadata(getAnnotationKeyFor(prop), Type); const getValueConverterKeyFrom = (name: string): string => `${vcBaseName}:${name}`; export const ValueConverter = objectFreeze({ name: vcBaseName, keyFrom: getValueConverterKeyFrom, isType(value: T): value is (T extends Constructable ? ValueConverterType : never) { return isFunction(value) && (hasMetadata(vcBaseName, value) || (value as StaticResourceType).$au?.type === converterTypeName); }, define>(nameOrDef: string | PartialValueConverterDefinition, Type: T): ValueConverterType { const definition = ValueConverterDefinition.create(nameOrDef, Type as Constructable); const $Type = definition.Type as ValueConverterType; // registration of resource name is a requirement for the resource system in kernel (module-loader) defineMetadata(definition, $Type, vcBaseName, resourceBaseName); return $Type; }, getDefinition(Type: T): ValueConverterDefinition { const def = getMetadata>(vcBaseName, Type) ?? getDefinitionFromStaticAu, ValueConverterType>(Type as ValueConverterType, converterTypeName, ValueConverterDefinition.create); if (def === void 0) { throw createMappedError(ErrorNames.value_converter_def_not_found, Type); } return def; }, annotate(Type: Constructable, prop: K, value: PartialValueConverterDefinition[K]): void { defineMetadata(value, Type, getAnnotationKeyFor(prop)); }, getAnnotation: getConverterAnnotation, find(container, name) { const Type = container.find(converterTypeName, name); return Type == null ? null : getMetadata(vcBaseName, Type) ?? getDefinitionFromStaticAu(Type, converterTypeName, ValueConverterDefinition.create) ?? null; }, get(container, name) { if (__DEV__) { try { return container.get(resource(getValueConverterKeyFrom(name))); } catch (ex) { // eslint-disable-next-line no-console console.error('[DEV:aurelia] Cannot retrieve value converter with name', name); throw ex; } } return container.get(resource(getValueConverterKeyFrom(name))); }, });