npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

typed-reflector

v1.1.2

Published

Metadata reflector with typing support.

Readme

typed-reflector

Typed metadata decorators and metadata reader for TypeScript.

This library provides:

  • typed metadata keys and values
  • decorator helpers for class, property, method, accessor, and parameter metadata
  • metadata lookup with inheritance and prototype traversal
  • a built-in metadata registry, without reflect-metadata
  • a default global registry for backward compatibility
  • optional isolated registries for advanced use cases
  • separate /legacy, /modern, and /universal entries

Installation

npm install typed-reflector

No reflect-metadata dependency is required.

Entry Points

The package exposes four entry points:

  • typed-reflector
    • compatibility alias of typed-reflector/legacy
  • typed-reflector/legacy
    • legacy TypeScript decorators
    • built on MetadataRegistry / GlobalRegistry
    • supports param() and getMetadataFromDecorator()
  • typed-reflector/modern
    • standard decorators
    • still stores metadata in MetadataRegistry / GlobalRegistry
    • uses standard decorator metadata objects only to locate the correct class/member target and inheritance chain
    • does not support param() or getMetadataFromDecorator()
  • typed-reflector/universal
    • one decorator runtime that accepts both legacy and standard decorator calls
    • intended for library authors exposing decorators to downstream users
    • supports only the API surface shared by both decorator systems

TypeScript Setup

Legacy Entry

Applies to both:

  • typed-reflector
  • typed-reflector/legacy

Enable legacy decorators in tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

emitDecoratorMetadata is not required by this library.

Modern Entry

Use /modern or /universal with standard decorators.

Do not enable experimentalDecorators for this mode.

Example tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "esnext.decorators"]
  }
}

typed-reflector/modern and typed-reflector/universal automatically ensure Symbol.metadata exists when their modules are loaded, but your toolchain still needs to compile standard decorators and provide context.metadata.

Quick Start

import { Reflector } from 'typed-reflector';

interface MetadataMap {
  role: string;
}

interface MetadataArrayMap {
  tags: string;
  keys: string;
}

const metadata = new Reflector<MetadataMap, MetadataArrayMap>();
const reflector = new Reflector<MetadataMap, MetadataArrayMap>();

@metadata.set('role', 'service')
class UserService {
  @metadata.append('tags', 'public', 'keys')
  profile!: string;

  @metadata.set('role', 'getter')
  get currentRole() {
    return 'service';
  }
}

const service = new UserService();

reflector.get('role', UserService); // "service"
reflector.get('tags', service, 'profile'); // ["public"]
reflector.get('role', service, 'currentRole'); // "getter"
reflector.get('keys', UserService); // ["profile"]

Concepts

Type Maps

Reflector<M, AM> accepts two type maps:

  • M: scalar metadata values
  • AM: array element metadata values

Example:

interface MetadataMap {
  name: string;
  priority: number;
}

interface MetadataArrayMap {
  tags: string;
  params: number;
}

With the definitions above:

  • set('name', 'x') is allowed
  • set('priority', 1) is allowed
  • append('tags', 'x') is allowed
  • param('params', 0) is allowed
  • wrong key/value combinations fail at compile time

Registry Model

The library stores metadata in a MetadataRegistry.

  • GlobalRegistry is used by default
  • this keeps old code working without extra setup
  • you can create an isolated registry with new MetadataRegistry()

Metadata lookup follows the prototype chain in a way similar to reflect-metadata.

Main Exports

import {
  Reflector,
  MetadataSetter,
  MetadataRegistry,
  GlobalRegistry,
} from 'typed-reflector';

Notes:

  • Reflector is the main class
  • MetadataSetter is kept only for API compatibility
  • MetadataSetter is now just an alias of Reflector
  • typed-reflector re-exports typed-reflector/legacy

The explicit legacy entry exports the same runtime:

import { Reflector, MetadataSetter } from 'typed-reflector/legacy';

