Output fields
Declare typed output fields and validate handler results.
Each leaf declares an output object. Field object keys are returned verbatim in the result object, so appId stays appId; unlike command and parameter keys, output keys are not converted to kebab case.
| Builder | Required handler value |
|---|---|
field.string(constraint, options?) | string |
field.int(constraint, options?) | integer number |
field.number(constraint, options?) | number |
field.bool(constraint, options?) | boolean |
field.object(children, constraint?, options?) | an object typed from children |
field.object(constraint, options?) | any object |
field.array(item, constraint?, options?) | an array of the item field's value |
field.array(constraint, options?) | any array |
field.enum(values, constraint, options?) | a member of values |
field.file(constraint, options?) | { upload_id, size, sha256 } (the caller sees a path) |
field.markdown(constraint, options?) | string |
field.path(constraint, options?) | string |
field.of(type, constraint, options?) | unknown |
field.nullable(field) | the wrapped field value or null |
Fields are required by default. Pass { required: false } for a field that can be absent. field.nullable(field.string('...')) declares a present field that can be string | null; it does not make the field optional.
The handler result type is inferred from output, including structured members and elements. TypeScript reports a missing required field or a wrong value type at the defineLeaf call. definePlugin throws PluginDefinitionError for a malformed structured field, a file field that is not top-level, or a second file output. The Fetch handler checks the returned object's top-level fields again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx handler_output_invalid error instead of a JSON success response. It does not look inside objects or arrays, so their members are checked only by TypeScript.
Structured fields
field.object({ ... }) types an object's members. Each member is a field.* builder, and members can be structured themselves. field.array(field.string('A tag.')) types each element; the element's required is ignored and the array's constraint defaults to the element's. field.enum(['open', 'closed'], 'Thread state.') restricts a string to fixed values, which the handler sees as literal types. The generated manifest carries the whole shape: object members as ordered children, an array element as items, enum values. crtr command help renders it. field.nullable keeps the members, element, or values of the field it wraps.
output: {
thread: field.object({
id: field.string('Thread id.'),
state: field.enum(['open', 'closed'], 'Thread state.'),
}, 'The thread.'),
messages: field.array(field.object({ body: field.string('Message text.') }), 'Messages, newest first.'),
}The older field.object('...') and field.array('...') forms still declare an untyped object or array.
File output
field.file(constraint, options?) declares a leaf's file output. A file output must be a top-level field, never an object member or array element, and a leaf may declare only one. It is optional by default, since a result may omit its file. When a capability provider is called, the request carries an upload link in the Crtr-Upload-Link header and its id in Crtr-Upload-Id. The handler PUTs the bytes to that link and returns { upload_id, size, sha256 }, with size in bytes and sha256 as a hex digest. The runtime fetches the file and replaces the field with its path. The same field.file builder declares a file parameter; see Parameters.
field.of accepts any non-empty manifest type string. Its handler value is unknown because crtr only has built-in runtime checks for its recognized type grammar. Use one of the named builders when the result type is known.