Multi-file packages and imports
A definition can be one file or several. Split it with relative ES module imports (./helpers.js) when a piece of logic is reusable or easier to read alone. Small definitions stay in one file.
imports.use is a different thing: it declares another definition whose events this one builds on. It is never for sharing code between your own files.
Package rules
Exactly one entry file. It calls
meta({...})and declaresexport function onBar(ctx)directly.meta()appears only in the entry file.Only the entry exports
onBar, and it must be declared there, not re-exported from a helper.Helpers export functions and
constvalues. They can callevents.declare,outputs.declareandindicators.declare.Import paths are relative and end in
.jsor.mjs:import { classify } from "./classify.js".Files not reachable from the entry can stay in the editor. They do not affect the definition, and lint warns.
In the builder each file has a role: main for the entry, helper for ordinary modules, and library as an optional tag for reusable helpers. A library file is still local to this definition.
Rejected
Code | Trigger |
|---|---|
| A package name or absolute path, such as |
| A relative import without |
| Importing a |
| A path that climbs above the definition's root |
| A path that matches no file |
| Files importing each other in a loop |
|
|
|
|
|
|
|
|
| A file nothing imports (warning) |
require() and module.exports are CommonJS and do not run: the bundler is strict ESM.
Example
// main.js (entry)
import { classify } from "./classify.js";
meta({ name: "Bar direction", kind: "flow", slug: "bar-direction" });
events.declare({
id: "classified",
intent: "signal",
payload: { direction: { type: { enum: ["up", "down"] }, description: "Bar direction" } },
paint: "pin",
});
export function onBar(ctx) {
const direction = classify(ctx);
if (!direction) return;
ctx.emit("classified", { direction }, { label: `Bar ${direction}`, price: ctx.close });
}// classify.js
export function classify(ctx) {
if (ctx.close > ctx.open) return "up";
if (ctx.close < ctx.open) return "down";
return null;
}A run over history uses the saved files of the whole package. Save after editing any file, helpers included.
imports.use
imports.use({
flowId: string; // the other definition's slug
alias: string; // local name, unique in this definition
version?: number; // pin a version of the other definition
}): voidimports.use records the dependency in the definition's manifest. Neither the chart nor a run over history passes the other definition's events to this one, so a definition cannot react to another definition's events. imports.subscribe is accepted by the runtime but does nothing and is not part of the typed API: do not use it.
For indicator values, including values another definition paints, use indicators.declare, not imports.use.
Rule | Trigger |
|---|---|
|
|
|
|
| Two imports with the same alias |
Drawing and dependencies in helpers
outputs.declare and indicators.declare work the same in a helper as in the entry. See Drawing on the chart and Declaring indicator dependencies.
How the Agent edits packages
The Definition authoring agent reads the live, unsaved draft with get_flow_files and writes with apply_flow_edits, a set of per-file patches (create, replace, edit, delete). When it renames or moves a file, it updates every importer's path in the same patch set. lint_flow and typecheck_flow then check the whole module graph of the draft.
