Skip to content

Commit

Permalink
Merge pull request #27 from medishen/dev/v2
Browse files Browse the repository at this point in the history
refactor(metadata): overhaul metadata handling with improved scoping and type safety
  • Loading branch information
0xii00 authored Jan 29, 2025
2 parents 5f5feff + 07be289 commit db2d1fc
Show file tree
Hide file tree
Showing 7 changed files with 684 additions and 124 deletions.
18 changes: 18 additions & 0 deletions packages/metadata/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Metadata Key Prefix
*/
export const PREFIX = 'meta';
/**
* Metadata Separator
*/
export const KEY_SEPARATOR = '::';
/**
* Metadata Scope
*/
export const MetadataScope = {
CLASS: 'class',
METHOD: 'method',
PROPERTY: 'property',
PARAMETER: 'param',
FUNCTION: 'function',
};
133 changes: 68 additions & 65 deletions packages/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Decorator, MetadataKey, MetadataParameterIndex, MetadataTarget, MetadataValue } from './types/metadata.types';
import { Decorator, MetadataKey, MetadataParameterIndex, MetadataScopeType, MetadataTarget, MetadataValue } from './types/metadata.types';
import ReflectStorage from './storage/storage-metadata';
import { constructKey, MetadataScope, MetadataScopeType, resolveTargetClass } from './utils/metadta.utils';
import { constructMetadataKey, identifyTargetType, parseMetadataEntries } from './utils/metadta.utils';
export * from './interface/metadata.interface';
export * from './types/metadata.types';
class Reflector {
Expand All @@ -12,25 +12,35 @@ class Reflector {
/**
* Define metadata for a target and optional property.
*/
defineMetadata<T extends MetadataValue>(metadataKey: MetadataKey, value: T, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): void {
const key = constructKey(metadataKey, propertyKey, parameterIndex);
const actualTarget = resolveTargetClass(target);
this.storage.set(actualTarget!, key, value);
defineMetadata<T>(metadataKey: MetadataKey, value: MetadataValue<T>, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): void {
const actualTarget = identifyTargetType(target);
const key = constructMetadataKey(metadataKey, actualTarget, propertyKey, parameterIndex);
this.storage.set(actualTarget.target, key, value);
}

/**
* Retrieve metadata for a target and optional property.
*/
getMetadata<T = MetadataValue>(metadataKey: MetadataKey, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): T | unknown {
const key = constructKey(metadataKey, propertyKey, parameterIndex);
let currentTarget = resolveTargetClass(target);
while (currentTarget) {
const metadataMap = this.storage.get(currentTarget);
getMetadata<V>(metadataKey: MetadataKey, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): MetadataValue<V> | undefined {
let resolvedTarget = identifyTargetType(target);
const metadataKeyFormatted = constructMetadataKey(metadataKey, resolvedTarget, propertyKey, parameterIndex);

while (resolvedTarget) {
const metadataMap = this.storage.get(resolvedTarget.target);
if (metadataMap && metadataMap.has(metadataKeyFormatted.toString())) {
return metadataMap.get(metadataKeyFormatted) as V | undefined;
}

if (metadataMap && metadataMap.has(key)) {
return metadataMap.get(key);
const parentPrototype = Object.getPrototypeOf(resolvedTarget.target);
if (!parentPrototype) {
break;
}
currentTarget = Object.getPrototypeOf(currentTarget);

resolvedTarget.target = parentPrototype;
}
const parentPrototype = Object.getPrototypeOf(target);
if (parentPrototype) {
return this.getMetadata(metadataKey, parentPrototype, propertyKey, parameterIndex);
}
return undefined;
}
Expand All @@ -39,34 +49,35 @@ class Reflector {
* Check if metadata exists for a target and optional property.
*/
hasMetadata(metadataKey: MetadataKey, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): boolean {
const key = constructKey(metadataKey, propertyKey, parameterIndex);
return this.storage.has(target, key);
const actualTarget = identifyTargetType(target);
const key = constructMetadataKey(metadataKey, actualTarget, propertyKey, parameterIndex);
return this.storage.has(actualTarget.target!, key);
}

/**
* Delete metadata for a target and optional property.
*/
deleteMetadata(metadataKey: MetadataKey, target: MetadataTarget, propertyKey?: MetadataKey, parameterIndex?: MetadataParameterIndex): boolean {
const key = constructKey(metadataKey, propertyKey, parameterIndex);
const actualTarget = resolveTargetClass(target);
const actualTarget = identifyTargetType(target);
const key = constructMetadataKey(metadataKey, actualTarget, propertyKey, parameterIndex);
if (!actualTarget) {
return false;
}
return this.storage.delete(actualTarget, key);
return this.storage.delete(actualTarget.target!, key);
}

/**
* Clear all metadata for a target.
*/
clearMetadata(target: MetadataTarget): void {
const actualTarget = resolveTargetClass(target);
if (!actualTarget) {
const actualTarget = identifyTargetType(target);
if (!actualTarget.target) {
throw new Error('Unable to clear metadata: The provided target is invalid or not properly defined.');
}
this.storage.clear(actualTarget);
this.storage.clear(actualTarget.target);
}
// Decorator factory
metadata(key: MetadataKey, value: MetadataValue): Decorator {
metadata<T>(key: MetadataKey, value: MetadataValue<T>): Decorator {
return (target: MetadataTarget, propertyKey?: MetadataKey, descriptorOrIndex?: MetadataParameterIndex | TypedPropertyDescriptor<any>) => {
if (typeof descriptorOrIndex === 'number') {
// Parameter decorator (target, prop, index)
Expand All @@ -86,60 +97,52 @@ class Reflector {
/**
* List all metadata keys for a target and optional property.
*/
getMetadataKeys(target: MetadataTarget, scope?: MetadataScopeType, propertyName?: string, parameterIndex?: number): MetadataKey[] {
const resolvedTarget = resolveTargetClass(target);
const metadataMap = this.storage.get(resolvedTarget!);

getMetadataKeys(target: MetadataTarget, scope?: MetadataScopeType): MetadataKey[] {
const resolvedTarget = identifyTargetType(target);
const metadataMap = this.storage.get(resolvedTarget.target);
if (!metadataMap) {
return [];
}

return Array.from(metadataMap.keys())
.filter((key) => {
const segments = String(key).split(':');
const currentScope = segments[0];

if (scope && currentScope !== MetadataScope[scope.toUpperCase()]) {
return false;
}

if (propertyName && segments.length > 1 && segments[1] !== propertyName) {
return false;
}

if (typeof parameterIndex === 'number') {
const hasParamSegment = segments.length > 3 && segments[2] === 'param';
const hasCorrectIndex = hasParamSegment && Number(segments[3]) === parameterIndex;
const keys = Array.from(metadataMap.entries());
const result = parseMetadataEntries(keys);

if (!hasCorrectIndex) {
return false;
}
}
const filteredKeys = result.filter((rs) => {
if (scope) {
return rs.scopes?.includes(scope);
}
return true;
});

return true;
})
.map((key) => {
const segments = String(key).split(':');
return segments[segments.length - 1];
});
return filteredKeys.map((rs) => rs.metadataKey!);
}

/**
* List all metadata for a target.
*/
listMetadata<K extends MetadataKey, V extends MetadataValue>(target: MetadataTarget): Map<K, V> | null {
const metadataMap = this.storage.list(target);
if (metadataMap) {
return metadataMap as Map<K, V>;
listMetadata<K extends MetadataKey, V>(
target: MetadataTarget,
): {
count: number;
metadata: Array<{
metadataKey: MetadataKey<K>;
metadataValue: MetadataValue<V>;
}>;
} | null {
const actualTarget = identifyTargetType(target);
const metadataMap = this.storage.list(actualTarget.target);
if (!metadataMap) {
return null;
}
return null;
}

/**
* List all metadata across all targets.
*/
listAllMetadata(): Map<string, Map<MetadataKey, MetadataValue>> {
return this.storage.allList();
const entries = Array.from(metadataMap.entries()) as [K, V][];
const parsedEntries = parseMetadataEntries<K, V>(entries).filter((entry): entry is { metadataKey: MetadataKey<K>; metadataValue: MetadataValue<V> } => entry.metadataKey !== undefined);
return {
count: parsedEntries.length,
metadata: parsedEntries.map((entry) => ({
metadataKey: entry.metadataKey,
metadataValue: entry.metadataValue,
})),
};
}
}

Expand Down
9 changes: 4 additions & 5 deletions packages/metadata/interface/metadata.interface.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { MetadataKey, MetadataTarget, MetadataValue } from '../types/metadata.types';

export interface MetadataStorage {
get(target: MetadataTarget): Map<MetadataKey, MetadataValue>;
set(target: MetadataTarget, key: MetadataKey, value: MetadataValue): void;
get<K extends MetadataKey, V>(target: MetadataTarget): Map<MetadataKey<K>, MetadataValue<V>>;
set<K extends MetadataKey, V>(target: MetadataTarget, key: MetadataKey<K>, value: MetadataValue<V>): void;
has(target: MetadataTarget, key: MetadataKey): boolean;
delete(target: MetadataTarget, key: MetadataKey): boolean;
clear(target: MetadataTarget): void;
keys(): string[];
list(target: MetadataTarget): Map<MetadataKey, MetadataValue> | null;
allList(): Map<string, Map<MetadataKey, MetadataValue>>;
}
list<K extends MetadataKey, V>(target: MetadataTarget): Map<MetadataKey<K>, MetadataValue<V>> | null;
}
30 changes: 9 additions & 21 deletions packages/metadata/storage/storage-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MetadataStorage } from '../interface/metadata.interface';
import { MetadataKey, MetadataTarget, MetadataValue } from '../types/metadata.types';

class ReflectStorage implements MetadataStorage {
private storage = new MemoryCacheStore<string, Map<MetadataKey, MetadataValue>>();
private storage = new MemoryCacheStore<string, Map<MetadataKey, MetadataValue<any>>>();
constructor() {
this.storage = new MemoryCacheStore({
policy: 'LRU',
Expand All @@ -20,22 +20,22 @@ class ReflectStorage implements MetadataStorage {
return `instance:${target.constructor.name}`;
}

private ensureMetadataMap(target: MetadataTarget): Map<MetadataKey, MetadataValue> {
private ensureMetadataMap<K extends MetadataKey, V>(target: MetadataTarget): Map<MetadataKey<K>, MetadataValue<V>> {
const targetKey = this.getTargetIdentifier(target);
let metadataMap = this.storage.get(targetKey);
if (!(metadataMap instanceof Map)) {
metadataMap = new Map<MetadataKey, MetadataValue>();
metadataMap = new Map<MetadataKey, MetadataValue<V>>();
this.storage.set(targetKey, metadataMap);
}
return metadataMap;
return metadataMap as Map<K, V>;
}
get(target: MetadataTarget): Map<MetadataKey, MetadataValue> {
get<K extends MetadataKey, V>(target: MetadataTarget): Map<MetadataKey<K>, MetadataValue<V>> {
const targetKey = this.getTargetIdentifier(target);
const metadataMap = this.storage.get(targetKey);
return metadataMap instanceof Map ? metadataMap : new Map<MetadataKey, MetadataValue>();
return metadataMap instanceof Map ? (metadataMap as Map<K, V>) : new Map<MetadataKey<K>, MetadataValue<V>>();
}

set(target: MetadataTarget, key: MetadataKey, value: MetadataValue): void {
set<K extends MetadataKey, V>(target: MetadataTarget, key: MetadataKey<K>, value: MetadataValue<V>): void {
const metadataMap = this.ensureMetadataMap(target);
metadataMap.set(key, value);
const targetKey = this.getTargetIdentifier(target);
Expand Down Expand Up @@ -64,29 +64,17 @@ class ReflectStorage implements MetadataStorage {
this.storage.delete(targetKey);
}

list(target: MetadataTarget): Map<MetadataKey, MetadataValue> | null {
list<K extends MetadataKey, V>(target: MetadataTarget): Map<MetadataKey<K>, MetadataValue<V>> | null {
const targetKey = this.getTargetIdentifier(target);
const metadataMap = this.storage.get(targetKey);
if (metadataMap instanceof Map) {
return metadataMap;
return metadataMap as Map<K, V>;
}
return null;
}
keys(): string[] {
return Array.from(this.storage.keys());
}

allList(): Map<string, Map<MetadataKey, MetadataValue>> {
const entries = this.storage.get('*', { pattern: true, withTuples: true }) as [string, Map<MetadataKey, MetadataValue>][];
const allMetadata = new Map<string, Map<MetadataKey, MetadataValue>>();

for (const [key, value] of entries) {
if (value instanceof Map) {
allMetadata.set(key, value);
}
}
return allMetadata;
}
}

export default ReflectStorage;
8 changes: 6 additions & 2 deletions packages/metadata/types/metadata.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export type MetadataKey = string | symbol;
export type MetadataValue = unknown;
import { MetadataScope } from '../constant';
export type MetadataKey<K = string | symbol> = K;
export type MetadataValue<T extends unknown> = T;
export type MetadataTarget = object | Function;
export type MetadataPropertyKey = string | symbol;
export type MetadataParameterIndex = number;
export type Decorator = ClassDecorator & PropertyDecorator & MethodDecorator & ParameterDecorator;
export type TargetType = 'class' | 'method' | 'function' | 'object' | 'unknown';
export type TargetIdentification = { type: TargetType; target: MetadataTarget };
export type MetadataScopeType = keyof typeof MetadataScope;
Loading

0 comments on commit db2d1fc

Please sign in to comment.