export type MetadataKey = string | symbol; type MetadataMap = Map; type PropertyMetadataMap = Map; const metadataStore = new WeakMap(); const propertyMetadataStore = new WeakMap(); function mapFor(target: object): MetadataMap { let map = metadataStore.get(target); if (!map) { map = new Map(); metadataStore.set(target, map); } return map; } function propertyMapFor(target: object, propertyKey: MetadataKey): MetadataMap { let properties = propertyMetadataStore.get(target); if (!properties) { properties = new Map(); propertyMetadataStore.set(target, properties); } let map = properties.get(propertyKey); if (!map) { map = new Map(); properties.set(propertyKey, map); } return map; } /** Stores `metadataValue` for `metadataKey` on `target` (and `propertyKey`). */ export function defineMetadata( metadataKey: MetadataKey, metadataValue: unknown, target: object, propertyKey?: MetadataKey, ): void { const map = mapFor(target); const existing = map.get(metadataKey) ?? []; existing.push(metadataValue); map.set(metadataKey, existing); if (propertyKey !== undefined) { const propertyMap = propertyMapFor(target, propertyKey); const propertyExisting = propertyMap.get(metadataKey) ?? []; propertyExisting.push(metadataValue); propertyMap.set(metadataKey, propertyExisting); } } /** Reads metadata declared directly on `target`. */ export function getOwnMetadata( metadataKey: MetadataKey, target: object, propertyKey?: MetadataKey, ): unknown | undefined { if (propertyKey !== undefined) { const properties = propertyMetadataStore.get(target); return properties?.get(propertyKey)?.get(metadataKey)?.[0]; } return metadataStore.get(target)?.get(metadataKey)?.[0]; } /** Reads metadata declared on `target` or inherited from its prototype chain. */ export function getMetadata( metadataKey: MetadataKey, target: object, propertyKey?: MetadataKey, ): unknown | undefined { let current: object | null = target; while (current) { const value = getOwnMetadata(metadataKey, current, propertyKey); if (value !== undefined) return value; current = Object.getPrototypeOf(current); } return undefined; } /** `true` when metadata is declared on `target` or an ancestor. */ export function hasMetadata( metadataKey: MetadataKey, target: object, propertyKey?: MetadataKey, ): boolean { return getMetadata(metadataKey, target, propertyKey) !== undefined; } /** Removes all metadata registered for `target`. */ export function deleteMetadata(target: object): void { metadataStore.delete(target); } /** Reads the most recent metadata entry declared for `target`. */ export function getLatestMetadata( metadataKey: MetadataKey, target: object, propertyKey?: MetadataKey, ): unknown | undefined { if (propertyKey !== undefined) { const properties = propertyMetadataStore.get(target); const entries = properties?.get(propertyKey)?.get(metadataKey); return entries?.[entries.length - 1]; } const entries = metadataStore.get(target)?.get(metadataKey); return entries?.[entries.length - 1]; } /** Owner of a decorated member. */ export function ownerOf(target: object): object { if (typeof target === "function") return target as object; return (target as { constructor: object }).constructor; } /** Stores metadata of a method or a parameter on the owning class. */ export function defineMemberMetadata( metadataKey: MetadataKey, metadataValue: unknown, target: object, propertyKey?: MetadataKey, ): void { defineMetadata(metadataKey, metadataValue, ownerOf(target), propertyKey); } /** Reads every metadata entry stored for a method. */ export function getMemberMetadata( metadataKey: MetadataKey, target: object, propertyKey: MetadataKey, ): unknown[] { const properties = propertyMetadataStore.get(target); const map = properties?.get(propertyKey); if (!map) return []; return map.get(metadataKey) ?? []; } /** Reads every metadata entry of a class, together with its property key. */ export function getAllMemberMetadata( metadataKey: MetadataKey, target: object, ): { propertyKey: MetadataKey; value: unknown }[] { const properties = propertyMetadataStore.get(target); if (!properties) return []; const entries: { propertyKey: MetadataKey; value: unknown }[] = []; for (const [propertyKey, map] of properties) { for (const value of map.get(metadataKey) ?? []) { entries.push({ propertyKey, value }); } } return entries; } /** Installs the proposal API on `Reflect` when the runtime does not provide it. */ export function installMetadataPolyfill(): void { const reflect = Reflect as unknown as Record; if (typeof reflect["metadata"] !== "function") { reflect["metadata"] = ( metadataKey: MetadataKey, metadataValue: unknown, ) => { return (target: object, propertyKey?: MetadataKey): void => { defineMetadata(metadataKey, metadataValue, target, propertyKey); }; }; } if (typeof reflect["defineMetadata"] !== "function") { reflect["defineMetadata"] = defineMetadata; } if (typeof reflect["getOwnMetadata"] !== "function") { reflect["getOwnMetadata"] = getOwnMetadata; } if (typeof reflect["getMetadata"] !== "function") { reflect["getMetadata"] = getMetadata; } if (typeof reflect["hasMetadata"] !== "function") { reflect["hasMetadata"] = hasMetadata; } } installMetadataPolyfill();