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
usesmay be missing. A tool can only be published with it. usesis 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
.jsfile. The instance refuses a state with inline script. - No inline style. Style belongs in a
.cssfile. - 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.
| Call | What 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.view | view state in the fragment of the address: read(), write(state), onChange(callback) |
Flow.texts | the 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.