For standard decorators:

import { MetadataSetter, Reflector } from 'typed-reflector/modern';

In /modern, MetadataSetter is also just an alias of Reflector.

For mixed downstream support:

import { Reflector } from 'typed-reflector/universal';

Reflector Construction

Default Global Registry

const reflector = new Reflector<MyMetadata, MyArrayMetadata>();

This instance reads and writes through GlobalRegistry.

Custom Registry

const registry = new MetadataRegistry();

const metadata = new Reflector<MyMetadata, MyArrayMetadata>({ registry });
const reflector = new Reflector<MyMetadata, MyArrayMetadata>({ registry });

Use this when you want metadata isolation between modules, tests, or plugin systems.

Decorator APIs

One Reflector instance can both define metadata and read it back.

const metadata = new Reflector<M, AM>();
const reflector = new Reflector<M, AM>();

Using separate variables is optional. They can be the same instance.

set(metadataKey, metadataValue, keysIndexMeta?)

Sets metadata directly and overwrites any inherited or existing value on the current target.

Supported targets:

  • class decorators
  • property decorators
  • method decorators
  • accessor decorators
  • static members

Example:

class Example {
  @metadata.set('name', 'field-name')
  field!: string;

  @metadata.set('name', 'method-name')
  method() {}

  @metadata.set('name', 'getter-name')
  get value() {
    return 1;
  }
}

If keysIndexMeta is provided for a member decorator, the member key is appended uniquely to that array metadata on the class:

class Example {
  @metadata.set('name', 'field-name', 'keys')
  field!: string;
}

reflector.get('keys', Example); // ["field"]

append(metadataKey, metadataValue, keysIndexMeta?)

Appends one item to an array metadata entry.

class Example {
  @metadata.append('tags', 'a', 'keys')
  @metadata.append('tags', 'b', 'keys')
  field!: string;
}

reflector.get('tags', Example, 'field'); // ["a", "b"]

appendUnique(metadataKey, metadataValue, keysIndexMeta?)

Same as append, but avoids duplicates.

class Example {
  @metadata.appendUnique('tags', 'a')
  @metadata.appendUnique('tags', 'a')
  field!: string;
}

reflector.get('tags', Example, 'field'); // ["a"]

concat(metadataKey, metadataValue, keysIndexMeta?)

Concatenates an array into existing array metadata.

class Example {
  @metadata.concat('tags', ['a', 'b'])
  field!: string;
}

param(metadataKey, metadataValue, keysIndexMeta?)

Writes array metadata by parameter index.

Supported targets:

  • constructor parameters
  • method parameters
  • static method parameters

Example:

class Example {
  constructor(
    @metadata.param('params', 10) first: string,
    @metadata.param('params', 20) second: string,
  ) {}

  method(
    @metadata.param('params', 30, 'keys') a: string,
    @metadata.param('params', 40, 'keys') b: string,
  ) {}
}

reflector.get('params', Example); // [10, 20]
reflector.get('params', Example, 'method'); // [30, 40]
reflector.get('keys', Example); // ["method"]

Important:

  • constructor parameter decorators are supported through param()
  • keysIndexMeta is only indexed for named members
  • constructor parameters do not add a key index because the constructor has no property key

transform(metadataKey, updater, keysIndexMeta?)

Low-level helper used by the other decorator methods.

It receives the current metadata value and returns the next value.

class Example {
  @metadata.transform('priority', (oldValue) => (oldValue || 0) + 1)
  field!: string;
}

Read APIs

get(metadataKey, instanceOrClass, key?)

Reads metadata from:

  • a class
  • an instance
  • a member on a class
  • a member on an instance

Example:

@metadata.set('name', 'base')
class Base {
  @metadata.set('name', 'field')
  field!: string;
}

const base = new Base();

reflector.get('name', Base); // "base"
reflector.get('name', base); // "base"
reflector.get('name', Base, 'field'); // "field"
reflector.get('name', base, 'field'); // "field"

