Ahead-of-time reflect metadata for TypeScript 7. Injects design:symbols,
design:paramtypes, design:propertytype, design:properties, and design:class
at build time — no decorators, no emitDecoratorMetadata.
Integrates with any build tool via unplugin
(Vite, Rollup, webpack, esbuild), or use the CLI with the TypeScript 7 API for
standalone tsgo projects.
Why does this exist? TC39 decorators are now standard, but
emitDecoratorMetadatawas never part of the spec and is being removed. RFLCT fills the gap by emitting type metadata from type annotations at compile time, using the samereflect-metadataruntime anddesign:*keys that the ecosystem already understands. Read the full Philosophy & Motivation.
import { Reflect, resolve } from "rflct";
interface Shape { sides: number; }
class Polygon {
public color: Reflect<string, { optional: true }>;
constructor(
public shape: Reflect<Shape>,
public label: Reflect<string>
) {}
}
container.bind(resolve<Shape>()).to(Polygon);When a class implements Reflectable, constructor parameters are
reflected automatically — no Reflect<T> needed:
import { Reflectable, resolve } from "rflct";
interface Shape { sides: number; }
class Polygon implements Reflectable {
constructor(
public shape: Shape,
public label: string
) {}
}Both forms produce identical metadata. Use Reflect<T, M> when you need
per-parameter metadata (optionality, names, tags), or Reflectable
when every constructor parameter is injected with default metadata.
After transformation:
const __RFLCT_Shape = Symbol.for("@acme/shapes@1|src/geo.ts|Shape");
class Polygon {
constructor(shape, label) {}
}
Reflect.defineMetadata("design:paramtypes", [
{ type: __RFLCT_Shape, metadata: {} },
{ type: String, metadata: {} }
], Polygon, undefined);
Reflect.defineMetadata("design:properties", ["color"], Polygon);
Reflect.defineMetadata("design:propertytype", [
{ type: String, metadata: { optional: true } }
], Polygon.prototype, "color");
container.bind(__RFLCT_Shape).to(Polygon);
Reflect.defineMetadata("design:symbols", Object.assign(
Reflect.getMetadata("design:symbols", Reflect) ?? {}, {
"@acme/shapes@1|src/geo.ts|Shape": __RFLCT_Shape,
"@acme/shapes@1|src/geo.ts|Polygon": Polygon,
}
), Reflect);| Guide | Description |
|---|---|
| Philosophy & Motivation | Why RFLCT exists — TC39 decorators, emitDecoratorMetadata removal, and migration strategy |
| Constructor Injection | Reflect<T> on constructor parameters — type mapping, optional metadata |
| Property Injection | Reflect<T> on class properties — design:properties registry, per-property metadata |
| Method Parameters | Reflect<T> on method parameters — prototype-level metadata |
| Resolve Calls | resolve<T>() — compile-time type resolution for DI bindings and map keys |
| Multi-Injection | Reflect<T[]> — array types with elementType for injecting collections |
| Metadata Arguments | Reflect<T, M> — attaching arbitrary metadata (optionality, constraints, names) |
| Class Metadata | Reflectable<T> — class-level metadata via implements clauses, auto-reflect for constructor params |
| Type-Only Symbols | How interfaces and type aliases get stable Symbol.for(...) runtime identities |
| Symbol Qualification | The package@major|path|Name format and cross-package interop guarantees |
| Internals | Architecture — transform pipeline, CLI vs unplugin, type serialization, module structure |
| # | Metadata key | What it does |
|---|---|---|
| 1 | design:symbols |
Global type registry — every class, interface, and type alias is registered process-wide |
| 2 | design:paramtypes |
Parameter type metadata — { type, metadata } entries for constructors and methods |
| 3 | design:propertytype |
Property type metadata — { type, metadata } entries for class properties |
| 4 | design:properties |
Property name registry — lists which properties on a class carry Reflect<T> annotations |
| 5 | design:class |
Class-level metadata via Reflectable<T> in implements clauses |
resolve<T>() is a fifth transformation that replaces calls with the runtime
identity of T at compile time.
npm install rflct reflect-metadataImport reflect-metadata once at your application entry point:
import "reflect-metadata";This polyfills the global Reflect.defineMetadata / Reflect.getMetadata API
that the generated code relies on. Do not import it in every file — a single
import per process is sufficient.
The plugin runs with enforce: 'pre' and outputs JavaScript by default
(types are stripped via oxc-transform), so it works regardless of what
TypeScript transpiler the consumer has — or doesn't have. Pass
{ transpile: false } to output TypeScript instead and let the bundler's own TS
plugin handle type stripping.
// vite.config.js
import { vitePlugin } from "rflct/vite";
export default {
plugins: [vitePlugin()],
};// rollup.config.js
import { rollupPlugin } from "rflct/rollup";
export default {
plugins: [rollupPlugin()],
};// webpack.config.js
const { webpackPlugin } = require("rflct/webpack");
module.exports = {
plugins: [webpackPlugin()],
};import { esbuildPlugin } from "rflct/esbuild";
await esbuild.build({
plugins: [esbuildPlugin()],
});For standalone tsgo projects without a bundler:
npx rflct -p tsconfig.jsonOptions:
-p, --project— path to tsconfig.json (default:tsconfig.json)-h, --help— show help
The CLI type-checks original sources via the TypeScript 7 API
(typescript/unstable/sync), then transforms and emits JavaScript.
Output goes to the outDir specified in your tsconfig.
// Marks a parameter/property for metadata injection. Erases to T.
type Reflect<T, Metadata = {}> = T;
// Phantom type for class-level metadata via implements clauses.
type Reflectable<T = {}> = { ... };
// Compile-time resolution — replaced by the transformer.
function resolve<T>(value?: abstract new (...args: any[]) => T): symbol | (abstract new (...args: any[]) => T);import { transform } from "rflct/transform";
// Output TypeScript (metadata injected, types intact)
const result = transform(source, fileName);
// result.code — transformed source
// result.transformed — whether any changes were made
// Output JavaScript (types stripped via oxc-transform)
const result = transform(source, fileName, { transpile: true });
// result.code — JavaScript output
// result.map — source map (JSON string)MIT
