crouter
Plugin

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.

BuilderRequired 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.

On this page