Member lookup checks both the constructor side and the prototype side, then traverses inheritance.

That means these cases work as expected:

  • instance fields and methods
  • accessors
  • static methods and static properties
  • inherited metadata from parent classes or parent prototypes

getArray(metadataKey, instanceOrClass, key?)

Same as get, but returns [] when no value exists.

reflector.getArray('tags', Example, 'field');

getProperty(metadataKey, instanceOrClass, key, alternate?)

Merges array metadata from:

  • class-level metadata on the current target
  • class-level metadata on alternate if provided
  • member-level metadata on key

Example:

@metadata.append('tags', 'class-tag')
class Example {
  @metadata.append('tags', 'field-tag')
  field!: string;
}

reflector.getProperty('tags', Example, 'field');
// ["class-tag", "field-tag"]

This API is useful when class-level array metadata acts like a default set, and member-level array metadata adds local detail.

getMetadataFromDecorator(decorator, metadataKey, options?)

Extracts metadata produced by a decorator function by applying it to a temporary target.

This is useful when you want to inspect metadata from a decorator factory without defining a real class just for that check.

Examples:

const classLikeDecorator = metadata.set('name', 'demo');
const methodLikeDecorator = metadata.append('tags', 'x');
const constructorParamDecorator = metadata.param('params', 1);
const methodParamDecorator = metadata.param('params', 2);

reflector.getMetadataFromDecorator(classLikeDecorator, 'name'); // "demo"
reflector.getMetadataFromDecorator(methodLikeDecorator, 'tags'); // ["x"]
reflector.getMetadataFromDecorator(
  constructorParamDecorator,
  'params',
  { index: 0 },
); // [1]
reflector.getMetadataFromDecorator(
  methodParamDecorator,
  'params',
  { key: 'method', index: 0 },
); // [2]

Options:

  • key: inspect member metadata instead of constructor/class-level metadata
  • index: treat the decorator as a parameter decorator

MetadataRegistry API

MetadataRegistry is the low-level storage abstraction.

const registry = new MetadataRegistry();

Available methods:

  • defineMetadata(metadataKey, value, target, propertyKey?)
  • getMetadata(metadataKey, target, propertyKey?)
  • getOwnMetadata(metadataKey, target, propertyKey?)
  • hasMetadata(metadataKey, target, propertyKey?)
  • hasOwnMetadata(metadataKey, target, propertyKey?)

Example:

class Base {}
class Child extends Base {}

registry.defineMetadata('role', 'base', Base);

registry.getMetadata('role', Child); // "base"
registry.getOwnMetadata('role', Child); // undefined
registry.hasMetadata('role', Child); // true
registry.hasOwnMetadata('role', Child); // false

Inheritance Behavior

Metadata is inherited during lookup.

@metadata.set('name', 'base')
class Base {
  @metadata.set('name', 'base-field')
  field!: string;
}

class Child extends Base {}

reflector.get('name', Child); // "base"
reflector.get('name', Child, 'field'); // "base-field"

If a child defines its own metadata, that value overrides inherited lookup for that exact target.

Accessor Decorators

Accessor decorators are supported through the same APIs used for methods and properties:

class Example {
  @metadata.set('name', 'getter')
  get value() {
    return 1;
  }

  @metadata.append('tags', 'setter')
  set value(input: number) {
    void input;
  }
}

Lookup works with either the class or an instance:

reflector.get('name', Example, 'value');
reflector.get('name', new Example(), 'value');

Built-in Safety

The registry stores metadata in a WeakMap and does not patch built-in JavaScript objects such as:

  • Object
  • Function
  • Object.prototype
  • Function.prototype

This avoids the global prototype pollution problems that metadata systems sometimes cause.

Backward Compatibility

Old code like this still works:

import { MetadataSetter, Reflector } from 'typed-reflector';

