oJob Recipes

Each YAML block below is a complete definition. Save it to a file and run ojob file.yaml. The periodic and subscriber recipes run until interrupted; the template recipe writes greeting.txt. These examples need only OpenAF. Larger integrations and their dependencies are listed in the examples map.

For paired JavaScript/oJob examples, bounded scheduling, cleanup tests, and package loading, see Authoring recipes. For a progressive walkthrough, start with Script to oJob.

1. Arguments, defaults, and output

Save as greet.yaml; run ojob greet.yaml name=Ada count=2 -json. Omit count to use 1. help describes the interface; check.in converts and validates input. ow.oJob.output respects the CLI’s output format.

help:
  text: Produce a greeting
  expects:
  - name: name
    desc: Person to greet
    mandatory: true
    example: Ada
  - name: count
    desc: Number of greetings (defaults to 1)
    example: "2"

ojob:
  logToConsole: false

todo:
- Greet

jobs:
- name: Greet
  check:
    in:
      name: isString
      count: toNumber.isNumber.default(1)
  exec: |
    if (args.count < 1 || args.count > 10 || Math.floor(args.count) != args.count) {
      throw "count must be an integer between 1 and 10";
    }
    var greetings = [];
    for (var i = 0; i < args.count; i++) greetings.push("Hello " + args.name);
    ow.oJob.output({ greetings: greetings }, args);

2. Compose preparation and formatting around a job

from runs before exec; to runs after it. Only Calculate is scheduled. The output is 42.

ojob:
  logToConsole: false

todo:
- Calculate

jobs:
- name: Prepare
  exec: |
    args.value = 21;

- name: Calculate
  from: [Prepare]
  to: [Display]
  check:
    in:
      value: isNumber
    out:
      result: isNumber
  exec: |
    args.result = args.value * 2;

- name: Display
  exec: |
    print(args.result);

3. Query and output without custom JavaScript

((key)): args selects the invocation map; ((from)) and ((to)) are paths in that map. A JMESPath expression needs ((type)): path. (output): args with ((path)) selects a property; (output): active would instead read $get("active").

ojob:
  sequential: true
  shareArgs: true
  logToConsole: false

todo:
- (pass):
    records:
    - { name: Ada, status: active }
    - { name: Grace, status: inactive }
- (query): "[?status=='active']"
  ((type)): path
  ((key)): args
  ((from)): records
  ((to)): active
- (output): args
  ((path)): active
  ((format)): json

4. Sequential iteration over argument maps

(each) visits each array element in order and merges that element’s properties into the child job’s arguments. It differs from the producer/consumer jobs[].each pattern below.

ojob:
  sequential: true
  shareArgs: true
  logToConsole: false

todo:
- (pass):
    items:
    - { name: Ada }
    - { name: Grace }
- (each): items
  ((key)): args
  ((todo)): [Greet item]

jobs:
- name: Greet item
  check:
    in:
      name: isString
  exec: |
    print("Hello " + args.name);

5. Producer/consumer fan-out

The producer calls each(map) for each work item. Consumer jobs receive that map plus init; pass other required values explicitly. eachThreads belongs on the producer and limits consumer concurrency. Output order is unspecified. This follows the structure used by docker/mirrorImages.yaml without invoking a registry or external command.

ojob:
  logToConsole: false

init:
  prefix: Item

todo:
- Produce

jobs:
- name: Produce
  each: [Consume]
  typeArgs:
    eachThreads: 2
  exec: |
    [1, 2, 3].forEach(function(value) {
      each({ value: value });
    });

- name: Consume
  check:
    in:
      value: isNumber
  exec: |
    print(args.init.prefix + " " + args.value);

6. Write a template result to a file

((outputFile)) selects a file. ((out)) selects a stored result key and does not write a file. Keep global argument templating disabled here so {{name}} is resolved using the template job’s data.

ojob:
  templateArgs: false
  logToConsole: false

todo:
- (template): "Hello {{name}}!"
  ((data)): { name: World }
  ((outputFile)): greeting.txt

7. Periodic service

Registration in todo and daemon: true are both needed for this service. Stop with Ctrl-C. The top-level daemon callback receives no args; scheduled work belongs in the periodic job.

ojob:
  daemon: true
  cronInLocalTime: false

todo:
- Tick
- Cleanup

jobs:
- name: Tick
  type: periodic
  typeArgs:
    cron: "*/5 * * * * *"
    waitForFinish: true
  exec: |
    log("Tick " + new Date());

- name: Cleanup
  type: shutdown
  exec: |
    log("Service stopped");

For retry tracking, add this map under the periodic job’s typeArgs:

cronCheck:
  active: true
  ch: oJob::cron
  retries: 3
  retryWait: 2000

The counter includes the initial attempt. The default channel is in memory; it does not retain missed-run history across process restarts. See state and scheduling.

8. Channel subscription

The channel exists before the subscriber is registered. Subscribe callbacks receive args.ch, args.op, args.k, and args.v. This example remains running after publishing one event so the asynchronous subscriber can process it; stop with Ctrl-C.

ojob:
  daemon: true
  channels:
    create:
    - name: events

todo:
- Observe
- Publish

jobs:
- name: Observe
  type: subscribe
  typeArgs:
    chSubscribe: events
  exec: |
    if (args.op == "set") print(args.v.message);

- name: Publish
  exec: |
    $ch("events").set({ id: 1 }, { message: "Hello subscriber" });

9. Recover from an error

A truthy return from catch marks the failure handled. Without that return, a logging-only handler leaves it unhandled. to is skipped when the body throws; put mandatory cleanup inside try/finally.

ojob:
  sequential: true
  shareArgs: true
  logToConsole: false

todo:
- Read optional value
- Report

jobs:
- name: Read optional value
  exec: |
    throw "Example failure";
  catch: |
    args.value = "fallback";
    return true;

- name: Report
  exec: |
    print(args.value);

10. Select a branch from an argument

Run with mode=fast or mode=full; the default is fast. (optionOn) takes the argument name, while ((todos)) maps its possible values to todo lists.

ojob:
  sequential: true
  shareArgs: true
  logToConsole: false

todo:
- Configure
- (optionOn): mode
  ((todos)):
    fast: [Fast]
    full: [Full]

jobs:
- name: Configure
  check:
    in:
      mode: isString.oneOf(['fast', 'full']).default('fast')

- name: Fast
  exec: |
    print("Fast selected");

- name: Full
  exec: |
    print("Full selected");

Adapting integration examples

For secrets, use (secget) with ((secOut)) to choose the destination in args, then validate the resulting fields. A configured secret repository is required; (run) is for an external definition, not a local job name.

For metrics, configure ojob.metrics.add for collectors, ojob.metrics.passive for an endpoint, and ojob.metrics.active for push delivery. The push interval is ojob.metrics.active.periodInMs, alongside openmetrics or nattrmon. Use instrumentation for tracing rather than treating metrics as job execution records.

For package jobs, copy the source definition’s ojob.opacks and its include/jobsInclude entries. HTTP/MCP shortcuts are supplied by those libraries. Review the source examples before adapting an integration to your environment.


This site uses Just the Docs, a documentation theme for Jekyll.