Plugin context
Every SDK handler receives a context carrying its inputs. Python, R and the Rust SDKs validate them before calling the handler. Node uses build-time contract checks and TypeScript typing. Bridge access depends on the runtime:
| Runtime | Input | App bridge |
|---|---|---|
| Node | input | Liatir |
| Python | ctx.input | ctx.liatir |
| R | ctx$input | ctx$liatir |
| Native Rust | Typed getters on ctx | ctx.liatir |
| WASM Rust | Typed getters on ctx | Unavailable |
Node — PluginContext
PluginContext<typeof liatirPlugin> is the recommended TypeScript helper for typing the .main(...) handler. It gives you:
input, inferred fromdefinePlugin({ inputs });Liatir, the Node plugin bridge to the running Liatir app.
import { definePlugin, field, type PluginContext } from "@liatir/api";
const liatirPlugin = definePlugin({
inputs: {
text: field.string({ label: "Text", required: true }),
},
outputs: {
length: field.number({ label: "Length", format: "integer" }),
},
});
export default liatirPlugin.main(async ({ input, Liatir }: PluginContext<typeof liatirPlugin>) => {
await Liatir.deps.check("node");
return { length: input.text.length };
});@liatir/api also exports PluginInput<S> and PluginOutput<S>, but most plugins only need PluginContext<typeof liatirPlugin>. The type is erased at runtime; the contract is enforced at build time and through TypeScript.
Python — ctx
The @plugin.main handler receives a context with the validated input on ctx.input (a dict; declared defaults are already applied). ctx.raw_input holds the original payload before validation.
from liatir import define_plugin, field
plugin = define_plugin(
inputs={"text": field.string(label="Text", required=True)},
outputs={"length": field.number(label="Length", format="integer")},
)
@plugin.main
def main(ctx):
return {"length": len(ctx.input["text"])}Python plugins also receive the Liatir bridge, exposed on the context as ctx.liatir. It mirrors the Node bridge namespaces with snake_case method names (ctx.liatir.jobs, ctx.liatir.deps, ctx.liatir.desktop.*, ctx.liatir.invoke), and reaches the app over the same local IPC server:
@plugin.main
def main(ctx):
ctx.liatir.deps.check("node")
return {"length": len(ctx.input["text"])}The connection is resolved lazily, so plugins that never touch the bridge run without requiring the app to be reachable.
R — ctx
The handler passed to plugin$main receives:
ctx$input: validated inputs as a named list, with declared defaults applied;ctx$raw_input: the original input object;ctx$liatir: the synchronous app bridge.
source("liatir.R")
plugin <- define_plugin(
inputs = list(text = field$string(label = "Text", required = TRUE)),
outputs = list(length = field$number(label = "Length", format = "integer"))
)
plugin$main(function(ctx) {
ctx$liatir$log$info("Counting characters")
list(length = nchar(ctx$input$text, type = "chars"))
})Bridge namespaces are jobs, deps, desktop$fs, desktop$app, log, progress, paths() and invoke(). Methods use snake_case, for example ctx$liatir$deps$check_many(c("samtools", "bcftools")). Bridge failures raise R errors; do not catch them and return a successful result.
Native/WASM context
INFO
Liatir provides managed Rust and C++ libraries for Native/WASM Plugins. WASM targets must support WASI Preview 1 and remain isolated from the app bridge.
Runtime validation
In Python, R, Rust and C++, the supplied SDK validates the input before the handler runs (defaults applied, required and typed fields checked) and the output after it returns (JSON, and no key outside the declared outputs). In Node, validation happens at build time — liatir build and liatir dev inspect the exported plugin shape after bundling — not on each run.
All runtimes use the existing .lia contract; see runtime preparation for preparation and executable permissions.