# typed-reflector

> Metadata reflector with typing support.

Latest version **1.1.2** (published 2026-03-19) · MIT license · 0 weekly downloads

## Install

```sh
npm install typed-reflector
pnpm add typed-reflector
yarn add typed-reflector
bun add typed-reflector
```

## Health

**Score 60/100 (C)** — status: stable.

Positive: has types; esm support; no vulnerabilities; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 1.1.2 |
| Published | 2026-03-19 |
| First published | 2021-11-13 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 351.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Nanahira |
| Maintainers | nanahira |
| Keywords | reflect, reflector, reflect-metadata |

## Links

- npm: https://www.npmjs.com/package/typed-reflector
- Repository: https://code.moenext.com/3rdeye/typed-reflector
- Issues: https://code.moenext.com/3rdeye/typed-reflector/issues
- npm.io page: https://npm.io/package/typed-reflector

## Recent versions

- 1.1.2 (latest) — 2026-03-19
- 1.1.1 — 2026-03-19
- 1.1.0 — 2026-03-19
- 1.0.14 — 2025-12-02
- 1.0.13 — 2025-11-04
- 1.0.12 — 2025-08-05
- 1.0.11 — 2022-07-20
- 1.0.10 — 2022-03-20
- 1.0.9 — 2021-12-28
- 1.0.8 — 2021-12-04
- 1.0.7 — 2021-12-04
- 1.0.5 — 2021-11-21
- 1.0.4 — 2021-11-14
- 1.0.3 — 2021-11-14
- 1.0.2 — 2021-11-13
- … 2 more at https://npm.io/package/typed-reflector/versions

## 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

```bash
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`:

```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`:

```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

```ts
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:

```ts
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

```ts
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:

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

For standard decorators:

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

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

For mixed downstream support:

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

## Reflector Construction

### Default Global Registry

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

This instance reads and writes through `GlobalRegistry`.

### Custom Registry

```ts
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.

```ts
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:

```ts
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:

```ts
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.

```ts
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.

```ts
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.

```ts
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:

```ts
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.

```ts
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:

```ts
@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.

```ts
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:

```ts
@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:

```ts
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.

```ts
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:

```ts
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.

```ts
@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:

```ts
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:

```ts
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:

```ts
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

```ts
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

```ts
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:

```ts
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

```ts
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:

```ts
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

```ts
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

```ts
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

```ts
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

---
_Source: https://npm.io/package/typed-reflector · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