const Metadata = new MetadataSetter<M, AM>();
const reflector = new Reflector<M, AM>();

MetadataSetter is retained as a compatibility export, but new code should prefer Reflector.

typed-reflector now behaves as a compatibility alias of typed-reflector/legacy.

/legacy

typed-reflector/legacy is the explicit legacy-decorators entry.

It exports the same API as typed-reflector, but makes the runtime choice explicit when you want to document or migrate between multiple decorator modes.

/modern

typed-reflector/modern is the standard-decorators entry.

It is intended for projects using the current decorator proposal rather than legacy TypeScript decorators.

/modern Exports

import {
  Reflector,
  MetadataSetter,
  MetadataRegistry,
  GlobalRegistry,
  ensureSymbolMetadata,
} from 'typed-reflector/modern';

Notes:

  • Reflector is the main class
  • MetadataSetter is a compatibility alias of Reflector
  • MetadataRegistry and GlobalRegistry are re-exported for compatibility and shared low-level access
  • ensureSymbolMetadata() is exported in case you want to initialize Symbol.metadata explicitly before other decorator code runs

/modern Construction

const metadata = new Reflector<M, AM>();
const reflector = new Reflector<M, AM>();

By default, /modern uses GlobalRegistry, just like the legacy entry.

You can also provide a custom registry:

import { MetadataRegistry, Reflector } from 'typed-reflector/modern';

const registry = new MetadataRegistry();
const metadata = new Reflector<M, AM>({ registry });
const reflector = new Reflector<M, AM>({ registry });

/modern stores metadata values in MetadataRegistry, just like the legacy entry.

It still depends on standard decorator metadata support at runtime:

  • decorators must receive context.metadata
  • class metadata objects must be reachable through Symbol.metadata

That metadata object is used only to locate the correct registry target and inheritance chain. The library does not store its own metadata values inside context.metadata.

/modern Supported APIs

The modern entry supports the same read APIs as the legacy entry:

  • get()
  • getArray()
  • getProperty()

It also supports these decorator factories:

  • set()
  • append()
  • appendUnique()
  • concat()
  • transform()

/modern Unsupported APIs

These legacy-only APIs are not available in /modern:

  • param()
  • getMetadataFromDecorator()

The main limitation is that standard decorators do not support parameter decorators, so constructor parameter metadata and method parameter metadata are intentionally excluded from /modern.

/modern Example

import { MetadataSetter, Reflector } from 'typed-reflector/modern';

interface MetadataMap {
  role: string;
}

interface MetadataArrayMap {
  tags: string;
  keys: string;
}

const metadata = new MetadataSetter<MetadataMap, MetadataArrayMap>();
const reflector = new Reflector<MetadataMap, MetadataArrayMap>();

@metadata.set('role', 'service')
class Example {
  @metadata.append('tags', 'field', 'keys')
  field = 'value';

  @metadata.set('role', 'method')
  method() {}

  @metadata.set('role', 'getter')
  get currentRole() {
    return 'service';
  }

  @metadata.append('tags', 'static')
  static boot() {}
}

const instance = new Example();

reflector.get('role', Example); // "service"
reflector.get('tags', instance, 'field'); // ["field"]
reflector.get('role', instance, 'method'); // "method"
reflector.get('role', instance, 'currentRole'); // "getter"
reflector.get('tags', Example, 'boot'); // ["static"]
reflector.get('keys', Example); // ["field"]

/modern Inheritance

Metadata lookup in /modern follows the registry targets derived from the Symbol.metadata prototype chain.

That means parent class metadata is visible from child classes and child instances:

import { Reflector } from 'typed-reflector/modern';

interface MetadataMap {
  name: string;
}

interface MetadataArrayMap {
  keys: string;
}

const metadata = new Reflector<MetadataMap, MetadataArrayMap>();
const reflector = new Reflector<MetadataMap, MetadataArrayMap>();

