Helper manifest
A helper library is two things: source, which the host evaluates, and a manifest, which describes what is in it.
This page is the manifest’s reference. For what libraries are and how to write one, see Helper libraries.
Why it exists separately from the source
Section titled “Why it exists separately from the source”Because three things need to know what a library offers without running it:
- The formula editor, for autocomplete and signatures.
- The checks, which compare a library with its manifest when it is published: a function
declared and not implemented, or implemented and not declared, refuses the publish. And
Validate reads every formula an import runs: one that calls a function the version in force
does not have is an error there, named with the function and the version’s date, with Keep
vN for this import beside it. What it cannot name is a call worked out as the formula runs
(
helpers[name]), which still fails on the row. - The AI, which is told what helpers are available so it proposes expressions that will work.
Deriving that by parsing the source would be guesswork. Declaring it is a contract.
The practical consequence: the source and the manifest have to name the same functions. A function the source has and the manifest does not refuses the publish, as one the manifest declares and the source lacks does. Adding a function means adding it in both places.
The shape
Section titled “The shape”{ "namespace": "acme", "version": "1.2.0", "scope": "org", "description": "ACME-specific transforms.", "functions": [ { "name": "accountCode", "description": "The ACME account code for a customer name.", "params": [{ "name": "name", "type": "string" }], "returns": "string", "examples": [ { "call": "helpers.acme.accountCode(row.Customer)", "result": "AC-0042" } ], "tags": ["accounts"] } ]}Library fields
Section titled “Library fields”| Field | Required | Notes |
|---|---|---|
namespace |
✓ | Lower-case identifier. Becomes helpers.<namespace>.*. |
version |
✓ | Semver, 1.2.0. Exactly three numeric parts. |
scope |
✓ | org or global. Organisation routes only write org. |
description |
What the library is for. | |
functions |
✓ | The list below. |
Function fields
Section titled “Function fields”| Field | Required | Notes |
|---|---|---|
name |
✓ | An identifier. |
description |
✓ | One line. Read by people and by the model. |
params |
✓ | Each with name and type; optional and description allowed. |
returns |
✓ | A type, as a string. |
examples |
{ call, result } pairs. |
|
tags |
For grouping in the editor. |
Types are free-form strings, not a checked type system — string, number,
Record<string, unknown>. They document intent for a reader and for the model; they are not
enforced at run time.
Descriptions and examples earn their keep
Section titled “Descriptions and examples earn their keep”These are not decoration. The description is what somebody sees in autocomplete at the moment they are deciding whether this is the function they want, and it is what the model reads when proposing a formula.
A function described as “formats a code” will be misused. Described as “The ACME account code for a customer name — returns an empty string when the customer is unknown”, it will not.
Examples are the highest-value field after the description, because they show shape and edge cases in less space than prose:
"examples": [ { "call": "helpers.acme.accountCode('Northwind Traders')", "result": "AC-0042" }, { "call": "helpers.acme.accountCode('Unknown Ltd')", "result": "''" }]Namespacing
Section titled “Namespacing”Functions are called under their namespace:
helpers.acme.accountCode(row.Customer)The one exception is the built-in core library, which is also flattened onto helpers.* — so
helpers.trim(…) works as well as helpers.core.trim(…). Your own libraries are always
namespaced, which keeps them from colliding with each other or with a future built-in.
Versioning
Section titled “Versioning”The manifest’s version is semver, and it is your label for this release of the library.
Sloose keeps its own count beside it: every publish is a version — v1, v2, v3 — and none is
overwritten, so a library’s page can show what each one held and make an older one current again
(which publishes it once more, as the next version).
Every template uses a library as it currently is, and so does every import unless it keeps an
older version by choice (libraryPins). So publishing changes what every other formula calling the
library computes from then on. Make the semver
mean something anyway — a changed return shape is a major bump, whatever it looks like from inside
the library — because it is what a person reading the manifest has to go on.
Where it is validated
Section titled “Where it is validated”When the library is published, before anything is stored. The manifest is checked against the
schema, its namespace must match the path it is written to (NAMESPACE_MISMATCH otherwise), an
organisation route will not accept scope: "global" (GLOBAL_LIBRARY) — a global library belongs to the
product, not to an organisation — and core, like the name of any built-in helper (trim, date, phone…), is Sloose’s own (RESERVED_NAMESPACE): a library by that name would take the helper’s place. Then the source
is compiled and loaded in the sandbox against the manifest, and a library that does not load is
refused with what stopped it (LIBRARY_DOES_NOT_LOAD) — see Helper
libraries.
A contract, hash-locked
Section titled “A contract, hash-locked”The manifest shape is one of Sloose’s versioned contracts, shared by the widget, the server, the SDK and every customer configuration repository. It does not change without a version bump, so a manifest written today keeps working.
Writing them
Section titled “Writing them”By hand, or as part of a configuration repository alongside the source — which is the better arrangement, because the manifest and the code it describes then change in the same commit and get reviewed together.