Skip to content

Decorators and wire names

Typra includes decorators for runtime concerns TypeSpec does not model by default.

DecoratorPurpose
@sampleSupplies generated test and example data.
@abstractMarks a model as not directly instantiated.
@@coerceExpands scalar input into an object during load.
@@factoryGenerates factory constructors for a model.
@runtimeCancellableAdds runtime-native cancellation to an operation without serializing it.
@syncMarks an operation as synchronously callable where targets support sync surfaces.
@effectRecords operation effect metadata such as atomic and nonFatal.
@optionalOperationMarks an operation as optional on generated callable surfaces.
@vectorAttaches callable behavior expectations to TypeSpec operations.
@@knownAsMaps a property to provider-specific wire names.
@@defaultForRecords provider-specific required defaults.
@parseAliasAccepts alternate input strings for a canonical union value.
@@knownAs(WireOptions.maxOutputTokens, "openai", "max_completion_tokens");
@@knownAs(WireOptions.maxOutputTokens, "anthropic", "max_tokens");
@@knownAs(WireOptions.temperature, "openai", "temperature");
@@defaultFor(WireOptions.temperature, "openai", 0.2);
model WireOptions {
maxOutputTokens?: int32;
temperature?: float32;
}

Typra records provider-specific names so generated surfaces can load and save against external wire formats without changing the canonical TypeSpec property name.

export class WireOptions {
maxOutputTokens?: number | undefined;
temperature?: number | undefined;
toWire(provider: string): Record<string, unknown> {
const wireMap = {
maxOutputTokens: { openai: "max_completion_tokens", anthropic: "max_tokens" },
temperature: { openai: "temperature" },
};
return Object.fromEntries(
Object.entries(this.save()).flatMap(([key, value]) =>
wireMap[key]?.[provider] ? [[wireMap[key][provider], value]] : []
)
);
}
}
@@coerce(
FixtureReference,
string,
#{ id: "{value}", label: "coerced reference" },
"reference",
"Load a reference from an id string.",
"ref-coerced"
);
@@factory(FixtureReference, "named", #{ id: "{id}", label: "{label}" }, #{ id: "string", label: "string" });
model FixtureReference {
id: string;
label?: string;
}

@@coerce lets load paths expand a scalar input into an object. @@factory adds named construction helpers to generated target surfaces.

export class FixtureReference {
static readonly shorthandProperty: string | undefined = "id";
static load(data: Record<string, unknown>, context?: LoadContext): FixtureReference {
if (typeof data === "string") {
return new FixtureReference({ id: data, label: "coerced reference" });
}
return new FixtureReference({ id: String(data["id"]), label: String(data["label"]) });
}
static named(id: string, label: string): FixtureReference {
return new FixtureReference({ id, label });
}
}

Use TypeSpec-native interfaces and operations for callable seams:

model Event {
name: string;
}
model EmitResult {
accepted: boolean;
}
interface EventSink {
emit(event: Event): EmitResult;
}

Use @vector on operations when behavior should become cross-runtime conformance evidence:

const EmitVectors = #[
#{ name: "accept-event", stage: "event", input: #{ event: #{ name: "started" } }, expected: #{ accepted: true } }
];
interface EventSink {
@runtimeCancellable
@vector(EmitVectors)
emit(event: Event): EmitResult;
@sync
format(event: Event): string;
@effect(#{ atomic: true })
append(event: Event): void;
@optionalOperation
@effect(#{ nonFatal: true })
observe(event: Event): void;
}

JSON-string vector sets for keyword-named and opaque wire inputs

Section titled “JSON-string vector sets for keyword-named and opaque wire inputs”

Vector input/expected values are opaque conformance evidence — Typra serializes them into the vector snapshot and compares them structurally; they are not type-checked against the operation’s parameters. TypeSpec object-value literals (#{ ... }) require keys to be bare, non-keyword identifiers, so they cannot express inputs whose domain models carry TypeSpec-keyword field names (such as model) or that replay opaque provider wire payloads with arbitrary keys. Quoting the key (#{ "model": ... }) is also rejected by the TypeSpec grammar.

For those cases, author the vector set as a JSON string — typically a triple-quoted string constant — which Typra parses into entries. The escape is whole-set: mix native and JSON-string sets by stacking @vector decorators.

const WireVectors = """
[
{
"name": "wire-replay",
"stage": "callable",
"input": {
"request": { "model": { "provider": "openai" } },
"response": { "id": "resp-1", "model": "gpt-4o-mini", "choices": [] }
},
"expected": "Hello!"
}
]
""";
interface Processor {
@vector(WireVectors)
process(request: CallRequest, response: unknown): unknown;
}

Operation decorators apply only to TypeSpec-native operations:

DecoratorRuntime meaning
@runtimeCancellableProjects a host-owned cancellation parameter such as AbortSignal, CancellationToken, Go context.Context, or a configured runtime token path. It never becomes a model field, vector input, or serialized transport binding.
@syncEmits a synchronous signature in targets that distinguish sync and async callable surfaces.
@effect(#{ atomic: true })Marks the operation as an atomic effect boundary for generated metadata, docs, and conformance evidence. Typra does not implement transactions.
@effect(#{ nonFatal: true })Marks a post-commit or side-effect operation whose failures are non-fatal metadata. Host apps own error policy.
@optionalOperationAllows an optional/default implementation shape. Prefer smaller capability interfaces unless compatibility requires optional methods.

protocol-scaffolds: "compile-only" emits test-only implementations that prove generated protocols compile. They intentionally throw or reject when called and are not runtime fakes.