Skip to content
Documentation
Contents

Building tools

Layout of a tool

Which files a tool has, what tool.json says and what flow.js brings.

A tool is a directory tools/<name>/ in the repository of your instance. It holds static files, and the instance serves them at /t/<name>/, on the same origin as the data interface. There is no build step: what is in the repository is what runs.

The files

tools/<name>/tool.json      title, description, entry file and what the tool needs
tools/<name>/index.html     the page, without inline script
tools/<name>/app.js         your script
tools/<name>/style.css      your stylesheet

The name consists of lowercase letters, digits and hyphens. tools/example/ in the clone serves as the template. new-tool.py and scaffold_tool copy it for a collection.

tool.json

{
  "title": "Tasks",
  "description": "Record open tasks and close them",
  "entry": "index.html",
  "uses": {
    "collections": ["aufgaben"],
    "buckets": [],
    "capabilities": []
  }
}

uses says what the tool needs: collections, buckets and capabilities such as mail.send. Keep it current when the tool reads another collection, stores files or sends email.

  • All three lists are there. An empty list means: none of these.
  • What the lists name must exist. A collection that is still waiting in the practice room counts.
  • In the draft uses may be missing. A tool can only be published with it.
  • uses is a statement, not a limit. The tool reaches whatever the person using it reaches. The list shows the administration which permissions belong to the tool.

What the instance enforces

The instance sets a content security policy for every tool. A tool cannot change it.

  • No inline script. Script belongs in a .js file. The instance refuses a state with inline script.
  • No inline style. Style belongs in a .css file.
  • No foreign sources. Scripts, fonts, images and calls come only from the instance itself. No CDN, no foreign API. A form does not submit to a server; data goes to the interface of the instance with fetch.
  • Only known file types: html, js, css, json, svg, png, jpg, jpeg, webp, ico, woff2, md, txt.
  • Limits: at most 200 files and 20 MB per tool, 5 MB per file.

This way data does not leave the instance through the browser.

Loading flow.js

flow.js is the client of the interface. It belongs to the instance, not to the tool. Load it before your script and never copy it into the tool:

<script src="/flow/v1/flow.js"></script>
<script src="app.js"></script>

It comes with every update of the instance. v1 only grows. Anything that would change how a function behaves would come as /flow/v2/flow.js next to it.

The functions

Every call returns a Promise.

CallWhat it does
Flow.schema(c)fields of the collection in schema order, with type, required flag and value list
Flow.query(c, body)reads rows, answers {records, hasMore, cursor}
Flow.count(c, body)counts, answers {count}
Flow.stats(c, body)figures for one field
Flow.aggregate(c, body)groups, answers {groups, more}
Flow.create(c, values)creates a row
Flow.update(c, id, version, values)changes a row
Flow.remove(c, id, version)deletes a row
Flow.options(c, field)choices for a reference field: {options: [{id, label}], hasMore}
Flow.fieldInput(field)a form element for a field from Flow.schema
Flow.viewview state in the fragment of the address: read(), write(state), onChange(callback)
Flow.textsthe messages for users, to replace

Reading and creating:

Flow.query("aufgaben", {
  filter: { field: "status", op: "eq", value: "offen" },
  sort: { field: "faellig", direction: "asc" },
  limit: 20
}).then(function (page) {
  page.records.forEach(show);
});

Flow.create("aufgaben", { titel: "Check the offer", status: "offen" });

Changing with the version you read, and handling errors:

Flow.update("aufgaben", row.id, row.version, { status: "erledigt" })
  .catch(function (error) {
    if (error.status === 409) {
      reload();                     // someone was faster
    }
    showMessage(error.message);
    error.fields.forEach(function (problem) {
      markField(problem.field, problem.message);
    });
  });

A failed call throws a Flow.FlowError with status, message and fields. show, reload, showMessage and markField stand for functions of your own tool here.

Flow.view keeps the view state in the fragment of the address (#…). The fragment never reaches the server, so a filter value appears in no log:

Flow.view.write({ status: "offen" });
var state = Flow.view.read();       // { status: "offen" }

Errors in the browser

flow.js reports to the instance what goes wrong in the browser: uncaught errors, rejected promises and calls the server refused. The reports appear in the admin area on the page of the tool, and the agent reads them with list_tool_errors. The instance does not record who caused the error.

The calls in detail are described under Reading and writing data.