Perry compiles a practical subset of TypeScript. This page documents what's not supported or works differently from Node.js/tsc.
Types are erased at compile time. There is no runtime type system — Perry doesn't generate type guards or runtime type metadata.
{{#include ../../examples/language/limitations.ts:erased-types}}Use explicit typeof checks where runtime type discrimination is needed.
Perry compiles to native code ahead of time. Dynamic code execution is not possible:
// Not supported
eval("console.log('hi')");
new Function("return 42");
Perry parses decorator syntax and supports compile-time-only transforms
(see the bundled @log example), but does not implement the runtime
metadata facilities (Reflect.metadata, Symbol-keyed metadata,
emitDecoratorMetadata type capture) that Angular / NestJS / TypeORM
DI containers rely on. See Decorators for the full
stance and a worked migration recipe.
TypeScript-style runtime metadata is not supported:
// Not supported
Reflect.getMetadata("design:type", target, key);
Use static ESM imports in Perry source:
// Supported
import { foo } from "./module";
// Not supported
const mod = require("./module");
const mod = await import("./module");
Perry has internal CommonJS compatibility paths for some npm package wrappers,
but user-written modules should use static import declarations.
Perry compiles classes to fixed structures. Dynamic prototype modification is not supported:
// Not supported
MyClass.prototype.newMethod = function() {};
Object.setPrototypeOf(obj, proto);
Object.getPrototypeOf(...) and Reflect.getPrototypeOf(...) are supported
for class/prototype inspection patterns, but Object.setPrototypeOf(...) /
Reflect.setPrototypeOf(...) do not mutate Perry's fixed class layout.
WeakMap, WeakSet, WeakRef, and FinalizationRegistry expose the expected
API shape, but their weak-reference semantics are pragmatic, not GC-accurate:
WeakRef keeps a strong reference internally, and FinalizationRegistry
records registrations but does not run cleanup callbacks after collection.
Proxy support is not a full engine-level trap layer for every possible dynamic object access. Prefer plain objects and explicit APIs unless a package only needs Perry's supported Proxy surface.
Perry supports real multi-threading via parallelMap and spawn from perry/thread. See Multi-Threading.
Threads do not share mutable state — closures passed to thread primitives cannot capture mutable variables (enforced at compile time). Values are deep-copied across thread boundaries. There is no SharedArrayBuffer or Atomics.
Not all npm packages work with Perry:
- Natively supported: ~50 popular packages (fastify, mysql2, redis, etc.) — these are compiled natively. See Standard Library.
compilePackages: Pure TS/JS packages can be compiled natively via configuration.- Not supported: Packages requiring native addons (
.nodefiles),eval(), dynamicrequire(), or Node.js internals.
For cases where you need dynamic behavior, use the JavaScript runtime fallback:
import { jsEval } from "perry/jsruntime";
// Routes specific code through QuickJS for dynamic evaluation
Since there's no runtime type checking, use explicit checks:
{{#include ../../examples/language/limitations.ts:type-narrowing}}- Supported Features — What does work
- Type System — How types are handled