179 lines
5.3 KiB
TypeScript
179 lines
5.3 KiB
TypeScript
export type MetadataKey = string | symbol;
|
|
|
|
type MetadataMap = Map<MetadataKey, unknown[]>;
|
|
type PropertyMetadataMap = Map<MetadataKey, MetadataMap>;
|
|
|
|
const metadataStore = new WeakMap<object, MetadataMap>();
|
|
const propertyMetadataStore = new WeakMap<object, PropertyMetadataMap>();
|
|
|
|
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<string, unknown>;
|
|
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();
|