@metadata.set('name', 'base')
class Base {
  @metadata.set('name', 'base-field', 'keys')
  field = 'value';
}

class Child extends Base {}

reflector.get('name', Child); // "base"
reflector.get('name', Child, 'field'); // "base-field"
reflector.get('keys', Child); // ["field"]

/modern Authoring Notes

  • Use one metadata decorator per key when possible. Relying on decorator stacking order for the same key can make tests and consumer code harder to reason about.
  • /modern decorators only work in standard decorator mode. If they are called like legacy decorators, the library throws a TypeError.
  • If you are building helpers on top of /modern, prefer the types exported by typed-reflector/modern, such as ModernDecorators, instead of the legacy decorator types.

/universal

typed-reflector/universal is intended for libraries that expose decorators to downstream users and do not want to force either legacy or standard decorator syntax at the publishing boundary.

It uses the same MetadataRegistry / GlobalRegistry storage model as the other entries, and dispatches at runtime based on how the decorator is invoked.

/universal Exports

import {
  Reflector,
  MetadataSetter,
  MetadataRegistry,
  GlobalRegistry,
  ensureSymbolMetadata,
} from 'typed-reflector/universal';

Notes:

  • MetadataSetter is a compatibility alias of Reflector
  • ensureSymbolMetadata() has the same purpose as in /modern
  • UniversalDecorators is exported from typed-reflector/universal for helper libraries

/universal Supported APIs

The universal entry supports only the common subset shared by legacy and standard decorators:

  • set()
  • append()
  • appendUnique()
  • concat()
  • transform()
  • get()
  • getArray()
  • getProperty()

/universal Unsupported APIs

These APIs are intentionally omitted because they are not portable across both decorator systems:

  • param()
  • getMetadataFromDecorator()

/universal Use Case

import { Reflector } from 'typed-reflector/universal';

interface MetadataMap {
  kind: string;
}

interface MetadataArrayMap {
  tags: string;
  keys: string;
}

const metadata = new Reflector<MetadataMap, MetadataArrayMap>();

export function Column() {
  return metadata.set('kind', 'column');
}

export function Public() {
  return metadata.append('tags', 'public', 'keys');
}

This is mainly useful when a library wants to export @Column()-style decorators and let the final application decide whether it is using legacy decorators or standard decorators.

Complete Example

import { MetadataRegistry, Reflector } from 'typed-reflector';

interface MetadataMap {
  kind: string;
}

interface MetadataArrayMap {
  tags: string;
  params: string;
  keys: string;
}

const registry = new MetadataRegistry();
const metadata = new Reflector<MetadataMap, MetadataArrayMap>({ registry });
const reflector = new Reflector<MetadataMap, MetadataArrayMap>({ registry });

@metadata.set('kind', 'service')
@metadata.append('tags', 'injectable')
class UserService {
  @metadata.append('tags', 'field', 'keys')
  repo!: unknown;

  constructor(
    @metadata.param('params', 'id') id: string,
    @metadata.param('params', 'token') token: string,
  ) {
    void id;
    void token;
  }

  @metadata.set('kind', 'resolver')
  resolve(
    @metadata.param('params', 'userId', 'keys') userId: string,
    @metadata.param('params', 'traceId', 'keys') traceId: string,
  ) {
    void userId;
    void traceId;
  }

  @metadata.set('kind', 'getter')
  get state() {
    return 'ready';
  }
}

const service = new UserService('1', 'secret');

reflector.get('kind', UserService); // "service"
reflector.get('tags', UserService); // ["injectable"]
reflector.get('tags', service, 'repo'); // ["field"]
reflector.get('params', UserService); // ["id", "token"]
reflector.get('params', service, 'resolve'); // ["userId", "traceId"]
reflector.get('kind', service, 'state'); // "getter"
reflector.get('keys', UserService); // ["repo", "resolve"]
reflector.getProperty('tags', service, 'repo'); // ["injectable", "field"]

License

MIT