genroc

docs / Reference

Project file

`.genroc`, a project's configuration file.

.genroc sits at the root of a project. genctl finds it by walking up from a definition file, and its directory is where resolvers run and what definitions is resolved against.

definitions: ["./definitions/**/*.genroc.yaml"]

resolvers:
  - name: import
    phase: typed
    ext: [.ts]
    command: [npx, genroc-import]
    types:
      Input: task.action.input.input
      Output: task.action.result

Keys

FieldTypeRequiredDescription
definitionsstring[]What genctl apply, types and schema read when given no -f: files or globs (** matches any depth), resolved against this file.
resolversResolver[]Source resolvers, tried in order and taken first-match on name and suffix; the built-in "process" is appended last, so listing one under that name overrides it.

Resolvers

A resolver turns a directive such as "$import: ./greet.ts" into a value before the definition is sent. Each entry registers one.

FieldTypeRequiredDescription
commandstring[]yesThe resolver binary and its arguments, run from this file's directory with the manifest on stdin.
namestringyesWhat a directive names: "$<name>: <argument>".
phasetyped | structuralyesWhat the resolver may do. "typed" fills a slot with text and runs after inference, with types in hand; "structural" fills a slot or spreads a mapping with any value and runs before it, so it is handed no types.
extstring[]Suffixes the argument's first word may end with (".ts", ".genroc.yaml"). Whole suffixes, not extensions; empty accepts anything, no argument included.
typesobjectDeclarations the resolver wants generated, as name to address: a genctl schema type address, relative to the task the directive sits in, e.g. task.action.input.input.

Which entry a directive uses

A directive takes the first entry whose name matches and whose ext accepts its first word, so order matters. ext lists whole suffixes (.genroc.yaml, not .yaml); an empty list accepts anything, including no argument at all. A directive whose name no entry carries, or whose file no entry with that name accepts, fails with a message saying which.

The built-in process resolver (importing a child process) comes after your entries, so an entry named process overrides it for the suffixes it accepts.

Phases

typedstructural
runsafter type inferencebefore it
may returna stringany JSON value, and a mapping where the directive is a << spread
may request typesyesno — refused when .genroc is read
run by genctl schema and the editornoyes

Use structural when the value changes what the definition means, such as spreading a child's name and raises into a task. Use typed when it fills a slot whose type is already known, such as a script's code.

Requesting types

A typed resolver can ask genctl for the types around each directive. types maps a name of your choosing to an address, and the resolver receives each as a JSON Schema (resolver protocol). What it does with them is its own business: @genroc/eval-node writes each name as an exported TypeScript type (TypeScript evaluation).

An address starts with its frame:

FrameResolves against
task.the task the directive sits in
process.the whole definition

An address with no frame is refused when .genroc is read: both a task's action and the process have an input, and the frame says which one is meant.

Finding an address

genctl schema type <process> lists every typed slot of a definition. Replace tasks.<id>. with task., or put process. in front of a top-level slot:

> genctl schema type pricing
input                     object{amount}
output                    object{ok}
tasks.call.action.body    object{amount}
tasks.call.action.result  object{ok}
tasks.call.output         object{ok}
genctl schema typein types
tasks.call.action.bodytask.action.body
tasks.call.action.resulttask.action.result
tasks.call.outputtask.output
inputprocess.input
outputprocess.output

An address keeps going into the schema, as task.action.input.input does: the input property of the action's input.

When there is no type

A requested type the directive's site does not have arrives as null, not as an error, since whether that matters is the resolver's call. That happens when:

  • a task. address is requested by a directive outside any task, or
  • the slot does not exist at that site — a script that takes no input has no task.action.input.input.