Package Exports
- @formspec/decorators
Readme
@formspec/decorators
Marker-only TC39 Stage 3 decorators for FormSpec CLI static analysis.
These decorators are no-ops at runtime — they carry zero overhead. The FormSpec CLI reads decorator names and arguments directly from the TypeScript AST, without executing your code.
Installation
npm install @formspec/decorators
# or
pnpm add @formspec/decoratorsQuick Start
import { Field, Group, Minimum, Maximum, EnumOptions } from "@formspec/decorators";
class UserForm {
@Group("Personal Info")
@Field({ displayName: "Full Name", placeholder: "Jane Doe" })
name!: string;
@Group("Personal Info")
@Field({ displayName: "Age" })
@Minimum(18)
@Maximum(120)
age?: number;
@Field({ displayName: "Country" })
@EnumOptions([
{ id: "us", label: "United States" },
{ id: "ca", label: "Canada" },
])
country!: "us" | "ca";
}Then generate schemas at build time:
formspec generate ./forms.ts UserForm -o ./generatedBuilt-in Decorators
Field Metadata
| Decorator | Argument | Purpose |
|---|---|---|
@Field(opts) |
{ displayName, description?, placeholder?, order? } |
Display metadata for a field |
@Group(name) |
string |
Assigns the field to a named UI group |
@ShowWhen(cond) |
{ field, value } |
Conditional visibility |
@EnumOptions(opts) |
EnumOptionValue[] | Record<string, string> |
Custom enum labels |
Numeric Constraints
| Decorator | Argument | JSON Schema Property |
|---|---|---|
@Minimum(n) |
number |
minimum |
@Maximum(n) |
number |
maximum |
@ExclusiveMinimum(n) |
number |
exclusiveMinimum |
@ExclusiveMaximum(n) |
number |
exclusiveMaximum |
String Constraints
| Decorator | Argument | JSON Schema Property |
|---|---|---|
@MinLength(n) |
number |
minLength |
@MaxLength(n) |
number |
maxLength |
@Pattern(regex) |
string |
pattern |
Type Inference
Several properties are inferred directly from TypeScript — no decorator needed:
| Property | Inferred From |
|---|---|
| Field type | TS type annotation (string, number, boolean, union types) |
| Required/optional | ? modifier or T | undefined |
| Default value | Property initializer |
| Deprecated | @deprecated JSDoc tag |
Extensibility API
Extending a Built-in Decorator
Use extendDecorator to create a specialised version of a built-in:
import { extendDecorator } from "@formspec/decorators";
const CurrencyField = extendDecorator("Field").as<{
displayName: string;
currency: string;
}>("CurrencyField");
class InvoiceForm {
@CurrencyField({ displayName: "Amount", currency: "USD" })
amount!: number;
}The CLI treats @CurrencyField as an extension of @Field — it inherits the Field schema behaviour and adds the custom currency property to the output.
Custom Decorators (with Extension Namespace)
Use customDecorator to create decorators for a CLI extension:
import { customDecorator } from "@formspec/decorators";
// Parameterised — takes arguments
const Tooltip = customDecorator("my-ui-extension").as<{ text: string }>("Tooltip");
// Marker — applied directly, no arguments
const Sensitive = customDecorator("my-ui-extension").marker("Sensitive");
class ProfileForm {
@Tooltip({ text: "Shown on hover" })
@Field({ displayName: "Bio" })
bio!: string;
@Sensitive
@Field({ displayName: "SSN" })
ssn!: string;
}The CLI emits these as x-formspec-my-ui-extension properties in the generated schema.
Custom Decorators (without Extension Namespace)
For decorators that don't need a CLI extension namespace:
import { customDecorator } from "@formspec/decorators";
const Title = customDecorator().marker("Title");
class MyForm {
@Title
@Field({ displayName: "Heading" })
heading!: string;
}How It Works
- You write decorated classes using TC39 Stage 3 decorator syntax.
- The FormSpec CLI uses the TypeScript Compiler API to statically analyse your source files.
- It reads decorator names and arguments from the AST — your code is never executed.
- JSON Schema and UI Schema are generated from the AST analysis.
This means:
- No reflection metadata required
- No runtime dependencies
- Zero bundle size impact (decorators are tree-shaken as dead code)
- Works with any bundler
TypeScript Configuration
These are standard TC39 Stage 3 decorators — no special tsconfig flags needed. Do not set experimentalDecorators: true (that enables the legacy decorator proposal).
License
UNLICENSED