import { IContainer } from './di'; import { Constructable } from './interfaces'; import { defineMetadata, getMetadata, objectFreeze } from './utilities'; export type StaticResourceType = { readonly aliases?: string[]; readonly $au?: PartialResourceDefinition<{ type: string; } & TDef>; }; export type ResourceType< TUserType extends Constructable = Constructable, TResInstance extends {} = {}, TResType extends {} = {}, TUserInstance extends InstanceType = InstanceType, > = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any new (...args: any[]) => TResInstance & TUserInstance ) & StaticResourceType & TResType & TUserType; export type ResourceDefinition< TUserType extends Constructable = Constructable, TResInstance extends {} = {}, TDef extends {} = {}, TResType extends {} = {}, TUserInstance extends InstanceType = InstanceType, > = { /** * Unique key to identify the resource. */ readonly key: string; /** * A common name for the resource. */ readonly name: string; readonly Type: ResourceType; readonly aliases?: readonly string[]; /** * @param aliasName - If provided, the resource will be registered with this alias key. */ register(container: IContainer, aliasName?: string): void; } & TDef; export type PartialResourceDefinition = { readonly name: string; readonly aliases?: readonly string[]; } & TDef; // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface IResourceKind { readonly name: string; keyFrom(name: string): string; } const annoBaseName = 'au:annotation'; /** @internal */ export const getAnnotationKeyFor = (name: string, context?: string): string => { if (context === void 0) { return `${annoBaseName}:${name}`; } return `${annoBaseName}:${name}:${context}`; }; /** @internal */ export const appendAnnotation = (target: Constructable, key: string): void => { const keys = getMetadata(annoBaseName, target); if (keys === void 0) { defineMetadata([key], target, annoBaseName); } else { keys.push(key); } }; const annotation = /*@__PURE__*/ objectFreeze({ name: 'au:annotation', appendTo: appendAnnotation, set(target: Constructable, prop: string, value: unknown): void { defineMetadata(value, target, getAnnotationKeyFor(prop)); }, get: (target: Constructable, prop: string): unknown => getMetadata(getAnnotationKeyFor(prop), target), getKeys(target: Constructable): readonly string[] { let keys = getMetadata(annoBaseName, target); if (keys === void 0) { defineMetadata(keys = [], target, annoBaseName); } return keys; }, isKey: (key: string): boolean => key.startsWith(annoBaseName), keyFor: getAnnotationKeyFor, }); export const resourceBaseName = 'au:resource'; /** * Builds a resource key from the provided parts. */ export const getResourceKeyFor = (type: string, name?: string, context?: string): string => { if (name == null) { return `${resourceBaseName}:${type}`; } if (context == null) { return `${resourceBaseName}:${type}:${name}`; } return `${resourceBaseName}:${type}:${name}:${context}`; }; export const Protocol = { annotation, }; const hasOwn = Object.prototype.hasOwnProperty; /** * The order in which the values are checked: * 1. Annotations (usually set by decorators) have the highest priority; they override the definition as well as static properties on the type. * 2. Definition properties (usually set by the customElement decorator object literal) come next. They override static properties on the type. * 3. Static properties on the type come last. Note that this does not look up the prototype chain (bindables are an exception here, but we do that differently anyway) * 4. The default property that is provided last. The function is only called if the default property is needed */ export function fromAnnotationOrDefinitionOrTypeOrDefault< TDef extends PartialResourceDefinition, K extends keyof TDef, >( name: K, def: TDef, Type: Constructable, getDefault: () => Required[K], ): Required[K] { let value = getMetadata(getAnnotationKeyFor(name as string), Type); if (value === void 0) { value = def[name]; if (value === void 0) { value = (Type as Constructable & TDef)[name] as TDef[K] | undefined; if (value === void 0 || !hasOwn.call(Type, name)) { // First just check the value (common case is faster), but do make sure it doesn't come from the proto chain return getDefault(); } return value; } return value; } return value; } /** * The order in which the values are checked: * 1. Annotations (usually set by decorators) have the highest priority; they override static properties on the type. * 2. Static properties on the typ. Note that this does not look up the prototype chain (bindables are an exception here, but we do that differently anyway) * 3. The default property that is provided last. The function is only called if the default property is needed */ export function fromAnnotationOrTypeOrDefault( name: K, Type: T, getDefault: () => V, ): V { let value = getMetadata(getAnnotationKeyFor(name as string), Type); if (value === void 0) { value = Type[name] as unknown as V; if (value === void 0 || !hasOwn.call(Type, name)) { // First just check the value (common case is faster), but do make sure it doesn't come from the proto chain return getDefault(); } return value; } return value; } /** * The order in which the values are checked: * 1. Definition properties. * 2. The default property that is provided last. The function is only called if the default property is needed */ export function fromDefinitionOrDefault< TDef extends PartialResourceDefinition, K extends keyof TDef, >( name: K, def: TDef, getDefault: () => Required[K], ): Required[K] { const value = def[name]; if (value === void 0) { return getDefault(); } return value; }