Decorators
Describes the decorator design CruftScript intends to ship: a sound subset of the TC39 Stage 3 decorators for classes, methods, accessors, and fields, with return types constrained to the decorated element and capabilities passed explicitly. None of it runs today; the parser rejects every @-form.
A decorator is a function that runs when a class is defined and can observe or replace the thing it is attached to. You write it with an @ in front of a class, method, accessor, or field. This page describes the decorator design CruftScript intends to ship. None of it runs today, and the honest state of things is at the bottom of the page.
@sealed class Point {}
@logged method() {}
@bind(3) accessor count = 0
The model is a deliberately narrow, sound subset of the TC39 Stage 3 decorators proposal — the same shape you get from decorators in current TypeScript, with the loose parts removed. If you have used decorators there, the surface is familiar: class, method, accessor, and field decorators; decorator factories that take arguments; stacking several on one element; and the accessor keyword for auto-accessors.
What a decorator receives
A decorator is called with the element it decorates and a context object. The context is a discriminated record — its kind tells you whether you are decorating a class, a method, an accessor, or a field — so the checker knows the exact shape available in each case.
function logged(target: Method, ctx: MethodContext): Method {
return function (...args) {
// wrap and forward
}
}
Two rules make this sound. First, a decorator's return type must be assignable to the element it decorates: a method decorator returns a method-compatible value or nothing, and a value of the wrong shape is a build-time error rather than a silent replacement. Second, an untyped target is unknown, never any, so you cannot reach into a target you have not typed without narrowing it first. The metadata a decorator reads and writes through its context is typed on the same terms.
Capabilities are explicit
A decorator runs with no ambient authority. It cannot reach a filesystem, a socket, or any other capability just because it is executing. If a decorator needs one, you pass it explicitly at the decoration site, and it is checked there like any other boundary-crossing value.
@audit(logCap) method() {} // the capability is handed in, not ambient
This is the same principle the rest of the language follows: authority is something you are given and can see in the source, never something code acquires implicitly. See concurrency and capability types for how capability tokens work.
Why it works this way
TypeScript's decorators type the target loosely and let a decorator return anything, so a decorator can quietly swap in a value of a different shape and the callers never learn. CruftScript's design closes that by constraining the return to the decorated element, typing an unknown target as unknown, and refusing ambient authority. The aim is that adding a decorator cannot change a program's type story behind your back, and cannot smuggle in authority the decorated code did not already hold.
The legacy pieces are left out on purpose. Parameter decorators, the older experimentalDecorators mode, and reflect-metadata are not adopted, because each depends on runtime type information or an untyped metadata channel that the sound model does not admit.
Limitations
- Decorators do not exist yet. This is the honest state: the parser rejects every
@-form outright with an unsupported-decorator error. Nothing on this page type-checks or runs today. A decorator has a real runtime component — code that executes at class-definition time — and that component has not been built. - Everything above is intended design, not current behavior. The typing rules, the context shapes, and the explicit-capability discipline describe what the feature is meant to do once it exists. Treat them as the plan, not as something you can rely on now.
- The legacy surface is out of scope. Even when decorators ship, parameter decorators,
experimentalDecorators, and reflect-metadata are not part of the design and will not be added to reach TypeScript parity.